Anatomy of an Agentic GTM System
The first post in this series named the debt. Two kinds — backward (what you’ve accumulated) and forward (what you haven’t adopted) — both compounding while you stand still. The diagnosis was the easy part.
This one is the architecture. What a GTM system looks like when it’s designed not to accumulate either kind of debt. Six layers, one isolated environment per engagement, built on Claude Code instead of bolted together from a dozen workflow wrappers.
One constraint shapes everything below: the system is built to be handed off. It’s a scoped build that ships with a first pulse — proof it runs, not just that it exists — and then belongs to the client. Every architectural decision that follows answers to that.
The architecture below is mature in places and being built out in others — that’s the nature of a system that runs and improves at the same time.
Three vectors, one account
A quick word on shape, because it explains why this is one system and not three.
Outbound, Content, ABM Ads. The default shape is three separate services — three teams running parallel campaigns, three retainers billed separately.
They’re not three departments — they’re three ways to land one message in front of the same account.
- Outbound is direct delivery — the message arrives at one inbox.
- Content is ambient delivery — the message shows up in the feed where the buyer already lives.
- Ads are paid delivery — the message reaches accounts that haven’t engaged yet, on a list you control.
The accounts are the same. The narrative is the same. Only the vector changes. Whether you run one layer or all three, the unit of work is the account, not the channel.
Why one isolated environment per engagement
The standard play is to build infrastructure once and rent it to every client. It looks efficient on paper — one stack, many seats — and on the left side of the productivity barbell, where volume and price compression do the work, it actually is.
We sit on the other side. Shared infrastructure becomes shared blast radius — one schema change breaks five clients, one prompt update needs five rounds of regression testing, one offboarding turns into a permissions audit across the entire workspace. One client’s data is one bad command away from another’s.
So we don’t share. Every client gets a self-contained repository — client-{slug}/ — with its own context, its own skills snapshot, its own workflows, its own data. Duplication is the explicit choice. It costs more to operate. It costs less to coordinate. At three to five concurrent engagements, the second number matters more than the first.
The other reason is hand-off. Self-contained means the system is portable by construction. We’ll come back to that.
Anatomy of Our System
What a working client-{slug}/ repo actually looks like. Not every file — a system in motion accumulates helper scripts, dead experiments, noisy logs — but the skeleton you can read top to bottom.
client-{slug}/
├── CLAUDE.md ← Layer 1: Brain (≤200 lines, scannable in 2 min)
├── README.md ← operating manual — how it's wired, how to run it
│
├── .claude/ ← Claude config: agents, skills, permissions
│ ├── settings.json ← permissions (deny list inside the container)
│ ├── settings.local.json ← personal overrides (gitignored)
│ ├── agents/ ← Layer 4: Agents — own context, orchestrate skills
│ │ ├── account-researcher.md
│ │ ├── reply-handler.md
│ │ └── performance-analyst.md
│ └── skills/ ← Layer 3: Skills — flat library; folders organize, don't orchestrate
│ ├── README.md
│ │
│ ├── cold-email/ # B2B outbound campaigns
│ │ ├── atl-messaging # VP/C-Level/Director
│ │ ├── btl-messaging # Managers/ICs
│ │ ├── first-touch # opening email
│ │ ├── follow-up # Email 2 + 3
│ │ ├── personalization
│ │ ├── re-engagement
│ │ └── subject-lines
│ │
│ ├── linkedin-ads/ # 3-layer funnel architecture
│ │ ├── audiences
│ │ ├── ads-outbound-sync # ABM plays
│ │ ├── bidding
│ │ ├── campaign-setup
│ │ ├── copy # VoC-driven
│ │ ├── creative # Thought Leader Ads
│ │ ├── measurement # Insight Tag + CAPI
│ │ └── optimization
│ │
│ ├── linkedin-content/ # Organic, founders/GTM leaders
│ │ ├── hooks # 8 hook formulas
│ │ ├── storytelling # AIDA, PAS, BAB
│ │ ├── formats # carousels, video, text
│ │ ├── scheduling # Golden Hour
│ │ ├── engagement
│ │ ├── cta
│ │ └── repurposing
│ │
│ ├── list-building/ # contact + account data
│ │ ├── define-icp # 3-layer + 100-point scoring
│ │ ├── source-companies # Apollo, Sales Nav, Google Maps
│ │ ├── find-contacts # Boolean search
│ │ ├── qualify-accounts # ABM tiers + intent
│ │ ├── clean-validate # email verification
│ │ └── deduplicate
│ │
│ ├── n8n/ # workflow automation
│ │ ├── workflow-design
│ │ ├── triggers-webhooks
│ │ ├── error-handling # DLQ, circuit breakers
│ │ ├── crm-automation # HubSpot, Salesforce, Slack
│ │ └── self-hosting # Docker + Postgres
│ │
│ └── signal-sourcer/ # signal-based selling
│ ├── job-changes # 14-45 day window post-change
│ ├── funding # 2-4 week window post-raise
│ ├── hiring # new role signals
│ ├── website-visitors # RB2B, Warmly
│ ├── company-events # M&A, IPO, leadership
│ ├── tech-changes # stack swaps
│ ├── competitor-signals # G2, review-mining
│ ├── content-engagement # Trigify
│ └── multi-signal # scoring + tiers
│
├── context/ ← Layer 2: Institutional knowledge
│ ├── profile.md
│ ├── icp-definition.md
│ ├── signal-library.md
│ ├── positioning.md
│ ├── competitor-radar.md
│ ├── personas/
│ │ ├── {persona-1}.md
│ │ ├── {persona-2}.md
│ │ └── {persona-3}.md
│ ├── business-assets/
│ └── docs/
│ ├── kickoff-summary.md
│ └── communication-cadence.md
│
├── workflows/ ← Layer 5: Decision trees for humans (per channel)
│ ├── outbound/
│ │ ├── enrichment.md
│ │ ├── signal-routing.md
│ │ └── campaign-build.md
│ ├── content/
│ │ ├── ideation.md
│ │ ├── campaign-build.md
│ │ └── post-writing.md
│ └── ads/
│ ├── list-building.md
│ ├── campaign-build.md
│ ├── ad-copy.md
│ └── ad-design.md
│
├── playbooks/ ← Conditional / situational workflows
│ └── new-signal-response.md
│
├── outbound/ ← Layer 6a: Outbound artifacts
│ ├── strategy.md
│ ├── scoring/
│ ├── campaigns/
│ └── logs.md
│
├── content/ ← Layer 6b: Content artifacts
│ ├── strategy.md
│ ├── posts/
│ └── logs.md
│
├── ads/ ← Layer 6c: Ads artifacts
│ ├── strategy.md
│ ├── lists/
│ ├── campaigns/
│ ├── creatives/
│ └── logs.md
│
└── data-sync/ ← Infrastructure: pull external performance data
├── README.md
├── .env
├── sync-campaign-results.py
├── sync-signal-performance.py
├── sync-content-performance.py
└── sync-ad-performance.py
The six layers
The tree shows the shape. The table below shows what each layer does.
| Layer | Path | What it is | For whom |
|---|---|---|---|
| 1 | CLAUDE.md | Brain — index, not reference | Claude (always in context) |
| 2 | context/ | Institutional knowledge — foundation for skills | Claude (read by skills) |
| 3 | .claude/skills/ | Methodology + execution — metadata always in context, body on match | Claude (activated on match) |
| 4 | .claude/agents/ | Workers with their own context window — orchestrate skills + tools | Claude (delegated to) |
| 5 | workflows/ | Decision trees around skills and agents | Humans |
| 6 | outbound/, content/, ads/ | Channel-shaped artifacts — strategy, campaigns, logs per channel | Internal review + hand-off |
A few things to notice once you’ve read the tree
-
Channels appear twice — by design.
workflows/outbound/is how humans operate the channel: enrichment paths, signal routing, campaign build.outbound/is what the channel produced: strategy, scoring, campaigns, logs. Decision trees on one side, artifacts on the other. The mirroring is intentional, not a duplication bug. -
data-sync/is infrastructure, not a layer of its own. Real Python scripts that pull performance data — LinkedIn Ads results, ESP metrics, CRM state — back into the repo where the skills can read it. Plumbing at the same conceptual level as.claude/settings.json. Important, but not a layer of the architecture. -
logs.mdper channel — append-only journal. What was done, when, with what result. The foundation for retrospectives and the reason hand-off doesn’t require a tribal-knowledge transfer. -
Skills are a flat library — the folders only organize them. Forty-odd
SKILL.mdfiles grouped into folders (cold-email/,signal-sourcer/, …) so a human can browse them — but the nesting orchestrates nothing. Every skill’s front-matter (name + description) sits in context at all times, cheaply; the body loads only when the work matches. Progressive disclosure happens at the body, not through the folder tree.Not headcount. Not seats. Skills.
Skills aren’t one component of the system. They’re the format that fuses methodology and execution into something Claude can activate on demand.
-
Orchestration lives in the agents, not the skills. A skill can’t choose which other skill to run — every skill is equally visible all the time, so there’s nothing to gate on. An agent can: it runs in its own context window, declares the skills and tools it’s allowed, and takes a bounded job off the main thread.
account-researcheris the clearest case — point it at an account, it pulls the signal and qualification skills in isolation and hands back a brief.
What we didn’t build
Architecture is as much about what’s not in the repo as what is. Here’s what we deliberately didn’t build — and why each one would have cost more than it returned at our stage.
| What we didn’t build | Why |
|---|---|
An agency-os repo with shared skills | The operational center is our own repo — canon-in-code. There’s nothing to execute in a separate notes app. |
| A virtual monorepo workspace | Context noise and cognitive load outweigh any cross-reference value. |
Per-client ~/.claude-configs/ machinery | Each engagement already runs in its own isolated container — isolation is structural, not a config layer bolted onto a shared workspace. |
| Automated skills sync from the library → client repos | Breaks self-containedness. Each client decides when (and whether) to absorb an updated skill. |
| A bespoke multi-layer permission model | The container boundary does the isolation; inside it, a sandbox plus a single .claude/settings.json deny list is enough. No custom workspace-permission machinery on top. |
| Custom MCP servers (Anthropic’s tool-integration protocol) | Off-the-shelf is enough until a specific bottleneck proves otherwise. |
| A multi-agent swarm or auto-orchestration framework | A few hand-written subagents (research, reply triage, reporting) cover what we need. Agents that spawn agents is complexity we haven’t earned. |
| Cross-client analytics infrastructure | Ad-hoc once per quarter is enough at this scale. A persistent dashboard would optimize for a problem we don’t have yet. |
Cross-reference is, honestly, the last thing I’d add.
The canonical skills library lives in our operational center
The skills you saw in .claude/skills/ are snapshots, not sources. The canonical library lives in our own operational center — a repo, written, debated, and refined in code — and gets copied into a client repo when an engagement starts.
That split is deliberate. Strategic thinking is not the same as the executable that ships. The reasoning behind a skill, the alternatives we rejected, the edge cases that made us split one skill into two — none of that needs to land in the client’s repo. What ships is the skill that survived the argument.
We don’t auto-sync. When a skill in the library evolves, each client repo decides whether and when to absorb the new version. Self-containment beats freshness; nobody wants a silent prompt change to land mid-campaign.
Strategy and execution both live in code — the repo is the operational center. The only thing kept out of it is a human layer (PM, journal) we keep AI-free on purpose.
Hand-off is structural, not aspirational
Every architectural decision in client-{slug}/ answers to one question: when this engagement ends, what does the client take with them?
The whole thing.
At the end of the project you keep the entire owned system — the full client-{slug}/ repository and its isolated environment, all skills (.claude/skills/), workflows, playbooks, context/, the channel artifacts (outbound/, content/, ads/), the data-sync/ scripts, and every dashboard. The root README.md is written as the operating manual — how the system is wired and how to run it — so the repo explains itself. We transfer the repository (gh repo transfer) and revoke our access.
The system was built to be handed off from the first commit, so the handoff is clean and immediate at project end. The only thing that comes after is optional: an advisory or operational tail, if you want one — a fresh conversation once the system is live, never a line you signed up front.
Hand-off is a promise, not aspirational. It’s on the site. It’s in the FAQ. It’s a personal standard.
Self-contained, isolated architecture turns hand-off into a git push and an access revoke. That’s the entire mechanism.
Operational principles
1. One isolated environment per engagement
No shared infrastructure between clients. Each engagement gets its own repo and its own isolated container from day one. Duplication is cheaper than coordination at boutique scale — and it makes hand-off architectural, not procedural.
2. Hand-off is a structural promise
Built so the entire system transfers as git push + access revoke — clean and immediate at project end. You own all of it. The only “after” is an optional tail.
3. Strategy and execution both live in code
The operational center is the repo — canon and execution in one place, not split across a notes app. The one layer kept deliberately out of it is an AI-free human layer (PM, journal). Fusing thinking and execution in code is the anti-debt move; carving out a human-only space is the deliberate exception.
4. Skills are a format, not a feature
Methodology, execution, and progressive disclosure in a single markdown document. Claude activates them on match, not by default.
5. Account is the unit, not channel
Outbound, content, ads — three vectors into the same account. Channels have separate feedback loops; the unit of work doesn’t change.
6. Build it once. Use it daily. Update when patterns emerge.
No skill for every task. No template for every situation. The system grows by addition only when repetition demands it.
Outro
Each layer answers a specific way debt creeps in. One isolated environment per engagement keeps shared state from piling up. Canon-in-code — not a separate notes app — keeps prompts from drifting. Self-contained hand-off keeps you from getting locked in.
And every one of those choices serves the same end: a scoped build that fires its first pulse, then becomes the client’s to own.
This architecture is complex, yes — but it’s complexity spent in one direction: against debt.