Faceted Prompting: 5-concern separation
This series is a record based on the harness environment as it existed at the time of writing (file contents, run logs, file counts). The harness has kept evolving since this series was written, so quoted file contents, counts, and procedures may differ from the current state. The focus of this series is the design ideas (five-facet separation, verification gates, growth discipline) rather than the specific values.
This chapter covers the philosophical core of the rebuild — Faceted Prompting.
The idea is simple: separate prompts into five concerns, and compose them declaratively at workflow definition time. Faceted Prompting is an official concept from nrslib/takt (AI Agent Orchestration Framework, MIT, by nrslib), defined in the canonical docs. What is the author's own: deciding how it maps onto Claude Code's mechanisms and how composition is designed for the independent port. The five-concern decomposition itself comes from takt (a deeper takt introduction follows in the next chapter).
2.1. Why "separation" was needed
Among the four symptoms covered in the previous chapter, symptom 2 (same instruction scattered across multiple facets) and symptom 4 (silent drop) were essentially caused by fuzzy boundaries.
Recall the example "don't run git commit until an explicit trigger word appears" written in 4 places pre-rebuild:
- The main-rules section of CLAUDE.md
- rules/git-workflow.md
- rules/safety-gate.md
- skills/commit/SKILL.md
Why these were scattered, broken down:
| Location | Author's framing | Actually |
|---|---|---|
| CLAUDE.md | Declaration of main rules at the top | The content is a norm (Policy) |
| rules/git-workflow.md | Norms for git operations | A norm (Policy) |
| rules/safety-gate.md | Norms for destructive operations | A norm (Policy) |
| skills/commit/SKILL.md | Commit procedure | Procedure (Instruction) — but normative wording is mixed in |
The framing was "different angles, so different files," but all of those angles are norms, not procedures. Splitting by angle (what about) is not the same as splitting by concern (what kind of). When concern becomes the unit, having the same norm in 4 places stops being structurally possible.
Faceted Prompting fixes this concern axis at five.
2.2. The five concerns
The new harness classifies the information inside prompts into the following five kinds:
| facet | what kind of | location | example |
|---|---|---|---|
| Persona | who (the actor behind the behavior) | agents/*.md body + top of CLAUDE.md | "you are a critical plan reviewer," "the main agent behaves like ..." |
| Policy | how (norm, "this is how it should be") | rules/*.md | "back claims with evidence," "commit requires an explicit trigger word" |
| Instruction | what to do (procedure) | skills/*/SKILL.md | "① architect spawn → ② plan-reviewer → ③ human approval ..." |
| Knowledge | what is (descriptive knowledge) | knowledge/*.md (shared) / repos/<repo>/ (per-repo) | API specs / glossary / design-decision history |
| Output Contract | what comes out (artifact template) | output-contracts/*.md | spec / plan / story / done / review templates |
Each in detail.
Persona — who
Persona defines the actor behind the behavior.
- main agent's Persona: the "who are we talking to" section at the top of
CLAUDE.md - subagent Persona: the body of each
agents/<name>.md(= system prompt)
What goes in a Persona: "who you are," "what your primary goal is," "what tone you respond with." Concrete norms and procedures do not go here. As an example, here is the Persona portion of the current agents/plan-reviewer.md:
You are a critical plan verifier. You verify plan.md against the spec.md original
and issue a VERDICT. You do not implement (Write is for review.md only; do not
modify code).
This is just one line each of "who" (critical plan verifier), "primary goal" (issue a VERDICT), and "constraint" (do not implement); the verification points and VERDICT format are in separate sections (## Verification points and ## Output contract). Persona is a switch that triggers the responsibility, and concrete norms belong in separate sections — that is the operational hygiene.
Pre-rebuild, agents/<name>.md files had commit discipline and verification discipline mixed inside Persona, and each subagent had its own slightly different wording. That was the direct result of fuzzy boundaries between Persona and Policy.
Policy — how
Policy writes how (this is how it should be).
- location:
rules/*.md - delivery: most are constantly loaded without
paths:(required to reach subagents) - size cap: 30 lines per file
A representative Policy is rules/verification.md. Here is its opening:
# Policy: verification
> No paths = constantly loaded. Not path-scoped because developer / auditor
> (subagents) depend on it.
## Back claims with evidence
- Show test / grep / command **output as evidence**. Don't claim "fixed" or
"works" without output.
- Verify command output **in full** before concluding. Don't falsely conclude
"no match" from a truncated `head` / `tail`.
- Don't blindly trust claims from subagents / tools. Independently re-verify
important conclusions.
What's written here is normative — "judge it this way," "behave this way." Concrete procedures are not included. Procedures like "run the test" or "execute grep" are written in separate Instructions (= SKILL.md).
Policy is strictly one-file-one-concern: verification.md is only about verification, fact-check.md is only about fact-check, and so on. This structurally prevents "same norm scattered across multiple Policy files."
Instruction — what to do
Instruction writes procedure (what runs in what order).
- location:
skills/<name>/SKILL.md - delivery: the main agent (= the orchestrator) picks up the trigger word (
/autodevetc.), loads the SKILL.md, and runs the flow itself. There is no path that delivers the SKILL.md body itself to a subagent — the orchestrator composes/embeds the procedure and acceptance criteria it needs into story.md (or the spawn prompt) instead - size cap: 80 lines per file (autodev is allowed ≤100)
A representative Instruction is skills/autodev/SKILL.md. Here's the opening:
---
name: autodev
description: Orchestrate from spec to implementation through a subagent chain.
A fail-closed verification-gate loop from ⓪' through ⑤. Launch
with /autodev <spec path>.
---
Launch with `/autodev <repo>/specs/<feature>.md`. You (the main agent) act as
the orchestrator (the takt-engine role) and run through ⓪' to ⑤.
## ⓪' Pre-launch assert + resume judgment
- realpath the spec-path argument → extract `<repo>` → ...
- ...
## ① architect spawn (read-only, fresh)
Inject spec.md + per-repo knowledge into the spawn prompt by absolute path
→ architect produces plan.md
What's written are ordered procedures, not normative wording. "The reader behaves this way" is declared step by step.
Do not write norms inside Instructions is the key. Norms like "leave evidence" or "confirm before commit" should already be in a Policy, so the Instruction doesn't need to restate them (restatement increases sync cost). An Instruction's role is to fire Policies in the right order.
Knowledge — what is
Knowledge writes descriptive knowledge (what is).
- location (shared):
knowledge/*.md(empty at bootstrap; the policy was to add it if needed — it was later added usage-driven) - location (per-repo): under
~/.claude/repos/<repo>/(CLAUDE.md / CONTEXT.md / INDEX.md / specs/ / handoff/) - delivery: reference from index (CLAUDE.md / INDEX.md) + Read on demand
Knowledge is the most flexible facet. "Facts," "specs," "vocabulary," "history" — anything that is descriptive but not normative or procedural goes here.
As an example, here is part of ~/.claude/repos/autodev-smoke/CONTEXT.md (the glossary):
# autodev-smoke — glossary (Ubiquitous Language)
- **fizzbuzz(n)**: a function that takes integer n and returns "Fizz" for
multiples of 3, "Buzz" for multiples of 5, "FizzBuzz" for multiples of both,
and the string form of n otherwise.
- **palindrome-check(s)**: a CLI that takes argument s and exits 0 if s is a
palindrome in Unicode code points (exact match with its reverse), 1 otherwise.
Empty string is a palindrome (exit 0). Raw comparison (case-sensitive).
This is descriptive fact (definitions), not norm or procedure.
A characteristic of Knowledge: it is the only facet where body-copy is forbidden. Policy / Instruction / Persona / Output Contract are basically delivered by copy when their body needs to be cited elsewhere (to avoid silent drop). Only Knowledge is fine with "reference from an index + Read on demand." That's because Knowledge is descriptive, and the order or oversight of composition doesn't translate into rule violations.
Output Contract — what comes out
Output Contract defines artifact templates.
- location:
output-contracts/*.md - delivery: each agent body's
## Output Contractsection names the relevant file, and the subagent reads it itself (same as Instruction, this doesn't use a frontmatter preload path) - size: each template is around 30 lines
Output Contract is takt's official facet (corresponding to the output_contracts: field each step carries in takt's workflow YAML), factored into its own directory in this rebuild. It templatizes subagent outputs so that the main agent can grep / parse them mechanically.
A representative example is output-contracts/review.md:
VERDICT: PASS|FAIL
> Output Contract (template). Generic verification report. Shared by
> plan-reviewer (spec ⇔ plan) and auditor (done.md evidence ⇔ spec original).
## Primary signal
<plan-reviewer: coverage of spec pass conditions / auditor: result of
comparing done.md evidence against expectations re-derived from spec original>
## Comparison details (N/A allowed)
- spec coverage / non-triviality (does an empty / no-op implementation pass?):
- code ⇔ spec (auditor: contradiction between `git diff <base>...<branch>` and
the spec):
- doc update omissions (story-listed = FAIL / unlisted inconsistency = Minor
warning):
- per-repo policy violation:
## Findings (when FAIL)
<the path to revisit and concrete fix instructions>
The fixed first line VERDICT: PASS|FAIL is important. With it, the main agent (orchestrator) can decide the branch with:
grep '^VERDICT:' .../review.md
alone, without reading the full review.md into context. This is a structural way to avoid the context pressure of long review bodies.
Output Contract did not exist pre-rebuild; this round adopts takt's official facet as the foundation for the autodev flow (verification-gate loop).
2.3. hooks are an enforcement layer, not a facet
Separate from the five concerns is hooks/ (Computational Sensors). These are not a facet but an independent enforcement layer:
Facets are Inferential Guides (instructions interpreted by the LLM); hooks are Computational Sensors (scripts that judge mechanically). In terms of the Guides × Sensors 4-quadrant frame, the five facets are a layered split of the left half (mainly Guides) and hooks are the right half (mainly Sensors).
The role of hooks is to stop deterministically when Policy / Output Contract violations happen probabilistically. For example, the Policy "commit requires an explicit trigger word" (rules/git.md) was empirically not always honored, and so hooks/check-git-commit-gate.sh was added to forcibly gate git commit (details in Chapter 5).
The reason hooks aren't a facet: enforcement is a tool for forcing norms or procedures, not the norm or procedure itself. The body lives in the five facets; hooks merely check after the fact and don't carry the body of knowledge.
2.4. Routing: where to put a new instruction
The most important operational use of the five-facet boundary is deciding where a new instruction goes.
The new harness fixes the following routing tree:
Following this flowchart:
- Want to write a procedure into a Policy? → it's an Instruction, put it in
skills/ - Want to write a norm into an Instruction? → it's a Policy, put it in
rules/ - Want to write a procedure into Knowledge? → it's an Instruction, somewhere else
- No place fits? → write it as philosophy at the top of CLAUDE.md, or no faceting needed
The decisions are fixed. Pre-rebuild, the phenomenon of "the same instruction scattered across four places" used to happen, because the boundary between the five concerns was fuzzy.
- "Norm or procedure?" — judge by the verb: "do X" (procedure) vs. "should be X" (norm)
- "Policy or Persona?" — ask whose behavior changes if I delete this sentence. If every subagent's behavior changes, it's Policy. If only one subagent's, it's Persona
- "Knowledge or Instruction?" — if the sentence is meaningless unless embedded in a procedure, it's Instruction. If it can be referenced independently, it's Knowledge
- "Output Contract or Knowledge?" — ask whether the content gets greped / parsed from another facet. If yes, it's Output Contract
2.5. Operating "one file, one concern"
In addition to the five-facet boundary, there is the discipline of one file, one concern.
It means "don't mix multiple concerns (Policy + Instruction or Policy + Knowledge) inside one file." Concretely:
rules/verification.mdonly writes verification Policy (no procedures or knowledge mixed in)rules/git.mdonly writes git-operation Policy (no concrete commit-command examples)skills/autodev/SKILL.mdonly writes the autodev procedure (refers out for normative wording instead of restating it)- The body of
agents/auditor.mdonly writes the auditor Persona (rules/verification.md is referenced, not body-copied)
Pre-rebuild, rules/ often mixed norm, example, and procedure. For example, rules/git-workflow.md mixed "commit requires an explicit trigger word" (norm), "use git add <file> to specify when committing" (procedure + norm), and "how to handle broken hooks" (procedure). Re-cutting along one-file-one-concern lines split these into:
- norms →
rules/git.md - procedures → the relevant
skills/<name>/SKILL.md
with each landing under 30 lines.
You might worry: "isn't it a DRY violation if Policy and Instruction both write about the same matter?" The new harness organizes this via the application boundary of DRY:
- DRY across concern boundaries (consolidating the same norm into one file) — enforced
- DRY within a concern (Instructions don't restate Policies) — enforced
- DRY across concern boundaries by copy (Instructions copying Policies) — avoided
When an Instruction restates a Policy, sync cost scales exponentially. The Instruction refers to the Policy by link; the body lives only in the Policy file. This compresses sync cost structurally.
2.6. Summary of this chapter
- Faceted Prompting splits prompts into 5 concerns (Persona / Policy / Instruction / Knowledge / Output Contract). It is nrslib/takt's official concept adopted by this series, not coined here
- hooks are not a facet but a separate enforcement layer (Computational Sensors)
- Routing a new instruction uses a 5-way + 1 (hooks) flowchart and minimizes hesitation
- One file, one concern is enforced; DRY across concern boundaries is avoided (Instructions reference Policies, never body-copy them)
- The pre-rebuild norm-duplication problem traced directly to fuzzy concern boundaries. Fixing the boundaries at five structurally reduced "the same norm scattered across facets"
The next chapter covers the other core of this series — adopting takt's philosophy. It introduces nrslib/takt itself (the origin of Faceted Prompting) and walks through how this harness independently ports it onto Claude Code, with a side-by-side correspondence table.