Skip to content

[Feature]: Optional agent-selection policy hook for orchestration worker-start #21747

Open

Description

@karoliang

Problem or use case

orca orchestration worker-start takes --agent and --model as a human decision, made once and then repeated by habit. With several subscriptions renewing on different dates, that decision goes stale within hours: the plan that was the obvious choice this morning is at 90% by the afternoon, while another has just rolled over and its unused window is about to expire.

Today the only ways to react are to remember, or to discover the wall by having a worker fail.

#20512 asks for automatic failover between several accounts of the same provider. The same gap exists one level up, across agents and models:

  • Mechanical edits and ordinary implementation do not need the strongest model, and running them on one burns a scarce allowance twenty times faster than a cheap model would. On a percent-bucket plan, model price is the burn multiplier.
  • Design, diagnosis, and anything touching auth, migrations or money is worth escalating, but only when there is headroom to escalate into.
  • Capacity in a window that resets in three hours is about to be thrown away; capacity in a monthly window is the scarce thing. Spending them in the wrong order wastes the first and exhausts the second.

None of that is Orca's business to decide, and it should not be. But Orca is the only component that knows the accounts, the per-account homes, and the usage.

Proposed solution

An optional policy hook, consulted by worker-start when configured, and ignored entirely when not:

// settings
"orchestration": {
  "agentPolicy": "/usr/local/bin/my-router --json"
}

Orca writes a JSON object on stdin:

{
  "spec": "<task text>",
  "worktree": "...",
  "repo": "...",
  "available": [
    {"agent": "codex", "account": "acct_a", "models": ["gpt-6-astra"], "usage": {...}},
    {"agent": "claude", "account": "acct_b", "models": ["claude-opus-5"], "usage": {...}}
  ],
  "requested": {"agent": "opencode", "model": null}
}

and reads one object back:

{"agent": "codex", "model": "gpt-6-astra", "effort": "high", "reason": "design work, and the weekly window resets in 3h with room left"}

Rules that keep it safe:

  • No hook configured, non-zero exit, invalid output or a timeout, and worker-start proceeds exactly as it does today with the requested agent. The hook can only ever refine a default, never block a launch.
  • The returned reason is recorded on the dispatch, so task-list and dispatch-show explain after the fact why a worker was started on a given agent.
  • Same hook consulted on an automatic retry after a rate-limit stall, which is where it composes with [Feature]: Automatic account failover on rate limit for managed Claude/Codex subscription accounts #20512: that issue chooses another account, this one can choose another agent entirely when no account of that provider has room.

Alternatives or additional context

The workaround is a wrapper script that decides and then calls worker-start, which is what I am building now as a standalone tool. It works, but it is strictly worse in two ways: it cannot see Orca's managed per-account state, so it reasons about host-level quota rather than the account the pane will really use, and it is out of the loop on any retry Orca performs internally.

Deliberately not proposed here: Orca shipping a policy of its own. The useful split is that Orca owns the facts and the mechanism, and the user owns the policy, which is also why this is a hook rather than a settings matrix.

Related: #20512 (account failover), #14952 (custom agent binaries for worker-start), #18329 (choosing model and effort at create time).

