In this article

  1. The problem: capability without governance
  2. Architecture: five concentric layers
  3. The pipeline: nine stages, three hard stops
  4. Filesystem-driven phase detection
  5. Role-specialized subagents with tool allowlists
  6. Hooks: enforcement by invariant, not by prompt
  7. The knowledge lifecycle
  8. Attribution and traceability
  9. Lessons learned

Most teams use AI coding assistants the same way: open a chat, paste some context, hope for the best. That works for snippets. It falls apart for enterprise delivery, where a change isn't done until it has a spec, tests, updated documentation, a reviewable PR, and a traceable link back to the business requirement.

Over the past months I built a framework that closes that gap. It wraps a large enterprise frontend — a Next.js/React application for logistics pricing at a large shipping company — in a spec-driven, human-gated agentic pipeline built on Claude Code. An AI agent can now take a Jira ticket end-to-end to a pull request, but only through a pipeline that enforces specs, approval gates, quality checks, and full attribution.

This article walks through the architecture, the design decisions, and the lessons learned.

The problem: capability without governance

Modern coding agents are capable enough to implement a mid-sized feature autonomously. That's exactly the problem. Without structure, you get:

The framework's thesis: agent capability should be earned through structure, not granted by default. Every power an agent has should be scoped, every critical transition should require a human, and every convention should be enforced by machinery rather than by prompt discipline.

Architecture: five concentric layers

The framework is organized as five rings, from most stable to most dynamic:

┌─────────────────────────────────────────────┐
│ 1. Cross-tool contract (AGENTS.md)          │  ← read by every AI tool
│ ┌─────────────────────────────────────────┐ │
│ │ 2. Governance (.ai/ constitution,       │ │  ← human-owned, agent read-only
│ │    SDD workflow, memory policy)         │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ 3. Agent runtime (.claude/ agents,  │ │ │  ← subagents, commands, hooks
│ │ │    commands, rules, hooks, memory)  │ │ │
│ │ │ ┌─────────────────────────────────┐ │ │ │
│ │ │ │ 4. Artifacts (spec/ per ticket) │ │ │ │  ← spec.md, plan.md, task.md
│ │ │ │ ┌─────────────────────────────┐ │ │ │ │
│ │ │ │ │ 5. Living docs (docs/<app>/)│ │ │ │ │  ← updated in same commit
│ │ │ │ └─────────────────────────────┘ │ │ │ │
│ │ │ └─────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

Layer 1 — the cross-tool contract. A single AGENTS.md at the repo root is the entry point for every AI tool the team uses — Claude Code, Cursor, and GitHub Copilot all read it. It declares the stack, canonical commands, a path-to-documentation mapping, and a three-tier boundary: things agents may always do, things they must ask first about, and things they may never do. Tool-specific files (CLAUDE.md, .cursorrules, Copilot instructions) are thin bridges pointing back to it. One contract, many tools.

Layer 2 — the constitution. A .ai/ directory holds the human-owned governance: an immutable constitution (human-in-the-loop mandate, commit attribution format, escalation rules, verification gates), a stage-by-stage spec-driven-development workflow, and a memory policy. Every file is marked "Agents: read-only. Humans update via PR." Agents can read the law; they cannot amend it.

Layer 3 — the agent runtime. This is where Claude Code's native primitives get composed: subagents, slash commands, rules, hooks, and an executable orchestrator.

Layers 4 and 5 — artifacts and living docs — are the outputs: one spec folder per ticket, and per-domain documentation that must move in the same commit as the code.

The pipeline: nine stages, three hard stops

The core deliverable is an end-to-end pipeline:

Jira ticket → input
0
Branch — create feature branch from ticket ID
1
Triage — read-only Jira analyst agent → implementation brief
2
Specifyspec.md with objective, boundaries, success criteria
⛔ HUMAN GATE — "Spec approved"
3
Clarify — open questions → recorded Decisions
4
Plan + Tasksplan.md + atomic task.md
⛔ HUMAN GATE — "Plan and tasks approved — implement"
5
Implement — code + tests, only from task.md
6
Docs — architecture / business-context / changelog, same commit
7
Verify — lint → tests → UI review → code review → security audit
8
Commit summary — structured agent work summary
⛔ HUMAN GATE — "Create PR"
PR with full attribution

The gates aren't suggestions in a prompt. The orchestrator returns a structured APPROVAL REQUIRED state and refuses to advance until the human replies with the approval phrase. Using exact phrases ("Spec approved", not "looks fine") was deliberate: it makes approval programmatically detectable and eliminates ambiguity about whether consent was given.

Filesystem-driven phase detection

The most satisfying design decision: the orchestrator is stateless. It infers the current pipeline phase entirely from what exists on disk:

This makes the pipeline idempotent and resumable. Kill the session, come back tomorrow, re-invoke the workflow — it picks up exactly where it left off, because the truth lives in artifacts, not in conversation memory. Anyone who has watched an agent lose its place in a long session will appreciate why this matters.

Role-specialized subagents with tool allowlists

Rather than one omnipotent agent, the framework defines five narrow specialists, each with an explicit tool allowlist declared in its definition:

Agent Job Tools
Jira analyst Fetch ticket + linked docs, produce an implementation brief 3 read-only MCP tools + file read. Cannot write anything.
Code reviewer Convention & correctness review; may fix critical findings Read/Edit/Write/shell, max 3 review cycles
Docs writer Update per-domain documentation only Read/Edit/Write — no shell access
Lint fixer Run lint + type-check, apply fixes Shell/Read/Edit, max 3 attempts
Security auditor Final gate before PR: auth, input handling, secrets Read/Edit/Write, max 2 fix cycles, verdict: APPROVED or ESCALATED

The allowlist is the point. The Jira analyst cannot be prompt-injected into modifying code, because the harness never gives it a write tool. The docs writer cannot run arbitrary commands. Capability isolation is enforced at the platform level — the same principle as least-privilege IAM, applied to agents.

Each agent also carries a bounded retry budget (2–3 cycles) followed by mandatory escalation to a human. Agents don't thrash; they ask.

Hooks: enforcement by invariant, not by prompt

Every team has conventions that live in a wiki and die in practice. "Update the docs when you change the code." "Run the tests you touched." Prompting an agent to remember these works about as well as it does with humans.

The framework moves this enforcement into runtime hooks:

The distinction matters. A prompt instruction is advice the model may deprioritize under context pressure. A hook is a machine check that fires every time, regardless of what the model is paying attention to. Wherever a convention could be checked mechanically, we moved it out of the prompt and into a hook.

The knowledge lifecycle: three layers with a promotion path

Agentic systems have a memory problem in both directions: they forget useful lessons, and they "remember" things that were never validated. The framework addresses this with three knowledge layers and a controlled flow between them:

Knowledge is promoted, never teleported: a lesson starts in memory, and only after it proves durable does /promote-to-docs move it into living documentation. The rules directory that constrains agent behavior can only be changed by humans. The result: the system learns from every PR, but agent-originated "knowledge" can never silently rewrite the rules that govern agents.

Attribution and traceability

Every commit an agent makes is prefixed [AGENT: <model>], and every PR body must include an "Agent Work Summary" — the agent's chain of thought: why this approach, what alternatives were rejected, what dependencies were chosen and why. Combined with the spec folder per ticket and changelog entries referencing the ticket, every line of agent-written code traces back through PR → spec → business requirement, with a named model on the commit.

When something breaks six months later, you know exactly what wrote it, why, and which human approved it.

Lessons learned

1. Structure beats prompting. Every convention we moved from prompt text into a hook, a tool allowlist, or a gated workflow stopped being violated. Prompts are suggestions; harness configuration is law.

2. Filesystem state is the best agent memory. Artifacts on disk survive context windows, crashed sessions, and model switches. Design pipelines so the next step is derivable from what exists, not from what was said.

3. Narrow agents are safer and better. A read-only Jira analyst produces better briefs than a do-everything agent, because its entire context is dedicated to one job — and it's structurally incapable of collateral damage.

4. Exact approval phrases sound bureaucratic and are worth it. Free-text approval invites the agent to interpret enthusiasm as consent. A fixed vocabulary makes the gate binary.

5. The framework is a product. It needs its own maintenance loop — harvesting feedback, consolidating memory, promoting learnings. The meta-commands that maintain the framework turned out to be as important as the delivery pipeline itself.

Closing

The interesting frontier in AI-assisted engineering isn't making agents more capable — the models handle that. It's building the scaffolding that makes their capability safe to use at enterprise scale: scoped permissions, human gates, machine-enforced conventions, and a knowledge lifecycle that lets the system improve without letting it rewrite its own rules.

Agents write the code. The framework decides what "done" means.
← All articles Get in touch