Architecture Blueprint · v1 Draft

An agent control plane
on Anthropic-managed agents.

A thin orchestration layer that schedules, governs, and observes a fleet of Anthropic Managed Agents — controllable from inside Claude itself, via a unified MCP tool surface.

Author · Rod Cordova Date · 2026-05-29 Status · Pre-spike review
00 — Origin

How we got here.

axon-gateway started with a simple promise: bring internal business intel to the people who need it, without making them ask me. The pattern was tidy — scheduled tasks scrape raw data, enrich it, persist to SQLite. MCP tools expose the result. Anyone in the workforce sits in Claude and asks a question; the data answers. That part works.

What didn't scale is the gap between fresh data arrived and an intelligent answer in front of a stakeholder. Three gaps surfaced once the basic ETL-to-MCP pattern was in place.

The proactive gap

axon's tools are reactive — they answer questions when asked. But most of the value of fresh intel is unprompted: a finding worth flagging, a number that moved, a recommendation the data implies. axon has no agent loop running over new data to produce insights and write them back. Every insight today requires a human to think to ask for it.

The proxy gap

Today I am the proxy. Users ask me questions; I forward them to one of my Claude Code sessions; that session does the analysis; I relay the answer back. Those sessions are functioning as pseudo-agents — they have context, tools, the ability to reason — but they're tethered to my keyboard. The pattern only scales if I'm at it.

Getting myself out of this chain is the actual problem to solve.

The surface gap

Claude is a capable Q&A interface, but it isn't a persistent dashboard and it isn't an upload surface. End users don't type questions to find out what's happening — they need to land on a dashboard that says "here's what changed", AND they need to drop next month's Viant xlsx into a UI that feeds the pipeline. Reading and writing are both first-class operator needs. Anthropic's cowork features are early and don't reach either side. The intel needs a real product surface with both directions covered — structured reporting for the eyes, structured ingestion for the hands.

Where that leaves us

These three gaps point to a different architecture. axon stays — the unified MCP surface is right, and the workforce-asks-Claude pattern is working. But the agent loop, the persistent UI, and the push channel all live outside axon's scope. The blueprint below is what fills those gaps without ripping out what already works.

01 — The Framing

Three planes, three owners.

Anthropic owns the runtime. Each agent is defined declaratively in Anthropic's console — system prompt, tool list, knowledge config. The catalog, versioning, and the per-run reasoning loop all live there. They expose an API to invoke, list, observe.

Cloudflare owns the ops. A small set of Workers handle what Anthropic doesn't ship natively: cron-driven scheduling, push delivery, run history, and a thin REST surface for control. Per-app D1 holds operational data and the schedule/run/subscription tables.

axon-gateway owns the unified MCP surface for analysts. Every tool — domain tools for each agent project, plus a new agents_* control-plane family — ships through one server. Analysts sit in Claude and say "pause syndication recommendations for the week" or "show me what the recommendations agent ran yesterday."

Per-app Next.js UIs own the end-user surface. Each agent project ships its own dashboard hosted on Cloudflare. End users land on the dashboard to see the state, and use the same UI to upload the next vendor xlsx into the pipeline. The web UI is a first-class plane, not a footnote — it's the read AND write surface for the people who don't live in Claude.

Two co-equal user surfaces. Analysts in Claude, end users in the dashboard. One state store underneath, one agent loop driving both.
02 — The Architecture

Data flow and ownership.

Read the diagram from the bottom up. The operator (in Claude) is the entry point. Their request flows up through axon's MCP layer, which fans out to the two underlying control surfaces: the Cloudflare scheduler Worker for state mutations (schedules, triggers, pauses), and Anthropic's Managed Agents API for catalog and run information.

