Skip to content

feat(phase3-B): Stop-gate enforcement, three-outcome routing, bounded retry + instrumentation #32

Description

@CameronCrow

Phase 3 (autonomous trigger), deliverable B. Build to the FROZEN INTERFACE in
planning/PHASE_3.md (Implementation design → FROZEN INTERFACE); do not change those shapes.

Reads the pending-ui/<session_id>.json that issue A writes.

What this is

The deterministic enforcement + loop integration: a Stop hook that refuses to let the agent
finish while UI changes are unverified, and routes the three outcomes. This is the phase's
load-bearing correctness.

Definition of done

  • A Stop hook (plugin hooks/hooks.json) reading the hook stdin JSON (session_id,
    stop_hook_active), plus pending-ui/<session_id>.json and verify-result/<session_id>.json
    per the frozen schemas. Prefer a Python script (python ${CLAUDE_PLUGIN_ROOT}/hooks/…).
  • Decision rules (verbatim from PHASE_3.md — get these exactly right):
    • UI touched, no matching verify-result yet → block ({"decision":"block","reason":…} or
      exit 2 + stderr), reason naming the files to verify.
    • outcome == "fail" → block, reason = the detail (expected-vs-actual) so the agent
      self-corrects.
    • outcome == "pass" covering the touched set → allow (exit 0).
    • outcome == "abstain" → allow, AND surface the abstain detail to the user. Abstain is a
      legitimate stopping point that escalates — NOT a reason to keep blocking. This is the single
      most important rule: if abstain does not satisfy the gate, an unverifiable change thrashes into
      the 8-block cap and then false-passes anyway.
  • Idempotent + stop_hook_active-aware + 8-block-cap-safe: never nag a turn with nothing to
    verify; detect re-entry; never burn the block budget in a loop.
  • The gate is satisfied only when covered covers ui_touched; a later edit to a new UI file
    re-opens it.
  • The mechanism that writes verify-result/<session_id>.json when UI checks run — a small
    addition to the cyclaudes pytest plugin (record outcome on session finish) OR a thin
    cyclaudes verify wrapper. Your call, behind the frozen file contract.
  • Bounded retry: cap the correct→verify cycles; on exhaustion, escalate (allow stop + surface
    a clear "could not satisfy after N tries" message) rather than looping invisibly.
  • Instrumentation: pass/fail/abstain counters persisted for audit, so early runs can be checked
    for swallowed abstentions.

Tests

  • Each decision branch: block-when-unverified, block-on-fail (reason carries the diff),
    allow-on-pass, allow-and-escalate-on-abstain.
  • stop_hook_active re-entry does not thrash.
  • Coverage: a new UI file after a pass re-opens the gate.
  • The verify-result writer produces the frozen schema for pass/fail/abstain.
  • Retry exhaustion escalates rather than blocking forever.

Note

Sibling issue A concurrently creates hooks/hooks.json; you both add an entry — keep your Stop
entry localized, a rebase on that one file is expected and fine.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions