Skip to content

design: Orchestrator route-first redesign (route-to-existing as primary, create as fallback)#1741

Open
wqymi wants to merge 6 commits into
mainfrom
docs/orchestrator-route-first-redesign
Open

design: Orchestrator route-first redesign (route-to-existing as primary, create as fallback)#1741
wqymi wants to merge 6 commits into
mainfrom
docs/orchestrator-route-first-redesign

Conversation

@wqymi

@wqymi wqymi commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add design document for the Orchestrator route-first redesign
  • Core thesis: the Orchestrator is a router, not a creator — its default action should be "route to an existing session" (session send), with create as a fallback when no existing session fits
  • This is a design-only PR — no product code changes

Key Design Points

  1. New session route operation — first-class routing primitive that gets the active session list, matches the task to an existing session, and routes via session send (or recommends create if no match)
  2. Harness context injection — live <active-sessions> block injected into the orchestrator's system prompt on every turn, so it always sees its fleet without needing explicit session list calls
  3. create demoted to fallback — only used when no existing session matches the task; --topic mechanism preserved but deprecated
  4. orchestrator.txt rewrite — decision guidance changes from decompose → dispatch (create) to route → (create only if none fits)
  5. 4-phase roadmap — context injection → prompt rewrite → route primitive → deprecated path cleanup

Problem This Solves

The current create-first default causes session explosion: the Orchestrator creates a new child session for every task, even when an existing session could handle same-theme work. Topic-based reuse (--topic) was a workaround but is fundamentally unreliable because it couples routing to string matching on LLM-generated labels.

Architecture Impact

File Change
packages/opencode/src/tool/session.ts New route verb; create topic logic removed; list summary format
packages/opencode/src/session/llm.ts <active-sessions> context injection in buildSystemArray
packages/opencode/src/session/prompt/orchestrator.txt Decision guidance rewritten for route-first
docs/harness/MiMo Orchestrator Mode.md Documentation update

Full details in: docs/compose/specs/2026-07-14-orchestrator-route-first-redesign.md

wqymi added 6 commits July 15, 2026 22:17
…ways halt

Add unconditional empty-step guard in processor.ts BEFORE the doom-loop
check. Genuine empty tool calls (undefined/null/empty object, even with
harmless extra fields like {_meta}) are always counted regardless of
error/summary/structured/finish state. When emptyStepCount hits
EMPTY_STEP_THRESHOLD=3, halt with error.

- Add emptyStepCount to ProcessorContext (persists across process calls)
- Add isEmptyInput helper that filters out _meta-prefixed keys
- Reset counter on non-empty input, increment on empty
- Regression test: single LLM response with 3 empty tool calls → stop
… empty tool calls

Replace the threshold-based empty-step guard (tolerate N, halt on N+1) with
an immediate retry mechanism that mirrors autoRetryTextToolCall:

- A SINGLE empty tool call now triggers an immediate retry: the bad
  assistant turn is discarded (error-tagged with EmptyToolCallError) and a
  synthetic user turn is appended to re-drive generation.
- Bounded by EMPTY_TOOL_CALL_RETRY_LIMIT (default 2, mirrors
  TEXT_TOOL_CALL_RETRY_LIMIT). On exhaustion the error stays terminal.
- The empty turn is discarded from context (toModelMessages skips
  error-tagged messages) so it cannot poison later context.

Changes:
- message-v2.ts: add EmptyToolCallError to error discriminated union
- flag.ts: add MIMOCODE_EMPTY_TOOL_CALL_RETRY_LIMIT (default 2),
  remove MIMOCODE_EMPTY_STEP_MAX_RECOVERY
- prompt.ts: replace handleEmptyStep (threshold-halt) with
  autoRetryEmptyToolCall (immediate retry), wire into all 3 classify
  sites (existing-assistant, fork, main)
- processor.ts: remove processor-level threshold-halt guard
  (EMPTY_STEP_THRESHOLD, emptyStepCount, isEmptyInput), retry now
  handled at prompt loop level
- empty-step-detection.ts: keep isEmptyStep + isEmptyInput (with
  _meta filtering), remove unused threshold constants and nudge messages
- Tests: update to assert retry behavior (single empty call → retry +
  error-tagged discard, exhaustion → halt, recovery → valid answer)
… on no-arg tools

isEmptyStep case (a) flagged every tool call with {} as empty, including
legitimate no-arg tools (plan_exit, plan_enter, future z.object({}) tools).
These tools accept no parameters - {} is the only valid call - but the
guard treated it as empty, triggering a retry loop that could never succeed.

Two fixes:
1. isEmptyStep now accepts an opts.toolHasArgs callback that checks whether
   a tools parameter schema has required fields. When toolHasArgs returns
   false (schema is z.object({}) or all-optional), {} input is NOT flagged.
2. Substantive text or reasoning alongside a tool call now prevents the
   empty classification - fixing the early-return bug where case (a)
   returned before the text/reasoning check at :60.

prompt.ts builds the lookup from the tool registry (z.ZodObject shape
introspection) and passes it to isEmptyStep via autoRetryEmptyToolCall.

Regression tests:
- No-arg tool called with {} - NOT flagged (with toolHasArgs)
- Args-accepting tool called with {} - still flagged
- Tool call + substantive text - NOT flagged (text overrides)
- Legacy (no opts) - backwards-compatible, all {} inputs flagged
…ry, create as fallback)

Add design document for the Orchestrator route-first redesign. Core thesis:
the Orchestrator is a router, not a creator — its default action should be
'route to an existing session' (session send), with create as a fallback
when no existing session fits.

Key design points:
- New session route operation as first-class routing primitive
- Harness injects live active-sessions context into orchestrator prompt
- create demoted to fallback (only when no existing session matches)
- orchestrator.txt decision guidance rewritten from decompose→dispatch
  to route→(create only if none fits)
- 4-phase implementation roadmap: context injection → prompt rewrite
  → route primitive → deprecated path cleanup
… injected context

Revision addressing user feedback: the Orchestrator is itself an AI that
understands semantics — routing decisions should be made by the AI, not by
tool-level matching algorithms.

Key changes:
- Removed session route operation and findBestMatch pseudocode entirely
- New Core Principle: 'AI Routes, Tools Provide + Execute'
- Added 'Why Tool-Level Matching Cannot Work' section
- R1 is now <active-sessions> context injection (harness provides the list)
- R2 is orchestrator.txt rewrite guiding AI to route-first
- No new tool verb needed — AI uses existing session send/create
- Phase 3 changed from 'hardcoded route primitive' to 'optional prompt
  strengthening if Phase 2 guidance is insufficient'
- Phase 1 + Phase 2 are the main body; no tool-layer matching ever
New section addressing the context bloat problem: injecting full session
details every turn wastes tokens on non-routing turns and scales poorly.

R1.1 analyzes 5 injection strategies (on-demand pull, full detail,
compact summary, conditional, incremental) and recommends compact
summary + on-demand detail:
- Default: inject compact one-liner per session (id|title|mode|status)
  only for non-terminal sessions (~30 tokens/session)
- On-demand: AI uses session ask/status for details when needed
- No dir or recent-activity in the injected block (confirmation-level
  info, not decision-level)

Also updated R1 example to match compact format, R2 prompt example to
reference compact format, and Code Impact section accordingly.
@wqymi wqymi force-pushed the docs/orchestrator-route-first-redesign branch from 477465b to f826382 Compare July 15, 2026 14:18
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