Activation fixes for the single-session presence gate. Two bugs blocked it,
both caught by live testing after all units were green:
1) Wrong branch key. presence_gate used Path.cwd().name, but under the Claude
Code bridge the hook process cwd is the project root, so every session keyed
to "AIPass": the gate never enforced one-live-session-per-branch and would
have rejected sessions project-globally (any 2nd interactive session in any
branch). Now _resolve_branch(hook_data) reads the event payload's cwd (the
real session dir) and walks up to the branch root (.trinity/ or apps/),
mirroring branch_loader. Applied in handle() and handle_stop().
2) Block never reached Claude Code. engine.dispatch() returned only stdout, so
the bridge could not surface a non-zero exit. dispatch() now returns
(stdout, exit_code) and the bridge exits with it on a block. Pre-existing gap
affecting every block hook on every event; now fixed engine-wide. An
intentional block (exit 2 + {"decision":"block"}) propagates; a crashing hook
(exit 2, non-JSON stdout) is logged and falls through, so the gate fails open.
Proven: 110 hooks unit tests pass (6 new for branch resolution); seedgo @hooks
100%, no type errors. Live bridge end-to-end (real live holder + real bridge):
duplicate into a held branch -> exit 2 + block reason naming the branch; a
different free branch -> exit 0 (per-branch isolation intact). Gate remains
dormant: not yet wired into provider settings.
Design: DPLAN-0225 / FPLAN-0289 P1 activation. Build by @hooks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqoxFdbDMirzkQ5kjRVVos
.aipass/ — project prompt & hook config
This folder holds the project-level prompt and hook configuration for the AIPass
repo, plus the templates aipass init stamps into every new project. It is the
project layer; each branch additionally has its own branch prompt at
src/aipass/<branch>/.aipass/aipass_local_prompt.md.
Nothing here is dead weight. Every file is live injection, live config, or a required new-project template. Superseded files live in
.archive/(never deleted).
One prompt system, every runtime
There is one source of prompt truth — the tier files — and all runtimes inject the same content. We do not keep separate prompts per CLI. Only the delivery differs:
| Runtime | How the same content is delivered |
|---|---|
| Claude Code | Tiered by cadence (FPLAN-0284): tier0_kernel.md every turn + tier1_navmap.md periodically + post-compaction |
| Codex CLI | Injected once at SessionStart (no per-turn cadence): the same tier content, combined |
⚠️ Migration in progress. The Codex SessionStart hook (
.codex/hooks/session_start_identity.py) currently still reads the legacyaipass_global_prompt.md. @hooks is wiring it onto the tier files. Retire for one runtime = retire for all — once Codex is on the tiers,aipass_global_prompt.mdis read by nothing and moves to.archive/.
Files
Live — this repo's prompt + config
| File | What it is |
|---|---|
tier0_kernel.md |
The kernel — tiny identity + drone --help reflex + don't-get-lost rules. The always-on core, for every runtime. |
tier1_navmap.md |
The navmap — full agent roster, framework, terminology. The periodic/fuller layer, for every runtime. |
hooks.json |
Claude Code handler registration for this repo — which prompt/gate/notification handlers fire on which events. |
PROMPT_STYLE.md |
The writing-style guide every prompt here follows. |
.gitignore |
Whitelist guard — only files listed here are tracked; everything else in .aipass/ is ignored. |
aipass_global_prompt.md |
Legacy single global — being retired. Disabled for Claude Code; Codex still reads it until its migration lands, then archived. Not the source of truth. |
Templates — stamped into new projects by aipass init (bootstrap.py)
| File | Stamps → | Notes |
|---|---|---|
project_hooks.json |
new project's .aipass/hooks.json |
REQUIRED — without it a new project's hooks never fire. Mirrors the live wiring (tier0 + navmap enabled, global disabled). |
project_CLAUDE.md |
new project's CLAUDE.md |
the project's Claude Code instructions. |
project_global_prompt.md |
new project's aipass_global_prompt.md |
Legacy — same retirement path as the global above (new projects ship tiers-only once Codex is migrated). |
(AGENTS.md — Codex's equivalent of CLAUDE.md — is generated by bootstrap.py
when no project_AGENTS.md template exists, so none is kept here.)
What a new project gets (aipass init)
bootstrap.py seeds a fresh project with the tiered system:
tier0_kernel.md+tier1_navmap.md→ the prompt content (every runtime)hooks.json(fromproject_hooks.json) → tier0 + navmap enabled, global disabledCLAUDE.md(fromproject_CLAUDE.md) + a generatedAGENTS.mdaipass_global_prompt.md(fromproject_global_prompt.md) → legacy, retiring with the above
aipass init update backfills the tier files + refreshes hooks for existing projects.
Changing a prompt here
Run the prompt-change playbook so a change reaches every runtime and every seed path:
drone @flow create . "What changed" prompt_change
Golden rule: live ≠ seeded. Editing this folder fixes this repo only. New projects
come from the project_* templates + bootstrap.py; fresh clones get their machine-local
wiring from setup.sh + .claude/provider_manifest.json + cadence.py defaults. And
every runtime (Claude Code + Codex) must point at the same tier content.
Archive & recovery
Superseded files move to .archive/ (never deleted — house rule). Recover from there, or
from git history, any time. Current archive: the pre-tiering
aipass_global_prompt.BACKUP-2026-06-09-S211.md snapshot.