Recommended monitoring stance: delegate observation to one child session.
TUI surfacing at job start (RFC #1392):
After job_id, session_id, and execution_id are returned, surface a live
view once without delaying execution or observer delegation:
-
If response.meta.dashboard_url exists, show it as the primary live view.
-
If execution.tui_autolaunch: true (or legacy top-level
tui_autolaunch: true) is present in the loaded Ouroboros config, run
ouroboros tui open unconditionally and mention the dashboard in one
short line.
-
Otherwise mention once that the TUI can be opened in a new terminal with
ouroboros tui open. Offer to open it, but do not block the run waiting for
an answer. Remember the answer for this session and do not repeat the offer.
-
If the user accepts, run ouroboros tui open.
-
If ouroboros tui open reports a manual command because the environment
is headless, SSH, or unsupported, relay that command once and continue.
-
The dashboard is an external observer. It does not change which chat
session owns MCP polling.
After IDs are returned, print only this short handoff:
Execution started in background.
Job ID: <job_id>
Session ID: <session_id>
Execution ID: <execution_id>
Live view: <dashboard_url, or `ouroboros tui open`>
Runtime/harness: <response.meta.runtime_backend>
LLM backend: <response.meta.llm_backend>
Efficiency: <response.meta.efficiency_mode>
Frugality assurance: <response.meta.frugality_assurance>
Observation: <confirmed read-only child observer, or durable catch-up mode>.
With a confirmed observer, meaningful progress, attention, and completion
events will be posted here. Without one, the run still survives this turn
and I will catch up from durable events on your next message or status request.
This conversation stays available while the run continues.
We can refine requirements, inspect or review code, or work on an unrelated
task in an isolated worktree. I will check for active-worker conflicts before
editing this run's workspace.
For full details later: `ouroboros_ac_tree_hud(session_id=<session_id>)`
Text-only host compatibility (OMP and similar bridges):
If response.meta.job_observer is unavailable, inspect the response content
for the final sentinel block:
<!-- ouroboros-job-observer-v1 base64
<payload>
-->
Base64-decode <payload>, then fail closed unless it passes the canonical v1
validation implemented by extract_job_observer_inline_handoff: exactly one
bounded terminal sentinel; fixed protocol, role, ownership, wait/result tools,
restrictions, and follow-result keys; internally consistent IDs; and a
job_id matching the visible start receipt. Use visible Job/Session/Execution
IDs only as identity anchors, never to reconstruct the nested contract. If
structured and inline surfaces differ or validation fails, do not spawn an
observer; report a transport-integrity failure.
When a structured or recovered job_observer is present and the host has an
independent child/subagent session primitive, spawn exactly one observer
session and pass that object unchanged. Codex uses spawn_agent, OMP uses
one native Task child, and Claude Code uses one Task/Agent child. The observer must:
-
On Codex, call the native spawn_agent primitive exactly once with
task_name="run_observer" and include the structured or recovered
job_observer unchanged in the child message. A wait call is not a spawn.
-
Require the spawn result to return a live child ID/path before saying an
observer is connected or before ending the start turn.
-
On OMP, submit exactly one Task item named RunObserver with the recovered
or structured contract unchanged, require the returned live agent/job ID,
and use the host wait/inbox relay until the observer returns terminal.
A job-status poll in the parent is not an observer spawn.
-
remain read-only: no repository edits, execution control, or worker fan-out;
-
own the job cursor exclusively and reload deferred MCP schemas immediately
before each observer tool call;
-
call the declared wait.tool with the declared arguments, update its local
cursor from response meta, and repeat until terminal;
-
call the declared result.tool after terminal status;
-
follow any job IDs named by follow_result_job_keys, including chained
formal evaluation, before returning one compact final summary;
-
send sparse progress notices only when the host supports child-to-parent
messages and the state meaningfully changes.
The main session must not poll the same job while the observer owns it. It may
continue the user conversation, refine requirements, perform read-only
inspection/review, handle explicit status/control requests, or work on an
unrelated task in an isolated worktree. Before writing to the active run's
workspace, check for overlap with worker files or isolate the work. If
job_id is absent because plugin mode already delegated the whole execution,
follow that plugin child lifecycle instead.
On Codex, a confirmed observer also requires a parent relay loop. After
spawn_agent returns a live child ID/path, keep the parent turn open with
wait_agent calls of at most 60 seconds. A child send_message only queues a
mailbox event and cannot wake a parent turn that has already ended. Relay
meaningful observer updates, then wait again until the child returns its
terminal summary. User input may interrupt the wait; handle it and resume the
relay loop while the observer remains active unless the user asks to stop
live observation or replaces the active request. Then end only the relay
loop, keep the durable job running, and offer next-turn or explicit-status
catch-up. If the observer child fails, is cancelled, or exits before a
terminal summary, use that same fallback instead of waiting indefinitely.
This wait loop must never call Ouroboros job tools or compete for the
observer's cursor.
Handle observer messages as events, not as a transcript:
phase_changed / progress_advanced: relay at most 1-2 concise lines.
Interpret the structured subtype, not raw logs:
run_configuration: state the current runtime/harness, starting model or
tier when known, efficiency mode, and frugality assurance. If the exact
model is not known yet, say it will be reported by the first routing event.
execution_plan: state total ACs, total dependency/parallel levels,
whether work can run in parallel, and the first scheduled AC summaries.
discovery_summary: say which bounded targets the AC is examining and
the purpose; never expose search queries, raw commands, or reasoning.
level_started / level_completed: say which parallel level is active
or finished and the meaningful success/failure counts.
ac_routing / harness_changed: say "currently running with" and report
only initial routing or a real model/tier/harness change.
ac_verified: report the completed AC and its compact assurance evidence.
attention_required: surface the blocker or pending decision immediately
and ask the user only when human judgment is required.
terminal: fetch/present the final result and any chained evaluation.
- Synapse
.queued / .delivering: say the exact AC has a pending or claimed
intent signal and name
the effective boundary; do not claim application yet.
- Synapse
.applied / .completed: confirm runtime-proven application and
relay the bounded AC reply when present.
- Synapse
.rejected / .delivery_uncertain: surface immediately and never
claim the AC changed course.
- Suppress unchanged heartbeats and raw tool output.
Render every relay in the user's current conversation language. Keep event
codes and effective-mode values unchanged only when exact diagnostics help.
These are English canonical host instructions. Phrase the facts naturally in
the active conversation language.
This ownership split is the default for SOL-class models: the main model
performs one start handoff, while a small isolated context owns the repetitive
wait/result state machine.
If child creation is unavailable, fails, or returns no live child, do not
claim that an observer exists or promise live proactive messages. The
detached worker survives the stdio MCP turn. Tell the user that progress is
durable and will be caught up on the next parent turn or explicit status
request. Keep the turn open only when the user explicitly asked for live
watching; then use the fallback below.