Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion docs/patterns/hook-governance-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,70 @@ matcher syntax is a pipe-separated substring match against the tool name. `Edit|
- [ ] test: verify a theseus subagent is NOT blocked — check is_subagent detection or create a lock file manually
- [ ] test: run autoformat by writing a deliberately un-formatted file; confirm prettier runs; confirm eslint errors surface as additionalContext if any remain

---

### 4. session-start halt/warn pattern

**event:** `SessionStart` hook — runs before any agent turn

**purpose:** surface critical infrastructure failures as hard blocks or amber warnings at session open, before the agent attempts any tool call that depends on those services.

Comment on lines +174 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not label SessionStart notices as hard halts

The new guidance describes this behavior as a “hard block,” but the provided implementation only emits systemMessage JSON and never exits non-zero; later text also says stdout is injected into context before turn one. That means sessions continue even when critical services are down, so operators relying on this as an enforced halt can still run tasks against unavailable infrastructure.

Useful? React with 👍 / 👎.

**problem it solves:** an agent that discovers a downed MCP server mid-task fails with a cryptic tool error, often after spending turns trying to recover. surfacing the failure at session start lets the operator fix it before wasting context.

**behavior — two tiers:**

- **halt (systemMessage):** for services the agent cannot function without. emit a `systemMessage` JSON object to stdout. Claude Code renders it as a system notice before the first agent turn.
- **warn (print to stderr):** for services that are degraded but not blocking. operator sees it in the terminal without interrupting the session.

**implementation:**

```bash
#!/usr/bin/env bash
# session-start.sh — fired by SessionStart hook

check_port() {
nc -z -w2 localhost "$1" 2>/dev/null
}

# critical: qdrant-shared must be reachable for memory ops
if ! check_port 8102; then
echo '{"type":"systemMessage","message":"qdrant-shared MCP (port 8102) is DOWN — memory reads/writes will fail. run infra-up.sh before continuing."}'
fi

# critical: valkey must be reachable for session state
if ! check_port 8110; then
echo '{"type":"systemMessage","message":"valkey MCP (port 8110) is DOWN — session state and rate-limit tracking unavailable. run infra-up.sh before continuing."}'
fi