Anthropic — Managed Agents Agent Catalog system prompts · versions knowledge configs · tools Runtime + Observability /v1/agents/{id}/runs /v1/agents/{id}/invoke Tool calls dispatched to remote MCP (axon) Each agent's tool list points at https://axon.../mcp Cloudflare — Agent Ops Scheduler Worker cron triggers per agent calls Anthropic /invoke Control-Plane REST GET /schedules · PATCH /schedules/:id POST /trigger · GET /runs Push Worker webhook → Slack / email Loaders + Scrapers (cron) TS Workers · Browser Rendering Cloudflare D1 · per agent project Control plane schedules · runs · subscriptions Domain data catalog · revenue · recommendations findings · alerts · ingest_inbox axon-gateway — unified MCP server Control-plane tools (agents_*) agents_list_catalog · agents_get_schedules · agents_get_runs agents_update_schedule · agents_trigger_now · agents_pause Domain tools (per agent project) syndicated_segments_* (15 read-only tools) project_2_* · project_3_* · ... Each tool maps to one app's D1 read or domain action. Operator entry point Claude — chat / desktop / Slack DM Per-app Next.js UI for visual ops (no separate operator dashboard) MCP tool calls control-plane writes agent & run info domain reads cron · /invoke run records remote MCP
Anthropic runs the agent · Cloudflare runs the ops, the per-app UI, and storage · axon serves analysts via MCP · the UI serves end users directly
03 — The Web UI

Read surface and write surface.

Each agent project ships its own Next.js dashboard hosted on Cloudflare Workers via OpenNext. The UI does two things end users need that Claude cannot — it presents structured intel passively (KPIs, charts, drill-throughs, alerts) and it accepts structured data from operators (drag-drop xlsx and csv uploads that feed the pipeline). Read surface and write surface, on one durable URL, behind Cloudflare Access.

What the dashboard shows

KPIs, time-series, top-N tables, drill pages, alerts, agent-written findings/recommendations/opportunities. Every number on every page reads directly from D1 via Drizzle. The dashboard is the surface where the agent loop's output becomes legible — a stakeholder lands here and sees the state without typing anything.

What the upload widget accepts

Vendor reports the agent loop can't fetch autonomously — monthly revenue xlsx, daily csv exports, occasional one-off enrichment files. The operator drags a file in; the UI PUTs the bytes to R2 with a content-hash key; an upload-router worker sniffs the vendor, dispatches the right loader, and the loader writes parsed rows back to D1. Within seconds the same dashboard reflects the new month. Idempotent on sha256 — uploading the same file twice is a no-op.

End user operator stakeholder Per-app Next.js UI Read surface — dashboards KPIs · charts · drill pages findings · recommendations · alerts Write surface — uploads xlsx · csv drop zone sha256-idempotent · sniff → loader D1 — Domain data revenue · catalog · alerts recommendations · findings · runs read directly via Drizzle R2 — uploaded bytes key = sha256(file) · audit trail Loader Worker — sniff & parse xlsx / xlsb / csv → typed rows → D1 SELECT PUT (xlsx) INSERT
The UI's two paths · reads land on D1 directly · uploads route through R2 to the loader, which parses and writes

What makes this different from a generic dashboard

The UI isn't separate from the agent loop — it's the same D1, the same schema. When the agent writes a recommendation, the dashboard surfaces it on the next render. When the operator uploads a vendor file, the loader writes rows the agent will reason over on its next scheduled tick. The UI, the agent, and the MCP tools all converge on one state store. The dashboard is what the agent thinks. The upload is what the operator tells it.

04 — The MCP Control Surface

One server. Two responsibilities.

The agents_* tool family in axon does two things: it lets operators control agent scheduling (update cron, pause, trigger now), and it pulls a full information surface from Anthropic's Managed Agents API (catalog, agent definitions, run history, latest output). Read tools fan to Anthropic + D1; write tools fan to the Cloudflare scheduler Worker.

Read tools — pull from Anthropic API and D1

agents_list_catalog read
List every Managed Agent in the Anthropic console, with version, owner, last-modified, and whether scheduling is enabled.
source · Anthropic GET /v1/agents
agents_get_agent read
Full agent definition — system prompt, tool list, knowledge config, model, temperature, current version.
source · Anthropic GET /v1/agents/{id}
agents_get_schedules read
Active schedules — cron expression, next-fire time, recent failure count, last successful run. Includes paused agents.
source · CF Worker GET /schedules → D1
agents_get_runs read
Run history per agent — invocation time, duration, token usage, tool calls made, status, output preview.
source · Anthropic GET /v1/agents/{id}/runs + D1 cross-ref
agents_get_run_detail read
One run in full — every tool call, every assistant turn, every error. The forensic surface when something goes wrong.
source · Anthropic GET /v1/agents/{id}/runs/{run_id}
agents_get_metrics read
Roll-up across all agents — daily tokens, daily cost, success rate, p50/p95 latency. Sliced by agent or by tool.
source · Anthropic API + D1 derived

Write tools — operator control of the scheduler

agents_update_schedule write
Change an agent's cron expression, or add a new schedule. Two-step pattern — first call returns a diff; second call with confirmation token applies.
source · CF Worker PATCH /schedules/{id}
agents_pause / agents_resume write
Pause an agent (cron skips invocation) or resume it. Useful when a vendor is down or a dataset is in mid-load.
source · CF Worker POST /schedules/{id}/pause
agents_trigger_now action
One-shot invocation outside the normal schedule. Mirrors a cron fire but records the run as ad-hoc for audit.
source · CF Worker POST /trigger/{id}
agents_subscribe_push write
Operator-level subscription management. "Send me the syndication daily digest in #intel-syndication" — recorded in D1, consumed by the push worker.
source · CF Worker POST /subscriptions

Safety

All write tools are wrapped in a two-step diff-then-confirm pattern. The first call returns the proposed mutation as JSON; only a second call with a returned confirmation_token applies it. The agent never mutates its own schedule autonomously without an operator-confirmed second call. This avoids the 2am recursion bug where an agent's failure mode is to disable itself.

05 — Scheduled Run Flow

From cron tick to Slack post.

The push-driven path. No operator involvement — the system surfaces intelligence proactively on schedule.

Daily syndication digest · 14:00 UTC
1
Cloudflare Cron Trigger
14:00 UTC tick fires the scheduler Worker for the syndication-daily-digest agent.
2
Scheduler Worker
Calls POST /v1/agents/syn-daily-digest/invoke with today's context window.
3
Anthropic Managed Agent
Reasons over its system prompt. Calls axon MCP tools (top_advertisers, top_segments, recommendations_list) to gather data.
4
axon MCP server
Each tool reads the syndication D1, returns scoped JSON. Agent assembles its summary.
5
Scheduler Worker
Receives the agent's output. Writes a row to D1 runs table. Emits a webhook to the push Worker.
6
Push Worker
Looks up active subscriptions for topic syndication_daily_digest. Posts to each channel via Slack webhook / email API.
7
Operator
Sees the digest in #intel-syndication at 14:01 UTC. No clicks required.
06 — Operator Control Flow

From a sentence in Claude to a paused agent.

The recursive control move. The operator never visits a dashboard or a CLI — they just describe the desired state to Claude and the MCP layer handles it.

"Pause the syndication recommendations agent until Monday"
1
Operator (in Claude chat)
"Pause the syndication recommendations agent through Monday — there's a vendor data issue I'm sorting."
2
Claude
Calls agents_list_catalog() to identify the agent by name. Resolves to syn-recommendations.
3
Claude
Calls agents_pause(id="syn-recommendations", until="2026-06-01") — first call, dry-run.
4
axon → Scheduler Worker
Worker returns a diff: "would pause syn-recommendations from now until 2026-06-01 00:00 UTC; next scheduled run was today at 16:00 UTC." Returns a confirmation_token.
5
Claude
Shows the operator the diff inline. "Confirm pause?"
6
Operator
"Yes." Claude calls agents_pause(..., confirmation_token=...) — second call, apply.
7
axon → Scheduler Worker → D1
Schedule row mutated. Cron Worker checks the paused flag on next tick and skips. Audit row written.
07 — End-User Upload Flow

From drag-drop to dashboard refresh.

The write path. An end user drops a vendor's monthly report into the UI; seconds later the dashboards reflect the new month and the next scheduled agent run will reason over it. No engineering involvement.

Monthly Viant Resonate Usage xlsx · operator upload
1
End user
Opens the per-app dashboard, drags 2026_05_Resonate_Usage.xlsx onto the /upload drop zone.
2
Next.js UI
Computes sha256 client-side, checks if it's a known hash (idempotent). If new: PUT bytes to R2 with the sha as the object key.
3
Upload Router Worker
Sniffs vendor from filename + content signature. Inserts an ingest_inbox row in D1 marked pending. Dispatches the right loader.
4
Loader Worker
Pulls the xlsx from R2. Parses with the vendor's adapter (header-driven, schema-tolerant). Chunked batch() INSERT into D1 — listing rows, revenue rows, segment-advertiser rows.
5
Upload Router Worker
Marks ingest_inbox row as ingested. Invalidates UI caches. Optionally enqueues an alert message ("new month ingested, $X across N advertisers").
6
Next.js UI
Returns a success banner with totals — rows kept, months covered, top-line revenue. End user sees the new month reflected on every dashboard within seconds.
7
Agent loop · next scheduled tick
Reads the freshly-loaded month via MCP tools. Produces a new digest / recommendation / finding over the updated state. Pushes via the configured channel.

The point worth stating plainly: the end user supplying data is what the agent reasons over next. The UI's upload widget isn't a side feature — it's how fresh ground truth enters the system. Without it, the agent only sees what the scrapers can fetch autonomously.

08 — Why Managed Agents

Anthropic owns the runtime so you don't have to.

The choice between Anthropic's Claude Agent SDK and Anthropic's Managed Agents platform was the load-bearing question. For 3 projects with 5 users on scheduled-runs-plus-push, Managed wins on time-to-ship and observability. The Agent SDK is the right answer for fully bespoke runtime behavior — sub-agent orchestration, custom retry, in-process state machines — but you'd be building ops you don't need.

Claude Agent SDK

  • Runtime · you host (Worker, container, lambda)
  • Management UI · none — build your own
  • Definition · code, deployed
  • Versioning · git + your CI/CD
  • Observability · wire your own
  • Scheduling · you build
  • Lock-in · low
  • Time to first agent · 2-3 weeks

Managed Agents

  • Runtime · Anthropic hosts
  • Management UI · Anthropic console — catalog, version diff, run inspector
  • Definition · declarative config
  • Versioning · console-managed with rollback
  • Observability · built-in run history + token spend
  • Scheduling · you still build (your CF Worker)
  • Lock-in · moderate (mitigable with swap-shaped abstraction)
  • Time to first agent · 2-3 days

The mitigation for lock-in: the scheduler Worker's "invoke agent" should be one function — today it calls Anthropic Managed Agents; tomorrow it could call the Agent SDK running on a Worker, or a third-party. Don't let Anthropic's API shape leak into your scheduler's domain model.

09 — Scope Discipline

What you don't build.

The framing is "control plane," not "platform." The distinction protects scope. A platform is something you build features on; a control plane is a thin layer that governs something owned elsewhere. Resist these temptations:

Don't build

  • An agent runtime. Anthropic Managed runs it.
  • An agent-control dashboard separate from Claude. The agents_* MCP tools are the control surface; Claude is the UI for them.
  • An agent versioning system. Anthropic console does it.
  • A heavy monorepo platform with shared packages, cookiecutter generators, build orchestration. Two repos that copy from a template work just as well at 3 projects.
  • A separate observability stack. AI Gateway + Anthropic's run history covers it for this scale.
  • A Slack bot framework. The push worker posts via webhook; analysts control via axon MCP. No bidirectional bot needed.
  • Per-agent /mcp endpoints. The unified axon surface is the whole value proposition — splitting it loses cross-tool composability.
  • A generic dashboard framework. Each agent project's Next.js UI is bespoke to its domain — that's the point. Shared UI primitives live in packages/ui; shared business logic doesn't.

If you start adding business logic into the scheduler — domain rules, output formatting, vendor-specific code paths — you've stopped building a control plane and started building a platform. That's the line.

10 — The Spike

The smallest loop that proves the architecture.

Before any platform investment, prove the architecture hangs together with the smallest possible loop. The syndication recommendations agent is the right pilot — smallest scope, most legible output, lowest blast radius.

Proof-of-architecture spike · five phases
1
Phase one · define the agent
Define one Managed Agent in the Anthropic console. Point its tools at https://axon.../mcp. System prompt: "summarize today's syndication recommendations."
2
Phase two · scheduler
Stand up one Cloudflare Worker with a cron trigger. Schedule: hourly, for fast iteration. Worker calls Anthropic /invoke, records the run to D1.
3
Phase three · push
Add one push hook — agent output posts to a test Slack channel via webhook. Confirm end-to-end push works.
4
Phase four · control surface
Add two axon MCP toolsagents_get_runs(limit=10) reading the D1 run table, and agents_list_catalog() reading Anthropic's /v1/agents.
5
Phase five · validate
From Claude.ai, ask "what did the recommendations agent produce in the last 10 runs?" The answer comes back via axon → D1, with cross-references to Anthropic's run detail. If this feels right, the architecture is real.

If all five pieces wire up cleanly, the rest of the build is execution. If the Anthropic API doesn't let agents call axon, or push delivery is fiddly, or the run-recording shape feels wrong — the spike surfaces it before any platform investment is committed.