Three weeks ago I wrote up my Claude Code + Obsidian setup: an orchestrator that never touches code, an executor sub-agent that does, a reviewer sub-agent that grades both, and a Python hook that stops anyone from declaring victory early. It had a diagram-shaped energy to it. I was proud of it.
That post ended with a section called “What I'd change,” where I listed a few gaps I was “knowingly leaving alone.” Then I actually used it every day for three weeks, and found out “knowingly leaving alone” is a phrase that ages like milk. So I ripped most of it out.
The new setup has fewer agents, more paperwork, and a couple of checkboxes in Obsidian that nothing moves past until I tick them. It's less clever and it works better. That's usually how it goes.
What went wrong with v1
The reviewer was grading the essay, not the code
To save tokens, the v1 reviewer read the step's ## Plan and the executor's ## Execution Log, and nothing else. No diff, no files. I even pointed this out in the old post, calling it “a different gap.” It wasn't a gap. It was the whole thing.
The execution log is written by the executor, about the executor. So the reviewer was checking whether the executor's description of the work matched the plan. An executor that writes “added input validation for empty IDs” and then validates the wrong field passes every time. That's not code review. That's reading a press release and approving the product.
Two to five times the tokens, spent on handoffs
“The orchestrator never edits code” sounded like discipline. In practice, every step meant spawning a cold executor with a 400-token note, letting it rediscover the codebase from zero, then spawning a cold reviewer to read what it wrote. Step 3 had no idea what step 2 had learned about the repo, because step 2's executor was gone and all it left behind was a one-line summary.
I put a whole section on token efficiency in that post. Six bullet points of rules for keeping costs down. In hindsight, if you need six rules to make an architecture affordable, the architecture is the problem.
A robot approving a robot, with me watching from the audience
The plan-review gate was Opus reviewing a plan written by Opus. I knew it was a correlated blind spot and wrote it off as a cost I hadn't measured yet. The bigger problem was that the loop had no place for me. The plan got approved, steps got executed, and I read about it afterwards in the vault.
An append-only audit trail is lovely. It's also what you read after something has died. I wanted to make the call before three repos of code got written against a plan I'd have rejected in ten seconds.
The tests were self-reported
The executor ran the tests and logged “a one-line result summary, not raw output.” Again, to save tokens. Which means the only proof that the tests passed was a sentence written by the agent whose work was being tested. Nobody defined which tests should exist, either. “Tests pass” is very easy to say when you also picked the tests.
Sessions wandered
v1 organised the vault by feature, not by repo, so one feature could span three codebases. Sounds flexible. In practice it meant a session working on one repo would happily open a sibling folder in ~/Documents/ because it “looked related.” And since the orchestrator kept one long session going across steps, the context grew until I remembered to /clear, which was roughly never.
The enforcement hook was a grep
The thing “actually enforcing” review was a Python script grepping Markdown for the string APPROVED. It worked, right up until a reviewer wrote “not APPROVED yet, see below.” You can guess how a substring match feels about that sentence.
The new setup, in one sentence
One Opus session does the work, one Opus sub-agent reviews the actual code, a real test command decides pass or fail, and nothing starts until I tick a box in Obsidian.
| v1 | v2 | |
|---|---|---|
| Who writes code | Executor sub-agent (Sonnet), cold every step | Main session (Opus), with full context |
| Sub-agents | Executor + reviewer | Just code-reviewer |
| Who approves the plan | Reviewer agent | Me, by ticking a checkbox |
| What review reads | Plan + the executor's log | The ADR + every changed file, in full |
| Tests | Self-reported in one line | Defined in the ADR first, then run as their own stage |
| Retry cap | 3, then “surface it” | 2, then blocked and my approval revoked |
| Scope | One vault, features span repos | One vault per project, locked to one repo |
| Session length | One long orchestrator session | One component per session |
One vault per project, and the vault knows where the code is
Every project gets its own vault under ~/Obsidian/, and the vault's index.md holds the two facts a session actually needs, in frontmatter:
---
type: project-index
repo: ~/Documents/maze-solver
test: npm test
---Nothing about the active vault is saved between sessions. The first thing every session does is ask which project this is for: an existing vault, a new one, or “don't track” for the times I just want to rename a variable without filing paperwork. Once I pick, it says Tracking in vault: maze-solver → repo: ~/Documents/maze-solver, and from then on it reads and writes code only in that repo. “Even if they look related” is in the rules for a reason. I've met the “looks related” instinct, and it does not stop at the folder boundary on its own.
Six stages, and two of them are mine
1. PLAN → 2. DECIDE (ADRs + test cases) → 3. IMPLEMENT
→ 4. REVIEW (subagent) → 5. TEST → 6. LOG → done
↑______ fix (max 2 retries) ______|Plan isn't one plan any more. It's a note with two or more approaches, each with pros, cons, and effort/risk, plus Claude's recommendation. At the bottom:
## Approval
- [ ] Approve A
- [ ] Approve BI read it in Obsidian, I tick one, and that's the exit. Claude recommends, I decide. Writing out approaches A and B means the plan can be wrong in a way I can see, which is a big step up from a plan that's quietly wrong and approved by its own twin.
Decide turns the approved plan into an ADR (an architecture decision record). The master note records the decision, the alternatives that got rejected, and the consequences I'm accepting. Then there's one note per component: one API, one function, one interface. Each component note has its interface, its steps, a mermaid sequence diagram, and its own - [ ] Approve implementation box.
Approval is per component. Once I tick one, Claude runs implement → review → test → log for that component without asking me anything else. I'm in the loop at the two points where my opinion is actually worth something (which approach, and is this component specced right), and out of it everywhere else.
One rule I like more than I expected: never edit the Decision section of an accepted ADR. If the decision changes, that's a new ADR, and the old one gets marked superseded with a link. v1's plans were append-only too, but a plan doesn't tell you why it's the plan. An ADR has to.
Test cases come before code
This is the change I'd keep if I had to throw away everything else. Every component ADR has a ## Test Cases section, written in Stage 2, before any implementation:
## Test Cases
- T1: returns 200 and the user when the id exists
- T2: returns 404 when the user does not exist
- T3: returns 400 when the id is malformedI review those lines when I approve the component, and they're the cheapest review I do all day. Three plain-English sentences tell me whether Claude understood the problem far faster than three hundred lines of code would. Then Stage 3 has to write a real test for every T. If Claude spots a missing edge case halfway through, it adds a T4 line to the ADR first and then writes the test. The spec stays ahead of the code, never behind it.
The reviewer reads the actual code now
There's exactly one sub-agent left, code-reviewer, and it's deliberately unfriendly. Its prompt opens with “You did NOT write this code and have no attachment to it. Your job is to find problems, not to be agreeable.” It gets two things: the component ADR path and the list of changed files. Not the main session's reasoning, not the chat history, not “here's what I was going for.” It reads the ADR, reads every changed file in full, and checks correctness, edge cases, error handling, security, readability, and, most importantly, whether every T in the ADR has a test that would actually fail if the behaviour broke.
Its answer has a fixed shape:
VERDICT: APPROVE | CHANGES_REQUIRED
ISSUES:
- [blocker|major|minor] <file>:<line> — <problem> — <suggested fix>
MISSING TEST CASES:
- <one-line description, or "none">It only approves with zero blockers and zero majors. It doesn't edit files and it doesn't run tests. Reviewing and testing are separate jobs, and a reviewer that also runs the tests starts treating “green” as “fine.”
Yes, this costs more tokens per review than v1's reviewer did. But I dropped a whole executor spawn per step, so the total still comes out lower. And now the review actually looks at the code.
Tests are their own stage
Once review approves, the main session runs the test command from index.md, inside the repo. Every test for this component has to pass, and nothing that passed before is allowed to break. If something fails, the code gets fixed and then goes back to review, not straight back to testing. Fixed code is new code, and new code gets reviewed. v1 had no rule like that, because v1 didn't really have a test stage.
Two strikes, and it hands the problem back
Every component has an attempts counter in its frontmatter. Each CHANGES_REQUIRED or failed test run bumps it. When it hits 2 and things still fail, Claude sets the component to blocked, logs it, tells me what's failing and what it would try next, and unticks my “Approve implementation” box.
That last part is my favourite line in the whole file. It isn't just stopping; it's formally handing my approval back. Nothing moves until I read what went wrong and tick the box again. v1's cap was three, picked arbitrarily, and the old post admitted as much. Two is just as arbitrary, but it fails faster, and the second failure usually tells me the spec is wrong, not the code.
Everything gets logged
Every review and every test run, pass or fail, gets an entry at the top of review-log.md:
## 2026-10-12 21:40 — [[adr/004-solver-api/02-solve-endpoint]]
- Stage: review
- Attempt: 1
- Verdict: CHANGES_REQUIRED
- Issues: [major] solver.ts:88 — unreachable goal returns [] instead of 422
- Tests: 0/0, failing: none
- Files: src/solver.ts, src/solver.test.tsEach entry also gets a wikilink under the component's ## History, so opening a component in Obsidian shows every time it got bounced, and why. Combined with the per-day session notes, it's the same audit trail v1 promised. The difference is that this one records decisions I actually made.
One component per session
Since every bit of state lives in the vault (statuses in frontmatter, decisions in ADRs, history in the log), a session has nothing worth holding on to once its component is done or blocked. So it says “Component finished — start a new session for the next one,” and I do.
The nice side effect is resuming. If I close the laptop mid-review, the next session opens the vault, finds a component sitting in in-review, and says “Resuming 02-solve-endpoint at review.” Then it picks up from the latest log entry. No context to rebuild, no “where were we.” v1 claimed the vault was its memory, but it still kept a lot of state in one long session. v2 actually means it.
Why Obsidian, still
Same reason as last time: it's a folder of Markdown, and Claude Code already reads and writes files. No plugin, no MCP server. But it's doing more for me now. Checkboxes are my approval UI. The graph view shows plans linking to ADRs linking to components linking to log entries. And when Claude checks for newly ticked boxes at the start of every turn (or when I type sync), Obsidian is effectively a control panel I can use from my phone.
What I gave up
Leaving this section out would be the exact mistake the last post made, so:
- It's slower. Every feature waits on me twice. If I'm busy, the work waits. That's the point, but it's still a cost.
- No parallelism. One component per session, one after another. v1 could in theory run several steps across repos. In practice it mostly ran several steps across my patience.
- Opus still reviews Opus. The correlated blind spot hasn't gone anywhere. What changed is that the reviewer now reads the code instead of a summary, a real test suite gets the final say, and I've signed off on both the approach and the test cases. Three independent-ish checks instead of one twin.
- No enforcement hook. The rules are back to being rules in a Markdown file. I'm relying on the checkbox, the status field, and the fact that a single main session following a stage list is much easier to keep honest than three agents passing notes. If it starts skipping stages, the hook comes back, and this time it'll parse YAML frontmatter instead of grepping for a word.
- It's a lot of ceremony for small changes. Which is what “don't track” is for. Not everything deserves an ADR. Writing one for a typo fix would be a cry for help.
The whole files
The CLAUDE.md lives in ~/Documents/, the folder that holds all my repos, so it applies to every session I start there. Change the vault path on line four and it should work as-is.
# Obsidian Second Brain — Workflow Rules
## Vault location
- All vaults live under: `~/Obsidian/` <!-- CHANGE to your path -->
- Repos live under: `~/Documents/`, where this CLAUDE.md sits.
- Nothing about the active vault is saved between sessions. Every session picks its vault fresh.
- Each vault stores its own project settings in the frontmatter of its `index.md`:
```markdown
---
type: project-index
repo: ~/Documents/<repo-folder> # code for this project
test: npm test # test command, run from inside `repo`
---
```
## Model & execution
- Run sessions on Opus (select it with `/model` at session start).
- Work sequentially in the main session: planning, ADRs, implementation, tests, and logging.
- The ONLY subagent is `code-reviewer` (also Opus), used in Stage 4 for independent review.
- One component per session. The vault holds all state, so a fresh session loses nothing
and keeps context small. When a component reaches `done` or `blocked`, tell me:
"Component finished — start a new session for the next one."
## Session start
On my FIRST message of every session, before doing anything else, ask exactly one question:
"Which project is this session for? Pick an existing vault, create a new one, or don't track."
List the folders in `~/Obsidian/` as the options.
- **Existing vault** → read its `index.md` frontmatter for `repo` and `test`.
If either is missing, ask me for it once and write it into the frontmatter.
Then check its ADRs for any component with status `in-progress`, `in-review`, or
`testing`. If one exists, say "Resuming <component> at <stage>" and continue it from
the vault notes and the latest `review-log.md` entry, instead of starting anything new.
- **New vault** → ask for the vault name, the repo folder under `~/Documents/`, and the
test command. Scaffold the structure below with those values in `index.md`.
- **Don't track** → work normally with no vault, and don't ask again this session.
After choosing, say "Tracking in vault: <name> → repo: <repo>".
For the rest of the session:
- Read and write vault notes ONLY in the chosen vault.
- Read and change code ONLY inside that vault's `repo` folder. Never touch other repos
in `~/Documents/`, even if they look related.
- Run the test command from inside `repo`.
## Vault structure (create only what's missing; never move or delete my notes)
```
<vault>/
index.md # project settings (frontmatter) + links to all plans and ADRs
review-log.md # every review + test run, newest first
sessions/YYYY-MM-DD.md # short log: what was discussed, decided, changed
plans/NNN-<slug>.md
adr/NNN-<slug>/
00-master.md
01-<component>.md
02-<component>.md
```
- Use `[[wikilinks]]` between related notes and YAML frontmatter on every note.
- Append a short summary to today's session log at the end of meaningful work.
---
# Orchestrator
Every feature moves through these stages in order. Never skip a stage.
Never write implementation code before Stage 3.
```
1. PLAN → 2. DECIDE (ADRs + test cases) → 3. IMPLEMENT
→ 4. REVIEW (subagent) → 5. TEST → 6. LOG → done
↑______ fix (max 2 retries) ______|
```
## Stage 1 — Plan
Trigger: I ask you to plan something.
Create `plans/NNN-<slug>.md` (NNN = next 3-digit number):
```markdown
---
type: plan
status: draft # draft | approved | rejected | done
created: YYYY-MM-DD
adr: # filled after approval, e.g. [[adr/003-auth/00-master]]
---
# <Title>
## Context
## Problem Statement
## Approaches
### A: <name>
- Pros:
- Cons:
- Effort / risk:
### B: <name>
...
## Recommendation
<your pick and why — I make the final call>
## Approval
- [ ] Approve A
- [ ] Approve B
```
Exit: an "Approve X" box is ticked, or I say "approve X".
## Stage 2 — Decide (ADRs)
1. Set plan `status: approved` and fill its `adr:` link.
2. Create `adr/NNN-<slug>/00-master.md`:
```markdown
---
type: adr-master
status: accepted # accepted | superseded
plan: [[plans/NNN-<slug>]]
date: YYYY-MM-DD
---
# ADR NNN: <Title>
## Context
## Decision
<chosen approach, and why>
## Alternatives Rejected
<other approaches and why not>
## Consequences
<trade-offs we're accepting>
## Components
| # | Component | Status |
|---|-----------|--------|
| 01 | [[01-get-user-api]] | not-started |
| 02 | [[02-create-user-api]] | not-started |
```
3. Create one file per API / function / interface:
```markdown
---
type: adr-component
status: not-started # not-started | in-progress | in-review | testing | done | blocked
parent: [[00-master]]
attempts: 0
---
# <Component name>
## Context
## Interface
<signature, request/response shape, errors>
## Steps
- step 1 — status: not-started
- step 2 — status: not-started
## Test Cases
<one line each: what the test verifies. Cover happy path, edge cases, errors.>
- T1: returns 200 and the user when the id exists
- T2: returns 404 when the user does not exist
- T3: returns 400 when the id is malformed
## Flow
```mermaid
sequenceDiagram
...
```
## Approval
- [ ] Approve implementation
## History
<links to review-log entries, newest first>
```
Exit: I tick "Approve implementation" on a component, or say "implement <component>".
Approval is per component; once approved, run Stages 3–6 for all its steps without asking.
## Stage 3 — Implement
1. Set component `status: in-progress`.
2. Implement the steps in order, updating each step's status as you go.
3. Write a real test for every Test Case listed in the ADR (T1, T2, …).
If you discover a missing edge case, add a new test case line to the ADR first.
4. Do NOT mark anything `done` yet. Set `status: in-review`.
## Stage 4 — Review (subagent)
- Always delegate review to the `code-reviewer` subagent. Never review your own code.
- Give it: the component ADR path, the list of changed files, and nothing else.
- The reviewer returns a verdict: `APPROVE` or `CHANGES_REQUIRED` with a list of issues.
- On `CHANGES_REQUIRED`: increment `attempts`, fix the issues, and request review again.
- On `APPROVE`: set `status: testing` and go to Stage 5.
## Stage 5 — Test
- Run the vault's `test` command (from `index.md`) inside its `repo` folder.
- All tests for this component must pass, and no previously passing test may break.
- On failure: increment `attempts`, fix the code, then go back to Stage 4
(fixed code must be reviewed again).
## Retry limit
If `attempts` reaches 2 and review or tests still fail:
- Set `status: blocked`, untick "Approve implementation".
- Log it (Stage 6) and stop. Tell me what's failing and what you'd try next.
- Do not continue until I respond.
## Stage 6 — Log
After EVERY review and EVERY test run (pass or fail), prepend an entry to
`review-log.md` in the vault root:
```markdown
## YYYY-MM-DD HH:MM — [[adr/NNN-<slug>/01-<component>]]
- Stage: review | test
- Attempt: 1
- Verdict: APPROVE | CHANGES_REQUIRED | PASS | FAIL
- Issues: <one line each, or "none">
- Tests: <passed>/<total>, failing: <names or "none">
- Files: <changed files>
```
Also add a wikilink to the entry under the component's `## History`.
When review and tests both pass:
- Set component `status: done`, update its row in the master's Components table.
- When every component is done, set the plan `status: done`.
## Sync
- At the start of every turn, check for newly ticked approval boxes in plans and ADRs.
- The command `sync` does the same check immediately.
## Rules that never change
- Never edit the Decision section of an accepted ADR. If the decision changes,
create a new ADR and mark the old one `status: superseded` with a link.
- Never delete review-log entries.And the one remaining sub-agent, at .claude/agents/code-reviewer.md:
---
name: code-reviewer
description: Strict, independent code reviewer. Use after any implementation step to review changed code against its ADR before tests run.
tools: Read, Grep, Glob, Bash
model: opus
---
You are a strict senior code reviewer. You did NOT write this code and have no
attachment to it. Your job is to find problems, not to be agreeable.
You will be given a component ADR path and a list of changed files.
1. Read the ADR: its Context, Interface, Steps, and Test Cases.
2. Read every changed file fully.
3. Check:
- Correctness: does the logic actually do what the ADR's Interface and Steps say?
- Edge cases: empty/null input, boundaries, malformed data, concurrency, timeouts,
large inputs, duplicate requests.
- Error handling: every error in the Interface is produced correctly; nothing is
silently swallowed.
- Tests: every Test Case (T1, T2, ...) has a real test that would fail if the
behaviour broke. Flag missing or weak tests, and missing edge cases.
- Security: input validation, injection, auth checks, secrets in code.
- Readability: unclear names, dead code, duplication.
4. Do not modify any files. Do not run the test suite; that is a separate stage.
Respond in exactly this format:
VERDICT: APPROVE | CHANGES_REQUIRED
ISSUES:
- [blocker|major|minor] <file>:<line> — <problem> — <suggested fix>
MISSING TEST CASES:
- <one-line description, or "none">
Only APPROVE if there are no blocker or major issues.The actual lesson
v1 was designed to take me out of the loop, and it was very good at that. It turns out that was the bug. The agents were fine. What was missing was a person deciding things at the two moments where a decision is cheap: before the approach is chosen, and before the code is written. Everything after that point can be automated, and now it is.
I'll probably write a v3 post in a month explaining why this one was also wrong. Consistency is key, and I'm working on it.