# warn: jaeger degraded — tracing lost but not blocking
if ! check_port 8120; then
echo "warn: jaeger MCP (port 8120) unreachable — traces will not be captured this session" >&2
fi
```

**emit shape:** `systemMessage` must be valid JSON on a single line to stdout. Claude Code reads the hook's stdout and injects the message into context before the first turn. stderr output appears in the terminal only.

**tiers by service type:**

| service | failure tier | rationale |
| ---------------------------------------------- | ------------ | ------------------------------------------- |
| primary memory store (qdrant-shared) | halt | agent cannot persist or recall knowledge |
| session state store (valkey) | halt | rate-limit tracking and caching unavailable |
| observability (jaeger) | warn | tracing lost but agent function unaffected |
| optional tools (pdf-reader, video-transcriber) | omit | absence is expected in most sessions |

**applied across the heraldstack:**

| repo | services halted on | services warned on |
| -------------------------------- | -------------------------------------------------- | ------------------ |
| shannon-claude-code-cli | qdrant-shared, valkey | jaeger |
| ux-testing-moodle-uploader | qdrant-shared, valkey, chrome-devtools | — |
| chrome-extension-moodle-uploader | qdrant-shared, valkey, chrome-devtools (port 8141) | — |

---

## reference implementation

- repo: `chasko-labs/chrome-extension-moodle-uploader`
- issue: BryanChasko/shannon-claude-code-cli#42
- files: `.claude/hooks/harald-code-gate.sh`, `.claude/hooks/git-orin-gate.sh`, `.claude/hooks/autoformat.sh`, `.claude/settings.json`
- files: `.claude/hooks/harald-code-gate.sh`, `.claude/hooks/git-orin-gate.sh`, `.claude/hooks/autoformat.sh`, `.claude/hooks/session-start.sh`, `.claude/settings.json`
57 changes: 57 additions & 0 deletions docs/patterns/max-turns-bounded-tasks-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# maxturns bounded tasks pattern

set an explicit `maxTurns` on any agent whose task has a known completion boundary.

## problem

an agent without a `maxTurns` ceiling will continue until it decides it is done, hits a context limit, or enters a loop. for agents with open-ended research tasks, this is appropriate — stopping too early loses value. for agents with bounded, verifiable tasks (file a PR, run a validation, scan a directory), an unbounded run is a risk: loop states persist, token costs accumulate, and the entropy anchor gets no signal that something went wrong.

a bounded agent that hits `maxTurns` surfaces as a failed run with a clear count. an unbounded agent that loops surfaces as a timeout or a confusing partial result.

## pattern

assess task boundedness before setting the field:

| task type | maxTurns | rationale |
| --------------------------------------------------- | -------- | -------------------------------------------------------- |
| single git operation (commit, PR, branch) | 20–40 | four to eight tool calls per step, bounded by repo state |
| file audit / scan (read-only pass over known scope) | 15–25 | bounded by file count, not open-ended |
| schema validation | 10–15 | deterministic, few steps |
| UX test session (multi-step automation) | 40–60 | bounded by test script steps, not research depth |
| deep research or open-ended synthesis | omit | value scales with depth; early cutoff loses signal |
| session anchor / orchestrator | omit | must remain alive for the full session |

### Claude Code format

in a theseus agent definition (`.claude/agents/*.md` frontmatter):

```yaml
---
name: hs-shannon-theseus-orin-github-ops
model: claude-sonnet-4-6
maxTurns: 40
---
```

### kiro-cli format

kiro does not expose a `maxTurns` field in agent JSON. bounded execution in kiro is enforced by the DUTIES contract (`DUTIES.md` exit conditions) and the poltergeist anchor's supervision pattern.

## calibration notes

- `maxTurns` counts agent turns (model responses), not tool calls. a single turn can contain multiple tool calls.
- set it 20–30% above the expected turn count for the happy path. too tight causes spurious failures on minor detours; too loose defeats the purpose.
- after a `maxTurns` failure, check the transcript for loop patterns before raising the ceiling — the limit often surfaces a real problem.

## applied across the heraldstack

| repo | agent | maxTurns |
| -------------------------------- | ----------------------------------------------- | -------- |
| shannon-claude-code-cli | hs-shannon-theseus-orin-github-ops | 40 |
| ux-testing-moodle-uploader | hs-shannon-theseus-liora-ux-field-tester | 60 |
| ux-testing-moodle-uploader | hs-shannon-theseus-stratia-ux-reviewer | 20 |
| chrome-extension-moodle-uploader | hs-shannon-theseus-orin-github-ops (ux variant) | 40 |

## reference

- source pattern: shannon audit (2026-04-11), propagated to ux-testing PR #14, chrome-extension PR #503
64 changes: 64 additions & 0 deletions docs/patterns/mcp-server-whitelist-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# mcp server whitelist pattern

declare an explicit, minimal mcpServers block in every agent definition — never inherit a global set by omission.

## problem

when an agent definition omits the `mcpServers` field, the harness loads every server configured for the session. a research agent inherits a filesystem MCP it never calls. a REST-only subagent inherits valkey, qdrant, and github tools that sit idle but consume context and expand the attack surface. the cost is invisible until you audit token usage or trace an unexpected tool call in a session log.

in Claude Code agent definitions, the `mcpServers` field is optional. omitting it does not mean "no servers" — it means "inherit all servers from the session's `.mcp.json`."

## pattern

every agent definition declares its MCP server list explicitly.

### REST-only agents (no MCP tools needed)

```yaml
mcpServers: []
```

an empty array is unambiguous: no servers load for this agent. applies to agents that work entirely through native tools (Bash, Read, Grep, Edit, Write) or make calls over HTTP without needing an MCP server.

examples: `hs-shannon-theseus-orin-github-ops` (uses `gh` CLI via Bash), `hs-shannon-theseus-stratia-codebase-mapper` (read-only native tools), validation agents whose output is pure reasoning.

### agents with bounded tool needs

list only what the agent actually calls:

```yaml
mcpServers:
- qdrant-shared
- context7
```

not the full session set. if the agent does one semantic query and nothing else, it gets one server.

### agents that legitimately need the full session set

leave the field absent — but document why. add a comment or a note in the agent's `## mcp` section explaining that full server inheritance is intentional. this makes the omission a deliberate choice rather than an oversight.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clarify full-session MCP exception to avoid implicit inheritance

This section tells readers to leave mcpServers absent for some agents, which directly conflicts with the file’s core rule (“declare an explicit, minimal mcpServers block” and “never inherit … by omission”). In practice, that ambiguity can cause teams to reintroduce implicit full-session inheritance for ordinary agents and miss it during audits, defeating the safety/token-reduction objective of this pattern.

Useful? React with 👍 / 👎.


## how to audit

run a pass over all agent definitions in `.claude/agents/`:

```bash
grep -L "mcpServers" .claude/agents/*.md
```

files missing `mcpServers` are candidates for review. for each: check whether the agent calls MCP tools. if not, add `mcpServers: []`. if it does, enumerate the specific servers it needs.

also check for dead `disallowedTools` blocks that were added to compensate for inherited servers. when `mcpServers: []` is explicit, there is nothing to disallow — remove the block.

## applied across the heraldstack

| repo | agents updated | result |
| -------------------------------- | --------------------------------------- | ---------------------------------------- |
| shannon-claude-code-cli | 6 REST-only agents | mcpServers: [] + disallowedTools removed |
| ux-testing-moodle-uploader | 2 agents (liora, orin) | mcpServers: [] |
| chrome-extension-moodle-uploader | 6 REST-only agents, 1 chrome-only agent | mcpServers: [] / ["chrome-devtools"] |

## reference

- source pattern: shannon audit (2026-04-11), propagated to ux-testing PR #14, chrome-extension PR #503
- first captured: heraldstack/shannon-claude-code-cli issue #52
Loading