Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

agent-cycle-hooks

Session-handoff discipline for AI coding agents: an armed cycle hook that stays armed until a cycle-matched dissolution archives it, and a dissolution gate an agent can only pass by supplying a value it could only have obtained by opening the handoff.

Two precise claims, stated narrowly on purpose. The hook does not "fire exactly once" on its own — it remains armed across sessions until a correct dissolution archives it, which is what makes an unacknowledged handoff resurface instead of vanishing. And the gate proves the agent opened the hook (it holds a value only present in the file), not that it understood or acted on it — semantic acknowledgement is a discipline you place on the agent, and the human is the one who confirms it. The gate defends the mechanical step; it does not certify comprehension.

Extracted from a live multi-agent engineering program at Tillar Technologies, where Claude- and GPT-family agents hand a project across sessions dozens of times per week. Both failure modes this repo defends against are not hypotheticals — each one happened, was measured, and is documented in the script's own header.

The pattern

  1. At close of business, the outgoing session writes HOOK_NEXT_CYCLE.md at the repo root: what is open, what is signed, where work stopped. First line declares the cycle: # Cycle 034 handoff — ...

  2. At session start, a hook injects that file into the next agent's context (Claude Code example below). The hook carries state only — a handoff file must never be able to mint new authority or override the project's standing rules, or your process is one poisoned handoff away from anything.

  3. The agent must acknowledge the handoff to the human first, then dissolve it:

    bin/dissolve_hook.sh --cycle 34        # or --digest <12+ hex of its sha256>

    Dissolution archives the file to cycles/ under a collision-proof timestamped name (never overwriting an existing archive), so a dissolved hook does not resurface and every cycle leaves an audit trail. Until that archival happens, the hook stays armed and reappears next session.

The argument is the point. Supplying the right cycle number or digest requires having read the hook — which is the precondition acknowledgement depends on. A bare invocation is refused and the hook stays armed for the next session.

The two incidents that made the gate necessary

  • The ritual-follower. An agent tasked only with a narrow authoring job performed the onboarding ritual it had inherited from the project docs — and dissolved the live hook of a cycle it knew nothing about. Agents execute instructions they've merely seen. A cycle-state ritual must not be executable by an agent that hasn't engaged with the cycle.
  • The worktree escape. A review agent in a disposable git worktree ran the documented absolute-path invocation — and archived the parent repository's live hook. Worktree isolation prevents accidental commits, not absolute-path writes.

What the gate actually checks (and what it doesn't)

  • Read-proof argument: --cycle N must match the hook's own first-line declaration (parsed strictly; no shell arithmetic — 30+1 is refused, and so is the arithmetic-expansion injection class it would open).
  • Tree identity: the caller must be physically inside the script's own tree, and — if the caller's directory resolves to a git toplevel at all — it must be this tree. Two guards on purpose: containment catches the no-git-directory case where discovery fails silently; the toplevel comparison catches nested worktrees/submodules that pass a pure prefix test. Git discovery runs with GIT_DIR, GIT_WORK_TREE, GIT_COMMON_DIR, GIT_CEILING_DIRECTORIES, and GIT_DISCOVERY_ACROSS_FILESYSTEM scrubbed, so inherited environment cannot redirect or blind it.
  • Non-oracular refusals: a mismatch message never reveals what would have matched. An early draft printed the expected cycle in its error — handing the next blind attempt exactly the credential it needed.
  • TOCTOU narrowing: the hook's bytes are re-hashed immediately before the archive move; a hook replaced mid-flight is refused. The residual window (recheck→rename) is stated in the script rather than papered over.
  • Scope limit, stated because controls must not claim more than they check: this stops the accidental dissolution — ritual-following — not a determined shell-capable adversary, who can always read the hook. Both measured incidents were accidents. Label your controls with what they actually hold.

Install (Claude Code example)

.claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          { "type": "command",
            "command": "cat HOOK_NEXT_CYCLE.md 2>/dev/null || true" }
        ]
      }
    ]
  }
}

Then instruct your agents (system prompt / project doc): read the hook, acknowledge it to the human, only then dissolve it — and a narrow-task subagent must never run the dissolution at all.

Tests

tests/test_dissolve_hook.sh — ten gated paths in throwaway fixtures: bare and wrong-value refusals leave the hook armed; outside-tree and nested-tree callers are refused; correct cycle and digest paths each archive once; scrubbed git-environment discovery still admits the legitimate caller; a fresh clone with no cycles/ directory still archives (the script creates it); a same-second second dissolution does not overwrite the first archive; and a symlinked hook is refused rather than archived as a mutable reference. Each of the last three is a defect a cross-lineage review found in an earlier draft, now a standing regression test.

Status and license

Source-visible for review and discussion. All rights reserved; license under consideration — open an issue if you want to build on it.

Tillar Technologies — governance outside the model.

About

Session-handoff discipline for AI coding agents: armed cycle hooks and a read-gated dissolution — built from two measured incidents

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages