← All Posts POST #09 · · 5 min read

Spec-Driven Development: How to Stop AI Agents from Rewriting Everything

Adopting a constitution-first development methodology to prevent AI coding assistants from being creative in ways you did not request. Also: LangGraph for autonomous agents.

#sdd#langgraph#ai-agents#development#workflow

Somewhere around the third time an AI coding assistant helpfully refactored a file I didn’t ask it to touch, invented an abstraction I didn’t need, and deleted a helper function it decided was redundant, I started thinking about guardrails.

Not as a joke - actual governance, with rules the AI can’t wander outside of unless I tell it to.

That’s Spec-Driven Development (SDD), and it’s how I build everything in the lab now.

The Problem

AI coding assistants are great at writing code and terrible at knowing when to stop. Left to their own devices, they’ll tidy up surrounding functions, rename variables for “consistency”, and leave you with a 400-line diff when you asked for a ten-line fix. The feature you asked for works. Half the things next to it might not.

The fix isn’t using dumber models - it’s putting them in a box before they start typing.

The SDD Workflow

Before an agent writes any code, we generate three short markdown files:

  • spec.md - What are we building and why? Includes user stories, functional requirements, and an explicit Out of Scope list. That out-of-scope list is doing half the work - it tells the coding agent what it isn’t allowed to touch.
  • plan.md - How will we build it? Covers architecture choices, data flow, and security boundaries, checked against the lab constitution.
  • tasks.md - What exact steps need to happen? An ordered checklist of small, testable tasks. The agent ticks off each task as it goes and stops at the bottom of the page.

In practice, I use an architect prompt to draft all three docs from a rough paragraph, check them over, and then hand tasks.md to the implementation agent (Claude Code) with instructions to execute the list and nothing else.

The Constitution

F:\Projects\homelab\.specify\memory\constitution.md is the standing rulebook every agent reads before proposing a design:

  • Identity & Secrets - Credentials live in the vault. No plaintext secrets in config files.
  • Service Accounts - Named, single-purpose accounts only. No generic agent or claude logins.
  • Networking - Six VLANs on 172.16.x.x, internal TLS via Step-CA.
  • Containers - Persistent services run in Docker behind Traefik TLS.
  • MCP Transport - stdio only, never SSE.
  • Agent Framework - LangGraph.

Why I Picked LangGraph (At the Time)

When I first evaluated agent frameworks in May, LangGraph came out ahead for three reasons:

  1. State machine routing fits infrastructure workflows naturally: discover hosts $\rightarrow$ check firewall rules $\rightarrow$ inspect switch port $\rightarrow$ report.
  2. LangChain MCP adapters (langchain-mcp-adapters) plugged straight into my existing stdio MCP servers:
from langchain_mcp_adapters.client import MultiServerMCPClient
from mcp_config import MCP_SERVERS
client = MultiServerMCPClient({"juniper": MCP_SERVERS["juniper"]})
tools = await client.get_tools()
  1. LiteLLM routing meant ChatOpenAI pointed at litellm.sdx.local could swap between Claude, Gemini, and local Gemma4 with a one-line parameter change:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="https://litellm.sdx.local/v1",
api_key=os.environ["LITELLM_MASTER_KEY"],
model="claude-sonnet-4-6",
http_client=httpx.Client(verify=os.environ["REQUESTS_CA_BUNDLE"])
)

The Agent Workspace

The F:\Projects\homelab\agents\ workspace in WSL2 holds a shared mcp_config.py registry so scripts don’t hardcode MCP server paths. One small trap I hit during setup: the agent’s local virtualenv shadowed /usr/bin/python3 in child processes, causing spawned MCP servers to crash with ModuleNotFoundError. Pinning /usr/bin/python3 explicitly in mcp_config.py fixed it.

The Payoff: Diffs You Can Actually Read

With spec.md, plan.md, and tasks.md in front of it, an AI coding session turns from an unpredictable renovation project into a predictable checklist run. The agent builds what’s on the list, stops at the end, and leaves the surrounding code alone.


Update - August 2026

The three-file spec workflow held up really well. Two clauses in the constitution didn’t, and the LangGraph topology agent I planned at the end of this post never got built.

  • The network topology agent wasn’t needed - My original plan was a Python state machine that queried the switch, cross-referenced OPNsense rules, and flagged anomalies. Once I stood up Homer (the Hermes Agent on UbuntuClaw) with the exact same MCP servers attached, it handled those queries conversationally without a custom state machine to maintain. The spec was right about the job and wrong about needing bespoke Python for it.
  • “Agent Framework: LangGraph. Always.” aged poorly - Putting a library preference into a project constitution before building anything real with it was premature. The rules that actually earned their keep were the boring, testable ones: stdio transport only, no plaintext secrets in config files, and named service accounts with narrow permissions.
  • The secrets rule survived the tool change - “Credentials live in VaultWarden” became “credentials live in the age vault” when I retired VaultWarden on 11 June 2026. The principle stayed identical even when the implementation changed.
  • The CLI mix shifted - When Google deprecated the old individual tier on Gemini CLI in June, I added Antigravity CLI on REX alongside Claude Code, while keeping Gemini CLI around for proxy-routed tasks.

Update - October 2026

  • Multi-session concurrency needed a hard lock - Once I started running multiple AI coding sessions across REX and remote dev machines against the same synced Docs/ tree (kept in sync via debounced rclone bisync watchers), two sessions occasionally edited the same runbook ten minutes apart and clobbered each other. I added a pre-tool hook (docs_guard) that records the SHA-256 hash of any doc when an agent reads it and blocks Edit or Write calls if the file on disk has changed in the meantime, or if another session holds an active lock.
  • Scheduled checks moved past both LangGraph and agentic cron - As I covered in the October update to Post 08 and Post 11, even letting an LLM agent run scheduled health checks via MCP tools proved less reliable than plain Python scripts (--no-agent) that query the same APIs and check the numbers directly. Use agents when the question is open-ended - use code when the checklist is fixed.