Skip to content

Thread mode: coordinator/worker threads (plugin side) - #45

Open
promptclickrun wants to merge 2 commits into
mainfrom
feature/thread-mode
Open

promptclickrun wants to merge 2 commits into
mainfrom
feature/thread-mode

Conversation

@promptclickrun

@promptclickrun promptclickrun commented Sep 18, 2026

Copy link
Copy Markdown
Owner

What this is

Optional per-conversation "thread mode" (plugin side), modeled on Anthropic's redesigned Projects: one coordinator conversation fans scoped work out to parallel worker threads, then assembles the finished result. Implemented per the locked protocol contract in PROTOCOL.md (verified against loopdy-plugin @ 83d05d2 and hermes-agent @ 287c56e9).

Contract summary (PROTOCOL.md)

Tools (toolset loopdy, in plugin.yaml provides_tools):

  • thread_spawn {threads: [{name, brief, context?}]} — launches in-process subagent workers via ctx.subagent_lifecycle.launch(SubagentLaunchRequest(goal=brief, context=<worker discipline>, role="leaf", correlation_id=name, metadata={threadmode, thread})). Persists each thread record with status spawning BEFORE launch() (closes the pre_llm_call race). Outside a live turn it fails clean with: "thread_spawn needs a live coordinator turn (no active Hermes parent session). Create the worker from the Loopdy thread view instead." Never leaks tracebacks.
  • thread_status {} — refreshes subagent-kind threads via lifecycle.status() (SubagentState mapped to the contract vocabulary), reports stored status for session-kind threads, returns the roster.
  • thread_collect {thread_name} — subagent kind: non-blocking lifecycle.result(); parses the first ## Result heading section (falls back to full text). Session kind: best-effort read of the worker session's recent messages, else returns a hint to read the worker session directly.
  • thread_note {thread_name?, note} — scratch notes, capped at 50 per thread / 2000 chars each; no thread_name = coordinator-level note.

State (profile-scoped PluginState via ctx.state):

  • threadmode:<coordinator_session_id> — coordinator record {version, enabled, threads{name, brief, kind, status, subagent_id, subagent_session_id, handle, worker_session_id, notes, result, timestamps}}.
  • threadmode:worker:<worker_session_id> — {coordinator_session_id, thread} index, written at subagent_start and at REST /register.
  • Thread names enforced as ^[a-z0-9][a-z0-9_-]{0,63}$, unique per coordinator.

REST (mounted in dashboard/plugin_api.py as /api/plugins/loopdy/native/threads/*):

  • POST /flag {session_id, enabled} — app calls on every open/resume while its toggle is on.
  • POST /register {coordinator_session_id, name, worker_session_id, brief} — 409 on duplicate name; registers session-backed workers (title convention "Thread: <name>").
  • GET /roster?coordinator_session_id=… — coordinator record minus raw handle dict.

Injection (additive pre_llm_call, fail-silent, returns {"context": ...}):

  • Coordinator turn: ## Thread mode — coordinator (roster table, delegate/review guidance, memory discipline — ONLY the coordinator writes MEMORY.md).
  • Worker turn (via parent_session_id match or the worker index): ## Your thread assignment (name, brief, scratch notes, sibling names+statuses, full worker discipline).
  • subagent_start records child_session_id and writes the worker index; subagent_stop maps child_status (completed/interrupted/failed) to terminal thread statuses.

Hard constraints honored: no Hermes changes, no plugin HTTP self-callbacks, no REST/fork for workers, no private data, focused diff.

Test results

  • New tests/test_thread_mode.py: 51 tests, all green — four tools, state transitions, name validation, spawn-outside-turn clean error (real agent.subagent_lifecycle error type), ## Result parsing, pre_llm_call coordinator/worker/index/fail-silent branches, subagent_start/stop observers, and the REST endpoints (real Hermes dashboard auth middleware + fixture provider).
  • Full suite: python -m unittest discover -s tests → 949 tests; the failure/error set is byte-identical to the main-branch baseline (40 pre-existing, all environmental: missing optional services, git-fixture deps, timing-sensitive middleware tests). No regressions.

Known gaps

  • No platform == "loopdy" gate on the thread-mode pre_llm_call injection (the contract does not call for one; the discipline text references Loopdy-only tools).
  • Session-kind thread_collect depends on a read-only SessionDB read of the worker session; any failure falls back to the "read the worker session directly" hint by design.
  • One contract wording note: the real lifecycle error is "No active Hermes parent session is available." (contract paraphrases it); matching is by substring, and the returned user-facing message is the contract's exact clean error.
  • The real subagent_stop hook payload carries no child_subagent_id (only child_session_id + child_status); stop-matching uses the child_session_id recorded at start, per the contract's mapping rule.

Future work (per contract §12)

The Anthropic-style project artifact/library layer (shared file library, generated-artifact accumulation) is explicitly out of scope here.

Conversation-first orchestration

Thread Mode follows the project-chat model: the user stays in one coordinator conversation and speaks normally. The coordinator decides when parallel work helps, chooses and launches worker threads without setup prompts, monitors them, collects results, and answers in the same chat. Simple work stays direct. Manual thread creation remains an optional fallback in the Threads rail.

user message → coordinator → automatic worker fan-out → collect/review → answer here

Follow-up validation

  • python -m unittest tests.test_thread_mode → 52 passed locally.

Implements the plugin half of the thread-mode protocol contract (PROTOCOL.md):
- Four loopdy tools: thread_spawn, thread_status, thread_collect, thread_note
- Plugin state under threadmode:<coordinator_session_id> and
  threadmode:worker:<worker_session_id> keys
- Additive pre_llm_call injection (coordinator + worker) and
  subagent_start/subagent_stop observers
- App out-of-turn REST: POST /native/threads/flag, POST /native/threads/register,
  GET /native/threads/roster
- 51 new tests in tests/test_thread_mode.py; full suite matches main baseline
Thread mode described manual worker creation as a normal path, which made the Threads surface feel like a setup form. Make the coordinator decide when parallel work helps, create and name workers itself, and return the assembled result in the ongoing chat.
@promptclickrun promptclickrun self-assigned this Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant