Skip to content
Aditya MhaskeSoftware Engineer

Articles / AI Workflow

The machine room

I don’t prompt.
I build the
machine that prompts.

Skills, hooks, MCP servers, subagent fleets, model routing, a context budget, and a lab notebook full of things that didn’t work. This is the actual apparatus — config, guardrails, numbers and all.

claude-code — real session, replayed

One task, start to PR. Every line below is a real surface: a skill trigger, an MCP tool call, a hook denying a command, three subagents in their own worktrees.

43

Skills authored

loaded on trigger, not on boot

9

MCP servers wired

61 tools, deferred until needed

7

Subagents in the fleet

one git worktree each

~2.4M

Tokens/day

78% served from cache

71%

PRs agent-drafted

100% human-approved

17

Experiments logged

6 killed, 8 written up

Architecture

Six layers between
an idea and a merge.

Most people describe their AI workflow as a vibe. Mine has an architecture, and every layer has something you can actually inspect. Click through it.

Layer 00

Intent

A goal, its constraints, and how we will know it worked.

  • Every run starts with a written goal and a definition of done — not a prompt.
  • If success is checkable, it becomes a rubric the agent grades itself against.
  • Ambiguity is resolved here, by me. It is the cheapest place to resolve it.
  • The spec goes in whole, up front. Long-horizon models do worse when you dribble it out over turns.

Layer 01 · Router

Different problems
deserve different brains.

Using the biggest model for everything is the most expensive way to look sophisticated. Task class picks the model, the effort level, and whether it runs live or in a batch overnight.

Task classModelKnobsCostWhy
Architecture, long refactors, root-cause huntsopuseffort xhigh · adaptive thinking$5 / $25 per MTokThe task is judgment, not typing. I give it the whole spec and let it run for 20 minutes.
Implementation from a settled spec, tests, review passessonneteffort high$3 / $15 per MTokNear-Opus on coding once the thinking is done. This is where most of my tokens go.
PLC log triage, doc extraction, label backfillshaikuBatches API · 200K ctx$1 / $5, halved in batch50k logs a day. Latency is irrelevant, unit cost is everything.
Client code that can't leave the machineQwen3-Coder · vLLM, local4×A10, paged attentionelectricitySome repos are never going over a wire. The workflow shouldn't change because of that.
Retrieval: embed + rerankBGE-M3 · bge-reranker-v2-m3self-hosted, Cloud Run L4~$0.004 / queryRetrieval quality is a model choice too. It just isn't an LLM choice.

The part nobody talks about

01

Prompt caching does more for the bill than model choice: reads land near a tenth of base input price, writes cost a quarter more once. Two hits and it has paid for itself.

02

So the system prompt is frozen bytes. No timestamps, no session IDs, no conditionally-assembled sections — any one of those invalidates the whole prefix and quietly triples the cost.

03

Tool definitions render first, which means adding one mid-conversation nukes the cache for everything after it. The tool list is sorted and stable.

04

78% cache-read rate is not a nice-to-have. It is the difference between this workflow being affordable and being a hobby.

Layer 02 · Context

43 skills.
None of them loaded right now.

A skill is a folder with a SKILL.md. Only its one-line description sits in context; the body loads when a task actually triggers it — that is how you carry a lot of expertise without paying for it every turn. Twelve of the forty-three are below. Open one and you get the real frontmatter.

skills/rag-eval-harness/SKILL.md
---
name: rag-eval-harness
description: Use when changing chunking, embeddings, search, fusion, or
  reranking. Freezes the eval set before the change, runs it after, and
  blocks a regression in recall@5, MRR, or faithfulness.
allowed-tools: Read, Grep, Glob, Write, Bash(python scripts/eval.py:*)
---

Freeze first, change second. An eval set written after the change grades the change it was written for.

skills/qdrant-ops/SKILL.md
---
name: qdrant-ops
description: Use for any Qdrant collection work — creating, tuning, migrating,
  or debugging recall. Covers HNSW m/ef, scalar vs product quantization,
  payload indexes, and multi-tenant partitioning.
allowed-tools: Read, Write, mcp__qdrant__*
---

Reads the live collection config through MCP first. Refuses to reason about a collection it hasn't looked at.

skills/pii-redaction/SKILL.md
---
name: pii-redaction
description: Use before any dataset, log dump, or prompt leaves the VPC.
  Runs the redaction pipeline, then verifies on a held-out sample and
  reports measured recall rather than asserting success.
allowed-tools: Read, Write, Bash(python pipelines/redact.py:*)
---

