Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
96b6a8a
chore: start orchestration v2 branch
hoangvu12 Oct 16, 2026
dcd9167
feat(engine): budget the fork transcript behind a coverage header
hoangvu12 Oct 16, 2026
8d501ac
Merge ticket 01: budgeted fork history
hoangvu12 Oct 16, 2026
6bd5932
feat(harness): classify usage limits and sign-outs as structured erro…
hoangvu12 Oct 16, 2026
c16093d
feat(pi): deliver prompts safely - dispositions, rejections, settle p…
hoangvu12 Oct 16, 2026
4cdd7ec
Merge ticket 02: Pi prompt delivery hardening
hoangvu12 Oct 16, 2026
f6934ce
fix(harness): read Claude's resetsAt as unix seconds and tighten the …
hoangvu12 Oct 16, 2026
c5eb459
Merge ticket 05: structured limit and auth errors
hoangvu12 Oct 16, 2026
dc3edf4
fix(pi): await the armed acceptance by reference
hoangvu12 Oct 16, 2026
206adcf
feat(engine): record side-chat creators and message origins
hoangvu12 Oct 16, 2026
539fa5f
Merge ticket 11: side-chat creator and message origin
hoangvu12 Oct 16, 2026
bda510d
fix: pass the origin through re-dispatches, drop a stray field, filte…
hoangvu12 Oct 16, 2026
fc75646
fix: drop misplaced agent_origin inserts; expect the slot's window label
hoangvu12 Oct 16, 2026
38c01b3
feat(pi): steer mid-turn - pi becomes a step-boundary harness
hoangvu12 Oct 16, 2026
41ff18d
feat(forks): continue the real Codex thread from forked chats
hoangvu12 Oct 16, 2026
b4b018a
Merge ticket 03: Pi steers mid-turn
hoangvu12 Oct 16, 2026
e5cf72a
fix(pi): bind the new error kind in the compaction test
hoangvu12 Oct 16, 2026
745839c
fix(ui): move the misfiled provider_turn into the entry literal
hoangvu12 Oct 16, 2026
ac471e3
fix(pi): stop shadowing the controls helper in the old-pi test
hoangvu12 Oct 16, 2026
bb6aff2
feat(accounts): harness accounts show whether they are signed in
hoangvu12 Oct 16, 2026
d7f9038
Merge feat/orchestration-v2 into ov2/17-native-fork-codex
hoangvu12 Oct 16, 2026
9989ba4
Merge feat/orchestration-v2: ticket 05 (structured error kinds), 11, …
hoangvu12 Oct 16, 2026
b737e28
Merge ticket 17: native fork for Codex
hoangvu12 Oct 16, 2026
81d545e
fix(codex): format the fork fallback prompt positionally
hoangvu12 Oct 16, 2026
de63147
feat(engine): wake an idle parent when a side chat turn ends
hoangvu12 Oct 16, 2026
f34bd73
feat(engine): mark the harness account signed out from a run's error
hoangvu12 Oct 16, 2026
da6bc80
Merge feat/orchestration-v2 (tickets 03 + 17) into ticket 12
hoangvu12 Oct 16, 2026
6eb0ab5
Merge ticket 08: harness account sign-in state
hoangvu12 Oct 16, 2026
b0774b9
fix(engine): give the sign-in test request the provider fork field
hoangvu12 Oct 16, 2026
598ac0e
Merge ticket 12: completion notice wakes an idle parent
hoangvu12 Oct 16, 2026
f286ef6
feat(forks): fork from any assistant reply
hoangvu12 Oct 16, 2026
7fb1962
fix(engine): settle the borrow errors from the notice and sign-in merges
hoangvu12 Oct 16, 2026
0ef2f3b
Merge ticket 21: fork from any reply
hoangvu12 Oct 16, 2026
c4848ae
feat(engine): record Limited chats and Resume after usage limits
hoangvu12 Oct 16, 2026
b5713c0
Merge feat/orchestration-v2 (tickets 08, 12, 17, 21) into limited chats
hoangvu12 Oct 16, 2026
a6a94b4
Merge ticket 06: limited chats and resume
hoangvu12 Oct 16, 2026
ccf2920
feat(engine): completion notices wait for busy parents, merge, and su…
hoangvu12 Oct 16, 2026
44cfe81
Merge feat/orchestration-v2 (tickets 06, 08, 17, 21) into ticket 13
hoangvu12 Oct 16, 2026
6a4c304
feat(forks): fork a chat into a different harness and model
hoangvu12 Oct 16, 2026
d335274
Merge ticket 13: completion notices for busy parents and restarts
hoangvu12 Oct 16, 2026
a8195bf
feat(engine): one-way harness switch between turns
hoangvu12 Oct 16, 2026
04697ff
fix(wire): emit NoticeSide the way ts-rs lays out undocumented fields
hoangvu12 Oct 16, 2026
b09d33e
fix(forks): review pass on the fork dialog and its overrides
hoangvu12 Oct 16, 2026
abfce66
Merge ticket 22: fork into another harness
hoangvu12 Oct 16, 2026
f006aad
Merge feat/orchestration-v2 (tickets 06, 08, 13, 21 + wire fix) into …
hoangvu12 Oct 16, 2026
24ef5cc
Merge ticket 24: one-way harness switch
hoangvu12 Oct 16, 2026
347d3cb
fix(engine): reach the workspace through the engine inner on harness …
hoangvu12 Oct 16, 2026
02e4b5c
fix(ui): settle the desktop compile errors from the account, fork and…
hoangvu12 Oct 16, 2026
0f09fe7
feat(mcp): teach notice-driven orchestration in the instructions
hoangvu12 Oct 16, 2026
ff86c3d
Merge ticket 16: MCP orchestration instructions
hoangvu12 Oct 16, 2026
f4dc844
feat(engine): resume at reset — durable limited-chat continuations
hoangvu12 Oct 16, 2026
fc7869e
fix(engine): settle the account and notice unit-test compile errors
hoangvu12 Oct 16, 2026
4b1e546
fix(web): give the ring test account a sign-in state
hoangvu12 Oct 16, 2026
e5ad030
Merge feat/orchestration-v2 (tickets 13, 16, 22, 24 + CI fixes) into …
hoangvu12 Oct 16, 2026
d9c48c0
Merge feat/orchestration-v2 (account/notice test fixes) into resume-a…
hoangvu12 Oct 16, 2026
e4fab48
fix(pi): answer the settle probe in the reject-while-streaming fake
hoangvu12 Oct 16, 2026
2b99960
Merge CI fix: Pi reject-while-streaming fake answers the settle probe
hoangvu12 Oct 16, 2026
72d9ce9
Merge ticket 07: resume at reset
hoangvu12 Oct 16, 2026
13a4926
feat(ui): continue a Limited chat on another harness
hoangvu12 Oct 16, 2026
a4fb979
fix(ui): review pass on the continue-on-harness confirm
hoangvu12 Oct 16, 2026
11bed3e
fix(ui): settle the two picker tests ticket 24 unlocked
hoangvu12 Oct 16, 2026
d155144
Merge branch 'feat/orchestration-v2' into ov2/25-continue-limited-cha…
hoangvu12 Oct 16, 2026
5a086f8
Merge ticket 25: continue a Limited chat on another harness
hoangvu12 Oct 16, 2026
bac3c51
fix(pi): keep a FIFO delivery record per input, not an ack-time counter
hoangvu12 Oct 16, 2026
90e03f8
Merge CI fix: Pi keeps a FIFO delivery record per steer
hoangvu12 Oct 16, 2026
3080089
feat(engine): resume a Limited chat on another harness account
hoangvu12 Oct 16, 2026
08f8a28
Merge commit '4b1e54686' into ov2/09-resume-on-another-account
hoangvu12 Oct 16, 2026
e8636c4
fix(engine): settle the resume-at-reset compile errors
hoangvu12 Oct 16, 2026
3492272
Merge commit '90e03f81c' into ov2/09-resume-on-another-account
hoangvu12 Oct 16, 2026
e7d4d72
feat(ui): offer Sign in again on the failover picker's signed-out rows
hoangvu12 Oct 16, 2026
6eefcdf
Merge ticket 09: resume on another account
hoangvu12 Oct 16, 2026
8bd1c9b
fix(engine): never replay a consumed steer in a boot auto-resume
hoangvu12 Oct 16, 2026
fdb637b
Completion notices: acknowledgement, stop, and the loop cap (ticket 14)
hoangvu12 Oct 16, 2026
7b8d814
Merge ticket 14: completion notice ack, stop and cap
hoangvu12 Oct 16, 2026
a0573d4
Merge CI fix: picker tests after the one-way harness switch
hoangvu12 Oct 16, 2026
14a2b4d
Merge ticket 04: no steer replay after restart
hoangvu12 Oct 16, 2026
5622ae3
feat(web): Limited chats, resume actions and account sign-in state
hoangvu12 Oct 16, 2026
7faa328
Completion notices render as system-notice cards (ticket 15)
hoangvu12 Oct 16, 2026
755cdcd
feat(mcp): fork chats with a fork_chat tool
hoangvu12 Oct 16, 2026
cd06c36
fix(mcp): review pass on fork_chat
hoangvu12 Oct 16, 2026
31e2819
fix(proto): ResumeAccountCandidates cannot derive Default
hoangvu12 Oct 16, 2026
6523fc0
Merge ticket 23: fork_chat MCP tool
hoangvu12 Oct 16, 2026
b22abb8
fix(web): code-review fixes for the Limited/accounts parity
hoangvu12 Oct 16, 2026
03a926a
docs(web): note the signed-out rules in the account ring's header
hoangvu12 Oct 16, 2026
3bdf1e0
fix(engine): settle the account-failover compile errors
hoangvu12 Oct 16, 2026
73c8646
feat(forks): continue the real Claude session from forked chats
hoangvu12 Oct 16, 2026
7c78a05
Merge ticket 10: web Limited chats and accounts
hoangvu12 Oct 16, 2026
f57eaa7
Review round: notice side rows are clickable controls, with tests
hoangvu12 Oct 16, 2026
37f3203
Tidy the notice test comparison and wrap two long lines
hoangvu12 Oct 16, 2026
a12461c
fix(engine): box the recursive failover attempt as a Send future
hoangvu12 Oct 16, 2026
717174f
Merge ticket 15: completion notice rendering
hoangvu12 Oct 16, 2026
54b1492
fix: settle wiregen layout and desktop compile slips from tickets 07/09
hoangvu12 Oct 16, 2026
7fa3740
Merge ticket 18: native fork for Claude Code
hoangvu12 Oct 16, 2026
55e67cf
feat(forks): continue the real OpenCode session from forked chats
hoangvu12 Oct 16, 2026
9a0e359
feat(harness): forks of Pi chats continue the real Pi session
hoangvu12 Oct 16, 2026
927af1a
fix(harness): review pass on the Pi native fork
hoangvu12 Oct 16, 2026
b8d4b5e
fix(opencode): review pass on the native fork
hoangvu12 Oct 16, 2026
04faf31
fix(harness): settle the Claude fork compile slips
hoangvu12 Oct 16, 2026
d383bef
Merge ticket 19: native fork for Pi
hoangvu12 Oct 16, 2026
8ea77ba
Merge ticket 20: native fork for OpenCode
hoangvu12 Oct 16, 2026
39667ff
feat(web): fork from any reply, harness choice, and the harness switch
hoangvu12 Oct 16, 2026
cc2a2c9
fix(proto): camelCase the switched failover outcome's fields
hoangvu12 Oct 16, 2026
cda58d9
Merge feat/orchestration-v2 (tickets 15, 17-20) into ticket 26's web …
hoangvu12 Oct 17, 2026
6ab1c6b
Merge ticket 26: web forks and harness switch
hoangvu12 Oct 17, 2026
8db774b
fix(ui): keep the picker switch test's entities alive until the frame…
hoangvu12 Oct 17, 2026
732abc1
fix(ui): the notice short id keeps eight characters
hoangvu12 Oct 17, 2026
26f1229
fix(harness): settle the opencode fake-server compile slips
hoangvu12 Oct 17, 2026
66344ce
Merge CI fix: OpenCode fork test compile errors
hoangvu12 Oct 17, 2026
1e299bf
fix(web): settle the typecheck after the ticket 15/26 merge
hoangvu12 Oct 17, 2026
ffc3e8b
Merge CI fix: web typecheck after tickets 15/26
hoangvu12 Oct 17, 2026
e3ff6d4
fix(harness): the opencode suite reads past the TurnRef bookkeeping
hoangvu12 Oct 17, 2026
f0a9fd9
Merge CI fix: OpenCode tests skip turn refs
hoangvu12 Oct 17, 2026
4e5879d
fix(engine): settle the limited-account test compile slips
hoangvu12 Oct 17, 2026
01849dc
fix(harness): settle the claude suite CI red after ticket 18 wired it in
hoangvu12 Oct 17, 2026
a4ab05f
Merge CI fix: claude harness suite fixture and assertions
hoangvu12 Oct 17, 2026
1ce8f33
fix(engine): code-review group A — completion notices, limits, forks,…
hoangvu12 Oct 17, 2026
de7086f
fix(engine): the signed-out reprobe test waits out the backoff
hoangvu12 Oct 17, 2026
1a0854c
fix(engine): a rejected Devin key still reads signed out
hoangvu12 Oct 17, 2026
0dca92f
fix(harness): settle the pi suite CI red the claude fix unmasked
hoangvu12 Oct 17, 2026
6dfac3b
Merge CI fix: pi harness suite fixture
hoangvu12 Oct 17, 2026
46f6575
fix(mcp): settle the tool-test stub compile slips
hoangvu12 Oct 17, 2026
06f6d16
fix(engine): the notice text test reads eight-character short ids
hoangvu12 Oct 17, 2026
d9ad366
fix(engine): settle the integration-test compile slips
hoangvu12 Oct 17, 2026
234c754
fix(engine): the torn steer test queries the id the tear cut short
hoangvu12 Oct 17, 2026
7085b11
fix(harness): code-review group B — pi delivery, claude launch probe,…
hoangvu12 Oct 17, 2026
90dee66
fix(engine): notice details cap by characters; the notices mock strea…
hoangvu12 Oct 17, 2026
c7d8872
fix(engine): the scheduled-resume reload test arms resets in the future
hoangvu12 Oct 17, 2026
4239f5a
refactor: code-review section D — shared notice verbs and side-chat h…
hoangvu12 Oct 17, 2026
eba49b3
fix(ui,mcp,web): code-review group C + glossary strings
hoangvu12 Oct 17, 2026
0cf1d87
Merge branch 'feat/orchestration-v2' into ov2/code-review-fixes
hoangvu12 Oct 17, 2026
16668c2
test: settle the merge — claude fallback results, eight-char short ids
hoangvu12 Oct 17, 2026
bbc36fe
Merge code-review fixes (groups A-D, glossary)
hoangvu12 Oct 17, 2026
ffc04d6
fix(doc,harness): notice sides live in roboco-doc; the pi tests impor…
hoangvu12 Oct 17, 2026
44000a2
fix(engine,ui): settle the review-fix compile slips; parked notices r…
hoangvu12 Oct 17, 2026
812ec39
fix(engine): atomic writes discard the persisted file handle
hoangvu12 Oct 17, 2026
e2bfa70
fix(engine): the failover test seeds the usage cache in its camelCase…
hoangvu12 Oct 17, 2026
51cf5ad
fix(engine): agent_origin tests fork a settled turn, drop the core be…
hoangvu12 Oct 17, 2026
ff66695
Merge CI fix: agent_origin tests
hoangvu12 Oct 17, 2026
70f1945
fix(engine): continue_on_harness, limited_chats and completion_notice…
hoangvu12 Oct 17, 2026
0a0aaf7
Merge CI fix: continue-on-harness, limited and notice test races
hoangvu12 Oct 17, 2026
e7a7ffd
fix(engine): the merged-notice test waits for the released row's turn
hoangvu12 Oct 17, 2026
64b3aec
fix(doc): write the harness-switch seam's harness field
hoangvu12 Oct 17, 2026
7ec7d31
Merge fix: harness-switch seams keep their harness
hoangvu12 Oct 17, 2026
e0611d3
fix(engine): the plain-error test waits for the flush, not the Errore…
hoangvu12 Oct 17, 2026
f9de5dc
fix(doc,engine): the registry session row carries the scheduled resume
hoangvu12 Oct 17, 2026
ddddb94
test(engine): completion-notice waits key on the run they wait for
hoangvu12 Oct 17, 2026
5f816e7
Merge fix: registry rows carry the scheduled resume; notice test waits
hoangvu12 Oct 17, 2026
46f37cb
test(engine): the notices suite waits up to 30 s on loaded CI runners
hoangvu12 Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,32 @@ jobs:
run: |
cargo test --locked -p roboco-harness --lib opencode::
cargo test --locked -p roboco-harness --test opencode
- name: Codex native driver integration tests
# t3-orchestration-v2 ticket 17: the fake app-server suite covers
# the native fork (thread/fork + fallback paste) and provider turn
# refs; it was previously not run by any workflow.
run: cargo test --locked -p roboco-harness --test codex
- name: Claude native driver integration tests
# t3-orchestration-v2 ticket 18: the fake-CLI suite covers the
# native fork (--resume + --fork-session + --resume-session-at, the
# refused-flag and missing-session budgeted pastes) and provider
# turn refs (message uuids); it was previously not run by any
# workflow (unix-gated like the fake CLI itself).
run: cargo test --locked -p roboco-harness --test claude
- name: Pi native driver integration tests
run: |
cargo test --locked -p roboco-harness --test pi
cargo test --locked -p roboco-engine --test pi_resume
cargo test --locked -p roboco-engine --test pi_resume --test pi_steering --test pi_forks
- name: Engine-local storage and recovery
run: |
cargo test --locked -p roboco-sync --lib
cargo test --locked -p roboco-update --lib
cargo test --locked -p roboco-engine --lib --test engine_local --test restart_resume --test codex_subagents --test local_profiles --test project_actions_rpc --test project_action_commands --test project_actions_remote --test remote_settings
# t3-orchestration-v2 ticket 23: the Roboco MCP tool tests (the
# fork_chat seam). The crate's unit tests were previously not run
# by any workflow.
cargo test --locked -p roboco-mcp
cargo test --locked -p roboco-engine --lib --test account_failover --test account_sign_in_state --test agent_origin --test codex_subagents --test completion_notices --test continue_on_harness --test engine_local --test harness_switch --test limited_chats --test local_profiles --test project_action_commands --test project_actions_remote --test project_actions_rpc --test remote_settings --test restart_resume --test resume_at_reset --test side_chats


# Mobile Rust core (spec D1, android-app#02): the crates the Android app's
# Kotlin painter leans on. Pure Rust, no GPUI system deps.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ Verification: reading + `git show` only (ticket 30's budget); no code
changed, no cargo. Test execution deferred to the wave-final batched pass
(user directive) — nothing here to execute.

- **Reversal (2026-10-16, orchestration v2 ticket 03 —
`.scratch/t3-orchestration-v2/issues/03-pi-steers-mid-turn.md`):** items
8 and 9 above are superseded. The T3 study measured that our
TurnBoundary classification held every message sent to a working Pi chat
in the engine queue until `agent_settled` — "our steering rides pi's own
steer queue" was true on the wire and false in delivery. Ticket 02
already moved the driver to upstream's `prompt` +
`streamingBehavior: "steer"` shape; ticket 03 flips the declaration and
the registry descriptor to `StepBoundary` together, selects steering
mode `"all"` at session start unless the user configured one (upstream
#630's overwrite guard now fully applies), and covers both with tests
(fixture `set_steering_mode` handling, an engine-level pi steering test
driving the real queue/steer flow).

- Wave-final batched verification (2026-10-03, merged main `cf94f415`): one
batched pass over all lanes — ui lib 1521/1521; engine 529/530 (the one
failure is the documented pre-existing
Expand Down
21 changes: 21 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,27 @@ _Avoid_: API version, app version (that is `version`)
The durable conversation and work history with a harness. Owns its transcript, configuration, and message queue; survives harness restarts.
_Avoid_: Conversation, thread, session (when it means the chat)

**Side chat**:
A chat with a parent chat: forked from it in the UI, opened blank next to it, or created by its agent through the Roboco MCP tools. Agent-created side chats record their creator; user-created ones never notify it. Lists under the parent instead of the sidebar's main list.
_Avoid_: Child chat, subagent (that is a harness-spawned agent inside a run)

**Completion notice**:
The engine-authored system message that tells an agent-created side chat's creator how a turn it started ended: finished, failed (one-line reason), stopped, interrupted (lost to an engine restart without auto-resume), or waiting for input. One merged notice per delivery; it points the parent at `read_chat` rather than carrying the side chat's output. Outcomes the parent already observed (through `wait_for_turn`, a send with wait, `read_chat` or `get_chat`) owe no notice; stopping the parent drops the notices owed to it; at most 25 consecutive notice-started turns may run without a user message in between — past the cap, notices park in the parent's queue, paused, until the user acts.
_Avoid_: Report, callback, acknowledgement

**Chat status**:
A chat's live run state: idle, working, awaiting input, or errored.
_Avoid_: Session, state (when it means chat status)

**Limited**:
An errored chat whose last run ended on a harness usage limit — the additive limit record (harness, window, reset time, account) on its status row. Offers Resume, Resume at reset, Resume on another account, and Continue on another harness (a one-way harness switch followed by the same continue prompt); queued messages wait behind the continuation. Not a separate wire status: the chat stays errored.

_Avoid_: Rate-limited (that is the harness event), throttled, capped

**Resume at reset**:
A pending continuation the engine owns for a Limited chat: at the limit's reset time it sends the continue prompt like a manual Resume. Durable across engine restarts (an overdue one fires after boot); cancelled by a new message, archive, or delete. Armed by the banner action or the "Auto-resume limited chats" engine setting.
_Avoid_: Scheduled task, retry, snooze

**Chat doc**:
The chat's durable document — transcript, command ledger, and message queue in one doc, owned by the engine.
_Avoid_: Session doc
Expand Down Expand Up @@ -80,6 +97,10 @@ _Avoid_: Message (a command is not a message), request
A user message waiting in the chat's queue, optionally held until the turn ends, delivered in order.
_Avoid_: Draft, pending message

**Harness switch**:
A one-way switch of an existing chat to another harness between turns: the engine records a "Switched to <harness>" seam in the transcript, drops the chat's harness session, and the next send starts a fresh session carrying the budgeted history of everything above the seam. Switching back is another switch, never a resume.
_Avoid_: Harness handoff, migration, resume (the old session is dropped, not resumed)

## Harnesses

**Harness**:
Expand Down
42 changes: 42 additions & 0 deletions crates/doc/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ pub struct SessionCommandEntry {
pub status: SessionCommandStatus,
#[serde(default)]
pub resolution: Option<String>,
/// The chat this Run/Steer was sent BY through the Roboco MCP server —
/// structured attribution (additive; ADR 0005). None for user-typed
/// sends and for rows written before the field existed. Executing it
/// stamps the same origin on the user message entry it writes.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub origin_chat_id: Option<String>,
}

impl SessionCommandEntry {
Expand Down Expand Up @@ -187,6 +193,7 @@ mod tests {
expires_at: None,
status: SessionCommandStatus::Pending,
resolution: None,
origin_chat_id: None,
}
}

Expand Down Expand Up @@ -348,6 +355,39 @@ mod tests {
assert!(!can_composer_cancel(&applied, "device-a"));
}

#[test]
fn run_and_steer_entries_carry_their_origin_chat_additively() {
for payload in [
SessionCommandPayload::Run {
request: run_request(),
message_id: "m1".into(),
},
SessionCommandPayload::Steer {
prompt: "go".into(),
message_id: Some("m2".into()),
},
] {
let sent = SessionCommandEntry {
origin_chat_id: Some("chat-origin-1".into()),
..entry("c1", payload, 1_000)
};
let value = serde_json::to_value(&sent).unwrap();
assert_eq!(value["originChatId"], "chat-origin-1", "{value}");
assert_eq!(
serde_json::from_value::<SessionCommandEntry>(value).unwrap(),
sent
);
}
// Rows from older writers (and user-typed sends) carry no origin.
let plain = steer("c1", 1_000);
let value = serde_json::to_value(&plain).unwrap();
assert!(value.get("originChatId").is_none());
assert_eq!(
serde_json::from_value::<SessionCommandEntry>(value).unwrap(),
plain
);
}

fn run_request() -> RunRequest {
RunRequest {
prompt: "hello".into(),
Expand All @@ -362,6 +402,8 @@ mod tests {
worktree: None,
mcp: None,
resume: None,
provider_fork: None,
resume_fallback_history: None,
}
}
}
111 changes: 107 additions & 4 deletions crates/doc/src/parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use serde::{Deserialize, Serialize};
use ts_rs::TS;

use roboco_proto::{AgentEvent, SUBAGENT_INPUT_KEEP, ToolCall, ToolDiff, UserInputQuestion};
use roboco_proto::{AgentEvent, HarnessId, SUBAGENT_INPUT_KEEP, ToolCall, ToolDiff, UserInputQuestion};

use crate::constants::MSG_INLINE_MAX;

Expand Down Expand Up @@ -120,6 +120,57 @@ pub enum SubagentStatus {
Failed,
}

/// What the engine recorded when a notifiable side-chat turn came to rest
/// (orchestration v2). The outcome of one row of a completion notice.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub enum NoticeOutcome {
Completed,
Errored,
Stopped,
/// The run was lost to an engine restart without auto-resume (ticket
/// 13): the work never finished — distinct from a stop, which the
/// user chose.
Interrupted,
WaitingForInput,
}

impl NoticeOutcome {
/// The outcome's verb phrase — the ONE vocabulary the engine's notice
/// text (`"title" (id) <verb>`) and every client's notice card share, so
/// the card and the prose an agent reads never drift (the web mirrors
/// it in `noticeOutcomeVerb`).
pub fn verb(self) -> &'static str {
match self {
Self::Completed => "finished",
Self::Errored => "failed",
Self::Stopped => "was stopped",
Self::Interrupted => "was interrupted",
Self::WaitingForInput => "is waiting for input",
}
}
}

/// One side chat a completion notice reports: its id and title, the
/// recorded outcome, and the one-line reason (error text, restart reason or
/// question summary) when the outcome carries one.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct NoticeSide {
/// The side chat's full id — clients open the chat with it; the notice's
/// text names the short form.
pub chat_id: String,
/// The side chat's title as of the notice, so the transcript reads it
/// without a registry lookup (a later rename never rewrites history).
pub title: String,
pub outcome: NoticeOutcome,
/// The one-line detail for errored (reason), interrupted (restart
/// reason) and waiting-for-input (question summary) rows; absent
/// otherwise.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}

/// One rendered part of an assistant message.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(tag = "kind", rename_all = "camelCase")]
Expand Down Expand Up @@ -219,6 +270,39 @@ pub enum MessagePart {
/// source does not rewrite history.
source_title: String,
},
/// The seam a one-way harness switch leaves in a chat's transcript
/// (t3-orchestration-v2 ticket 24): everything above ran on another
/// harness whose provider session the switch dropped; the next send on
/// `harness` carries the budgeted history of everything above. Written
/// once per switch by the engine; renders as a "Switched to <harness>"
/// divider. Old desktop builds' unknown-kind fallback yields an empty
/// text part (invisible). There is no switch BACK: returning to the old
/// harness is just another one-way switch.
#[serde(rename_all = "camelCase")]
HarnessSwitch {
id: String,
/// The harness the chat switched TO (wire name, e.g. "codex").
harness: HarnessId,
},
/// The engine's completion notice (orchestration v2): an engine-authored
/// SYSTEM entry in a parent chat reporting the recorded outcomes of its
/// agent-created side chats' turns — finished, failed (one-line reason),
/// stopped, interrupted by an engine restart without auto-resume, or
/// waiting for input (one-line summary). The entry's sibling
/// text part carries the same notice as plain text, so older layouts —
/// whose unknown-kind fallback yields an empty part — stay readable.
/// Written only by the engine when it delivers a notice; never a user
/// message, never agent content.
#[serde(rename_all = "camelCase")]
CompletionNotice {
id: String,
/// One row per side chat the notice reports (a single outcome when
/// one side chat finished; several when outcomes merged — ticket 13).
/// Defaults for leniency: a partial row reads as an empty notice
/// rather than a decode failure.
#[serde(default)]
sides: Vec<NoticeSide>,
},
}

impl MessagePart {
Expand All @@ -230,7 +314,9 @@ impl MessagePart {
| MessagePart::Tool { id, .. }
| MessagePart::Input { id, .. }
| MessagePart::Error { id, .. }
| MessagePart::Fork { id, .. } => id,
| MessagePart::Fork { id, .. }
| MessagePart::HarnessSwitch { id, .. }
| MessagePart::CompletionNotice { id, .. } => id,
}
}

Expand Down Expand Up @@ -268,6 +354,19 @@ impl MessagePart {
source_title,
..
} => source_chat_id.len() + source_title.len(),
MessagePart::HarnessSwitch { harness, .. } => {
// Fixed-size structure (the wire name): never grows, so it
// never drives a continuation split.
serde_json::to_vec(harness).map_or(0, |v| v.len())
}
MessagePart::CompletionNotice { sides, .. } => sides
.iter()
.map(|side| {
side.chat_id.len()
+ side.title.len()
+ side.detail.as_ref().map_or(0, String::len)
})
.sum(),
}
}
}
Expand Down Expand Up @@ -424,7 +523,7 @@ pub fn fold_event_into_parts(out: &mut Vec<MessagePart>, event: &AgentEvent) {
}
}
}
AgentEvent::Error { message } => {
AgentEvent::Error { message, .. } => {
let id = format!("e{}", out.len());
out.push(MessagePart::Error {
id,
Expand Down Expand Up @@ -496,11 +595,15 @@ pub fn fold_event_into_parts(out: &mut Vec<MessagePart>, event: &AgentEvent) {
// AvailableCommands feeds the engine's per-harness command cache, not
// the transcript. UserMessage becomes its own doc ENTRY (the engine's
// subagent sink writes it), never a part of the assistant message.
// TurnRef names the provider turn the streaming entry belongs to —
// engine bookkeeping (stamped when the segment finishes), never a
// rendered part.
AgentEvent::AssistantMessageCompleted { .. }
| AgentEvent::Usage { .. }
| AgentEvent::ContextUsage { .. }
| AgentEvent::AvailableCommands { .. }
| AgentEvent::UserMessage { .. } => {}
| AgentEvent::UserMessage { .. }
| AgentEvent::TurnRef { .. } => {}
}
}

Expand Down
Loading
Loading