Repository navigation
Orchestration v2: Pi steering, completion notices, limited chats, native forks - #26
Merged
Merged
Conversation
A forked side chat's first prompt pasted the entire source transcript as raw JSON with no size limit (t3-orchestration-v2 ticket 01, spec story 58): long chats burned the new session's context before it started. One reusable engine piece, crates/engine/src/transcript_budget.rs, now builds what a fresh provider session receives instead: - contents: user and assistant text, command runs (input, exit code, output), errors and file-change names; reasoning and attachments stay out; continuation fragments join into one message; - selection: the latest user, latest assistant and first user messages, then the newest remainder, newest first, within 16k tokens counted at one token per UTF-8 byte; a transcript that fits whole rides complete with no note; long items are truncated with a visible marker; - a coverage header names the omitted count and points at the read_chat MCP tool with the source chat's id; - the new user message is never counted, never shortened. fork_history_prompt (sessions.rs) routes through it for both the fresh bootstrap and the owed-history warm/steer/resume paths; the native-command gate is untouched. Tickets 09, 17-20, 22 and 24 reuse the same builder. Tests: unit suite in the module (selection, truncation, contents policy, budget) plus side-chat engine integration tests for a long fork, a short fork, an oversized single message and the omission header. side_chats was not run by any CI workflow; ui-tests.yml now includes it. Not run locally per the verification policy; CI is the signal. No wire or persisted-name changes (ADR 0005): the builder reads the doc and writes only the delivered prompt.
…r kinds
AgentEvent::Error gains an optional structured kind (ADR 0005, additive on
the wire): usageLimit {window, resetsAt} or signedOut. The error message
stays the rendering source; older payloads and clients are unchanged.
- Claude Code: the blocking rate_limit_event now reads the CLI's resetsAt
(unix millis) and classifies as a usage limit with the window label; the
terse assistant rate_limit code classifies as a usage limit and
authentication_failed as signed out (parent and subagent paths). Overage
spending keeps its existing not-a-limit handling.
- Codex: turn-level usageLimitExceeded/rateLimitExceeded (turn/failed,
turn/completed with an error, the bare error notification, and child
threads) classify as a usage limit; unauthorized classifies as signed
out. The reset time comes from the latest account/rateLimits/updated
snapshot, which the driver now tracks (exhausted window, later reset
binds). A classified turn failure moves its text onto the Error event so
the transcript keeps exactly one error part while the kind reaches the
engine; unclassified failures keep their historical Done-only shape.
- Mock harness's scripted usage-limit error carries the kind too, so
engine-side Limited state can be tested without a real CLI.
- Wiregen: AgentErrorKind joins the export manifest; the generated web
types are regenerated by hand to match ts-rs byte-for-byte (no local
builds per the ticket override; the CI gate verifies).
- Tests: proto wire-shape pin (additivity both directions), Claude and
Codex normalize unit tests, fake-CLI scenarios (usage-limit,
rate-limit-completed, auth) and harness-seam assertions for every ticket
criterion, including no-kind/no-text-change for other errors.
…robe Orchestration v2 ticket 02 (.scratch/t3-orchestration-v2/issues/02): make the Pi harness prompt path robust. Pi stays a turn-boundary harness here; ticket 03 flips that. - Every prompt - turn starts and mid-run steers alike - goes out as `prompt` with streamingBehavior "steer": pi starts a run when idle and queues the prompt when an extension run is streaming, atomically against settlement. The bare `steer` command and bare `prompt` are no longer used. - The acknowledgement is read by disposition: started (a run began), queued (delivered at its user message_start echo - the consumed signal), handled (no run; the turn ends once pi reports it is not streaming, and any pending delivery record for it is retired). pi older than 0.99 reports none: inferred from whether a run starts (the settle probe). - A message counts as delivered only after pi accepts it: a rejected prompt never ends the session. The message returns to the driver queue (the engine at-least-once ledger keeps its entry until the Steered boundary), the rejection surfaces, and the message re-delivers once the turn ends - with a bounded retry rate (one immediate at-rest re-delivery per acceptance cycle, then the probe path and its re-arm delay). - No fixed acceptance timeout kills the run: pi preflight (extensions, the Roboco MCP bridge, compaction) may take arbitrarily long; Stop still interrupts it (one Interrupted Done, never an error-then-interrupted pair). - Turn end is agent_settled, then a state probe requiring not streaming, not compacting and zero pending messages, guarded by a generation counter bumped by agent_start, compaction_start, every prompt or steer (send and acknowledgement) and every settle. The probe re-arms after every prompt acknowledgement racing it and after any busy report; a stranded steer is recovered through clear_queue; a probe that keeps failing ends the turn errored - never a silent kill, with the failure counter reset for the next turn (T3 bugs 16846/19983 not copied). Tests (fake-pi-rpc.sh prior art): handled, rejected-while-streaming, slow preflight (31 s), stop-interrupts-slow-preflight, settle race, busy probe re-arm, probe failure, old-pi dispositionless inference. Not run locally (CI policy).
…classified Done
Review fixes on the previous commit:
- Claude Code's rate_limit_event resetsAt is unix SECONDS, not millis —
the repo's own live 2.1.228 capture (fixtures/claude/
live-2.1.228-background-subagent.jsonl) carries 1786985400 against a
session at 1786971716 (a five-hour window resetting 3.8h out; as millis
it would be January 1970). The parser now converts seconds → millis
like the Codex snapshot path, and the fixture/tests feed seconds.
- turn/completed only empties the Done's error text when the classified
Error event actually carried it (kind without a message no longer drops
the text silently).
- Comments: the mock knob no longer names the not-yet-defined Limited
state; the unknown-window "usage" label is documented on the proto
variant; window_label wording follows the glossary.
- Claude's assistant rate_limit code names the plain "usage" label
directly instead of fishing the fallback out of rate_window_label("").
Groundwork for completion notices (orchestration v2 ticket 11), all additive on the wire (ADR 0005): - a side chat created through the Roboco MCP create_chat / create_chats by an origin chat durably records the creating chat and a notify flag (default on; notify: false opts out per request). UI forks, blank side chats and human CLI creates never carry the mark, so they never notify; - Run and Steer commands (QueueCommand originChatId), queued rows (QueueMessage originChatId) and the user message entries they write carry the originating chat as structured data - through run, steer, hold-for-turn-end, the Stop that cancels a held send, and the retry re-issue (attribution text is unchanged on top of it); - wiregen: new AgentOrigin type; Chat.agentOrigin, SessionMessageEntry / SessionCommandEntry / QueuedMessage .originChatId, MutateParams createChat.agentOrigin; - glossary: Side chat in CONTEXT.md; - ui-tests.yml: the engine-local step runs --test agent_origin.
# Conflicts: # .github/workflows/ui-tests.yml
…r the error assertion
Orchestration v2 ticket 03 (.scratch/t3-orchestration-v2/issues/03): messages sent to a working Pi chat reach pi at its next step boundary, exactly as for Claude Code and Codex, instead of sitting in the engine queue until the whole run ends. - The harness declaration and the engine registry descriptor flip to StepBoundary together (the engine routes queue holds, the queued row action and MCP send_message auto mode off the descriptor; the queue row now reads "Steer" for pi). This reverses wave-4 ticket 30 items 8-9 - the reversal is recorded in that ticket decision record. - set_steering_mode "all" is sent at session start unless the user configured a mode (pi agent-dir or project .pi/settings.json), so a burst of steers is delivered together, in order. Best-effort: an older pi without the command logs and runs on its own default. Title runs never touch the setting. PiHarness::with_agent_dir pins the settings dir for hermetic tests. - Steers carry their attached images: the mid-run prompt stages the mailbox message attachments as inline ImageContent blocks, exactly like a turn-start prompt. - Stop sends clear_queue before abort: steers pi accepted but never consumed are cancelled with the turn - the engine drops their at-least-once ledger entries on the interrupted exit and their user entries already sit in the transcript, the same outcome as a held send cancelled by Stop. - A run that recovered from an error through a successful compaction retry ends completed: a successful compaction_end with willRetry clears the turn error that triggered it. Tests (fake-pi-rpc.sh prior art, extended): steering-mode default and the never-overwrite guard (agent-dir and project settings, session-file markers); a steer burst delivered together in order; a steer carrying an image (wire-level base64 assert); stop clearing pi queue before abort (fixture asserts the wire order and the stranded text); a compaction-recovered run completing. New engine integration suite pi_steering.rs (message-queue prior art, but driving the real Pi harness through the fixture): a mid-run message from a queued row's Steer action is steered into the live turn - in the transcript, queue empty, turn still working, one turn, no follow-up re-run - and a Stop with an unconsumed steer leaves it in the transcript unrun with no re-dispatch. Registry test: every descriptor mirrors its harness declaration. ui-tests.yml runs the new suite beside pi_resume. Not run locally (CI policy). No wire changes (ADR 0005): SteeringMode already carried step-boundary; nothing new crosses the wire.
The tracer bullet for native forks (t3-orchestration-v2 ticket 17,
spec stories 57, 58 and 66): forking a Codex chat now continues the
source's real harness session instead of pasting history into a fresh
one, with the budgeted transcript from ticket 01 as the fallback.
- Transcript entries record an additive provider turn reference
(harness, provider session id, turn ref). A new AgentEvent::TurnRef
carries it: the Codex driver emits it whenever the app-server adopts
a turn id (start response, turn/started, steer-as-new-turn), and the
engine stamps it on the assistant entry when its segment finishes.
- The ForkSideChat RPC records a native-fork intent on the new chat
(doc meta, additive) when the boundary entry carries a ref, instead
of only severing the session: source harness, provider session, and
the inclusive boundary turn.
- The fork's first dispatch injects the intent into RunRequest
(additive providerFork). The Codex driver calls thread/fork
{threadId, lastTurnId} instead of thread/resume; on success nothing
is pasted and the reported session counts as holding the history.
- Fallbacks: no ref (old chats), a changed harness, or a refused
thread/fork all fall back to the budgeted history — the engine
attaches the ticket-01 selection as the request's fallback prompt
and the driver pastes it in front of the user message on the fresh
thread it starts. The intent is consumed once.
- transcript_budget gains budgeted_transcript_prefix (the selection
without the user message) for the fallback; the paste path is
byte-identical.
- Wire: additive only (AgentEvent::TurnRef, RunRequest.providerFork,
SessionMessageEntry.providerTurn; generated TS regenerated by hand
in the wiregen layout). The codex fake app-server gains
fork-success/fork-refused scenarios; the codex test suite is now
wired into ui-tests.yml (it was not run by any workflow before).
Tests: proto wire round-trips, doc stamp/meta round-trips and salvage,
prefix composition, engine side-chat tests (ref-driven fork, consumed
once, no-refs and changed-harness fallbacks, entry stamping), Codex
harness tests (thread/fork params and no paste, refused-fork budgeted
paste, one TurnRef per turn). Not run locally per the verification
policy; CI is the signal.
The bulk field insertion anchored on the last field-looking line of each SessionMessageEntry literal, which for the dictation test's literal was the nested UserInputQuestion's options line — so provider_turn landed inside UserInputQuestion (which has no such field) instead of the entry. Re-audited every RunRequest/SessionMessageEntry literal the insert touched with a brace-depth-aware scanner (innermost enclosing literal must be the owning type; every non-spread literal must carry its field): this was the only misplacement.
Each harness account row gains two additive wire fields (ADR 0005): sign_in_state (signed-in / signed-out / unknown) and limited_until (the latest reset among exhausted windows). Older payloads parse as unknown. Engine (agent_accounts): - ProbeError::SignedOut is the hard-rejection signal: a saved Claude or Grok slot whose own refresh was refused (400 invalid_grant), a fresh token the provider still rejects, or a Devin key refusal (keys never lapse). It persists in the usage cache and marks the account signed out; every other failure (expired-but-CLI-refreshes, rate limit, network) leaves the state alone — ambiguous, never signed-out. - UsageEntry records the sign-in evidence; a successful probe flips it to signed-in. A signed-out mark recorded against credentials the slot no longer holds reads back as unknown, and fresh credentials lift the 30s freshness gate, so the reload right after a sign-in re-probes. - mark_account_signed_out records a run's structured signed-out error (ticket 05's kind; the engine wiring calls this) durably, offline. - The probe's failure reason stays on the row beside cached windows - stale meters no longer hide a sign-out (the display bug). Desktop (Settings -> Accounts and the composer account popover): - Signed-out rows carry a danger mark, show the reason under/beside their meters, and offer Sign in again, which runs the provider's own login flow (inline in the popover: browser + poll, or the paste-code fallback) for the account's provider group. - Switching to a signed-out account confirms first. Wiregen: AgentSignInState joins the export manifest; the generated web types are hand-updated to match ts-rs byte-for-byte (no local builds per the ticket override; the CI gate verifies). Tests: proto wire-shape pin (additivity both directions), usage-entry state transitions and staleness, the reason mapping, limited_until, and mock-server flows for the refused refresh, the ambiguous active login, the sign-in-again round trip, the run-error mark, and Devin revocation. All not run locally per the ticket override; CI on the PR is the signal.
Brings tickets 02, 03, 05 and 11 (Pi prompt delivery, Pi mid-turn steering, structured limit/auth error kinds, side-chat creators and message origins) plus their CI fixes under the native-fork branch. Three textual conflicts, resolved by keeping both sides: - schema.rs / SessionMessageEntry.ts: the entry gained BOTH ticket 11's origin_chat_id and ticket 17's provider_turn (field order matches the merged Rust struct: durationMs, originChatId, providerTurn); - AgentEvent.ts: ticket 05's error `kind` and ticket 17's `turnRef` variant (between userMessage and subagent, per declaration order). Auto-merges audited and repaired where the two bulk field inserts met: - my dispatch call site in the new ref test gained the 6th origin arg; - transcript_budget's test helper gained origin_chat_id (ticket 11's insert never reached this branch's file); - agent_origin.rs (ticket 11's new tests) gained provider_fork and provider_turn on its literals. A brace-depth-aware audit (every SessionMessageEntry literal carries both new fields, every RunRequest literal carries provider_fork, every field sits at its literal's own depth) now reports clean; dispatch/ steer call sites all pass the origin argument.
…02/03 follow-ups # Conflicts: # crates/proto/src/entities.rs # web/packages/proto/src/generated/index.ts
Completion notices, the tracer bullet (orchestration v2 ticket 12): - a turn of an agent-created, notifying side chat is notifiable when its starting message or a steer within it came from the parent: the run handle carries the notifiable parent, armed at dispatch/steer time from the durable Chat.agentOrigin row (notify: false opts out; user-created side chats have no row and never notify; a user message starting a turn resets the claim, a mid-turn parent steer arms it); - when a notifiable turn ends, the engine records an outcome from its own turn-end state - completed (parked session, quiesce watchdog or final Done, gated on the completion marker), errored with a one-line reason, stopped, or waiting for input with a one-line summary of the question - and wakes an idle parent with an engine-authored notice message that starts a parent turn on its own harness; a busy parent is left to ticket 13 (the notice is never steered mid-turn); - the notice is a SYSTEM entry, never a user message, carrying a new MessagePart::CompletionNotice marker (per side chat: title, full id, outcome, one-line detail) with the notice text as a plain sibling part so older layouts stay readable; the text names each side chat by title and short id and points the agent at read_chat for the full result; - wiregen: new NoticeOutcome / NoticeSide types (additive, ADR 0005), hand-generated TS alongside; ui-tests.yml's engine step now runs --test completion_notices; - base repairs from ticket 11's fallout so the crate compiles: the two 5-arg dispatch_with_source_context calls in sessions.rs (auto-resume now threads the revived user entry's origin; the orphaned-steer re-dispatch keeps the steer's origin via RoutedSteer.origin_chat_id) and the transcript_budget test helper's missing origin_chat_id.
Wire ticket 08's account state to ticket 05's structured error kind: when a run's AgentEvent::Error carries kind signedOut (directly, or tagged inside a subagent event — the child runs on the same login), the engine resolves the harness's live login and marks its account signed out on the harness-accounts store the RPCs list. The mark is best-effort (logged, never failing the run), once per run, and lands before the run's terminal state settles. - SessionsEngine gains the accounts hook (set at assembly, replaceable so a test's isolated store swaps in over the detected one); Inner::note_run_signed_out drives AgentAccounts::mark_live_signed_out, which lists first so the live login's slot exists even when no accounts view ever ran, then marks. - AgentAccounts::with_mocked_endpoints: doc-hidden test seam mirroring override_cli — every provider endpoint on one local mock server, the same set the in-crate provider tests build on (mocked_endpoints, moved up from provider_tests). - Engine integration tests (account_sign_in_state.rs, on the ui-tests engine line): a scripted Claude-shaped harness emits the same classified error the Claude driver attaches to authentication_failed; the run marks the active account signed out through ListAgentAccounts, and a later successful probe against the mock usage endpoint flips it back to signed-in. A second test covers the subagent-tagged shape. Not run locally per the ticket override; CI verifies.
Brings the PR branch's Pi mid-turn steering (03), the Codex native fork with provider turn refs (17) and the CI fixes onto the completion-notice branch. Conflicts (both in crates/engine/src/sessions.rs, both the ticket-11 5-arg dispatch_with_source_context fallout the PR branch had also fixed by passing None): resolved keeping this branch's origin-preserving calls - the auto-resume re-dispatch threads the revived user entry's origin, and the orphaned-steer re-dispatch keeps the steer's origin via RoutedSteer.origin_chat_id, so re-issued parent messages keep their turns notifiable. Ticket 17 integration in the new ticket-12 code: SessionMessageEntry literals gained provider_turn (the engine-authored notice carries none - no provider turn backs it, a fork falls back to the budgeted paste like the fork seam), the test RunRequest literals gained provider_fork, and the transcript_budget test helper kept a single origin_chat_id after the auto-merge had duplicated it. AgentEvent's TurnRef arm and the fold's catch-all were already handled by the PR branch side.
# Conflicts: # .github/workflows/ui-tests.yml # crates/engine/src/sessions.rs
… limits
B1 pi caps a message it keeps rejecting (two deliveries): the turn ends
errored, never an Error + Completed loop
B2 a failed settle probe clears stale delivery records and drains the
driver-owned queue like a clean settle (no wedged mailbox)
B3 Stop only takes the already-settled shortcut when nothing is in
flight; a probe armed mid-run still clears the queue and aborts
B4 a quiescent probe never ends the turn while a prompt or steer awaits
its acknowledgement (re-arms after the delay)
B5 steer images load before the call is queued: bursts keep mailbox
order on the wire
B6 a stranded steer recovers as its original message (attachments and
config intact)
B7 the settle ref capture is bounded and a Stop mid-capture closes the
turn interrupted at once
B8 the unbounded acceptance is documented as deliberate
B9 pi fork notice uses glossary wording (chat transcript, trimmed)
B10 claude resetsAt parses leniently (number, float, numeric string);
a malformed value never fails the frame
B11 a claude fork whose first result errors before any assistant output
retries once fresh with the budgeted transcript
B12 same for a failover resume carrying resume_fallback_history
B13 codex rateLimitExceeded is a usage limit only with a live exhausted
window; past resets are ignored
B14 pi fork_boundary returns a named ForkBoundary
D one prompt_with_history helper for every driver's fallback paste
Tests added (not run; CI): pi late-ack, mixed image burst, reject cap,
queued-behind-failing-probe, stranded image steer, Stop with armed probe;
claude fork bad uuid, resume refused after launch, lenient resetsAt;
codex limit classification (live vs lifted reset, bare 429).
No wire changes.
…eader - NoticeOutcome::verb (crates/doc) is the one outcome vocabulary: the engine's notice text and the desktop notice card both read it (the card's "stopped"/"interrupted"/"waiting for input" drifted from the engine's "was stopped"/...); the web mirror follows. - roboco_proto::SIDE_CHAT_MESSAGE_HEADER_TAIL: the notifying side-chat message header's tail, shared by the MCP server that writes it and the transcript that folds it (the MCP use lands with group C). - agent_accounts: failover_candidates' doc moved off failover_signed_out; rank_failover_candidates' doc says the org penalty is the primary key. Tests updated (not run; CI): desktop + web notice verb tables. No wire changes.
C1 desktop failover candidates: a failed load is remembered per chat
(no refetch every render) with a "Retry accounts" pill
C2 fork / continue dialogs take their stale-load guard from a monotonic
Shell counter (a reopened dialog never receives an old load)
C3 the failover outcome notice is keyed by chat id
C4 cancel_sign_in's CancelAgentLogin is detached, never in action_task
C5 paste-code submit sets "submitting" only after the engine lookup
C6 a refused harness switch restores the previous config and reports
the reason (desktop failure chip via HarnessSwitchFailed; web draft
revert + failure notice)
C7 the harness switch has its own task slot (harness_switch_task)
C8 harness_switchable reads the staleness-checked indicator
C9 user-made side chats never inherit the parent's agent_origin
C10 web Resume sends only the continue prompt (bare send): staged files,
review comments and the typed draft stay untouched
C11 web surface-registry: every hook above the picker early returns
C12 web banner Resume-at-reset / Cancel errors surface via the notice
C13 web fork dialog: a fork settling after close never writes into a
reopened dialog
C14 web Limited banner gated by the send overlay (limitedBannerShows)
C15 RESUME_CONTINUE_PROMPT emitted by wiregen into @roboco/proto
constants; the web re-exports it
C16 MCP instructions: the parent hears of a side chat's reply only when
it did not opt out (notify: false)
C17 MCP fork_chat sends targetDeviceId like the desktop dialog;
AgentOrigin serialized, not hand-built
C18 continue dialog default pick via resolve_default_harness (test calls
it); the Limited strip renders from a LimitedAction model the test
asserts; harness_switch test asserts no resumable session instead of
the internal empty-string marker
Glossary: continue dialog/banner (harness, chat), fork dialog note
(transcript), MCP fork instructions (chat, harness).
Wire: additive generated constant RESUME_CONTINUE_PROMPT
(web/packages/proto/src/generated/constants.ts, wiregen constants_ts).
Tests added/updated (not run; CI): desktop refused-switch revert, stale
Working unlock, user side chat origin, Limited strip actions; MCP
instructions + fork payload; web bare Resume, fork close race,
limitedBannerShows.
The PR branch now forwards a result frame's text on Done: the two new
Claude launch-probe fallback tests (B11/B12) expect it. The short-id
helpers keep eight characters ("chat-sid"): the engine's char-boundary
test and the web notice tests asserted seven.
…elease under their row id; a refused switch drops the local picks
…fore restart, confirm steers - user_created_chats_are_never_marked_agent_created seeded only a user entry; the fork RPC copies through the latest complete reply, so it refused. Seed a settled turn like the agent-fork test does. - agent_created_side_chats_record_their_creator_and_notify_durably reassembled while the first EngineCore (and its data-dir instance lock) was still alive. Drop it first, as restart_resume does. - steer_commands_stamp_their_origin_on_the_user_entry: HeldHarness drained steers without a Steered boundary, so the run exit re-dispatched the unconfirmed steer as a fresh turn that hung past the single finish. The harness now confirms each steer like a step-boundary agent.
…s tests match the engine - continue_on_harness queue_run sends from the chat row's cwd (/tmp): queued rows dispatch from the row and a harness session only resumes from the cwd it was opened in, so the follow-up never resumed the continuation's session. - The Limit stand-in steers at the turn boundary like the Claude driver and the opening turn runs the held Run's model, so the mid-turn Run is held instead of replacing the limited turn; the test waits for the hold before ending the turn. - Waits for a new turn key on the mock holding the run (prompt recorded, after subscribing to finish) instead of Working, which publishes before run() subscribes and could drop the finish signal. - The loop-cap test's last wait is for the 27th notice entry; 26 was already true before round 27.
hoangvu12
marked this pull request as ready for review
October 17, 2026 11:10
push_part never wrote DocPartJson.harness, so every HarnessSwitch part pushed into a session doc read back as an empty text part: the seam was invisible to the switch tests and to the engine's history-seam gating (is_harness_switch_seam), so a post-switch send carried no history.
RegistryDoc::upsert_session (and the legacy seed) never wrote scheduledResume — only the old WorkspaceDoc writer did — so the armed continuation never reached the workspace mirror: not on arm, not on the boot republish. resume_at_reset: the scripted harness subscribes to finish before logging the prompt, and the queued-row test releases the continuation's turn so the row can follow it.
Scripted takes its turn before logging the prompt, so a seen prompt means the run already holds its scripted turn and the next run cannot take it. run_to_idle replaces queue_run + wait_idle: it waits for the run's own prompt (its Working row is published by then) before the idle wait, which otherwise passes on the chat's previous Idle row. The 25-round loop-cap loops wait for each round's notice turn to start before waiting the parent idle (the notice entry lands before that dispatch).
hoangvu12
added a commit
that referenced
this pull request
Oct 17, 2026
Orchestration v2 (PR #26): - Pi steers at step boundaries: messages sent mid-run land in the running turn instead of waiting for it to end. - Side chats report back: a parent chat gets one completion notice when its side chats finish, fail, stop or wait for input. - Limited chats: a chat that hits a usage limit shows when it resets, can resume at the reset, switch account (Claude Code, Codex) or continue on another harness. - Native forks for Codex, Claude Code, Pi and OpenCode; other harnesses fork by pasting the trimmed transcript. - Switching a chat's harness carries the conversation over.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the Orchestration v2 spec (local tracker:
.scratch/t3-orchestration-v2/spec.md): Pi steering, completion notices, limited chats with account failover, and native forks with harness switch.Closes the spec
t3-orchestration-v2and tickets (local markdown under.scratch/t3-orchestration-v2/issues/):No local builds or tests were run (user policy); CI on this PR is the verification signal.