Activity

  1. karoliang commented on Sep 21, 2026

    @karoliang
    Author

    Follow-up from implementing the local router: Rightsize #21 records the execution contract needed to make quota-aware admission trustworthy.

    Read-only inspection of Orca1.4.205 and upstream ffb79c71e04e79df08179530f8e7bc3683363e3b found that WorkerStartParams has agent/model/effort but no explicit account reference or prepared-session handle. The structured setup gate waits for repository setup, rather than native-account preparation. Internal account services alone do not let a caller prove which account will consume the task.

    A small versioned preparation/launch boundary would let the external policy own its decision while Orca owns execution:

    • Prepare without task inference; return an opaque selected account reference, provider-confirmed quota identity, host/runtime incarnation, native session, and effective model/effort/permissions. No token export.
    • Submit the task to that same prepared context with a durable caller idempotency key; reject account/config changes between preparation and submission.
    • Return the existing run/task/dispatch identity joined to the exact native turn/message and account. Preserve the current unknown-outcome distinctions.
    • Expose attributable terminal/usage/cancellation evidence and read-only recovery after a lost acknowledgement, without replaying the prompt.

    The advisory hook proposed here can retain its existing fallback semantics. A caller that has explicitly enabled managed quota admission also needs a strict path: an unverified preparation or account mismatch cannot proceed on another account, and internal retries must not silently spend outside the admitted identity. This can be a separate opt-in contract; no built-in routing policy or credential store is requested.

    No worker was launched or installed runtime modified during the inspection. Is there an existing supported preparation boundary that supplies these guarantees, or should this extend the worker-start contract?

  2. naochan3 commented on Oct 5, 2026

    @naochan3

    +1, with a user-side perspective and a short field report.

    Field report. On Windows (Orca 1.4.220) I run a Claude chat as the coordinator and let it delegate to Codex workers via orca orchestration worker-start --agent codex --model … --effort …, driven only by a routing table in the repo's instruction file (large implementation → gpt-6-astra, small fixes and image generation → gpt-6.1-sol, design/writing/final judgment → the coordinator itself). It works end to end today: the coordinator picked Codex on its own for a multi-file task, waited for worker_done, reviewed and fixed the result, then released the worker. So the plumbing is there; what is missing is exactly what this issue describes, plus a few things below.

    What I would add to the proposal

    1. Routing should be the default experience, not a power-user setting. As a user I want to choose one default model (the one I talk to) and never think about models again. Picking agent/model/effort per task is the part people are bad at and do not want to do.
    2. "Do not delegate" must be a valid answer. Delegation has real overhead (my multi-file task took ~14 min including handoff, and the coordinator had to rework parts of it). The policy should be able to return "coordinator handles it" so small or context-heavy tasks are not shipped out just because a hook exists.
    3. Record the outcome next to the reason. reason on the dispatch is great. Recording duration, outcome and whether the coordinator had to rework the result next to it would let users (and router authors) check whether a routing rule actually helps, instead of trusting a black box. Today I log this by hand in a markdown table.
    4. Show the decision in the chat, with a one-click override. e.g. "→ Codex · gpt-6-astra · high (large multi-file change, weekly window resets in 3h)". If it is wrong, I should be able to say "do it yourself" or pick another target without re-prompting.

    Quota awareness (the core of this issue) is the gap I hit most: neither the coordinator nor the routing table can see remaining Claude/Codex windows, so routing ignores the one signal that actually changes the right answer during the day.

  3. xiemintech commented on Oct 10, 2026

    @xiemintech

    Adding a concrete quota-aware orchestration use case for local Claude Code and Codex subscriptions across multiple worktrees:

    • The coordinator should be able to read the current account actually bound to each worker, plus machine-readable quota for each account and window (provider, opaque account ID, 5-hour/weekly remaining percentage, reset time, observation time/freshness, and auth/error state). Related: [Feature]: Expose provider usage and rate-limit windows over the CLI and RPC as JSON #21746.
    • A configurable admission rule should support, for example: when Claude's 5-hour remaining quota falls below 5%, stop dispatching new work to that account; use another eligible Claude account or Codex instead. The account choice must be bound to the launched worker, including headless worker-start and retries. Related: [Feature]: Select the Claude account for headless/agent spawns via CLI (--account / orca account list) #8770 and [Feature]: Automatic account failover on rate limit for managed Claude/Codex subscription accounts #20512.
    • If no eligible account/provider has capacity, the policy needs an explicit defer/hold result and automatic retry after the relevant reset. That is a requested extension to this issue's current "hook can never block a launch" rule. A running turn should reach a safe checkpoint rather than be killed mid-turn; already-running workers should not silently change credentials.
    • Expose the selected provider/account and decision reason, and treat stale/unknown usage as distinct from confirmed available quota. An opt-in strict mode should refuse a launch if the selected account cannot be verified, instead of silently falling back and consuming a different subscription.

    Acceptance case: with Claude account A at 4% remaining in its 5-hour window, a queued task in any worktree is assigned to eligible account B or Codex; if neither has capacity, it stays queued and resumes after a verified reset.

    Additional handoff requirement: Orca should be able to continuously refresh a dedicated, machine-managed quota section in the project handoff document, so a replacement coordinator or worker in any worktree can pick up the latest subscription state and scheduling decision. Update it before dispatch/handoff and when quota, account binding, or reset status materially changes. Include provider, opaque account label, each window's remaining %, reset time, observed-at/freshness, current routing/paused state, and the reason for any switch. Mark unavailable or stale measurements explicitly; do not write access tokens, cookies, email addresses, or other credentials. The section should be atomically replaced rather than appended on every poll, to avoid stale entries and noisy file conflicts.

  4. karoliang commented on Oct 13, 2026

    @karoliang
    Author

    Follow-up from the blocked consumer launch on Orca 1.4.224: external routing accepts the complete repair task and its failure evidence, but the launch cannot prove that the receiving OpenCode worker uses the subscription account whose quota was probed.

    MiniMax and GLM are separate provider token-plan accounts; OpenCode is their client. This host currently has no managed OpenCode profile. The CLI supports adding a private OpenCode profile, but registering/selecting one alone does not supply the required receiving-worker receipt. No credentials were changed, worker launched or account gate bypassed during this inspection.

    Read-only inspection of upstream source at 4d65146 identifies existing seams to reuse (source main is not installed-runtime proof):

    The narrow missing contract is an opt-in, versioned runtime-owned receipt from the actual receiving process, or a runtime-owned execution channel that proves it delivers the task to that same prepared process/account. It should bind an opaque provider-plan principal matching the external quota probe, effective provider/model/effort, execution-host/process or session incarnation, worktree/cwd, full UTF-8 task hash, and Run/Task/Dispatch. No token export or new routing/scheduling policy is requested.

    Missing capability, wrong account, credential rotation, model fallback, changed task or reused/unknown process must refuse before task/model input. Existing-terminal attach must not bypass the check. Lost-ack recovery must discover the same receipt/attempt without replaying the task. Profile selection, requested-settings echo or a separate probe server must not be labelled verified receiving identity.

    Could a maintainer identify the supported existing boundary, or an owning source task/PR and target runtime version for this extension? The consumer will keep the launch held until the installed runtime can prove this contract, then perform one bounded source-only retry with unchanged full requirements, failure history and independent review. The advisory policy hook can retain its existing compatibility behavior; this request is specifically the strict account-bound launch path described in the earlier follow-up.

  5. Gabrielgvl commented on Oct 14, 2026

    @Gabrielgvl

    +1, with a field report from an external router we run in front of worker-start. It has handled about 245 routes since Oct 9 on 1.4.221.

    For each task, the router does three things:

    • It asks a classifier for the minimum capability tier.
    • It walks a hand-ordered, cheapest-first chain of agent, model and effort choices for that task kind and tier.
    • It skips any provider whose quota window is exhausted. That comes from account list usage, plus a local cooldown that a worker writes when it hits a 429.

    Grading and review tasks also exclude the implementer's quota provider.

    What we ran into that a hook contract should cover:

    1. The chosen model has to reach the worker. For pi and devin, worker-start rejects --model (orchestration worker-start: accept --model (and --effort) for the pi and devin agents #26971 / feat(orchestration): support pi and devin worker launch models #27012). So we start the agent in our own terminal and attach it with --terminal. Orca records those terminals as external, and worker-release keeps them. They leak unless the caller closes them; we had 100+ idle agent processes before we noticed. A hook that returns a model only helps if worker-start can apply that model for every agent.
    2. Usage data can be stale. account list entries are sometimes hours old. We treat an entry older than 30 minutes as advisory, and only trust an "exhausted" window that has a future reset time. Please include the observation time for each window in the hook input, as @xiemintech suggests.
    3. Verifiers need independence. A verification task must not run on the same quota provider as the work it checks. A pass-through constraint field in the hook input, such as providers to exclude, would cover this without Orca having to know about grading.

    +1 to recording reason on the dispatch. Today we keep our own append-only log of routing decisions because the dispatch carries none.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions