Problem
An owner tick runs a fresh headless claude but captures no session id and discards the model output, so there is no way to answer "what did the last tick actually do / why did it fail?" — unlike a playbook run, which has a pinned session id and a full transcript.
|
playbook run |
owner tick |
| invocation |
AutoRunArgv: claude --session-id <id> -p … |
SkipPermissionsRun: claude -p --dangerously-skip-permissions |
| session id |
✅ pinned + recorded |
❌ none (sessionless) |
| transcript |
✅ jsonl on disk → flow transcript <task> |
❌ none written |
| model output |
✅ rendered |
❌ cmd.Stdout/Stderr = io.Discard (runSkipPermissions) |
| status |
run task status + close-out sweep |
last_tick_status: ok/error/dead |
| audit trail |
full transcript + brief + updates |
only the journal note the tick writes |
Today a tick captures only tick_pid, tick_started, last_tick_at, last_tick_status, and a owners/<slug>/ticks/<ts>.log that holds only flow's own supervisor prints — the actual claude run's stdout/stderr is discarded.
What we lose
- No replay of the orchestration decision — why a tick dispatched task X, or decided "nothing to do", is unanswerable.
- Opaque failures (sharpest loss) — on
last_tick_status='error' the output is discarded, so the failure reason is gone.
- No per-tick token/cost attribution.
What softens it (why this is P2, not P1)
The tick's real work is orchestrated through tasks/playbook-runs (orchestrate-never-inline), and those each have a session id + transcript (inspectable via flow transcript / flow show playbook). So downstream work is auditable; only the thin orchestration tick itself is a black box. The journal note (owners/<slug>/updates/<today>-tick.md) is the intended human trail, but it's self-reported and may be empty on error.
Proposed fix
The "fresh session each tick" invariant does not require discarding the id — each tick can mint its own new session id (still fresh, never resumed) and record it:
- Switch the headless tick from
SkipPermissionsRun (discard) → a session-pinned headless run (like AutoRunArgv: claude --session-id <new-id> -p …).
- Record
last_tick_session_id on the owner row.
- Surface it:
flow transcript owner <slug> renders the last tick; flow owner show shows it.
Scoping
- Minimal: pin + record
last_tick_session_id, expose via flow transcript owner <slug>.
- Fuller: a per-tick run record (session id + status + timestamp) shown as a "recent ticks" list in
flow owner show, mirroring how playbook runs render.
Relevant code
internal/harness/claude/claude.go — runSkipPermissions (discards output), AutoRunArgv (the session-pinned pattern to mirror)
internal/app/owner_tick.go — ownerTickRunner, cmdOwnerTick, recordOwnerTick
internal/flowdb/owners.go — owner row (would add last_tick_session_id)
Context: surfaced while reviewing the owner harness (#71).
Problem
An owner tick runs a fresh headless
claudebut captures no session id and discards the model output, so there is no way to answer "what did the last tick actually do / why did it fail?" — unlike a playbook run, which has a pinned session id and a full transcript.AutoRunArgv:claude --session-id <id> -p …SkipPermissionsRun:claude -p --dangerously-skip-permissionsflow transcript <task>cmd.Stdout/Stderr = io.Discard(runSkipPermissions)last_tick_status: ok/error/deadToday a tick captures only
tick_pid,tick_started,last_tick_at,last_tick_status, and aowners/<slug>/ticks/<ts>.logthat holds only flow's own supervisor prints — the actualclauderun's stdout/stderr is discarded.What we lose
last_tick_status='error'the output is discarded, so the failure reason is gone.What softens it (why this is P2, not P1)
The tick's real work is orchestrated through tasks/playbook-runs (orchestrate-never-inline), and those each have a session id + transcript (inspectable via
flow transcript/flow show playbook). So downstream work is auditable; only the thin orchestration tick itself is a black box. The journal note (owners/<slug>/updates/<today>-tick.md) is the intended human trail, but it's self-reported and may be empty on error.Proposed fix
The "fresh session each tick" invariant does not require discarding the id — each tick can mint its own new session id (still fresh, never resumed) and record it:
SkipPermissionsRun(discard) → a session-pinned headless run (likeAutoRunArgv:claude --session-id <new-id> -p …).last_tick_session_idon the owner row.flow transcript owner <slug>renders the last tick;flow owner showshows it.Scoping
last_tick_session_id, expose viaflow transcript owner <slug>.flow owner show, mirroring how playbook runs render.Relevant code
internal/harness/claude/claude.go—runSkipPermissions(discards output),AutoRunArgv(the session-pinned pattern to mirror)internal/app/owner_tick.go—ownerTickRunner,cmdOwnerTick,recordOwnerTickinternal/flowdb/owners.go— owner row (would addlast_tick_session_id)Context: surfaced while reviewing the owner harness (#71).