#Problem
Haak reviews things. Some things are confidential — PhD candidates, embargoed manuscripts, grant proposals. The review infrastructure (personas, skills, editorial workflows) lives in haak, but confidential materials cannot.
This recurs. Every admissions cycle, every embargoed review, every personnel evaluation faces the same tension: we want haak's tools but not its git history.
Solving this revealed a more general problem: any sustained work context (a paper going through review, an opinion piece in progress, a grant evaluation) benefits from the same scoping pattern. Confidential scopes are a special case of universal agent scoping.
#Principles
Three design principles govern all architecture in haak:
- No hidden state. All state lives in human-readable files (
.mdor
.yaml). No ambient context, no implicit flags, no databases. If something matters, it's a file you can open and read.
- Encapsulation. Information must not leak between agents, reviewers, or
scopes via model context. All sharing happens through explicit, stored documents. If agent A needs something from agent B, it reads a file B wrote.
- Parallelizable by default. No singletons, no global state, no
serialization. Multiple scopes can run simultaneously.
- No unnecessary structure. Don't create layers, ceremonies, or
abstractions ahead of need. If a skill already does the work, don't wrap it. If a file already carries the state, don't add a manager.
- Traceability. Every output traceable to its inputs — which persona,
which literature, which config, which model produced it. Transcripts, metadata, provenance chains. Transparency shows current state; traceability shows how it got there.
- Human decides policy. The system provides infrastructure; the human
makes all policy decisions — what's confidential, what privacy level, which reviewers, when to use local vs cloud. The system never enforces policy; it provides machinery for the human to choose.
- Documents, not code. Haak is
.mdand.yaml. No Python, no shell
scripts, no programming languages. Code belongs in external projects.
#Main Process Interaction
The main Claude process (not a subagent) handles direct user interaction. Three conventions:
- Name-addressing — "hi editor: ..." or "architect, ..." spawns that agent with the message as its prompt. The main process routes; it doesn't impersonate.
- Don't over-delegate — Answer simple questions directly (Glob/Grep/Read). Use built-in Explore for lightweight searches. Only spawn custom agents for their actual roles.
- Teach as you go — Explain Claude Code mechanisms (hooks, agents, skills, frontmatter, composition) and design trade-offs when relevant. Don't belabor general programming.
#Two Kinds of Scoping
#Specialization (role-based, permanent)
Each role has a fixed base agent template in .claude/agents/:
| Role | Capability | Web access | Memory |
|---|---|---|---|
| Librarian | Searches literature | Monopoly (only agent with web) | None |
| Reviewer | Evaluates through a persona lens | None | None |
| Editor | Orchestrates, judges, synthesizes | None | Project |
| Writer | Produces opinion pieces | None | None |
| Auditor | Verifies provenance and boundaries | None | None (future) |
The librarian's web monopoly is the key architectural move. Content-producing agents (writer, reviewer) cannot search on their own. All literature arrives pre-fetched, through consistent methods, fully traceable.
#Scope (project-based, context-passed)
Agents receive scope context in their Task prompt, not via file assembly. Base agents in .claude/agents/ remain fixed; scope details are passed at invocation time. All durable state lives in the project folder (reviews, transcripts, panel, scope config).
Orchestrating skills pass scope context when spawning agents:
Task(
subagent_type="reviewer", # Uses .claude/agents/reviewer.md
prompt=f"""
## Scope: rv-2026-02-sautory-novelty
- **Working directory**: projects/rv-2026-02-sautory-novelty/
- **Outputs**: projects/rv-2026-02-sautory-novelty/reviews/
- **Round**: R1
- **Persona**: personas/doya-k.md
Review the manuscript at projects/rv-2026-02-sautory-novelty/source/manuscript.md
using the persona above. Focus on {focus_areas}.
"""
)
The lifecycle:
skill reads scope.md + panel.md assignment
→ constructs Task prompt with scope context
→ spawns base agent (e.g., reviewer.md) with prompt
→ agent reads scope context from prompt
→ agent completes, writes output to project folder
→ SubagentStop hook routes transcript to scope directory
The two scoping dimensions compose: a reviewer (specialization) receives 5ht-novelty scope context (project) with the Doya persona assignment. Each invocation is fresh — agents have no memory, receive only what's in the prompt. For multi-round review, the same base agent is invoked with updated round number in the prompt.
#How It Works
#Transcript routing without global state
The SubagentStop hook receives agent_type in its payload (e.g., reviewer). The hook checks:
- If an
scope.mdexists in the working directory → route transcript there - Else → route to default
projects/.transcripts/
No session pointer. No ambient state. The working directory + agent type determine routing.
#Where things live
For regular papers (everything in haak):
projects/rv-2026-02-sautory-novelty/
├── source/ # manuscript
├── reviews/ # review outputs
├── .transcripts/ # agent transcripts
└── scope.md # scoped agent config (paths, panel, round)
For confidential scopes (split across haak + external):
# In haak (tracked in git)
scopes/indp-2026/
└── scope.md # config only: external path, type, dates
# External (not in haak, not in git)
~/projects/indp-2026/
├── candidates/ # source materials (PDFs)
├── outputs/ # extracts, reviews, summaries
└── .transcripts/ # agent transcripts (confidential)
The haak scope directory is a pointer file. Everything confidential — source materials, outputs, transcripts — lives externally. One scope.md links them.
#Git safety
# scopes/<slug> — only scope.md is tracked
# (no confidential content in scope.md — just paths and dates)
No .gitignore needed in .claude/agents/ — base agents are permanent, scope context is passed in prompts.
#Session isolation
One scope per Claude session. If the user asks to switch, the main context warns: "This session has context from [X]. Working on [Y] risks cross-contamination. Recommend starting a new session."
For confidential scopes, the warning is stronger: "This session has seen confidential materials from [X]. Strongly recommend a new session for [Y]."
This is soft enforcement via instructions, not hard enforcement via hooks. Claude Code cannot restrict Read paths, so practical isolation (fresh agent contexts, no memory, everything passed inline) is what we have. The warning makes the boundary visible to the user.
#Confidential Scope: Additional Concerns
#Local model for sensitive extraction
Personal data (candidate PDFs, personnel files) must not be sent to the Anthropic API. A local model (Qwen3-30B-A3B via Ollama, running on M1 Max 64GB) reads raw documents and produces structured extracts. Claude works downstream on the extracts.
The handoff point — where local extraction meets cloud reasoning — is the user's decision per scope. The system doesn't enforce a privacy level; it provides the infrastructure for the user to choose.
#Separate workspace
Confidential materials live in an external project directory. Not gitignored within haak — physically separate. One git add . mistake in haak cannot expose confidential data because the data isn't there.
#Boundary audit
On demand, the auditor scans haak for artifacts that reference scope content — stray tmp files, memory entries, unexpected transcripts. Reports findings for the user to clean up. Not paired with setup; run whenever.
#Agent Roster
Five base agents, each with a fixed specialization. Scoped instances inherit from these templates.
| Agent | Role | Web | Memory | Stateful | Model |
|---|---|---|---|---|---|
| Architect | Designs skills, agents, hooks | Yes | User | Yes | Opus |
| Librarian | Searches, verifies, traces citations | Monopoly | None | No | Sonnet |
| Editor | Orchestrates panels, synthesizes reviews | None | Project | Yes | Opus |
| Reviewer | Evaluates manuscript through persona | None | None | No | Opus |
| Writer | Produces opinion/perspective pieces | None | None | No | Opus |
| Auditor | Verifies provenance and boundaries | None | None | No | Sonnet |
Relations:
- The librarian serves all other agents. It is the only agent with web
access. Editor, reviewer, and writer delegate all literature work to it.
- The editor orchestrates. It spawns reviewers (in parallel) and requests
literature from the librarian. It does not produce content itself.
- Reviewer and writer are content producers. They receive everything
pre-assembled (persona, literature, topic, output path) and work in isolation. No web, no memory, no cross-scope awareness.
- The auditor reads across boundaries. It compares transcripts, inputs,
and outputs to verify encapsulation held. Currently a stub.
- The architect is meta. It designs and maintains the other agents, skills,
and hooks. It does not participate in operational workflows.
Scoping: All content-producing agents (reviewer, writer, editor) get scoped instances (tmp-<role>-<slug>.md) when a sustained work context is set up. The librarian and architect are never scoped — they operate cross-scope (librarian stateless, architect meta).
#Orchestration
No new orchestration layer. Existing skills (/edit-ms, /opinion) already run in main context and already spawn subagents. No changes needed — they already pass context in Task prompts.
There is no /engage or /disengage ceremony. The project folder is the scope. Skills operate on it directly. For confidential scopes, a one-time setup writes the external pointer (scope.md). Boundary checks are standalone auditor invocations, not paired with setup.
#What Gets Built
- Transcript hook update — modify
capture-transcript.shto check
working directory for scope.md and route transcripts accordingly. Simpler than before (no slug parsing from agent name).
- Scope config template — standard
scope.mdformat for
both regular and confidential scopes.
#Deferred
- Local model integration — Qwen3-30B-A3B is installed. The integration
pattern (how skills invoke Ollama, output format, error handling) needs its own design. This includes extract schemas: what structured data the local model produces for each scope type (candidate review, manuscript review, grant review). Separate proposal.
- Auditor agent — stubbed with provisional checks. Will be built when
real audit needs arise from operational scopes.
#What Doesn't Get Built
- No new agent types. Scoped agents are instances of existing roles.
- No new orchestration layer. Existing skills already orchestrate.
- No agent persistence between invocations. Single-use only.
- No anonymization pipeline. The user decides their privacy threshold.
- No encryption or access control beyond filesystem separation.
- No hard enforcement of session isolation. Soft warnings only.
#FAQ
How do we handle confidential materials? External workspace + pointer file. Confidential data lives physically outside haak. The scope.md in haak is just a pointer (paths and dates). One git add . mistake can't expose what isn't there.
How do we prevent cross-scope leakage? Single-use agents with no memory. Each agent is created fresh, receives only its scope's context, writes output, and is destroyed. No shared context windows, no ambient state. The Severance model.
How do we route transcripts without global state? The SubagentStop hook checks the working directory for scope.md. If present, route transcript to that scope's .transcripts/ directory. If not, route to default projects/.transcripts/. Simple directory-based routing.
Why pass scope context in Task prompts instead of creating temp agent files? Simpler, more transparent (context visible in transcript), and violates fewer principles. Temp files add complexity (creation, archival, cleanup, .gitignore) without clear benefits. The base agent definition + scope.md + Task prompt provide the same traceability with less machinery.
Why no engage/disengage ceremony? The project folder is the scope. Skills operate on it directly. Adding a ceremony would violate principle 4 (no unnecessary structure).
How does orchestration work without a new layer? Skills (/edit-ms, /opinion) already run in main context and already spawn subagents. They already pass context in Task prompts. No changes needed.
Can we review a review? Yes. Nesting reviews inside source documents is the normal structure — projects/<slug>/ contains reviews of the source manuscript, and those reviews can themselves be reviewed. The structure is recursive. A proposal can be reviewed; an opinion piece can be reviewed; a review can be reviewed. This is a feature, not a bug — it's how the system exercises self-reflection.
Why does the librarian have a web monopoly? Content-producing agents (reviewer, writer) cannot search on their own. All literature arrives pre-fetched, through consistent methods, fully traceable. This enforces both encapsulation (no ad-hoc context injection) and traceability (every source has a provenance chain).
What about personal data and the Anthropic API? Local model (Qwen3-30B-A3B via Ollama) reads raw sensitive documents and produces structured extracts. Claude works downstream on the extracts. The handoff point is the human's decision per scope (principle 6).
haak · created 2026-02-17 · modified 2026-02-17 · claude
Architecture 06 — Agent Architecture — 2026 — Zachary F. Mainen / HAAK