Skip to content

refactor(hooks): strip hardcoded audit subagent from TurnEnd, add @@INJECT protocol#2337

Open
m8f1m8f1 wants to merge 13 commits into
esengine:v1from
m8f1m8f1:refactor/turnend-pure-hook-gate
Open

refactor(hooks): strip hardcoded audit subagent from TurnEnd, add @@INJECT protocol#2337
m8f1m8f1 wants to merge 13 commits into
esengine:v1from
m8f1m8f1:refactor/turnend-pure-hook-gate

Conversation

@m8f1m8f1
Copy link
Copy Markdown

@m8f1m8f1 m8f1m8f1 commented May 30, 2026

Closes #2155 — supersedes #2156

What

Extend Reasonix hook system from 4 lifecycle events to 10, covering the full AI Agent lifecycle. This PR also refactors TurnEnd from a hardcoded audit-subagent gate into a pure hook mechanism with @@INJECT/@@warn stdout protocol — providing the interface, not the business logic.

All hook events (4 existing + 6 new)

# Event Fire point Blocking Timeout Purpose
1 SessionStart First step() call, before TurnStart warn only 10s Environment check, project validation, context injection
2 TurnStart step() start, per-turn block 5s Budget check, context check, per-turn prompt injection
3 PreToolUse Before tool dispatch (existing) block 5s Security blocking, auto-approve
4 PostToolUse After tool success (existing) warn 30s Lint, log, result validation
5 UserPromptSubmit User submits prompt (existing) block 5s Input validation, filtering
6 PreModelCall Before model chat block (3-strike) 10s Prompt audit, sensitive content filter, custom system prompt injection
7 PostModelCall After model response warn 30s Post-processing, usage logging
8 TurnEnd Agent finishes responding, before yield done block (3-strike) 10s Quality gate
9 Stop Turn end (existing) warn 30s Logging, session tracking
10 SessionEnd Session exit warn 10s Cleanup, summary report, cross-session persistence

Key features

SessionStart — context injection (Claude Code parity)

SessionStart hook stdout is injected into the system prompt, achieving Claude Code parity. Users write their own scripts to inject cross-session recovery, project briefs, etc. Empty stdout or no hook = zero overhead.

TurnEnd — pure hook gate with @@INJECT/@@warn protocol

TurnEnd fires when the agent finishes its response and is about to end the turn. It is in BLOCKING_EVENTS:

  • exit 2 = block: the loop executes continue, keeping the turn alive for self-correction.
  • exit 0 = pass: normal turn completion.
  • 3-strike guard: 3 consecutive blocks force the turn to end, preventing infinite loops.

The @@INJECT/@@warn stdout protocol lets hook scripts communicate back:

Protocol Effect
@@Inject: Injected as user message — tells the agent what to fix
@@warn: Yields a system warning
No @@Inject Default fallback message used

This replaced the previously hardcoded audit subagent. TurnEnd now provides the mechanism only — users write their own scripts to decide what checks to run and what feedback to inject.

Example hook script:

import json, sys
payload = json.load(sys.stdin)
text = payload.get('lastAssistantText', '')
if 'TODO' in text.upper():
    print('@@INJECT: Remove TODO markers before finalizing.')
    print('@@WARN: TurnEnd gate blocked.')
    sys.exit(2)
sys.exit(0)

Safety mechanisms

  • PreModelCall 3-strike guard, TurnEnd 3-strike guard
  • SessionEnd reentry guard (quittingRef)
  • EPIPE protection for fast-exit hook scripts
  • All new HookPayload fields optional — zero breakage

Backward compatibility

  • Existing 4 events unchanged, existing settings.json unmodified
  • All 124 hook/loop/quit tests pass

Changes (9 files)

File Change
src/hooks.ts Event types, arrays, timeouts, payload fields
src/loop.ts 6 new fire points + TurnEnd @@INJECT/@@warn parsing
src/cli/ui/App.tsx SessionEnd hook integration
src/cli/ui/hooks/useQuit.ts beforeQuit + quittingRef reentry guard
src/core/events.ts, src/server/api/hooks-events.ts Phase type sync
tests/ New tests for all events

Refactor note

Supersedes #2156 which had a hardcoded audit subagent. Based on review feedback, hooks should not embed business logic. Replaced with @@INJECT/@@warn protocol.

Checklist

  • npm run verify passes locally
  • No Co-Authored-By: Claude trailer
  • No edits to CHANGELOG.md

Admin added 13 commits May 29, 2026 17:26
- runTurnEndAudit(): spawns read-only subagent with 20s timeout

- AUDIT_SUBAGENT_SYSTEM_PROMPT with DO NOT modify hard constraint

- AUDIT_SUBAGENT_READ_ONLY_TOOLS: 6 read-only tools

- AbortSignal.any() combines turn abort + timeout

- appendAndPersist audit report as user role on block

- Empty assistantContent and aborted turn early-exit guards

- 5 new loop-level tests covering 3-strike guard, block-continue, fallback

- V-TE-01~05 fixes: feedback injection, counter reset, timeout 30s to 10s

- BLOCKING_EVENTS exported for test consumption

- V-TE-05 automated type synchronization tests
- quittingRef prevents multiple process.exit calls

- beforeQuit error does not prevent process.exit

- null transcriptRef does not crash
- Move SessionStart from constructor fire-and-forget to step() await

- _sessionStartExecuted guard ensures single execution

- Hook stdout appended to prefix.system via replaceSystem()

- Empty stdout or no hook = zero overhead

- 4 new tests: injection, once-only, empty-stdout, no-hook
- Audit finding: catch block only logged to console, user could not see the error

- Now yields a warning event so the UI surfaces the failure
- Defense-in-depth: cap each hook stdout fragment at 4096 chars

- Prevents system prompt bloat from misbehaving hooks
- Remove AUDIT_SUBAGENT_SYSTEM_PROMPT, AUDIT_SUBAGENT_READ_ONLY_TOOLS
- Remove runTurnEndAudit() method
- Remove spawnSubagent import
- Add @@INJECT/@@warn protocol parsing for user-defined feedback
- TurnEnd hooks now control injection behavior via stdout protocol
- Update tests to cover @@INJECT/@@warn instead of audit subagent

TurnEnd is now a pure hook gate  same mechanism as Claude Code Stop hook.
@m8f1m8f1
Copy link
Copy Markdown
Author

@esengine This PR supersedes #2156. Based on your feedback about the audit subagent being outside the scope of a hook mechanism, we've stripped it entirely. TurnEnd is now a pure hook gate with @@INJECT/@@warn stdout protocol — hook scripts decide what checks to run and what feedback to inject; the gate only provides block/pass + protocol parsing.

The PR body has been updated with the full event table and usage examples. No rush — whenever the release batch lands.

@esengine esengine added the v1 Legacy TypeScript line (0.x) — v1 branch, maintenance only label May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v1 Legacy TypeScript line (0.x) — v1 branch, maintenance only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(hooks): extend hook system from 4 to 9 lifecycle events

2 participants