← All posts

September 24, 2026 · 10 min read

My Claude Code + Obsidian setup

claude-codeai-agentstooling

The first time I let Claude Code run a multi-step task across three repos unsupervised, it did something I only noticed two days later: it quietly decided step 4 wasn't necessary, skipped it, and told me everything was done. Nothing was on fire. Nothing failed loudly. It just made a judgment call I never signed off on, and the only record of that decision was a wall of chat scrollback I'd already closed.

That's the actual problem with agent orchestration, and it's not the one people worry about. It's not “will the AI write bad code.” It's “where does the plan live once the session that made it is gone, and who checks the work before it counts as done.” Sub-agents spin up with fresh context and no memory of each other. Chat history isn't queryable, isn't diffable, and evaporates the moment you hit /clear. You need a plan that outlives the session, written somewhere a human and a dozen future agents can both read it.

I already had that somewhere. It's called Obsidian, and I'd been using it for notes for years before it occurred to me that “a folder of plain Markdown files with a nice reading view” is also, structurally, exactly what an orchestrator needs for memory. No MCP server, no Obsidian plugin, nothing to install. Claude Code already reads and writes files. A vault is just a folder. I pointed it at one.

The mental model: repos vs. features

Before any of the automation, one distinction has to be nailed down or the whole thing collapses into a mess of ambiguous folders: a repo is an actual codebase where code changes. A project or feature is a vault entry — an initiative that can span several repos. A single feature like “add rate limiting” might touch an API service, a background worker, and a frontend, in three separate repos, but it's one plan in the vault. Conflate the two and you end up with plans scattered by repo instead of by intent, which is useless the moment a feature touches more than one.

Every step note has to state its target repo explicitly. It sounds pedantic until a sub-agent with a fresh context window opens a step note and has no idea which of your six repos it's supposed to be touching.

Three roles, not one giant context

The orchestrating session never edits code. That's the whole trick. It plans, delegates, and gates — it does not touch a single file. Two other roles do the actual work:

Orchestrator (this session)  — plans, delegates, reviews-gates. Never edits code.
executor sub-agent           — implements one step, in the repo(s) stated
reviewer sub-agent           — approves/rejects the plan, then the execution against it

The orchestrator runs at a middling effort level by default and only gets bumped up if I've actually seen it miss something — not preemptively, because more thinking isn't free and most planning steps don't need it. The executor runs hot, because it's doing the real work of reading code and writing code. The reviewer runs at a lower effort than you'd expect, because reviewing a scoped diff against a written plan is a much narrower task than writing the diff in the first place.

One detail I'd get wrong if I hadn't been burned by it: model fields in the config are aliases — opus, sonnet — never hardcoded version strings. Models get replaced, and a config pinned to a specific version quietly goes stale the day a better one ships. The trap on the other side of that is assuming an effort setting ports 1:1 when the underlying model changes underneath the alias — it doesn't. Effort tiers aren't calibrated the same way across model generations, so every time the alias resolves to something new, I re-test the effort level per role instead of carrying the old number forward out of habit.

The vault layout

Everything the orchestrator writes lives in one predictable shape, nested under the feature, never under the repo:

vault/
  <project-or-feature>/
    plans/
      <task-slug>/
        00-master-plan.md   # goal, steps, repo per step, status, ## Plan Review
        step-01-<name>.md   # ## Plan / ## Execution Log / ## Review (append-only)

Both the master plan and each step note only ever get appended to, never rewritten. The master plan picks up a ## Plan Review section once the reviewer signs off on the overall approach. Each step note has three further sections: ## Plan (written by the orchestrator, before any work happens), ## Execution Log (written by the executor, after), and ## Review (written by the reviewer, last). Because nothing gets overwritten, the whole thing is a complete audit trail by construction — I can open any feature from three weeks ago and read exactly what was proposed, whether the approach itself was approved, what actually got built, and who signed off on it, without reconstructing anything from chat history.

The loop

The orchestrator follows nine steps every time, and two of them are review gates that check different things: step 2 checks whether the plan itself is sound before any code moves, step 6 checks whether the code that got written actually matches that plan. Neither gate substitutes for the other — more on why that split exists right after the list.

  1. Identify the project or feature from the request, not from whatever directory the terminal happens to be sitting in. Write or update 00-master-plan.md with steps, target repo per step, and status.
  2. Delegate the master plan itself to reviewer — one call per feature, not per step. It checks the approach, not implementation detail: right repos, sensible sequencing, nothing obviously missing from the step breakdown. On PLAN CHANGES REQUESTED, revise the master plan and resubmit, capped at two rounds — a plan that can't converge in two passes needs a human rewrite, not a third automated attempt. On PLAN APPROVED, proceed.
  3. Write each step-NN.md under ## Plan — target repo and done-criteria only, no implementation detail. Keep notes short, roughly 300–500 tokens; split into another step instead of padding one out.
  4. Delegate to executor with just the step-note path. No extra context, no history.
  5. Delegate to reviewer on the same note.
  6. On CHANGES REQUESTED, re-run the executor with the reviewer's feedback attached and repeat. Capped at three rounds per step — on the third rejection, stop looping and surface the step as needing a human, instead of retrying forever.
  7. On APPROVED, mark the step done and advance. No skipping ahead, no batching unreviewed steps.
  8. Once everything's done, write a short closing summary and the list of repos touched into the master plan.
  9. /clear between unrelated tasks. Stale context resends on every single message, and it's pure waste once the task it's from is finished.

Step 2 is the newer addition, and it exists to close a gap the original version of this setup had: the execution-review gate only ever checked whether the executor's output matched the plan, not whether the plan itself was a good idea. A confidently wrong plan would sail through every review, because “matches the plan” is exactly what the reviewer was checking for — it had no mechanism to object to the plan on its own terms. Reviewing the master plan once, before any step executes, is the cheap fix: one reviewer call per feature instead of per step, and it catches a bad approach where the cost of being wrong is lowest — before three repos' worth of code gets written against it.

Every role gets the same anti-stall rule, because the most common failure mode I've seen isn't a wrong answer, it's a right answer that stops one move too early: a text summary announcing what it's about to do next, without doing it. Status text is not a stopping point. Either take the next tool action or hand off with an explicit blocker written into the note — and after two or three automatic self-continuations on the same step with no progress, stop and hand back rather than looping on the same failed approach a fourth time.

The sub-agents

Both roles are defined as ordinary Claude Code sub-agents, checked into the repo so they're never one-offs typed into a prompt:

.claude/agents/executor.md

---
name: executor
description: Executes one step from a vault sub-plan in the repo(s) it specifies.
model: sonnet
effort: high
---
Work only in the repo(s) the note states. Implement only that step's "## Plan"
intent. Read only files this step touches, not the whole repo. Run tests/builds
yourself; log a one-line result summary, not raw output. Append "## Execution
Log" (repo, changes, files, summary, deviations). Don't self-approve. Before
editing, check current repo state (git status/diff) against what the plan
assumes — flag drift in the note instead of silently overwriting.
.claude/agents/reviewer.md

---
name: reviewer
description: Reviews a master plan for soundness before execution starts, and
  reviews executor output against each step's plan before it's marked done.
model: opus
effort: medium
---
Two modes, chosen by which file you're handed:

If handed 00-master-plan.md (no step notes yet): judge the approach, not
implementation detail. Right repos for the goal? Sequencing sound? Anything
obviously missing from the step breakdown, or steps that don't need to exist?
Append "## Plan Review": verdict line (PLAN APPROVED / PLAN CHANGES REQUESTED)
+ concise reasoning.

If handed a step-NN.md: read only "## Plan" + "## Execution Log" unless
insufficient to judge. Check intent match, correct repo, correctness, scope
creep, edge cases. Append "## Review": verdict line (APPROVED / CHANGES
REQUESTED) + concise reasoning specific enough to act on without a full
re-read.

Never confuse the two verdict formats — the hook that gates completion greps
for both independently.

“Don't self-approve” in the executor prompt is doing more work than it looks like. Without it, a sub-agent under time pressure to report success will occasionally decide its own output is fine, actually, and skip the handoff. The reviewer existing as a separate agent with a separate context window — one that never saw the executor's reasoning, only its plan and its log — is the entire point. It can't rationalize alongside the mistake it didn't make.

The part that actually enforces it

A rule in a Markdown file is a suggestion. Nothing stops an agent from skimming past “no step advances without APPROVED” under time pressure — I've watched it happen. So the review gate isn't just written down, it's wired into a hook that blocks task completion outright:

{"hooks":{"TaskCompleted":[{"hooks":[{"type":"command","command":"python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/check_review.py","timeout":5000}]}]}}

check_review.py reads every step note referenced in the feature's master plan and exits with code 2 — which blocks completion and feeds the script's stderr back as guidance — unless both gates are satisfied: the master plan carries a PLAN APPROVED line under ## Plan Review, and every step note carries an APPROVED line under ## Review. Missing either one blocks completion, on purpose — a hook that only checked the step-level gate would have let the original problem right back in. The gotcha, which cost me an afternoon of thinking my hook was working when it wasn't: some Claude Code hook events are notify-only, they fire but can't actually stop anything. Test this once, on purpose, with a deliberately unapproved note, before you trust it. I didn't, the first time, and found out my enforcement mechanism was enforcing nothing for about a week.

Why the tokens matter here specifically

Every sub-agent spins up a fresh, uncached context. That's the cost of the isolation that makes the reviewer trustworthy, but it means this workflow can burn two to five times the tokens of just doing the work in one thread. A few rules keep that from becoming the actual bottleneck:

A worked example

A feature called add-rate-limiter that touches an API service, a worker, and a web frontend ends up looking like this in the vault, independent of how those three repos are actually laid out on disk:

vault/checkout-flow/plans/add-rate-limiter/
  00-master-plan.md          # repos: api, worker, web
  step-01-schema-design.md   # repo: api
  step-02-worker-hook.md     # repo: worker
  step-03-frontend-form.md   # repo: web

The feature name, checkout-flow, has nothing to do with any of the three repo names. That's the whole point of keeping them separate — the folder is organized by what a human would ask for, not by what happens to compile.

The whole file

This is the full CLAUDE.md, generic and ready to drop into whatever root directory holds your repos and your vault — swap the placeholder names for your own and it should work as-is.

# Orchestration Setup — /path/to/your/workspace

Root for all repos/services and an Obsidian vault ("vault", plain Markdown,
no MCP server — Claude Code reads/writes it via normal filesystem tools, same
as any repo). Applies to every Claude Code session started here.

## Project/Feature vs. Service/Repo

- Repo: an actual codebase under this root (e.g. api, worker, web) — where
  code changes.
- Project/feature: a vault initiative (e.g. "checkout-flow") that can span
  multiple repos. Never conflate the two. Every step note must state its
  target repo(s).

## Roles & Models

| Role | Model | Effort | Job |
|---|---|---|---|
| Orchestrator (this session) | opus | medium (raise to high only if evals show a real gap) | Plans, delegates, reviews-gates. Never edits code. |
| executor sub-agent | sonnet | high (or medium for trivial steps) | Implements one step, in the repo(s) stated |
| reviewer sub-agent | opus | medium | Approves/rejects executor output against the plan |

Model fields use aliases (opus, sonnet), not hardcoded version strings, so
this file doesn't go stale on the next model bump — when a new model ships,
re-test effort levels per role instead of carrying old settings forward.
Effort level names don't correspond to the same amount of thinking across
model families — never assume an effort tier ports 1:1 from one model to
another.

## vault Structure

vault/
  <project-or-feature>/
    plans/
      <task-slug>/
        00-master-plan.md   # goal, steps, repo per step, status, ## Plan Review
        step-01-<name>.md   # ## Plan / ## Execution Log / ## Review (append-only)

Master plan status table includes a repo column. Folders are named by
feature, never by repo — one feature can touch many repos.

## Orchestrator Loop

1. Identify project/feature (from the request, not cwd). Write/update
   00-master-plan.md with steps, target repo per step, status.
2. Delegate 00-master-plan.md to reviewer for a plan review — one call per
   feature, not per step. Checks approach only: right repos, sound
   sequencing, nothing obviously missing from the step breakdown. PLAN
   CHANGES REQUESTED → revise the master plan, resubmit. Cap at 2 rounds; a
   plan that can't converge in 2 passes needs a human rewrite, not a 3rd
   automated attempt. PLAN APPROVED → proceed.
3. Write each step-NN.md under ## Plan — target repo + done-criteria only,
   no implementation detail. Keep notes short (~300-500 tokens); split into
   another step instead of padding one note.
4. Delegate to executor with just the step-note path — no extra context.
5. Delegate to reviewer on the same note.
6. CHANGES REQUESTED → re-run executor with reviewer feedback attached, repeat.
   Cap at 3 rounds per step; on a 3rd CHANGES REQUESTED, stop looping and
   surface the step to the orchestrator's own output as needing human input,
   instead of auto-retrying indefinitely.
7. APPROVED → mark step done, advance. No skipping/batching unreviewed steps.
8. All done → short closing summary + repos touched, in the master plan.
9. /clear between unrelated tasks — stale context resends every message.

## Anti-Stall Rule (all roles)

Status/progress text is not a stopping point. Don't end a turn on a text-only
summary that announces a next step without taking it — either take the next
tool action or explicitly hand off with a clear blocker. Stop after 2-3
automatic self-continuations on the same step rather than looping forever;
if still stuck, write the blocker into the step note and hand back to the
orchestrator rather than repeating the same approach.

## Sub-agents

.claude/agents/executor.md:
---
name: executor
description: Executes one step from a vault sub-plan in the repo(s) it specifies.
model: sonnet
effort: high
---
Work only in the repo(s) the note states. Implement only that step's "## Plan"
intent. Read only files this step touches, not the whole repo. Run tests/builds
yourself; log a one-line result summary, not raw output. Append "## Execution
Log" (repo, changes, files, summary, deviations). Don't self-approve. Before
editing, check current repo state (git status/diff) against what the plan
assumes — flag drift in the note instead of silently overwriting.

.claude/agents/reviewer.md:
---
name: reviewer
description: Reviews a master plan for soundness before execution starts, and
  reviews executor output against each step's plan before it's marked done.
model: opus
effort: medium
---
Two modes, chosen by which file you're handed:

If handed 00-master-plan.md (no step notes yet): judge the approach, not
implementation detail. Right repos for the goal? Sequencing sound? Anything
obviously missing from the step breakdown, or steps that don't need to exist?
Append "## Plan Review": verdict line (PLAN APPROVED / PLAN CHANGES
REQUESTED) + concise reasoning.

If handed a step-NN.md: read only "## Plan" + "## Execution Log" unless
insufficient to judge. Check intent match, correct repo, correctness, scope
creep, edge cases. Append "## Review": verdict line (APPROVED / CHANGES
REQUESTED) + concise reasoning specific enough to act on without a full
re-read.

Never confuse the two verdict formats — the hook that gates completion greps
for both independently.

## Enforcement Hook

.claude/settings.json — block TaskCompleted unless both gates show approval:
{"hooks":{"TaskCompleted":[{"hooks":[{"type":"command","command":"python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/check_review.py","timeout":5000}]}]}}

check_review.py: for the feature's master plan, exit 2 (blocks + returns
stderr as guidance) if there's no PLAN APPROVED line under ## Plan Review, or
if any referenced step note has no APPROVED line under ## Review. Missing
either gate blocks completion — checking only the step-level gate lets a bad
plan through as long as execution matched it. Verify this actually blocks
completion for TaskCompleted specifically (test once with a deliberately
unapproved note) — some hook events are notify-only.

## Token Efficiency Rules

Sub-agents each spin a fresh, uncached context — this workflow can run
2-5x the tokens of single-thread work, so:

- Pass sub-agents only the note path + minimal instruction, never full history.
- Verbose output (tests/builds/greps) stays in the executor's own context; only
  a short summary goes in the shared note.
- Reviewer reads only Plan + Execution Log by default, not full diffs/repo.
  Plan review runs once per feature against the master plan, not once per
  step — it doesn't multiply with step count.
- Don't over-fragment steps — each spawn reloads CLAUDE.md/tool schemas; size
  steps as meaningful reviewable units, not one-liners.
- Use medium effort on executor for trivial/mechanical steps; reserve high
  for real design ambiguity.
- Prefer CLI tools (git, gh) over MCP where both exist — no added schema
  overhead in context.
- Set effort per-role at spawn time (frontmatter), not as a mid-session toggle
  — changing effort between requests invalidates the prompt cache.
- Keep this file itself lean; it loads into every session on every message.

## Example

vault/checkout-flow/plans/add-rate-limiter/
  00-master-plan.md          # repos: api, worker, web
  step-01-schema-design.md   # repo: api
  step-02-worker-hook.md     # repo: worker
  step-03-frontend-form.md   # repo: web

## Rules

- Orchestrator never touches code directly — delegation only.
- No step executes before the master plan carries a written PLAN APPROVED.
- No step advances without a written APPROVED in its note.
- Plans/logs/reviews are permanent Markdown in the vault, not session context.
- No MCP server, no Obsidian plugin — plain filesystem access only.

What I'd change

For a while, the review gate here only checked one thing: did the executor's output match the plan. It never checked whether the plan was any good in the first place — which meant a confidently wrong plan would sail through every downstream review, because “matches the plan” was the entire bar. The plan-review gate in step 2 above is the fix, and it's the reason this section is shorter than it used to be. I'm leaving it in the post rather than quietly editing the earlier version away, because the failure mode is worth naming: a review gate is only as good as what it's checking against, and I'd built one that couldn't object to its own instructions.

Two things I'm knowingly leaving alone. First, the orchestrator and reviewer share the same model. That's a real correlated blind spot — whatever that model is systematically bad at, it's bad at in both roles, so “PLAN APPROVED” is closer to “internally consistent” than “independently verified.” I'm keeping it anyway: splitting model providers per role adds real operational cost — different auth, different rate limits, a second thing to keep current — for a benefit I haven't actually measured yet. I'd revisit it the moment I catch a concrete case the reviewer missed because it shared the planner's blind spot, not before.

Second, the reviewer still doesn't read the diff during execution review — only the plan and the execution log, by design, to keep token cost down. That's a different gap than the one I just fixed: even with a sound plan approved up front, a subtle bug three functions deep that the executor described accurately but implemented wrong would still get through, because the reviewer is checking the executor's account of the work, not the work itself. I don't yet have a good rule for when a step is important enough to justify the extra token cost of a real diff review.

The three-round cap on execution retries is also still arbitrary — I picked it without much evidence and haven't run this long enough across enough step types to know if two is plenty or five is what a genuinely tricky step needs.

If you run something like this and find a cheaper way to give the reviewer diff-level visibility without blowing up its context, or a better number for either retry cap, I'd genuinely like to hear it.