">99% redaction" is only a number if something measured it. This skill measures it every time.

skills/prompt-regression/SKILL.md
---
name: prompt-regression
description: Use when editing any production prompt, tool description, or
  skill body. Runs the golden set before and after and reports per-case
  deltas, so a prompt change is reviewed like a code change.
allowed-tools: Read, Edit, Bash(python evals/golden.py:*)
---

A prompt is production code with a terrible type system. It gets the same regression discipline.

skills/no-mistakes/SKILL.md
---
name: no-mistakes
description: Use before pushing, opening a PR, or declaring work complete.
  Runs the full validation pipeline and reports what actually failed with
  the real output — never a summary claiming success.
allowed-tools: Read, Bash(make verify:*), Bash(git status:*)
---

Paired with a Stop hook that runs the same commands, so it can't be talked out of failing.

skills/adversarial-review/SKILL.md
---
name: adversarial-review
description: Use after implementation, before the PR. Reviews the diff from
  a cold context with no memory of why the choices were made. Reports every
  finding with confidence and severity; filtering happens downstream.
allowed-tools: Read, Grep, Glob, Bash(git diff:*)
---

Told to report everything, including low-confidence findings. Ask a reviewer to be conservative and it will be — silently.

skills/bigquery-cost-guard/SKILL.md
---
name: bigquery-cost-guard
description: Use for any BigQuery work. Dry-runs every query, reports bytes
  scanned and estimated cost, verifies partition and cluster pruning, and
  refuses to run anything above the configured ceiling without approval.
allowed-tools: mcp__bigquery__dry_run, mcp__bigquery__query, Read
---

The one $400 SELECT * taught this skill. It has not happened again.

skills/observability-wiring/SKILL.md
---
name: observability-wiring
description: Use when adding a service, endpoint, or background job. Ensures
  structured logs, a latency histogram, an error counter, and trace spans
  exist before the path is considered done.
allowed-tools: Read, Edit, Grep
---

Debugging production without telemetry is just guessing with extra steps.

skills/terraform-review/SKILL.md
---
name: terraform-review
description: Use on any terraform plan output. Flags resource replacement,
  public ingress, IAM widening, and state-destroying changes. Summarises
  blast radius before anything is applied.
allowed-tools: Read, Bash(terraform plan:*), Bash(terraform show:*)
---

'It's just a config change' is how infrastructure incidents start.

skills/paper-to-repro/SKILL.md
---
name: paper-to-repro
description: Use when reproducing a method from a paper. Extracts the exact
  setup, builds a minimal runnable baseline, and reports where the numbers
  diverge from the paper instead of quietly matching them.
allowed-tools: Read, Write, WebFetch, Bash(python:*)
---

Five IEEE papers of my own taught me how much a method's description leaves out. This skill asks about the gaps.

skills/ablation-planner/SKILL.md
---
name: ablation-planner
description: Use before running experiments. Enumerates the variables worth
  isolating, proposes the minimum run set, and states in advance what result
  would falsify the hypothesis.
allowed-tools: Read, Write
---

Deciding what counts as failure before the run is the only defense against reading the result you wanted.

skills/decision-log/SKILL.md
---
name: decision-log
description: Use after any non-obvious decision. Writes one lesson per note
  with a one-line summary, records what was rejected and why, and updates an
  existing note rather than duplicating it.
allowed-tools: Read, Write, mcp__obsidian__*
---

Memory the agent can read back is worth more than memory I can. Same vault, retrievable by both.

Layer 04 · Guardrails

Asking politely
is not a safety system.

A hook is a shell command the harness runs at a fixed point in the loop. It is deterministic code, not a suggestion in a prompt — which means it holds at 2am on the hundredth turn, when a prompt wouldn't.

.claude/settings.json
{
  "hooks": {
    "SessionStart": [
      { "hooks": [{ "type": "command", "command": ".claude/hooks/warm.sh" }] }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": ".claude/hooks/deny-destructive.py" }]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": ".claude/hooks/protect-paths.py" }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": ".claude/hooks/format.sh", "timeout": 30 }]
      }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": ".claude/hooks/gate.sh", "timeout": 300 }] }
    ]
  }
}
SessionStart

Prints the branch, the open PR, last night's CI result, and today's error budget into context.

The session starts grounded in reality instead of asking me for it.

PreToolUseBash

Parses the command and rejects destructive shapes — force pushes, collection drops, prod migrations, curl-to-shell.

Exits 2. The call never runs, and the reason goes back to the agent as feedback it can act on.

PreToolUseEdit|Write

Blocks writes to migrations already applied, lockfiles, and anything under infra/prod/.

Same exit 2. Some files are mine to change, not the agent's.

PostToolUseEdit|Write

Reads the tool payload from stdin, formats and type-checks only the files that actually changed.

Style stopped being a review comment about two years too late.

Stop

Runs the full gate: lint, types, tests, secret scan, and the frozen eval set.

Refuses to let the turn end while anything is red — and hands back the actual failure output.

Layer 03 · Tool plane

Give it the system,
not a description of it.

MCP servers are how the agent stops guessing. It reads the live index config, the real schema, the actual stack trace. Nine servers, sixty-one tools, loaded on demand rather than all at once.

ServerTransportSurfaceWhat it buys me
qdrantstdiocollection_info · search · upsertRead the live index instead of trusting a stale diagram of it.
postgresstdio (read-only role)query · schema · explainReal schemas, real cardinalities, zero write path.
bigquerystdiodry_run · query · table_infoCost estimate before the scan, every time.
githubhttpPRs · reviews · checks · issuesThe agent opens and updates its own PRs, and reads its own CI failures.
gcp-loggingstdiotail · query · metricsProduction errors become a first-class input, not a screenshot I paste.
sentryhttpissues · events · releasesReproduce from the real stack trace and the real release.
playwrightstdionavigate · click · screenshotThe UI agent looks at the page. Vision plus tools beats vision alone.
obsidianstdiosearch · read · appendMy decision log, retrievable by the agent that helped write it.
linearhttpissues · cycles · commentsThe ticket is the spec. It should be readable where the work happens.

And then I made it installable.

All of it is packaged as one plugin, `am-ai-stack`, published to a private marketplace.

A plugin.json bundles the skills, the subagent definitions, the slash commands, the hook config, and the MCP server list into a single installable unit.

New machine, new repo, or a teammate who wants the same guardrails: one install command and the whole workflow comes with it — versioned, reviewable, revertible.

Parallelism

Seven specialists
beat one generalist.

An agent carrying every tool picks the wrong one. An agent with a narrow prompt and four tools doesn't. So the work fans out to a fleet — each with its own model, its own allowlist, and its own git worktree.

spec-writeropus

tools: Read, Grep, Glob, Write

Turns a rough ask into constraints, edge cases, and a definition of done.

implementersonnet

tools: Read, Edit, Write, Bash

Executes a settled spec file by file. Never decides architecture.

adversarial-revieweropus

tools: Read, Grep, Glob

Cold context, no memory of the rationalizations. Reports everything.

test-writersonnet

tools: Read, Write, Bash

Writes tests against the spec, not against the implementation.

bench-runnersonnet

tools: Read, Bash

Runs the frozen eval set and reports deltas with the raw output.

log-triagerhaiku

tools: Read, Grep, mcp__gcp-logging__*

Reads 50k PLC logs a day so nothing upstream has to.

docs-writerfable

tools: Read, Write

Turns a merged diff into a changelog entry and a PR description a reviewer will actually read.

Each agent is a markdown file in .claude/agents/ with its own model, its own tool allowlist, and its own system prompt. Narrow prompts and narrow tools beat one agent carrying everything.

Parallel agents run in separate git worktrees, so three of them can edit the same repo at once without touching each other's files.

They don't share a context window — only reports come back. That's the whole point: the reading happens somewhere I don't pay for it.

.claude/agents/bench-runner.md
---
name: bench-runner
description: Runs the frozen retrieval eval and reports deltas. Use for any
  change touching chunking, embeddings, search, fusion, or reranking. Give it
  the branch and the baseline commit; it returns a table plus raw output.
tools: Read, Grep, Glob, Bash
model: sonnet
---

Run the eval set at scripts/eval.py against both commits. Report recall@5,
MRR@10, faithfulness, and p95 latency as a delta table, then paste the raw
output underneath. Never summarise a failure as a success. If a run errors,
report the error and stop — do not substitute a partial result.

Layer 02 · Context

The window is a ledger.
Every line item is a choice.

A million tokens of context is not permission to use a million tokens of context. Quality degrades from clutter long before you hit the ceiling, so every slice of the window has a rule attached to it.

System prompt + CLAUDE.md

~4k

Frozen bytes. Nothing dynamic above the cache breakpoint, ever.

Skill metadata (43 skills)

~1.8k

One line each, always resident. Bodies load only when a task triggers them.

Tool schemas (61 tools)

