feat(TASK-035): wire turn/session/compression seams + top-level agent bracket

Turn bracket (repl::TurnBracket): turn.started on entry and exactly one of
turn.{completed,interrupted,failed} on exit, with the interrupted-vs-failed
discrimination (abort_signal.aborted_ctrlc()) in one place. Wired at the
three outer boundaries: ask(), start_directive, and shell_execute — never at
the per-LLM-round before/after_chat_completion seams.

Session seams: session.started fires for newly created sessions only
(use_session's new_from_ctx paths; resume fires nothing), session.ended in
exit_session while the session is still attached, session.compressed on the
success path of both compress_session call sites (auto-compression and the
.compress command).

Session-held role hooks: Session snapshots the role's HooksMap on set_role,
clears it on clear_role/sync_agent, and re-reads it from the role file on
load; resolved_hooks gains a session leg so role hooks keep firing when the
role lives in the session (fresh set_role and resumed sessions alike).

Top-level agent bracket: agent.started/completed/failed once per agent RUN —
headless --agent dispatch and the interactive agent-context lifetime (enter
via .agent or startup; exit via switch-away or REPL teardown), never per
message. COYOTE_AGENT_ID is synthesized as top-<agent-name>-<nonce>.

Exit-path seams call hooks::drain_pending with a 2s bound so exit hooks
finish spawning before process teardown; interactive mid-session events
never drain.
This commit is contained in:
Dark-Alex-17 committed 2026-09-16 13:44:35 -06:00
1 parent 363104fe77
commit 8710737964
5 files changed
+778 -32

No files matched your search

+12 -4
View File
@@ -97,9 +97,10 @@ impl RequestContext {
/// (whitelisted through `global_hooks` when an agent is active), then
/// role, then agent. Duplicate names across scopes all fire.
///
/// Role hooks resolve only while the role is held directly on the context;
/// a role held by an active session is not consulted, because sessions do
/// not carry hooks. Resolution through sessions is a call-site concern.
/// Role hooks resolve from the role held directly on the context or,
/// when the active role was moved into (or restored by) a session, from
/// the hooks snapshot the session captured via `Session::set_role` /
/// `Session::load_from_ctx`.
#[allow(dead_code)]
pub fn resolved_hooks(&self, event: HookEvent) -> Vec<ResolvedHook> {
resolve_hooks(
@@ -108,7 +109,14 @@ impl RequestContext {
self.agent
.as_ref()
.map(|agent| (agent.global_hooks(), agent.name())),
self.role.as_ref().and_then(|role| role.hooks()),
self.role
.as_ref()
.and_then(|role| role.hooks())
.or_else(|| {
self.session
.as_ref()
.and_then(|session| session.role_hooks())
}),
self.agent
.as_ref()
.map(|agent| (agent.hooks(), agent.name())),