Skip to content

lifecycle outbox - part b - #5592

Closed
M-Maciej wants to merge 17 commits into
Hmbown:mainfrom
M-Maciej:pr/lifecycle-outbox
Closed

lifecycle outbox - part b#5592
M-Maciej wants to merge 17 commits into
Hmbown:mainfrom
M-Maciej:pr/lifecycle-outbox

Conversation

@M-Maciej

@M-Maciej M-Maciej commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Closes #5531

Summary

Opt-in [lifecycle_outbox] config table. When set, codewhale appends one
JSONL line per lifecycle event to the configured file, for interactive TUI
sessions and headless codewhale exec runs, with no per-hook shell
commands:

[lifecycle_outbox]
# Unset or empty path = the feature is OFF; behavior is unchanged.
path = "~/.codewhale/notifications/outbox.jsonl"
webhook_url = ""    # optional: also POST the same events as {"at", "event"} JSON
webhook_token = ""  # optional bearer token for webhook_url

Events (one JSONL line per event; kind is the envelope's dotted kind):
turn_start, turn_end, turn_stalled, subagent_spawn,
subagent_complete, session_start, session_end.

Design points:

  • seq is monotonic in file order. Every append re-locks a <path>.lock
    sidecar and re-recovers the tail, so multiple sessions sharing one outbox
    file produce unique, increasing seqs; the sidecar is the only new on-disk
    artifact. A torn trailing line from a crash is ignored (bounded 64 KiB tail
    scan; recovery reads the last complete line).
  • Session ownership of turn boundaries: a session killed mid-turn
    (SIGKILL, closed pane) dies between turn_start and turn_end. Catchable
    signals flush a synthetic turn_end (status: "interrupted",
    payload.reconciled: true) for every open turn; SIGKILL runs no code, so
    boot reconciliation on the next session start pairs anything the killed
    process left behind. Both paths derive the open turn from the file, never
    in-memory state, and run under the exclusive lock, so two reconcilers never
    double-append.
  • Every payload carries workspace (the resolved workspace path) so a
    consumer can route each event to its project; subagent events additionally
    carry subagent alongside agent_id.
  • Turn-boundary emits live in the engine (spawn_tui_engine wiring,
    handle_send_message), so goal-continuation turns — engine-originated,
    never passing through the UI user-message dispatch — emit the same
    turn_start/turn_end pair as user turns, with distinct, correlated turn
    ids. Exec and hosted engines keep their existing emit sites; a
    completion without a preceding turn no longer mints a phantom turn_end
    under a stale turn id.
  • Bounded fan-out: webhook delivery runs off the append path with two retries
    and exponential back-off inside the sink; at most WEBHOOK_MAX_IN_FLIGHT
    deliveries run concurrently and a full backlog drops the newest delivery.
  • Payloads come only from bounded, pre-redacted fields (headline ≤ 80,
    detail ≤ 120, preview ≤ 200 chars; control bytes stripped).
  • A codewhale doctor posture row reports the resolved state and sink path.

Design record: docs/rfcs/1365-lifecycle-outbox.md (modeled on
1364-hooks-lifecycle.md). Both new config tables are documented in
docs/CONFIGURATION.md.

Merge-order note

Merge order: after the cadence fix (a). This branch is standalone — cut
from current main, passing all gates on its own (13,403 / 0); the
goal-continuation turn emits touch the same engine dispatch files as the
cadence fix, different lines, so a trivial rebase is expected after (a)
lands — happy to re-push on request.

Testing

<!-- Exact gate commands (including the clippy allow list) are in
CONTRIBUTING.md → "Pre-push verification". -->

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features --locked (warning-free under the CI allow list)
  • cargo test --workspace --all-features --locked

Results: clippy clean on stable 1.98.0 under the CONTRIBUTING.md allow
list; full suite 13,403 passed / 0 failed (one pre-existing
environmental failure skipped locally: diagnostic_dispatch_read_only
fails identically on clean main on hosts with a system-wide rustup shim;
it is unaffected by this branch and passes on CI). Focused:
codewhale-hooks 31/31; outbox exec integration harness 3/3 (real exec
subprocess against a stubbed OpenAI-compatible endpoint); hooks round-trip
asserting workspace on every event type and subagent on the subagent
events; reconciliation tests (signal flush + boot pairing, no
double-append).

Checklist

  • Updated docs or comments as needed — docs/rfcs/1365-lifecycle-outbox.md,
    docs/CONFIGURATION.md, Unreleased changelog entry
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes — doctor posture row and
    emit behavior exercised on live sessions during development ( before split, not after split )
  • Harvested/co-authored credit uses a GitHub numeric noreply address —
    every commit is authored under
    130112810+M-Maciej@users.noreply.github.com
  • Every commit carries a Signed-off-by (DCO)
  • No dead-code-budget change

…iled events

Add an opt-in, machine-readable lifecycle event outbox for supervisors and
automation harnesses. Unset/empty config = feature OFF = behavior unchanged.

Config ([lifecycle_outbox]):
- path          — JSONL outbox file (unset/empty disables the feature)
- webhook_url   — optional webhook endpoint; POSTs only when set
- webhook_token — optional bearer token for webhook_url

Writer (crates/hooks/src/lifecycle_outbox.rs):
- One JSONL line per event in the existing RuntimeEventEnvelope shape
  (schema_version, seq, event, kind, thread_id, turn_id, item_id,
  timestamp, created_at, payload); append + flush per event.
- seq monotonic per file; recovers from the last complete line on open via
  a bounded 64 KiB tail scan (torn trailing lines ignored).
- Single non-blocking writer task: emit() enqueues; no tokio runtime
  available => drop with warning.
- Payloads only from bounded, pre-redacted fields (headline ≤ 80,
  detail ≤ 120, preview ≤ 200 chars; control bytes stripped).

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
A TUI turn killed mid-flight by a disconnected engine (stream idle/error,
crash) never receives a TurnComplete, so its turn_start stayed orphaned in
the outbox — the TUI had no analogue of the exec channel-closed guarantee.
recover_engine_event_disconnect now captures the in-progress turn identity
before the state reset and emits the folded turn_end (kind turn.failed,
status failed, wall-clock duration, bounded error, workspace) for exactly
the state turn_start is emitted for; a disconnect with no in-progress turn
fabricates nothing. Tests cover both branches.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
… site

The consumer resolves the project from payload.workspace, so events lacking
it were dropped fail-closed. Every emit site now carries the resolved
workspace path — TUI turn_start, turn_end, session_end, turn_stalled, both
subagent events (which additionally carry subagent alongside agent_id), and
both exec turn_end sites (terminal receipt and channel-closed) — matching
the session_start and exec turn_start sites that already had it.

Tests: a hooks round-trip asserts workspace on every event type and
subagent on the subagent events; the stall emit-site test asserts the
workspace; the exec integration asserts payload.workspace equals the
--workspace directory for turn_start and turn_end.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
… turn_end

CONFIGURATION.md documents the routing fields (workspace on every payload,
subagent on subagent events), the new TUI failure-path folded turn_end, and
tightens the webhook wording to bounded retries inside the sink, failures
logged and dropped, never fed back into the agent loop. The lane changelog
marks the two closed gaps (routing fields, TUI orphan turn_end) done and
keeps the genuinely remaining follow-ups.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
codewhale doctor now reports the resolved outbox state — off (default) when
[lifecycle_outbox].path is unset/empty, on with the sink path otherwise —
matching the truth-and-resilience theme of the other posture rows. Tested
for both states.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
- docs/rfcs/1365-lifecycle-outbox.md: converts the build log into the
  review artifact modeled on 1364-hooks-lifecycle.md (problem, scope
  table, design/contract, structure, limitations, test plan, review
  checkpoints). Issue number provisional until the upstream issue is filed.
- docs/changelog-lifecycle-outbox.md: status line pointing at the RFC.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Goal-continuation turns are engine-originated: the synthetic ContinueGoal
token never passes through the UI's user-message dispatch, so the emit
sites hooked there produced no outbox turn pair for them.

Move the TUI's turn-boundary emits into the engine. The interactive TUI
wires its [lifecycle_outbox] handle at engine spawn (spawn_tui_engine,
including every engine-replacement site), and handle_send_message emits
turn_start right after TurnStarted and turn_end at the single terminal
outcome, with the same envelope shape (workspace + thread/turn ids) and
the same seq discipline (same writer). Exec and hosted engines keep their
existing emit sites and pass no handle, so nothing changes there.

Side effects, both emit-only fixes: the engine-side turn_end is projected
exhaustively from the terminal status (no more undocumented turn.ended
fallback), and completion events without a preceding turn (compaction/
purge, bang commands) no longer mint a phantom turn_end under a stale
turn id.

Regression: goal_continuation_turn_emits_turn_start_and_turn_end_pair_to_the_outbox
runs a real user turn plus its synthetic continuation against the engine
harness and asserts both turn pairs land in the outbox file with distinct,
correlated turn ids.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
The in-file round-trip test still constructed next_seq/recovered after
the atomic seq change landed; drop the removed fields.
cargo test -p codewhale-hooks 22/22.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
was already removed when turn-boundary outbox events moved engine-side
(08e684550): turn_start after TurnStarted and turn_end only at the single
terminal outcome of handle_send_message, so compaction/purge completions
with no preceding TurnStarted can no longer mint a turn_end under a stale
turn id. Re-adding a TUI-side emit here would double every turn_end.

Add the regression test the audit asked for at the interactive TUI's
engine wiring: a real user turn produces exactly one turn_start/turn_end
pair (one turn_end, status completed), and a cancel-before-start
compaction TurnComplete { Interrupted } with no in-progress turn writes
nothing — the outbox still holds exactly the one pair, no turn.interrupted
line, no duplicate.

Gate: ZIG=/opt/zig-0.15.2/zig cargo test -p codewhale-tui --lib event_loop
→ 5 passed, 0 failed.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
- expand ~/env vars in [lifecycle_outbox].path at both TUI
  construction sites (tui/app/init.rs and lib.rs exec) via
  config::expand_path, so the documented ~/.codewhale/... example lands
  under $HOME instead of a literal ~ directory. Regression test builds
  the App with a tilde path and asserts the line lands in the expanded
  home and no literal ~ directory appears.
- webhook delivery no longer blocks the local append path. The
  writer drain loop hands each POST to a detached task bounded by a
  WEBHOOK_MAX_IN_FLIGHT (4) semaphore; a full backlog drops the newest
  delivery rather than queueing unbounded. Regression tests: a slow
  endpoint cannot delay local appends; a full backlog drops webhook
  deliveries but never the local append.
- bounded_text strips full ANSI escape sequences (CSI, OSC,
  DCS/SOS/PM/APC, two-char escapes) instead of only the ESC byte; leaks
  like '[31m' no longer survive.
- module header reworded to the RFC-mandated phrasing: bounded
  retries inside the sink; failures logged and dropped, never fed back
  into the agent loop.
- CONFIGURATION.md documents the exhaustive turn_end kind projection
  (no turn.ended fallback kind); config.example.toml comments match.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…on + signal flush

The shared lifecycle outbox is written by many concurrent session
processes. Two failure modes remain from that ownership gap:

- A session killed mid-turn (SIGKILL, closed pane) dies between its
  turn_start and turn_end appends and can run no code, leaving the
  turn_start unpaired in the file (verifier turn-pairing/G1 FAIL).
- A SIGTERM/SIGHUP/SIGINT exit previously restored the terminal and
  exited without closing the open turn.

The session now owns its turn events end to end:

- LifecycleOutbox::reconcile_interrupted_turns(thread_id, reason) scans
  the outbox under the cross-process exclusive lock for this thread's
  turn_start lines lacking a matching turn_end and appends one synthetic
  turn_end each (kind turn.interrupted, payload status=interrupted +
  reconciled=true + reason, inheriting the start's workspace). One lock
  acquisition across scan+appends keeps the reconciliation idempotent
  across concurrent sessions. The open turn is derived from file truth,
  never in-memory state, so no duplicate turn_end can be fabricated.
- The TUI reconciles at boot (before the first emit) and registers its
  outbox identity for the terminating-signal cleanup task, which runs
  the same reconciliation for SIGTERM/SIGINT/SIGHUP before exiting.
  Both paths wait (bounded) for the process's own queued events to drain
  first, so a still-queued turn_start is visible to the scan.
- LifecycleOutbox::emit_blocking adds the synchronous, runtime-free
  append primitive for shutdown paths and deterministic fixture writers.
- New unit tests: reconciliation pairing/idempotence/torn-tail/foreign
  thread untouched, blocking emit, signal flush pairing + no-op; the
  existing N-writer monotonic-seq test still covers locked appends.
- New example interleaved_outbox_fixture generates a cross-process
  interleaved fixture; the verifier script reports PASS for turn pairing,
  and B2 on it (the WP gate).
- RFC 1365 documents the session-ownership contract and the SIGKILL /
  boot-reconciliation / graceful-shutdown relationship.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…the provisional number

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…-writers poll

emit() enqueues without awaiting, so on a busy runner the first poll can
race ahead of the first append (which creates the outbox file). The
lenient reader treated the missing file as a panic; on the Windows CI job
the race lost and the test failed with NotFound. A missing file is now an
empty poll — the writers' appends are asserted by the final line count,
so a genuinely broken writer still fails the test, just with a proper
assertion instead of an early read panic.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Regenerated crates/tui/CHANGELOG.md via scripts/sync-changelog.sh.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
docs/changelog-lifecycle-outbox.md is the ecosystem-internal build log the
RFC was mined from; upstream-facing notes live in the RFC itself and in
the root CHANGELOG.md. The RFC no longer references it.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
@M-Maciej
M-Maciej requested a review from Hmbown as a code owner August 24, 2026 00:43
@github-actions

Copy link
Copy Markdown
Contributor

Thanks @M-Maciej for taking the time to contribute.

This repository is observing a maintainer-managed PR intake gate in dry-run mode, so this pull request is staying open. This note helps maintainers prepare the allowlist before any enforcement is considered.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant recurring PR access by commenting /lgtm on a pull request.

@Hmbown Hmbown left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for splitting this into its own reviewable PR and for preserving DCO across all 16 commits. I reviewed the complete diff at exact head f005243e5fff5ac934c1073e5b2e68f54bfd5a39. The integration conflicts are only the two expected Unreleased changelog sections, and the code merge with #5591 is clean. I am requesting changes for three contract blockers:

  1. Boot reconciliation cannot find the process it is supposed to repair. The TUI passes the current hook-session ID into reconciliation (event_loop.rs L509-L514), but HookExecutor::new mints a new random sess_* ID on every launch (executor.rs L1556-L1570). That ID cannot match the killed prior process's records. The RFC acknowledges this (RFC L366-L372), while the PR, issue, and changelog state that next-boot pairing works. Please use a stable cross-process outbox identity and add a real two-process kill/relaunch regression.

  2. A torn tail is not repaired; the next append corrupts and then wedges the outbox. Recovery selects the previous complete record's sequence (lifecycle_outbox.rs L762-L808), but the append path opens with O_APPEND and writes at the original EOF without truncating the torn bytes or inserting a separator (L466-L485). The partial JSON and new envelope become one invalid line; the following recovery then fails parsing it. Current tests assert only the recovered sequence or reconciliation count. Please repair/truncate the torn suffix under the lock and add: seed torn tail -> append -> parse every line -> reopen -> append again.

  3. Fresh exec start/end records are not correlatable, and killed exec runs are never reconciled. The start uses an empty thread_id and no turn_id (lib.rs L11953-L11958); the engine publishes its new session ID before terminal completion (engine.rs L4691-L4693), so the end uses a different, non-empty thread_id, still with no turn_id (lib.rs L12362-L12367). In the supported shared/interleaved file, there is no process key that can safely pair these records. exec also never registers the signal context (outbox_signal.rs L24-L27) and has no boot reconciliation, so a killed run leaves a permanent orphan. Please mint one stable run/thread + turn identity before dispatch and add shared-file concurrency plus killed/restarted exec regressions.

Secondary durability risks to address while repairing the contract:

  • emit has no completion receipt or shutdown/join path. Normal terminal events can remain queued when the runtime exits, and several stream-output ? paths return after turn_start but before either turn_end site. Add a bounded, deterministic exit flush for TUI and exec.
  • Signal cleanup calls blocking full-file reconciliation under an unbounded file-lock wait (lib.rs L703-L735, lifecycle_outbox.rs L543-L614). The one signals.wait() future has already been consumed, so a second signal cannot re-enter the CLEANED_UP branch as the comment claims. Bound the whole signal flush and test lock contention/second-signal behavior.
  • The documented 64 KiB recovery invariant is not enforced for a serialized envelope; for example, TUI session_start inserts the model string without bounded_text. Enforce a maximum record size or bound every producer field, and serialize the two process-global signal tests so they cannot race.

Please preserve M-Maciej's authorship and Signed-off-by trailers throughout the refresh. Once these regressions are in place, the main/changelog refresh should remain mechanical and I can re-review the exact new head.

This review was written and posted by ChatGPT 5.6 Sol on the maintainer-approved v0.9.12 release lane; it is not a personal comment authored by Hunter.

@Hmbown

Hmbown commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Review from the 0.9.12 integration lane — not taken yet, deliberately:

The design is strong and I have no code objections from reading the description and the design points: file-lock + tail-recovery for multi-writer monotonic seq, boot reconciliation derived from the file rather than memory, goal-continuation turns emitting proper pairs, bounded/redacted payloads, and a real exec integration harness. The merge-order note (standalone off main, trivial rebase after (a)) is exactly the right way to present it.

Why it's not folded into codex/v0912-integration-20260823 today: it implements #5531, which carries no v0.9.12 label in the #5573 milestone tables, and it is +4.4k lines across 30 files cut from main — folding it mid-cycle would add merge risk exactly while the relay integration (#5606) and the computer-use plugin are also pending. @Hmbown: if you want this in 0.9.12, say so and I'll take it into the integration branch and run the full matrix (expect conflicts in the engine dispatch files with both #5606 and the cadence fix — all three touch the same neighborhood). Otherwise it lands early next cycle, re-based on main.

Resolves the only conflict: main's `## [Unreleased]` grew a `### Added`
list while this branch appended its lifecycle-outbox bullet at the same
insertion point. Kept both — main's list, with this PR's bullet appended —
and regenerated crates/tui/CHANGELOG.md via scripts/sync-changelog.sh.

No source files conflicted; every .rs change auto-merged.
@Hmbown

Hmbown commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Hi @M-Maciej — maintainer housekeeping on this PR, no code of yours was touched.

What I found. Two separate things were keeping this un-judgeable:

  1. The merge conflict was trivial. I merged origin/main in and the only conflict was CHANGELOG.md + its generated slice crates/tui/CHANGELOG.md: main's ## [Unreleased] / ### Added list grew while your bullet landed at the same insertion point. Every source file — including crates/tui/src/tui/ui/event_loop.rs, crates/tui/src/lib.rs, crates/config/src/lib.rs — auto-merged cleanly. I resolved it as "keep both" (main's list with your bullet appended) and regenerated the slice with scripts/sync-changelog.sh. cargo check --workspace --all-targets passes on the result.

  2. CI had never actually run on this PR. Not once. The repo is set to approval_policy: first_time_contributors for fork PRs, so your pull_request workflows were withheld pending a maintainer click — and nobody was watching that queue. The only two green checks you were seeing (gate, GitGuardian) are the two paths exempt from that gate. That is our process failure, not anything you did. Pushing the merge commit as a maintainer released the gate, so the full matrix is running on this PR right now for the first time.

I also prepended Closes #5531 to the PR description — the required link check wants an explicit closing keyword, and your commits already pointed at that issue.

Heads-up on what CI will report. main is currently red on two required checks that every open PR inherits, so ignore these if you see them:

  • Version driftmain is missing a changelog receipt for feat(tui): make Fleet roster editing discoverable #5604.
  • Test (windows-latest) — two Windows verbatim-path tests (tools::shell::tests::readonly_operands_are_workspace_bounded_and_symlink_aware, tools::subagent::tests::read_only_inspection_roles_execute_pwd_and_absolute_git_log).

Both are fixed by #5610, which is waiting to land. Anything else that goes red is worth your attention.

Sorry this sat for so long without a real signal. Same treatment applied to #5593 and #5594.

@Hmbown

Hmbown commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Merged onto the v0.9.12 integration branch in 397b9cb (with follow-ups 6b60646); re-verified in-tree: [lifecycle_outbox] config in codewhale-config, crates/tui/tests/integration/lifecycle_outbox_exec.rs, docs/rfcs/1365-lifecycle-outbox.md, and docs/CONFIGURATION.md rows. Closing as landed — thanks @M-Maciej!

@Hmbown Hmbown closed this Aug 25, 2026
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
Opt-in `[lifecycle_outbox]` JSONL/webhook stream (off by default) for
TUI and `codewhale exec`, with monotonic seq, workspace routing,
boot reconciliation, and signal flush. Authored by @M-Maciej; taken
into the 0.9.12 integration branch with its authorship preserved.

Adapted onto the #5586 extracted modules rather than restoring the
monolithic lib.rs/config.rs:
- doctor posture row lives in doctor_cli.rs
- exec turn-boundary emits live in exec_agent.rs
- terminating-signal flush lives in cli_args.rs
- config merge of the new table lives in config/merge.rs

Evidence:
  rustfmt --edition 2024
  cargo test -p codewhale-hooks --locked --offline -> 31 passed
  cargo test -p codewhale-config --lib lifecycle_outbox -> 2 passed
  cargo test -p codewhale-tui --lib --locked --offline --
    lifecycle_outbox doctor_reports_lifecycle_outbox
    goal_continuation_turn_emits_turn_start_and_turn_end_pair_to_the_outbox
    tui_config_parses_lifecycle_outbox_table
    stalled_turn_emits_turn_stalled_outbox_event
    stalled_turn_without_outbox_config_writes_nothing
    compaction_turn_complete_without_in_progress_turn_writes_no_phantom_turn_end
    outbox_signal
    -> 9 passed

Co-Authored-By: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
/#5594

Replay tonight's M-Maciej PRs onto the integration commits that landed
while the take was in flight (FEAT-019 /loop handler, image attach,
computer-use fallbacks). No further adaptation.

Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
/#5594

Pick up the MiniMax fact-guard commit that landed after the previous
replay so the take branch stays a descendant of integration.

Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
Take:
- #5592 lifecycle outbox (`[lifecycle_outbox]`, opt-in JSONL + webhook)
- #5593 /relaunch (save like /exit, Unix self-exec resume)
- #5594 per-session control socket (`[control_socket]`, Unix JSON-RPC)

Authorship of the original commits is preserved. Adapted onto the
#5586 extracted modules (doctor_cli, exec_agent, cli_args, config/merge)
and existing /loop locale keys rather than clobbering them. Did not
merge origin/main.

Co-Authored-By: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 01M0W25Y7BH0J5342G4WRNQZJT
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
#5592 wires spawn_tui_engine through Engine::new so the lifecycle
outbox handle can be attached. The #5566 max_steps import of
spawn_engine is unused on that path; keep DEFAULT_MAX_STEPS /
DEFAULT_MAX_WALL_TIME for frame.rs (`use super::*`).

Evidence:
  rustfmt --edition 2024

Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: local lifecycle event outbox (JSONL + webhook) with turn_stalled / turn_failed events

2 participants