deferred

Tool search loads the handful this turn needs. Loading all 61 costs more than it's worth.

Retrieved code

~30k

Agent-selected through grep and read. I never bulk-paste a directory.

Working transcript

the rest

Compacts around 150k. Tool results get cleared before thinking does.

Subagent exploration

0

Happens in their windows. Only the report crosses back into mine.

The lab notebook

Seventeen run.
Eight written up.

This is the section most workflow write-ups leave out, and it's the only one that tells you whether someone actually runs experiments. Six of the seventeen got killed; three of those are in here. Every entry has a hypothesis I wrote before the run and a result I didn't get to choose.

Cross-encoder rerank on the PLC retrieval path

Shipped

Hypothesis

Hybrid search gets the right chunk into the top 50; a reranker can get it into the top 5.

What happened

recall@5 0.71 → 0.86 for +38ms p95 and $0.004 a query. Cheapest quality win of the year.

One agent, all 61 tools

Killed

Hypothesis

More tools means fewer dead ends.

What happened

It picked the wrong tool roughly one turn in five. Split into six narrow agents with scoped allowlists and the wrong-tool rate collapsed. Tool count is a cost, not a feature.

DSPy-optimized prompt for the triage classifier

Shipped

Hypothesis

An optimizer will beat my hand-written prompt on the frozen set.

What happened

It did — +6 F1. It also produced something no human can review. It ships, but only behind a golden-set regression test I trust more than the prompt.

Local 30B model as the default coder

Killed

Hypothesis

Good enough at a fraction of the cost.

What happened

Fine at the last 20% — boilerplate, mechanical edits. Lost the first 80%, which is judgment. It still runs overnight on well-scoped work, and that's the right job for it.

Auto-commit hook on every green test run

Killed

Hypothesis

Never lose work, always have a checkpoint.

What happened

Killed inside a day. The history became unreadable and bisect stopped meaning anything. Now it stages and nothing more. Automation that damages the record isn't a shortcut.

Semantic cache on RAG answers

Iterating

Hypothesis

Similar questions can safely reuse an answer.

What happened

31% hit rate at cosine 0.94 — and 4 wrong hits per 1,000, which for a diagnostics tool is 4 too many. Threshold moved to 0.97, hit rate dropped to 12%, still watching.

Agent-written eval sets

Iterating

Hypothesis

The bottleneck on evals is writing them, and that's automatable.

What happened

Fast, and initially useless — it graded a confidently wrong answer as correct. Works now, but only over human-verified golden fixtures. The agent writes cases; it does not get to define truth.

Voice → spec → PR while walking

Shipped

Hypothesis

Whisper plus a tightly scoped skill can turn a walk into small merged changes.

What happened

Genuinely useful for the small stuff — copy fixes, dependency bumps, flaky test quarantines. Useless for anything I'd normally draw a diagram for, which is most things worth doing.

In practice

What the hours
actually look like.

AI didn't change what I build. It moved almost all of my hours upstream, into deciding what should be built and proving that it was.

08:40

Read the night shift

Three overnight PRs, one red. The SessionStart hook has already put CI results and the error budget in context. I'm reviewing decisions, not diffs.

09:15

Write the goal, not the prompt

One page: constraints, edge cases, definition of done. If it's gradeable, it becomes a rubric. This is where I'm most useful and it takes the longest.

10:00

Fan out

Spec goes to the fleet. Implementer, test-writer, and bench-runner in three worktrees. I read reports, not transcripts.

12:30

Argue with the reviewer

Adversarial-reviewer flags an unbounded fan-out. It's right. We loop back to the plan rather than patching the symptom — the plan was wrong, not the code.

14:45

Watch the number

Bench-runner posts the eval delta into the PR. recall@5 up, p95 up too. That tradeoff is mine to make, and it's the one decision here a model shouldn't own.

16:30

One approval

CI green, gate green, benchmark in the body. I approve and merge. The approval is the job.

22:10

Queue the dark hours

Well-scoped, low-blast-radius work on the local model — migrations, test backfills, dependency bumps. Clear specs in, results waiting at 08:40.

Five things I’d tell you at the start

01

The agent writes the code. I own the decision. That line never moves.

02

Anything I'd explain twice becomes a skill. Anything I'd enforce twice becomes a hook.

03

Context is a budget with line items. Treat it like a bucket and quality drops before cost does.

04

Freeze the eval set before the change. An eval written afterwards grades the change that wrote it.

05

The killed experiments are the point. A workflow with no failures in it is a brochure.