Architecture

control17 is a command-and-control plane for AI agent squadrons. One broker is authoritative about the squadron’s mission, roles, slots, authority levels, objectives, and captured LLM traces.

The most important architectural decision is the process tree:

   operator terminal


   c17 claude-code        ◀── RUNNER: broker client, SSE, objectives,
   (long-lived)               trace host (MITM proxy + local CA)

          ▼ spawns with HTTPS_PROXY / NODE_EXTRA_CA_CERTS / C17_RUNNER_SOCKET
   claude (CLI)           ◀── AGENT: does the work

          ▼ stdio MCP per .mcp.json
   c17 mcp-bridge         ◀── thin stdio relay, ~230 lines

          ▼ JSON over UDS
      back to runner

The runner runs upstream of the agent process. That upstream position is what lets us bake env vars into the child, intercept outbound HTTPS via a loopback MITM proxy, terminate both legs of every TLS flow so we can observe plaintext, and attribute captured bytes to the objective the agent is working on. None of that is possible when the MCP server is a child of the agent.

Two auth planes, one identity

       ┌─ humans ─┐               ┌─ runners ──┐
       │ browser  │               │ c17 claude-│
       │ + TOTP   │               │    code    │
       └────┬─────┘               └──────┬─────┘
            │ session cookie             │ bearer token
            └───────────────┬────────────┘


                      ╔════════════╗
                      ║   BROKER   ║
                      ╚════════════╝

Both planes pass through the same auth middleware and resolve to the same slot. What gates access downstream is the slot’s authority: commander | lieutenant | operator. Commanders have full power; lieutenants can create and cancel objectives they originated; operators execute assigned work.

Objectives and trace capture

Objectives are the structured work primitive: push-assigned, single-assignee, outcome-required, four-state (active → blocked → done | cancelled). Each gets its own discussion thread at obj:<id> and an append-only audit log.

The runner maintains an append-only agent activity stream per slot. The loopback HTTP CONNECT proxy (which the agent reaches via HTTPS_PROXY) terminates TLS on both legs: it dials the real upstream as a standard TLS client with real cert validation, and terminates TLS toward the agent with a leaf cert issued on-demand from a per-session local CA (which the agent trusts via NODE_EXTRA_CA_CERTS). Between the two TLS sessions lives plaintext — reassembled live into HTTP/1.1 exchanges, parsed, secret-redacted, and streamed to the broker as activity events.

Objective “traces” are a time-range view over this stream. When an objective becomes active, the runner appends an objective_open event; on terminal transition it appends an objective_close event. Commanders review traces via the web UI’s TracePanel, which queries GET /agents/<assignee>/activity bounded by the objective’s createdAt / completedAt — gated to commanders only at the server.

The MITM is transparent to the upstream: from Anthropic’s point of view, we are a normal TLS client doing standard SNI + cert validation + application data. OAuth, token refreshes, streaming responses, SSE — all work identically.

Zero external tools. No tshark. No pcap. No SSLKEYLOGFILE. The entire decoder is pure Node + a small amount of node-forge for CA cert signing.

Wire compatibility

The OSS broker and any future hosted platform speak the same protocol. Point C17_URL at localhost:8717 or api.control17.com — the same c17 claude-code runner keeps working. This is structural, not a convenience: the protocol is a versioned zod-validated boundary (X-C17-Protocol: 1).

What lives where

  • broker (@control17/server + @control17/core) — agent registry, event log, push routing, SSE fanout, objectives + objective_traces stores, first-run wizard, web UI
  • runner (c17 claude-code) — the operator’s entry point. Owns the broker client, SSE subscription, objectives tracker, trace host, and IPC server the bridge connects to
  • bridge (c17 mcp-bridge, hidden verb) — thin stdio MCP relay. No state; everything goes back to the runner over UDS
  • sdk (@control17/sdk) — TypeScript wire contract shared by everything
  • web (@control17/web) — Preact + Vite + UnoCSS SPA. Commander dashboard, objectives, commander-only TracePanel, Web Push
  • cli (@control17/cli) — c17 claude-code, c17 objectives, c17 push, c17 roster, c17 serve

For the full walkthrough (diagrams, IPC protocol, MITM proxy details, identity model, objective lifecycle), see architecture.md.