> ## Documentation Index
> Fetch the complete documentation index at: https://docs.steerholm.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> How Steerholm sits between agents and servers and enforces policy.

## Overview

Steerholm is two planes over one boundary: agents act on the action plane, you govern from the control plane. Every action an agent takes flows through the daemon, which authenticates the agent and checks it against your policy before it reaches a server — so an agent can only ever do what you granted.

This page is the technical view of [the control plane](/concepts/control-plane): how a grant becomes a governed connection, and how the boundary is enforced on every request.

## System diagram

<div
  className="arch-diagram"
  style={{
background: 'linear-gradient(135deg, var(--arch-grad-a, #0c0c1d) 0%, var(--arch-grad-b, #111128) 100%)',
borderRadius: '14px',
padding: '24px 20px',
border: '1px solid var(--arch-frame, #2d2d5e)',
boxShadow: 'var(--arch-shadow, 0 22px 46px -22px rgba(0,0,0,.55))',
fontFamily: 'system-ui, -apple-system, sans-serif',
color: 'var(--arch-ink, #e2e8f0)'
}}
>
  <div style={{ textAlign: 'center', marginBottom: '8px' }}>
    <span style={{ color: 'var(--arch-indigo-label, #818cf8)', fontSize: '12px', fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }}>Agents</span>
  </div>

  <div style={{ display: 'flex', justifyContent: 'center', gap: '10px', marginBottom: '14px' }}>
    {['VS Code', 'Claude Code', 'Cursor', 'OpenCode'].map((name) => (
            <div key={name} style={{ background: 'var(--arch-node-bg, #1a1a2e)', border: '1px solid var(--arch-node-border, #4a4dae)', borderRadius: '8px', padding: '8px 16px', textAlign: 'center' }}>
              <span style={{ fontWeight: 600, color: 'var(--arch-node-ink, #c7d2fe)', fontSize: '14px' }}>{name}</span>
            </div>
          ))}
  </div>

  <div style={{ textAlign: 'center', margin: '6px 0', color: 'var(--arch-arrow-indigo, #6366f1)', fontSize: '12px' }}>▼ <span style={{ color: 'var(--arch-arrow-indigo-soft, #525280)' }}>Streamable HTTP + Bearer token</span></div>

  <div style={{ background: 'var(--arch-panel-bg, #1a1a35)', border: '2px solid var(--arch-panel-border, #4a4dae)', borderRadius: '12px', padding: '18px', margin: '10px 0' }}>
    <div style={{ textAlign: 'center', marginBottom: '14px' }}>
      <span style={{ color: 'var(--arch-panel-label, #9196d4)', fontSize: '12px', fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }}>Steerholm daemon</span>
      <span style={{ color: 'var(--arch-panel-label, #9196d4)', fontSize: '12px', fontWeight: 600, letterSpacing: '0.08em'}}> /mcp</span>
    </div>

    <div style={{ background: 'var(--arch-auth-bg, #4a4dae)', borderRadius: '8px', padding: '10px 14px', fontWeight: 600, color: 'var(--arch-on-accent, #fff)', fontSize: '14px', marginBottom: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px' }}>
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />

        <path d="M7 11V7a5 5 0 0 1 10 0v4" />
      </svg>

      Agent Verification
    </div>

    <div style={{ display: 'flex', gap: '8px', marginBottom: '10px' }}>
      {[{ l: 'coding-agent', p: 'read only' }, { l: 'ops-agent', p: 'full access' }, { l: 'ci-agent', p: 'git only' }].map((s) => (
                  <div key={s.l} style={{ background: 'var(--arch-session-bg, #16162a)', border: '1px solid var(--arch-session-border, #3b3d8f)', borderRadius: '8px', padding: '8px 10px', textAlign: 'center', flex: 1 }}>
                    <div style={{ fontWeight: 500, color: 'var(--arch-session-ink, #c7d2fe)', fontSize: '13px' }}>{s.l}</div>
                    <div style={{ color: 'var(--arch-session-sub, #818cf8)', fontSize: '11px', marginTop: '2px' }}>{s.p}</div>
                  </div>
                ))}
    </div>

    <div style={{ display: 'flex', gap: '8px' }}>
      <div style={{ background: 'var(--arch-emerald, #10b981)', borderRadius: '8px', padding: '10px 14px', fontWeight: 600, color: 'var(--arch-on-accent, #fff)', fontSize: '14px', flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px' }}>
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
        </svg>

        Policy Enforcement
      </div>

      <div style={{ background: 'var(--arch-teal, #0d9488)', borderRadius: '8px', padding: '10px 14px', fontWeight: 600, color: 'var(--arch-on-accent, #fff)', fontSize: '14px', flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px' }}>
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <circle cx="12" cy="12" r="10" />

          <path d="M8 12h8" />

          <path d="M12 8l4 4-4 4" />
        </svg>

        Tool Routing
      </div>
    </div>
  </div>

  <div style={{ textAlign: 'center', margin: '6px 0', color: 'var(--arch-emerald-label, #10b981)', fontSize: '12px' }}>▼ <span style={{ color: 'var(--arch-arrow-emerald-soft, #065f46)' }}>spawn / connect</span></div>

  <div style={{ textAlign: 'center', marginBottom: '8px' }}>
    <span style={{ color: 'var(--arch-emerald-label, #10b981)', fontSize: '12px', fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }}>MCP Servers</span>
  </div>

  <div style={{ display: 'flex', justifyContent: 'center', gap: '10px' }}>
    {[
            { name: 'filesystem', icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{ stroke: 'var(--arch-server-icon, #6ee7b7)' }} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> },
            { name: 'git', icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{ stroke: 'var(--arch-server-icon, #6ee7b7)' }} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><line x1="6" y1="9" x2="6" y2="21"/></svg> },
            { name: 'database', icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{ stroke: 'var(--arch-server-icon, #6ee7b7)' }} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg> }
          ].map((s) => (
            <div key={s.name} style={{ background: 'var(--arch-server-bg, #0a2a1e)', border: '1px solid var(--arch-server-border, #10b981)', borderRadius: '8px', padding: '8px 16px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px' }}>
              {s.icon}
              <span style={{ fontWeight: 600, color: 'var(--arch-server-ink, #d1fae5)', fontSize: '14px' }}>{s.name}</span>
            </div>
          ))}
  </div>
</div>

## Request flow

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#4a4dae', 'actorTextColor': '#fff', 'actorBorder': '#6366f1', 'activationBorderColor': '#6366f1', 'activationBkgColor': '#2d2d5e', 'signalColor': '#6366f1', 'signalTextColor': '#e2e8f0', 'noteBkgColor': '#1e1e2e', 'noteTextColor': '#e2e8f0', 'noteBorderColor': '#4a4dae', 'altSectionBkgColor': '#16162a', 'loopTextColor': '#e2e8f0', 'labelBoxBkgColor': '#1e1e2e', 'labelTextColor': '#10b981'}}}%%

sequenceDiagram
    participant Agent
    participant Daemon as 🧭 Steerholm /mcp
    participant Server as MCP Server

    Agent->>Daemon: MCP request (Streamable HTTP + Bearer token)

    Note over Daemon: 🔐 Resolve agent
    Note over Daemon: 🛡 Check policy

    alt ✅ Allowed
        Daemon->>Server: Forward request
        Server-->>Daemon: Response
        Daemon-->>Agent: MCP response
    else ❌ Denied
        Daemon-->>Agent: AUTHORIZATION_DENIED (-31001)
    end
```

## Entry points

<CardGroup cols={2}>
  <Card title="holm — control plane" icon="terminal" color="#10b981">
    Your governance surface. Manage servers, agents, policies, and the daemon — this is where you control what every agent can do.
  </Card>

  <Card title="/mcp — action plane" icon="globe" color="#10b981">
    The agents' surface: they connect here over Streamable HTTP with a Bearer token to act. Requires authentication and has no admin commands.
  </Card>
</CardGroup>

Agents connect to `/mcp`; they do not run the `holm` admin CLI.

## Server processes

Servers are shared by all agents — the daemon runs one set of server processes and isolates agents by policy, not by per-agent processes.

<CardGroup cols={2}>
  <Card title="Stdio servers" icon="box" color="#10b981">
    Started and managed by the daemon as shared server processes.
  </Card>

  <Card title="Streamable HTTP servers" icon="globe" color="#10b981">
    Connected by the daemon as shared server sessions.
  </Card>
</CardGroup>

## Connection flow

<Steps>
  <Step title="Agent connects">
    The agent connects to `http://127.0.0.1:4767/mcp` over Streamable HTTP.
  </Step>

  <Step title="Authentication">
    The agent sends `Authorization: Bearer steer_sk_...`. The daemon resolves the agent by checking the access key against stored hashes. The access key determines the agent — it cannot be self-declared.
  </Step>

  <Step title="Request authorized">
    The shared MCP server resolves the agent's policy and filters or routes tools through daemon-managed servers.
  </Step>

  <Step title="MCP traffic flows">
    Standard MCP traffic flows through Steerholm. Every `call_tool` is checked against the policy before forwarding.
  </Step>
</Steps>

## Summary

| Aspect | How Steerholm handles it |
| - | - |
| Boundary | Agents reach servers only through the daemon's `/mcp` endpoint |
| Authentication | Bearer access key → agent lookup (the key determines the agent) |
| Authorization | Per-agent policy files, checked on every `call_tool` |
| Default deny | No policy = no access |
| Errors | `AUTHORIZATION_DENIED` (`-31001`), `SERVER_UNAVAILABLE` (`-31002`) |
