Monitors¶
A monitor turn is a real agent session member whose work is one
supervised OS command instead of an LLM turn. sase monitor start hands a slow command
off to a detached supervisor process and returns immediately, so an agent can run
just check, wait on a CI job, or sleep before a deploy without blocking its own turn.
SASE agents are single-turn: a provider turn runs, the runner captures it, and the agent
is done. Provider-native background-execution or scheduled wake-up tools assume a
multi-turn session and do nothing useful here — the turn ends and the wake-up never
fires. Monitors are the SASE-native replacement: use /sase_monitor (or
sase monitor start directly) instead of any built-in monitor, background-execution, or
scheduled wake-up tool.
The agent-session picture¶
Starting a monitor promotes the calling sase-agent to an agent session, exactly as
%id(suffix, session=parent) would, and adds the monitor as a named proc member. No
successor is launched yet. After the command settles, the frozen outcome branch may
launch one ordinary follow-up, complete through the host, or do nothing. stopped and
lost always do nothing:
sase-agent "acme" before right after `sase monitor start`
────────────────────────────────────────────────────────────────────────────────
acme (one-turn agent, RUNNING) acme (agent session)
├─ acme--0 DONE ← starter turn, killed
└─ acme--mon TESTING ← monitor named proc
After the command finishes on an ordinary continue branch:
acme
├─ acme--0 DONE
├─ acme--mon TESTED ← monitor finished
└─ acme--1 RUNNING ← follow-up turn
| Term | Meaning |
|---|---|
| starter turn | The agent turn that ran sase monitor start (absent for host-started monitors) |
| monitor turn | The --mon named proc representing the supervised command |
| supervisor | The detached process that runs the command and streams its output |
| follow-up | The agent turn launched under the session after the command finishes |
sase monitor start, run from inside an agent, is the last thing that agent does: it
kills the calling agent's turn (the same handoff mechanism sase plan propose and
sase questions use), while the supervisor keeps the command running under the same
workspace claim. The workspace is never released and re-claimed between the starter and
the follow-up — the follow-up sees exactly the tree the monitor started with, plus
whatever the command itself changed.
A monitor turn has an ordinary artifacts directory, just like an agent turn, so
everything that already understands agent sessions — the Agents tab, session roster,
runtime aggregation, sase chat, %wait/#fork resolution — works on it with no
special casing. There is no separate monitor store: a monitor's durable record is its
agent_meta.json plus done.json, and sase monitor list/show are queries over the
existing agent artifact index.
Starting a monitor¶
Host finalizer recovery and conflict-repair turns cannot hand off through
sase monitor start, sase pipe, plan proposal, or questions. Those turns are owned by
an already-running finalizer that must receive their result in the same invocation; a
handoff cannot complete that contract. The CLI refuses before creating the handoff.
Finish the repair and any verification inline, or report the blocker to the host.
sase monitor start \
-p verify \
-r 'Verify the refactor before replying to the user' \
-t 45m \
-n 'Fix anything just check reported, then reply to the user.' \
-- just check
When just check-full is explicitly requested (typically to repair a CI failure), use
the same verify profile — never run it inline.
Use the same shape when the command you need is an agent-completion gate. The intended
composition is a monitor running sase agent wait, followed by a normal follow-up agent
that acts on the result:
sase monitor start \
-s WAITING -S WAITED \
-n 'agents finished; land the epic' \
-- sase agent wait -a
That keeps the wait outside the current provider turn, preserves the workspace claim, and records the wait output where the follow-up can inspect it.
- The command is the remainder after
--(for example-- just check). That is the formsase monitor start --helpshows.-c/--commandstill works as a hidden compatibility alias for a single shell string, but new invocations should use--. A single word after--is used verbatim as the shell command, so quote a pipeline or&&chain as one argument (-- 'just fix && just check'). Several words are re-quoted withshlex.join, so each stays one literal argument and shell operators among them are not interpreted. SASE already runs the command with/bin/sh -c, so do not wrap it inbash -corsh -c;sase monitor startwarns when it sees that redundant wrapper. -p/--profile verifysupplies the standard verification policy:TESTING/TESTEDlabels,--next-output auto, no continuation after success, and a recovery continuation after failure or timeout.-n/--nextreplaces the built-in recovery instruction. Profile selection alone never authorizes host completion; prepared completion still requires-f/--completion.-s/--start-statusand-S/--stop-statusare required when no profile supplies them — the present-tense label shown while the command runs (e.g.TESTING) and the past-tense label shown when it finishes (e.g.TESTED). Each is capped at 20 characters; over-length values are truncated with a trailing…and a warning.TESTING/TESTEDis the pair forjust checkandjust check-full; a different kind of wait picks its own pair (for exampleSLEEPING FOR 300s→SLEPT FOR 300s).-r/--reasondefaults torun command.-t/--timeoutdefaults to1h(bare seconds, or90s/45m/2h). Pass both when the default reason or budget would be misleading.--idle-timeout/-iis optional. It kills a command that stops producing output for the given duration, while still allowing intentionally quiet commands when omitted.--next/-nis the shared follow-up instruction. Without a profile, policy, or prepared completion, omitting it makes the monitor fire-and-forget: the command still runs to completion and its output, exit state, and runtime are recorded, but no agent launches afterward.--model/-mselects a model or alias for an ordinary follow-up (for exampleopus,opus@high,@small, orcodex/gpt-5). It may accompany--next, a profile, an outcome policy, or a prepared completion that has a recovery branch. When omitted, an ordinary follow-up inherits the starter's model and reasoning effort.-o/--next-output auto|tail|file|nonecontrols how much retained command output is handed to the follow-up agent.autois the default and selects outcome-aware evidence;tailembeds a bounded raw tail;filepoints at refs and log locators;nonegives only the outcome summary andsase monitor show --all-linespointer.-k/--checkpoint FILEbinds an authored YAML/JSON checkpoint by content digest. Use it to preserve the objective, constraints, findings, unresolved decisions, remaining work, source refs, or coverage separately from--next.-P/--policy FILEloads a per-outcome JSON/YAML policy. It is mutually exclusive with-p/--profile; both are validated and frozen before SASE creates the monitor member.-f/--completion REFbinds a single-use host-completion intent produced bysase final prepare. The monitored argv must exactly match the intent's verification command.--label/-L,--agent/-a(--laneremains accepted as a deprecated alias),--cwd/-C, and--tail-lines/-Tare optional; seesase monitor start --helpfor the full list.-k,-P,-p, and-fneed themonitor_continuation_recordsfeature flag, which is on by default.
--cwd may name any directory inside a managed checkout, including a repository opened
with sase repo open. The command runs in that directory, but the monitor records and
claims the checkout that contains it, and an ordinary follow-up starts at that
checkout's root. When the directory is inside the caller's own workspace, the caller's
claim moves to the monitor; inside a different managed workspace, the monitor takes a
fresh claim and the caller keeps its own. A directory outside every managed checkout
uses workspace 0.
Before it creates the monitor member or binds a completion intent, sase monitor start
checks the target workspace's claims. If another live process holds that workspace, the
start fails with could not claim workspace for monitor: … and leaves no monitor row
behind. A claim left by the same agent whose process has died is taken over instead.
Only one monitor may be running per agent at a time. Repeating the same full request
returns the existing running record; changing the command, cwd, reason, label, timeout,
idle timeout, next action, model, status labels, tail lines, profile, checkpoint,
policy, completion intent, output policy, or joined run is rejected until the active
monitor settles. A lost monitor is never implicitly replayed: repeating the identical
request is refused with a pointer to sase monitor show, while a different request may
start a new monitor.
Joining a detached ToolRun¶
(Agents only.) sase monitor start -J/--join RUN adopts an existing starter-scoped
detached ToolRun — the kind sase tool run -d starts — instead of starting a command.
The run keeps one id end to end, and its executing proc stays the owner; the monitor
only follows it:
sase monitor start -J 0f1a2b3c -p verify -n 'finish check'
The display command replays the original sase tool run words (ad-hoc argv included),
the default label is tool:<name> (joined), and the default reason is
finish <tool> (joined run). -J cannot be combined with a command remainder, -c,
-f (pass -n for a follow-up instead), or -a: the join always runs on the calling
agent's lane. The caller must be the agent that started the run; anything else — outside
an agent, a run with no starter scope, or another agent's run — is refused with exit
2. A run that already settled, has a stop request, or is joined elsewhere is refused
with exit 1, showing the state and a sase tool show RUN pointer. No monitor starts
on any refusal.
The join records atomically after the monitor member exists and before its proc submits;
a refused or failed start tears the member down (releasing the join) and runs nothing.
The monitor proc follows the run into the monitor log without a deadline, then renders a
compact summary with the triage footer and exits with the run's mapped code. stop,
timeout, and a lost joiner converge through the detached-run watchdog with the
stop_requested reason preserved, and sase tool stop RUN on a joined run stops the
active joining monitor (suppressing its follow-up) rather than the run's proc.
Tool-run wrapping¶
A verify-profile monitor runs its command inside sase tool run, so the run is
recorded with the monitor as owner and the starter agent attributed — plain
-- just check is equivalent to -- sase tool run check and agents need not remember
the wrapper. Only the argv the proc supervisor execs is wrapped: monitor_command and
monitor_execution_argv stay exactly as written, so every prepared-completion -f
binding keeps resolving the raw command. The wrapper is built from the supervisor's own
Python (sys.executable -m sase), never PATH. The starter's provider rides along as
SASE_TOOL_RUN_PROVIDER for the run's demand context; ceilings are never forwarded,
because a monitor has none.
Where the table below resolves to a tool run, the start reserves that run up front — a
created hand-off run owned by the monitor id — and the proc execs the claiming worker
instead of the wrapped argv, so one semantic run is never recorded twice. The run id
prints in the start output (Tool run), rides the --json envelope (tool_run_id),
persists as the flat monitor_tool_run_id meta field, and shows in sase monitor show,
so the follow-up can sase tool show RUN -F, sase tool stop RUN, or
sase tool wait RUN on it. If the reservation cannot be committed, the start keeps the
wrapped argv (fail-open) and pre-writes one sase: tool run not reserved (...) reason
line to the monitor log.
| Monitor | Proc argv |
|---|---|
Host-owned execution_argv launch (an epic sase bead work) |
unchanged, never wrapped |
Exactly one sase tool run … already |
unchanged |
SASE_TOOL_BYPASS set or monitor.tool_wrap: off |
unchanged, plus one reason line in the log |
No profile or a non-verify profile under the default monitor.tool_wrap: verify |
unchanged, plus one reason line in the log |
| The project tool catalog cannot be loaded | unchanged, plus one reason line in the log |
| A simple command equal to a catalog tool's argv, with the monitor's cwd at that catalog's project root | <sase> tool run <name> (named upgrade) |
Anything else under -p verify (or with any or no profile under monitor.tool_wrap: all) |
<sase> tool run -- /bin/sh -c CMD (ad-hoc) |
Rows are checked top to bottom and the first match wins. A simple command is one shell
word-split with no operators, redirects, globs, expansions, or leading NAME=value
assignment: just check upgrades by name, while just install-venv && just check,
just check 2>&1 | tail -20, or FOO=1 just check wraps ad-hoc with the command
verbatim, so the run is at least as faithful as the raw command. Extra arguments match
only where that tool's args: allow policy permits them. Every unwrapped-by-policy case
writes exactly one sase: running unwrapped (<reason>) line at the top of the monitor
log, so the log explains itself. monitor.tool_wrap (off | verify | all, default
verify) changes the policy without a code change: all widens wrapping to every
monitor and off disables it; see Tool runs for the environment contract and
the guarded recipes this pairs with.
Checkpoints and outcome policies¶
An authored checkpoint is a bounded (at most 256 KiB), UTF-8 YAML or JSON object. It
must provide at least one of objective, constraints, findings,
unresolved_decisions, or remaining_work; each accepts a string or a list of strings.
Optional source_refs and coverage identify the evidence and scope already handled.
Do not put next or next_action in the file: the checkpoint records durable state,
while -n/--next records what the successor should do.
objective: Finish the parser refactor safely.
constraints:
- Preserve the public CLI.
findings:
- The focused tests pass.
remaining_work:
- Run the full verification gate.
source_refs:
- file:explicit:parser-notes
coverage:
- parser
- cli
An explicit outcome policy chooses continue, none, or complete independently for
completed, failed, and timeout. It may also spell out stopped and lost, but
those two outcomes never dispatch. A continue branch can set next_action, model,
and evidence policy; an explicit branch overrides the shared --next, --model, and
--next-output values. A complete branch is rejected unless --completion binds a
prepared intent.
completed:
action: none
failed:
action: continue
next_action: Repair the failed verification, then finish the task.
model: opus@high
timeout:
action: continue
next_action: Diagnose the timeout without rerunning the command blindly.
SASE freezes the validated policy, inherited route, prepared-completion ref, and checkpoint before changing workspace claims. Editing the source files afterward cannot change a running monitor's settlement decision.
Prepared host completion¶
sase final prepare turns a normal finalizer declaration plus one exact verification
command into a host-sealed, single-use intent. Preparation publishes the current final
context and records the relevant repositories' HEAD, index, dirty paths, and protected
or foreign state. It does not accept a final submission, run a finalizer, commit, or
end the turn.
Prepare retains observations for every relevant repository as evidence, but eligibility and the worktree-staleness fingerprint use only repositories with a repository decision. Those are the repositories host completion can commit; unrelated sidecars may have pre-existing dirt or receive concurrent commits without making a safe completion stale.
{
"success_message": "Required checks passed in {duration}.",
"verification": { "command": ["just", "check-full"] },
"declaration": {
"schema_version": 2,
"context_digest": "<from sase final context>",
"plan_digest": "<from sase final context>",
"payloads": [
{
"instance_id": "commit",
"payload": {
"repositories": [
{
"repo_id": "<repository obligation id>",
"action": "commit",
"message": "docs: refresh user documentation"
}
],
"deferrals": []
}
}
]
}
}
Prepare the wrapper, then bind the returned intent_ref to the matching command:
sase final prepare completion.json -j
sase monitor start -p verify -f '<intent-ref>' -- just check
Binding is atomic. A command mismatch creates no monitor, and a failed monitor startup
returns the intent to the prepared state; once a live monitor has bound it, the intent
cannot be reused. If the command completes successfully and the sealed command,
finalizer plan, repository observations, workspace, and diagnostics remain eligible, the
host installs the prepared declaration and runs its finalizers without another model
turn. The monitor reports Completed by host and retains a completion receipt.
A failed or timed-out command, stale repository state, or another eligibility failure
invalidates host completion and launches one ordinary recovery continuation instead.
That successor receives the reason and must repair or finish normally; without --next,
it is told to diagnose the failure or stale verification and then finish the requested
change. Stopped and lost monitors never complete or continue automatically.
The monitor's host-completion status (Host final in sase's TUI) reads finalizing
while the finalizers run, completed_by_host once they succeed, and recovery after
the intent was invalidated and handed to a recovery continuation. needs_attention
means a commit receipt is ambiguous, so SASE neither completes nor continues
automatically; inspect the receipt and finish the change by hand. A monitor that carries
a host-completion status cannot be resumed with sase monitor resume. See
Commit Finalizer for the declaration side of the
protocol.
Opt-in no-new prepared completion (E4 verdict-completion)¶
The default prepared intent accepts pass: only a verify monitor whose child exited
zero can complete. An explicitly prepared accept: no-new intent additionally permits a
verify monitor whose settled ToolRun is the bound named verification to complete when
that run's verdict is no_new_failures — every failure item KNOWN or FLAKY, none NEW or
UNKNOWN. The manifest spells it accept: no-new (omitted means pass); only a verify
monitor whose settled ToolRun is the bound run may use it, so a raw command, an
unrelated ToolRun, a lost monitor, or a missing owner link can never complete no-new.
For the no-new path the host re-observes each obligated checkout at the last host-owned
precommit boundary and Rust-looks-up its covering receipt: the verified fingerprint and
source run, expiry, invalidation, verdict, policy, and current tree must all still
agree, and every obligated repository must appear in the receipt's fingerprint. Any
mismatch withholds all commits and launches ordinary recovery with the typed reason.
Resumed host completion and multi-repo obligations re-verify the same receipt and source
run before finishing; a stale once-checked result is never reused. The default
accept: pass path keeps its existing eligibility and gains no receipt requirement.
Commits and bead closes record verdict provenance: the receipt id, source run, verdict,
and KNOWN list when a covering receipt authorized the completion, or unverified for
the ordinary /sase_final path without one. The unverified mark is nonblocking
provenance, not a refusal. No-new never masks NEW/UNKNOWN items and never changes a
child's exit code.
Resolving the implicit agent¶
--agent / -a is only needed to start a monitor outside an agent turn (no
SASE_AGENT_NAME set) or to target a different agent than the caller. From inside an
agent -- including an epic phase lane and a promoted agent session -- omitting it
resolves the calling agent turn metadata-first:
- The caller's own artifacts dir (
SASE_ARTIFACTS_DIR), when it belongs to the caller. - An exact
SASE_AGENT_NAMEmatch against an artifact's own name. - The newest non-monitor member of the caller's own session, when
SASE_AGENT_NAMEnames a session container rather than a concrete turn -- session members can replace one another inside a single process, leavingSASE_AGENT_NAMEset to the session while the running turn's own artifacts carry the concrete member name. A settled--monmember is never selected here, even when it is the newest member of the session.
An unresolvable caller (no artifacts match any of the above) is a clear error naming
-a/--agent, not a silent fallback to the current working directory or another agent's
lane. sase monitor show/stop with no id resolve the same way, against the caller's
own durable session -- never a parent's or sibling's.
The stored command string is executed with /bin/sh -c, so shell quoting, redirection,
and variable expansion in a single-string command are the caller's responsibility.
Monitors are for batch commands: do not use them for interactive programs or commands
that require a TTY.
Supervision guarantees¶
The detached supervisor owns the command's process group and writes combined stdout and stderr to a bounded rotating log. Completion is based on process exit, not pipe EOF, so a backgrounded grandchild that holds stdout open cannot keep the monitor running after the command process exits. Total timeouts and TERM-to-KILL escalation are checked on every supervisor tick, independent of whether the command is quiet, chatty, writing partial lines, or emitting non-UTF-8 bytes. Non-UTF-8 output is retained with replacement characters rather than crashing the supervisor.
Monitor logs are bounded. The active log is live_reply.md; when it rotates, readers
stitch the rotated live_reply.md.1 and active file where appropriate. Very large
output therefore keeps a recent on-disk view and a head-plus-tail retained summary for
the follow-up agent rather than preserving unlimited bytes.
The command does not inherit the starter agent's SASE_AGENT* identity or
SASE_ARTIFACTS_DIR, so tools run by the command cannot accidentally write artifacts or
variables into the dead starter's directory. It receives SASE_MONITOR_ID,
SASE_MONITOR_ARTIFACTS_DIR, and SASE_MONITOR_DIAGNOSTICS_DIR instead. Only the
command's own tools/run_silent stages record into that diagnostics directory: a
stage's children never inherit it, so a nested tools/run_silent cannot add stages of
its own.
Surviving the starter's teardown¶
sase monitor start, run from inside an agent, hands the command to a supervisor and
then kills the calling agent's runner group as part of the same handoff. The supervisor
must not be a casualty of that kill: it is spawned through a double-fork bootstrap that
reparents it to PID 1 before start_monitor returns, so a process-tree teardown
launched after the call cannot reach it by walking PPIDs. The supervisor also sets
SIGHUP to ignored and installs its SIGTERM/SIGINT handling in the first statements
it runs, before any expensive import, closing the startup window in which a stray signal
could kill it silently.
On Linux, when the starter runs inside a SASE-owned systemd unit or scope such as
sase.service or under a reachable user systemd manager, the supervisor also moves into
its own transient systemd-run --user --scope (with OOMPolicy=continue), so
restarting that service does not kill a running monitor. Set
SASE_DETACH_SCOPE_DISABLE=1 to opt out.
A start is fast, and never silent¶
An in-agent sase monitor start has to finish inside the harness's own tool budget:
Codex's yielding exec_command hands control back to the model after about 30 seconds
while the command keeps running, and anything still running when the turn ends is
killed. So the start touches only what it needs and says so at once:
- Inside an agent it prints one line to stderr before any slow work
(
sase monitor start: starting monitor for lane <lane>; ... wait for it to exit), so a yielded result is never empty. All stdout, including the--jsonenvelope, still prints before the runner is killed. - The calling agent is resolved by reading its own
SASE_ARTIFACTS_DIRdirectly. The full project scan is only the fallback for a missing, stale, or foreign pin. - The lane's existing monitors are read once, through the artifact index's
agent_sessioncandidate filter, and that snapshot answers the replay check and the--monsuffix allocation. A lane read does not hydrate other lanes' records or walk the project's source directories. - Each phase of the start (identity, lane lock, replay lookup, lane resolution, claim
preflight, member creation, ToolRun reservation, supervisor spawn and ack, intent
persistence) is timed. The timings are logged at debug level and written to
monitor_start_timing.jsonin the new member's artifacts directory.
On a host with about 12,000 artifact records this took an in-agent start from roughly 20-40 seconds to well under one.
Startup acknowledgement¶
The monitored command runs under the durable proc service's detached supervisor, and
start_monitor never hands back a running record for a supervisor that is not
provably alive, because its caller's very next act — inside an agent — is to kill
itself. Once the supervisor has taken ownership, it writes a .proc_started
acknowledgement in its proc runtime directory. start_monitor waits for that marker for
up to 20 seconds (SASE_PROC_START_ACK_TIMEOUT_SECONDS overrides the budget), polling
the supervisor's liveness too so a supervisor that is already dead fails fast instead of
waiting out the full budget. A failed start hands the workspace claim back to the
still-live starter exactly as it held it (never releasing it into the free pool),
returns any bound completion intent to the prepared state, tears the member down as
terminal failed, and raises MonitorError — so the starter agent stays alive,
sase monitor start exits non-zero, and nothing downstream ever hands off to a phantom.
A monitor owns its workspace until it is reconciled¶
A running monitor's workspace claim is not released just because its supervisor's pid looks dead. The stale-claim sweeper reconciles a monitor's own markers first (killing a confirmed-dead process group, finalizing the log, disposing the claim, and running or recording the follow-up disposition) and only then releases the claim — and if reconciliation itself fails, the sweeper leaves that claim in place rather than guessing. This closes the window where another agent could be handed a workspace a monitor is still using: a not-yet-reconciled dead supervisor is exactly the state a live monitor is in from the outside, so a bare dead-pid check is not sufficient evidence to release.
Status and bucket¶
The displayed status is always the configured label. Because those labels are arbitrary
strings, the underlying bucket (Running / Done / Failed, used for grouping and
counting) is tracked separately from the label, from monitor_state:
monitor_state |
Bucket | Displayed status |
|---|---|---|
running |
Running | start status |
completed |
Done | stop status |
failed |
Failed | stop status (+ exit code or supervisor error) |
timeout |
Failed | stop status (+ total-timeout or idle-timeout note) |
stopped |
Done | stop status |
lost |
Failed | stop status; command outcome is unknown |
The stop-status label is descriptive text, not a success condition. It is reused for
every terminal state, including an explicit stop; use the bucket, monitor_state, exit
code, and output to decide what happened.
Display contract¶
A monitor's identity on every surface is the ordered pair of its two labels. Three orthogonal signals carry the rest:
| signal | carries | mechanism |
|---|---|---|
| hue | which kind of monitor this is | one deterministic accent color per pair |
| weight | live or settled | bold while running, normal weight once settled |
| glyph | how it went | ✓ completed, ⊘ stopped, ✗ failed, ⧖ timeout, ⚠ lost |
Failure keeps red: failed, timeout, and lost render bold red regardless of the
pair accent. Two different pairs can share a color; the words still differ. Reusing one
pair across related monitors (for example every just check wait as TESTING /
TESTED) makes those rows read as one lane.
A monitor is terminal only after it is settled: the command has exited or been
reconciled, the log has been finalized, the workspace claim has been released or
transferred, and the follow-up has launched or its disposition has been recorded.
Polling commands such as sase monitor show --follow and %wait continue waiting while
a monitor is stopped but not yet settled.
A failing just check shows up as a Failed member with its exit code visible, and the
follow-up agent still launches so it can fix what broke. A timed-out command is killed
(its whole process group, not just the shell), and the follow-up is told plainly which
budget fired: total runtime or no-output idle time. A lost monitor means the
supervisor belongs to a previous boot, so SASE cannot know whether the command finished
or what it changed. Lost monitors are not automatically re-run, and their recorded
follow-up action is not launched.
The follow-up agent¶
When the frozen outcome branch selects continue, one follow-up agent turn launches
under the same agent session once the command finishes and the monitor settles. A shared
--next, the verify profile, or an explicit policy may supply that branch. It
receives:
- the starter's full prior conversation, via
#fork, once the starter's own record has settled (settlement waits up to 60 seconds for it); the follow-up joins the session it forks and does not wait on or list itself, though it still waits for any other live session member; - the original
--reasonand the resolved next instruction, verbatim, under its own heading; - the authored checkpoint, when supplied, as protected state distinct from the next action;
- a command-run breakdown: outcome, exit code, elapsed time vs. the timeout budget, and
the selected output policy from
--next-output; - the full log path and the exact
sase monitor show <id> --all-linesinvocation to read more than the tail.
By default, the follow-up inherits the starter's model and reasoning effort. Pass
--model / -m to replace that routing with a model, provider-qualified model, or
model alias; an optional @effort suffix travels with the selection. %model text in
--next remains literal prompt text and does not control routing.
Failure triage in follow-ups¶
When a failed monitor has a settled ToolRun with triage, its follow-up prompt inserts a
## Failure triage section before selected diagnostics. It gives the verdict, NEW and
UNKNOWN items with their evidence and possible owners, KNOWN/FLAKY counts, and the exact
sase tool show RUN -j command. The section is absent only when no stored triage is
available; it never changes the monitor result or the command's exit code.
Continuation history is replayed from versioned parent links rather than reconstructed from turn names. Each ancestor is hydrated once in order, including local authored prompt segments, host instructions, final responses, checkpoints, and monitor-result evidence. A missing ancestor is disclosed as a gap; SASE does not guess across uncertain legacy history. Artifact-run pruning keeps old runs that a live or recoverable continuation still references, and SASE registers required checkpoints and monitor results as portable artifacts, so replay can still hydrate them after the local files are gone.
Automatic retries keep failed-attempt evidence under attempts/<N>/, but continuation
replay excludes superseded failed attempts from the ancestor chain. A later #fork
therefore follows the settled attempt and its original launch lineage without injecting
obsolete retry errors as extra conversation turns. Prior attempts remain inspectable in
the TUI's attempt view.
Before invoking the provider, SASE measures the fully expanded continuation against its
context and transport budget. If essential content is too large, the provider is not
called: the follow-up becomes not-launchable, the budget decision and composed prompt
remain inspectable, and recovery guidance asks for an adequate checkpoint or a route
with more context. The monitored command is not rerun merely to reconstruct context.
A follow-up that cannot launch keeps its recovery evidence. Before SASE releases the
monitor's workspace, it saves a best-effort git diff HEAD plus untracked files as
diagnostics/worktree_recovery.diff (recorded as monitor_worktree_recovery_diff_path)
and links it from the saved prompt. An agent waiting on that monitor gets a
terminally blocked wait notification that names the
sase monitor resume <id> command and the saved diff. If the follow-up was blocked only
because the starter had not settled yet, a later sase monitor resume repairs the
missing parent link and launches normally.
The launch is not coupled to a workspace-claim handoff that can fail: if the monitor's
own workspace claim can no longer be transferred to the follow-up (for example, a stale
sweep already released it), the follow-up still launches — first against a fresh claim
on the same workspace, then against workspace 0 if that workspace has since been taken
by another agent. Either fallback is recorded as a degraded launch, and the
follow-up prompt says plainly which happened, because a follow-up in a different
workspace than the monitor ran in cannot assume the command's artifacts are present.
Only when a follow-up genuinely cannot be launched at all is it dropped — and even then
the composed prompt is persisted as a durable artifact so the instruction can be
replayed by hand instead of surviving only as an error string. See
Visibility below for how a dropped or degraded follow-up is surfaced.
The follow-up prompt's body is enclosed in a macro-disabled region, so directives,
#macro references, and $(...) command substitution inside --reason, --next,
table fields, diagnostics, and embedded output are delivered as literal text. Only the
routing prefix remains live: #fork:, %model:, %effort:, a %queue(...) line that
carries the monitor's recorded queue weight (plus any recorded priority or capacity), a
%auto line when the starter requested automatic gate resolution, and the starter's VCS
workspace reference. When --next-output tail is used, retained output is also fenced
and labeled as untrusted program output. The command and cwd fields are fenced too, so
directive-shaped strings inside a shell command or path remain literal even if the
disabled region is ever removed. --next-output auto defaults completed runs to facts
and refs, failed runs to bounded selected diagnostics when available, and timeouts to a
bounded raw tail. Use --next-output file for large or hostile logs when the follow-up
should inspect the log explicitly, or --next-output none when the outcome summary and
sase monitor show --all-lines pointer are enough. The continuation evidence limits
live under monitor.evidence_limits in sase.yml; the shipped defaults are 8 KiB
selected diagnostics, 4 KiB fallback tail, 12 KiB total raw excerpt budget, and 200
raw-tail lines.
Runner slots¶
A monitor is not a way to free runner capacity. The session keeps its one weighted claim
against max_running_agents for the monitor's
whole lifetime, and the monitor inherits the starter's queue weight. The starter's
runner process exits at handoff, but occupancy stays continuous: the monitor member
counts as soon as it has a recorded supervisor pid. An ordinary follow-up carries that
weight forward in its %queue(...) prefix and, as a serial session member, continues
the session's claim. A fire-and-forget monitor (an outcome policy with no continuation)
still holds the claim until the command settles. In-process successors such as
sase pipe keep the same session's claim as well; they never become a second occupant.
The host-owned monitor that launches an approved epic is the exception: it records an
explicit zero queue weight and consumes no capacity, because the phase agents it
launches claim their own. That host-set zero applies to the monitor member only:
successors inherit the starter's weight (or the default 1.0), never the monitor's
zero. Only a user-authored %q(w=0) propagates zero weight to successors.
Holding a claim and waiting for one stay separate. Only a root or a live parallel clan member parks at the gate. Serial session members — the monitor and any ordinary follow-up included — ride the claim the session already holds. See Agent queued for a runner slot.
Inspecting and stopping monitors¶
sase monitor list # active monitors, newest first
sase monitor list --all --agent acme # include finished monitors for one agent
sase monitor list --status failed --status timeout
sase monitor show <id> # details plus an output tail
sase monitor show <id> --follow # stream new output until it finishes
sase monitor show <id> --all-lines --output-only
sase monitor show <id> --diagnostics # selected failed-stage diagnostics
sase monitor show <id> --range 0:65536 # retained raw-output byte range
sase monitor resume <id> [-j] [-k checkpoint.yml] [-m codex/gpt-5]
sase monitor stop [<id>] # stop a running monitor; omit id to target
# the calling agent's active monitor
ID accepts a monitor id (or unique prefix), the monitor turn's agent name, or the
owning sase-agent name. sase monitor stop never launches the recorded follow-up agent,
even when --next was given.
sase agent kill -n <name> uses the same stop behavior when the name resolves to a live
monitor member or its owner. sase monitor stop remains the clearest explicit form.
Every subcommand can emit machine-readable output, but not with the same flag: start,
list, resume, and stop take -j/--json, while list and show take
-f/--format (table/markdown/json for list, markdown/json for show).
sase monitor show has no -j — use --format json there. Short options also
differ by subcommand: -f is --completion on start but --format on list and
show, and -a is --agent on start but --all on list, where -l is --agent.
See sase monitor --help and each subcommand's --help for the complete flag
reference, or CLI Reference.
--diagnostics reads the bounded stage diagnostics selected for a failed monitor;
--range START:END reads a bounded byte interval from retained raw output. Both default
to at most 65,536 bytes and accept -b/--max-bytes. They are snapshot modes, so neither
can be combined with --follow; diagnostics and ranges are mutually exclusive, and a
range cannot be combined with --all-lines.
sase monitor resume <id> reconciles a terminal monitor's frozen result and launches an
eligible requested follow-up without rerunning the monitored command. Repeating a
successful resume returns the same acknowledged successor. Supplying -k/--checkpoint
or -m/--model creates an immutable manual-recovery branch only when the original
delivery was not already acknowledged. Concurrent receiver adoption is revalidated under
the delivery lock before any undelivered branch is fenced, so an acknowledged delivery
is never overwritten. A dispatching branch whose launch receipts or process identity
cannot prove the receiver uninvoked is recorded as needs_attention instead of spawning
another successor. When settlement recorded a follow-up as not-launchable only because
the starter had not settled in time, resume first repairs that missing parent link.
Ineligible monitors — still running, stopped or lost, fire-and-forget, owned by host
completion, or already delivered, among others — make resume exit 2 and print the
precise eligible resume command to stderr when one exists; with -j, the JSON result
carries code and suggested_command fields.
Reading monitors also performs dead-supervisor reconciliation. sase monitor list, the
sase's TUI Agents tab refresh path, and the axe scheduler look for running monitor turns
whose supervisor identity is no longer alive. Same-boot dead supervisors are reconciled
to failed: SASE kills the recorded process group, finalizes the log, disposes the
workspace claim, and launches or records the follow-up disposition. Pre-reboot
supervisors reconcile to lost; their command effects are unknown, so the follow-up is
recorded as not launched.
In sase's TUI¶
A monitor row renders with an amber ⚙ glyph beside the agent list's bash/python step
glyphs and omits a left-side title — identity is the right-hand %id
(<session>--mon), not the configured monitor label or command. A live elapsed suffix
shows while running, or an exit-code / timeout badge once terminal. Monitor turns appear
in the session roster and contribute to the session's total runtime (unlike a gate turn,
whose window is a human wait and is excluded from that total), but — like workflow steps
— they are not counted as agents in the Statistics tab or
tribe/clan summaries: a session with one agent and one monitor turn is a one-agent
session that ran one command. A collapsed session or clan container row carries an amber
⚙N badge for its running monitors and a grey ⚙N badge for its finished ones, so both
counts are visible without expanding the subtree; the two badges partition the subtree's
monitors exactly, and a failed, timed-out, or lost monitor counts in the finished (grey)
lane along with a clean completion. The tribe panel title aggregates both lanes across
the whole tribe, so a fully collapsed panel still reports running and completed
monitored work.
Selecting a monitor row keeps the ordinary agent header and renders a MONITOR detail
section in place of the usual prompt and reply body. It opens with compact Result,
Next, and Evidence rows, then shows the shell-highlighted Command, whichever of
Cwd, Reason, Next action, Next model, Next output, profile, policy, and
completion fields were recorded, then Status (the effective label in its pair accent,
with the other half dim after a →) and State (a colored glyph plus the machine state
name, with (exit N) appended once an exit code is known). Timeout reports elapsed
time against the budget (3m12s of 45m0s budget), falling back to a plain Elapsed row
for a record with no recorded budget, and an --idle-timeout adds its own
Idle timeout row. Next come the full Monitor id, its short form, and the exact
sase monitor show <short-id> --follow command to stream the rest from a shell. When
recorded, trailing rows show a follow-up error (Follow-up) or degradation
(Degraded), the host-completion status (Host final), and the evidence and
continuation locators: Diagnostics, Retained log, Result ref, Checkpoint,
Node ref, Manifest, Budget, and Saved prompt.
Beneath that, an OUTPUT block renders the captured stdout/stderr. Because
live_reply.md holds a command's raw merged output rather than prose, it is rendered as
a plain ANSI-aware log rather than through the markdown path used for agent replies, and
a monitor whose retained output was capped shows an
… output truncated (head + tail retained) … notice above it. A monitor that has not
written anything yet shows No output yet..
When the monitor's session (or its starter) is selected, that same block appears inline
as a MONITOR phase in the AGENT REPLY stream, at the starter's position in the session
conversation: an amber ⚙ MONITOR divider, the command, the recorded detail fields, and
the full captured output. File-hint mode renders the monitor document with [N] markers
on the command and log instead of falling back to the empty prompt view.
With a running monitor row selected, the Agents tab's kill key (x by default)
stops the monitor instead of killing an agent: it opens a Stop Monitor confirmation
that defaults to Keep running, and confirming runs the same stop_monitor path as
sase monitor stop, so no follow-up agent launches. On a settled monitor row, x
behaves like an ordinary dismiss. See Agent Row Glyphs and
Sequential Agent Sessions.
A monitor row is also a fork target, active or settled: F opens a prompt prefilled
with #fork:<id>, where <id> is the monitor's exact durable proc ID (not the reusable
<session>--mon proc name), so a later monitor that reuses the same proc name can never
hijack an already-queued fork. The footer and prompt label still show the friendly proc
name. Because a monitor has no chat, retry, edit-chat, and rename are never offered for
it. The injected fork content is the same command execution record described above
(Command, Status/State, timeout/elapsed, and the captured OUTPUT), explicitly
labeled as untrusted program output rather than a prior conversation. An implicit
%wait on a monitor fork target releases on that monitor's terminal state — completed,
failed, timed out, stopped, or lost — not only on a clean completion.
Visibility¶
A stalled monitor handoff — a supervisor that never reported a real outcome, or a
follow-up that never launched — is not something a project owner should have to notice
by its absence. Two independent conditions render distinctly, in the Agents tab and
sase monitor list, wherever the plain exit-code/timeout badges above do not already
cover them:
- A terminal monitor with no recorded exit code. A
failedorlostmonitor whose supervisor never reported a real exit code (died on arrival, or belongs to a previous boot) renders with a red⚠badge in place of the exit-code badge — the command's outcome is unknown, not merely non-zero. - A dropped or degraded follow-up. A monitor whose outcome selected
continuebut did not launch, or launched degraded, renders with an amber⚑flag independent of the monitor's own state — a monitor can finish cleanly and still strand its follow-up.
sase monitor list marks the same monitor row with the ⚑ flag next to its STATE
cell (in both the table and --format markdown output) so a stalled handoff is visible
without --json plumbing; sase monitor show <id> prints a Follow-up error line for
a dropped follow-up and a Follow-up degraded line for a degraded one, and both
commands' JSON envelopes carry followup_outcome (launched / launched-degraded /
not-launchable / host-completed), followup_error, and followup_degraded_reason,
plus versioned result, evidence, continuation, and context_budget objects for
compact clients.
Ordinary continuation delivery is keyed by monitor result and outcome branch. SASE reserves the successor identity before spawning it, and the intended receiver acknowledges that reservation before its provider is invoked. Concurrent settlement or reconciliation therefore discovers the same delivery instead of launching a duplicate model turn. A dispatch that cannot be proved safe remains visible as not launchable or needing attention with its saved prompt and delivery record.
Monitors themselves are notification-neutral: a monitor is an execution and handoff
mechanism, not a workflow that files notifications, so neither a completed monitor nor a
dropped --next appends a notification row. The badges and flags above, plus
monitor_followup_outcome / monitor_followup_error in agent_meta.json and
done.json, are the durable signals — read them with sase monitor list,
sase monitor show <id>, or the Agents tab. Two related notifications belong to other
features: an agent whose %wait targets a monitor that ended without a launchable
follow-up gets a wait_checks "never self-resolve" notification, and the approved-epic
launcher below releases the planner's completion notification when its monitor settles.
Example: approved epic launches¶
Launching an approved epic (sase bead work <plan> --yes-to-all …) is itself a
long-running command, so it normally runs under a monitor rather than a bare detached
proc. Its monitor turn reads EPIC APPROVED while running and uses the configured
EPIC CREATED label after every terminal state, even failure, timeout, stop, or loss;
check the state and exit details instead of treating that label as success. A successful
launch attempts to back-fill the epic ID; when that metadata lands, the planner row
itself moves to EPIC CREATED, and otherwise it remains EPIC APPROVED. The launch
acquires an operational workspace lease for the project and runs in that leased
checkout, never in the user's primary checkout; the lease's claim moves to the monitor.
The monitor records an explicit zero queue weight, and no follow-up agent is recorded —
sase bead work launches the phase agents itself. If the planner's agent session cannot
be resolved (a very old artifacts layout, a wiped agent), the launch falls back to a
detached proc in the same leased workspace rather than silently dropping the approval.
Other monitor-start errors fail the approval instead of using the proc fallback. The
planner's completion notification is held until the launch monitor settles, folds in the
launch outcome, and is published once even if more than one settlement path observes the
result. AXE's epic_launch_flush job flushes a held notification whose launch never
settled after a 90-second grace period, with a resume command. See
Plan Approval Flow for the approval side of that handoff.
The host-owned epic launcher keeps sase bead work as the visible logical command. If
an editable-source update is swapping that checkout when approval arrives, a minimal
pre-import bootstrap prints
sase: waiting for the source-tree swap to finish before launching, waits for the
shared lock, and only then starts sase bead work against one consistent source tree.
This waiting exception is specific to host-owned approved-epic launches; running
sase bead work directly remains fail-fast during a swap.
Pipe vs. monitor¶
sase pipe '<prompt>' (the /sase_handoff skill) looks similar — it also kills the
calling agent and continues the run as a new session member — but it solves a different
problem. A monitor runs and waits on an OS command; nothing about the command's content
is an LLM turn. Pipe hands the agent's own unfinished turn to a fresh successor: no
command runs, nothing is captured or timed out, and the successor's prompt is written by
the agent, not derived from a command's outcome.
Concretely:
| Monitor | Pipe | |
|---|---|---|
| What runs | A supervised OS command | Nothing — the successor is an ordinary LLM turn |
| New member's turn | --mon named proc, then a --<n> follow-up |
One --<n> (or --<name>) session member |
| Follow-up prompt source | --next text plus a command-run breakdown |
The PROMPT argument, verbatim |
| Bound | One monitor active per agent | max_agent_pipe_chain config field |
Before this command existed, agents got a successor by monitoring a no-op command:
sase monitor start -s SLEEPING -S SLEPT -r '...' -n '<the real prompt>' -- sleep 1
That only worked because sase monitor start already kills the caller and its
supervisor already launches a session follow-up once the command settles — a monitor
supervisor, a proc row, and a one-second sleep, purely to obtain a hand-off. Use
sase pipe for a hand-off instead; the sleep 1 --next '...' pattern is no longer
necessary. See the /sase_handoff skill for the command's flags and hazards.
See also¶
- Agent Clans, Sessions, and Tribes for how a monitor turn fits into a sequential agent session.
- CLI Reference for the full
sase monitorcommand table. - sase's TUI User Guide for how monitor rows render in the Agents tab.
- Agent queued for a runner slot for occupancy versus
admission, including why a monitor still counts against
max_running_agents.