Implementation Levels¶
Quick Start (Level 1)¶
The simplest useful version — manifest + tier1 loading, no gates.
1. Install¶
# Copy hooks to your project
mkdir -p .agent/hooks
cp hooks/on_session_start.py .agent/hooks/
cp hooks/validators.py .agent/hooks/
# Install dependency
pip install pyyaml
2. Create your config¶
Copy config.example.yaml to your project root as startup-config.yaml:
tiers:
tier1:
- name: project-rules
source: docs/rules.md
description: "Core project rules and conventions"
- name: infra-report
type: checks
description: "Infrastructure health"
checks:
- name: git-clean
command: "git status --porcelain"
validator: empty_output
- name: tests-pass
command: "npm test --silent 2>&1 | tail -1"
validator: "contains:passing"
optional: true
gates:
block_until_tier1: false # Level 1: no blocking
3. Add the hook¶
Add to your .agent/settings.json (or copy from examples/level-1-minimal/):
{
"hooks": {
"SessionStart": [{
"matcher": "",
"hooks": [{
"type": "command",
"command": "python3 .agent/hooks/on_session_start.py",
"timeout": 60000
}]
}]
}
}
4. Start a session¶
the agent sees this output at session start:
STARTUP: 2 OK, 0 FAIL
Manifest: /tmp/manifest-abc123.json
Tier 1: 2 files (45 lines)
ACTION REQUIRED: Read manifest, then read all Tier 1 files.
- /tmp/tier1-project-rules-abc123.md (30 lines, project-rules)
- /tmp/tier1-infra-report-abc123.md (15 lines, infra-report)
Add to your project instructions:
## Level 2: Add Gates
Level 1 relies on the agent voluntarily reading files. Level 2 **enforces** it —
the agent cannot use any tool except Read until all tier1 files are loaded.
### What changes
1. **PreToolUse gate** — blocks Bash, Write, Edit, Agent, etc. until tier1 is
complete. Only Read is allowed (so the agent can load the files).
2. **UserPromptSubmit gate** — injects "read files first" into the agent's context
if tier1 is incomplete. Also tracks prompt count and warns at thresholds.
### Install
```bash
cp hooks/gate_check.py .agent/hooks/
cp hooks/on_prompt_submit.py .agent/hooks/
Update startup-config.yaml:
Copy settings from examples/level-2-gated/settings.json or add the
PreToolUse and UserPromptSubmit hooks to your existing settings.
How it works¶
- SessionStart generates tier1 files + writes sentinel with
stage: "tier1_pending" - the agent tries to use Bash → PreToolUse gate reads sentinel → tier1 incomplete → DENIED
- the agent reads tier1 files → gate_check tracks each Read in sentinel
- All tier1 files read → sentinel updated to
stage: "complete" - the agent tries Bash again → gate checks sentinel → tier1 complete → ALLOWED
The UserPromptSubmit hook adds a second layer: if the agent somehow ignores the PreToolUse denial, the prompt gate injects a message saying "read files first" that the agent sees before composing its response.
Level 3: On-Demand Tier 2¶
Not all rules are needed every session. Tier 2 files load only when relevant keywords appear in the agent's tool calls.
What changes¶
Add tier2 definitions with trigger keywords to your config:
tiers:
tier2:
- name: api-rules
triggers: ["api", "endpoint", "REST", "swagger"]
source: docs/api-rules.md
- name: deploy-guide
triggers: ["deploy", "CI", "pipeline", "release"]
source: docs/deploy-guide.md
How it works¶
- the agent runs
Bash("curl api.example.com/...") - PreToolUse gate scans the command text for tier2 triggers
- Finds "api" matches the
api-rulestrigger → DENIES with message: "Tier 2 files triggered — read before proceeding: api-rules" - the agent reads the file → gate tracks it → next tool call is allowed
Keyword scanning is limited to prevent false positives: - Only scans specific JSON fields (command, file_path, prompt, description) - Only scans the first N characters of each field (default: 120) - Case-insensitive matching
Cross-Check Drift Detection¶
After tier1 loads, run a single drift check comparing expected vs actual state. This catches stale references before they cause problems.
Add a script that checks your project's invariants:
# Example: verify rule count in docs matches actual count
expected = manifest["expected_counts"]["rules"]
actual = len(list(Path("rules/").glob("*.md")))
if expected != actual:
print(f"DRIFT: rules count {expected} in manifest vs {actual} on disk")
The cross-check runs once per session (tracked by cross_check_done in sentinel).
When drift persists across sessions, the cross-check generates
write_back_suggestions — stored in the sentinel file under that key. Each
suggestion is a structured object proposing a specific manifest update:
{
"write_back_suggestions": [
{
"field": "expected_counts.rules",
"current_value": 72,
"suggested_value": 78,
"reason": "rules count on disk (78) has differed from manifest (72) for 3 sessions"
}
]
}
These suggestions are advisory — they are not auto-applied. The agent or user reviews them and updates the config if appropriate. This keeps the manifest accurate without requiring manual audits.
Level 4: Full Architecture¶
Stop Hook¶
Block session exit until cleanup is done:
stop:
require_clean_repos: true
require_transcript: false
require_self_verification: true # blocks exit if infra files edited after last check
require_audit_pass: true # runs audit checks before allowing exit
# require_session_summary: true # DB mode only — not generated by default
max_retries: 8
The stop hook returns exit code 2 (retry) when checks fail. the agent sees the failure message and can fix the issue (e.g., commit uncommitted files). After max retries, it exits cleanly to avoid trapping the user.
require_self_verification — if any infrastructure file (config, manifest,
hook scripts) was edited during the session, the stop hook blocks exit until the
agent re-runs validation. This prevents sessions from closing with broken infra.
require_audit_pass — invokes audit.py before exit. The audit runner
validates project invariants (rule counts, file integrity, config consistency).
If any check fails, exit is blocked with actionable error messages.
require_session_summary (DB mode only, not generated by default) — ensures
a session summary is saved to the database before exit, preserving continuity
across sessions. Enable manually when using SQLite or PostgreSQL.
PostToolUse Hook¶
The PostToolUse hook (on_edit.py) fires after every tool use and provides
three capabilities:
{
"hooks": {
"PostToolUse": [{
"matcher": "",
"hooks": [{
"type": "command",
"command": "python3 .agent/hooks/on_edit.py",
"timeout": 10000
}]
}]
}
}
-
Edit tracking — logs every file edit to the
rule_logtable with timestamp, file path, and tool used. Creates an audit trail for the session. -
Save reminders — when the agent has made significant changes without saving, the hook emits a reminder to persist work.
-
Rule Zero enforcement — scans edited files for scattered content (rules, facts, or decisions written inline instead of in the canonical store). When detected, warns the agent to consolidate into the proper location.
No-Truncation Enforcement¶
At Level 4, the system verifies that DB stores match source length. When content is written to the database, the stored text is compared against the original source to detect truncation. This catches cases where large files are silently cut short during ingestion.
Consistency Checker¶
The setup wizard generates a project-specific consistency checker at Level 4. This checker validates cross-references between config, manifest, and hook scripts — catching mismatches like a config referencing a hook file that doesn't exist, or a manifest listing a source file that has moved.
Output-Based Validators¶
Shell exit codes lie. A piped command like git status | grep -v node_modules
returns 0 even when there are uncommitted files (because grep succeeded). The
validator registry parses stdout instead:
| Validator | Passes when |
|---|---|
empty_output |
stdout is empty (whitespace-only) |
not_empty |
stdout has content |
contains:text |
stdout contains text (case-insensitive) |
equals:text |
stdout exactly equals text (stripped) |
regex:pattern |
regex matches anywhere in stdout |
Add custom validators by extending validators.py:
Session-Scoped Isolation¶
All temp files include the session ID suffix (-{SESSION_ID}). This prevents:
- Concurrent sessions overwriting each other's state
- Resumed sessions reading stale sentinel from a previous run
- Race conditions between multiple AI coding agents instances