One supervised clawd process, built from 18 SwiftPM targets. Protocols live in
ClawCore, implementations live in sibling Claw* targets, and
clawd is the only target that imports all of them.
Drawn from
Package.swift, imports and conformances in Sources/, docs/ARCHITECTURE.md
16 libraries, ClawTestSupport, the clawd executable, 17 test targets
Platforms
macOS 15 or later and Linux; swift-tools 6.1
composition root
ClawCore seam
implementation
untrusted side, or a relaxed default
refused
test-only
Runtime
System context
clawd starts every connection it has. It long-polls Telegram instead of
receiving webhooks, calls model and tool endpoints, and opens no listening socket. Local
state lives under one state root: ~/.swift-claw by default, or
CLAW_STATE_ROOT.
Ctrl/Cmd + wheel zooms. Drag or scroll pans once zoomed in. Double-click fits.
Loading…
Two classes of egress. Configuration or code fixes the pinned endpoints.
Web pages and MCP servers are arbitrary destinations: their results are untrusted, and the
trifecta gate can require approval before a call. Both model routes sit behind one
LLMProvider seam, and each call uses one of them. The ChatGPT route is
unofficial and can change without notice.
Three HTTP clients
Telegram follows redirects. The LLM and tool clients have redirects disabled, so a bearer token cannot follow a redirect to another host (RuntimeHTTPClients).
One daemon per state root
clawd run takes an exclusive flock and holds it until exit. A second daemon exits with the already-running code. auth login, auth logout, mcp set-token and mcp clear-token need the same lock.
State root
claw.sqlite (GRDB, WAL, FTS5), workspace/, secret.key with the secrets.enc, llm-credentials.enc and mcp-credentials.enc envelopes, and an optional mcp.yaml.
Compile time
Module dependency graph
Package.swift declares these edges, and the import lines in
Sources/ match them exactly. Read from the top: clawd imports every
library, the implementation libraries sit in the middle, and every target imports
ClawCore at the bottom. Only five imports run from one implementation library
to another.
orange rail: clawd imports each library below it
arrow: one library imports another
teal rail: every target above imports ClawCore
dashed card: test-only, not linked into clawd
clawd
executable · composition root
Parses the command, loads config, takes the state-root lock, loads secrets, then builds every concrete type and the ServiceGroup.
imports ClawAgent for AgentRuntime, SessionLaneRegistry and ContextBuilder
MessageRouter
AccessControl
TurnRunner
OutboxDispatcher
swift-service-lifecycle · swift-log
imports
ClawAgent
lib
Agent runtime: context assembly, the run loop, budgets, cancellation, per-session lanes.
AgentRuntime
ContextBuilder
SessionLaneRegistry
swift-log
ClawTelegram
lib
Thin Bot API client: long-poll, update normalization, rich messages with a plain fallback, file download.
imports ClawHTTP for AsyncHTTPExecutor and HTTPClientProfile to build the three runtime clients
TelegramClient
RuntimeHTTPClients
async-http-client
imports
ClawHTTP
lib
AsyncHTTPClient behind the Core HTTP seams, with redirect and decompression profiles. No Telegram or provider policy.
AsyncHTTPExecutor
HTTPClientProfile
async-http-client · swift-nio
ClawExec
lib
execute_code backend: a fresh apple/container VM per call, plus probe, reap and canary. macOS 26 on arm64 only.
imports ClawSubprocess for SubprocessRunning to drive the container CLI
ContainerBackend
ExecSandboxSettings
no package dependencies
imports
ClawSubprocess
lib
Process launch with bounded output capture, timeouts and process-group teardown. Also the cross-process InstanceLock.
SwiftSubprocessRunner
SubprocessCommand
InstanceLock
swift-subprocess 1.0.0 · swift-system on Linux
ClawLLM
lib
Both wire adapters behind LLMProvider: OpenAI-compatible Chat Completions and ChatGPT Codex Responses. SSE, retries, cost.
imports ClawAuth for ChatGPTCredentialSource and ChatGPTOAuthClient
ProviderStackFactory
OpenAICompatibleProvider
ChatGPTResponsesProvider
swift-log · embeds Prices.json
ClawSecrets
lib
AES-GCM envelopes under one 0600 key: runtime secrets, LLM credentials, MCP tokens. Crash-aware publication.
imports ClawAuth so LLMAuthDoctor reads token expiry the same way the live source does
SecretStoreResolver
EncryptedLLMCredentialStore
EncryptedMCPCredentialStore
swift-crypto
importsimports
ClawAuth
lib
ChatGPT device-code OAuth, the model catalog, the refreshing credential actor, and one workflow per clawd auth command.
ChatGPTCredentialSource
ChatGPTOAuthClient
AuthLoginWorkflow
no package dependencies
Each arrow is a direct import between two libraries. Any other use across these targets goes through ClawCore types, and clawd supplies the concrete implementations.
No library-to-library import in either direction
ClawData
lib
GRDB persistence: schema, migrations, and the …GRDB stores behind the Core persistence protocols. SQLite errors map to StoreError.
ClawStores
ClawDatabase
MappedDatabase
GRDB.swift
ClawWorkspace
lib
Workspace files (SOUL, AGENTS, USER, TOOLS, MEMORY), skills with YAML frontmatter, and the mcp.yaml loader.
FileSystemWorkspace
MCPConfigLoader
Yams
ClawTools
lib
Tool registry, policy gate and the built-in tools: web, files, memory, skills, execute_code, Coder.
ToolRegistry
ToolPolicyGate
GatedToolDispatcher
no package dependencies
ClawMCP
lib
MCP client: Streamable HTTP transport on the Core HTTP seam, one session per server, and the MCPTool adapter.
MCPServerSession
MCPCatalogResolver
MCPTool
swift-sdk (MCP) · swift-log
ClawCoder
lib
Native Codex delegation: process ownership, Git workspaces, result inspection. macOS and Linux.
CodexBackend
CoderRequestPreparer
CoderProcessInspector
swift-subprocess 1.0.0 · swift-system on Linux · embeds CodexResult.schema.json
ClawAppleSpeech
lib
On-device voice transcription behind VoiceTranscribing. macOS 26; an empty module on Linux.
AppleSpeechTranscriber
SystemVoiceTranscriber
no package dependencies · system Speech framework
all 17 targets above import ClawCore
ClawCore
lib · seams and value types
The contracts every other target programs against: 46 protocols and the shared value types. It imports no Claw target.
Three layers, five cross-imports.clawd imports all 16
libraries, and every target imports ClawCore. Apart from those, the only
imports between libraries linked into clawd are the five arrows.
ClawTestSupport also imports five targets but is linked only into tests.
Siblings meet through ClawCore
coder_submit lives in ClawTools and needs CoderService from ClawGateway. Neither target imports the other. The tool holds any CoderServing, a ClawCore protocol, and DaemonBuilder.prepareCoder passes the concrete service in.
Persistence stays behind protocols
Only clawd and ClawTestSupport import ClawData. ClawGateway and ClawAgent never name a …GRDB store, ClawStores or ClawDatabase. They hold store protocols from ClawCore.
Compile time
Seams and their implementations
A consumer stores an any protocol type from ClawCore. The concrete
type lives in a sibling target, and clawd passes it in. The rows below come from
a scan of conformances and any or generic uses in Sources/. Test
doubles in ClawTestSupport are left out.
ClawCore seams grouped by area. “Held by” lists the targets that store or accept the protocol type.
clawd run is where concrete types meet their seams. It finishes every check that
can fail cheaply before it builds the object graph. On the way out it drains live work before
it commits credentials and closes clients.
Boot RunCommand · RunComposition · DaemonBuilder
Config.EnvironmentLoader parses AppConfig from the environment. Invalid config exits with its own code.
Instance lock.InstanceLock takes the state-root flock and holds it until exit.
Secrets and MCP inputs. The fail-closed resolver unseals secrets.enc, then mcp.yaml and mcp-credentials.enc load. Secret failures exit non-retryably.
Redacting logger. The log backend starts with every secret and MCP token in its redaction set, before the first log line.
Stores.EnvironmentLoader.openStores opens claw.sqlite into ClawStores and seeds the owner allowlist.
Clients and bot identity. Three HTTP clients, then getMe. With group chats configured, a missing bot @username stops the boot.
Provider roster.ProviderStackFactory builds the primary and optional fallback route on the LLM client. Turns and schedule parsing share one PrimaryRouteCooldown.
Object graph.DaemonBuilder.build prepares the sandbox and Coder, pins the MCP catalog before the tool registry exists, then builds the agent stack, learning and the services.
Boot hook, then serve.setMyCommands, reconcile orphaned runs, reconcile Coder, replay approvals (skipped if Coder failed to reconcile), reconcile learning. Then ServiceGroup.run().
Shutdown RuntimeShutdownCoordinator
Signal. SIGTERM or SIGINT starts a graceful shutdown. ServiceLifecycle stops services in reverse registration order.
Lanes first.LaneAdmissionShutdownService is registered last, so it stops first: it closes admission, cancels lane tasks and waits up to 30 seconds.
A drain timeout is fatal. If a lane is still live, FatalProcessTerminator ends the process. Credentials and clients are not closed under live tasks.
Coder join.CoderService.shutdown() must prove its process cleanup was joined and persisted. Otherwise the exit is fatal.
Credential commit. Every route's LLMCredentialSource.shutdown() runs, primary first, so a token rotation is either saved or reported as failed.
Clients close. The LLM client, then Telegram, then the tool client. The instance lock is released when run returns.
ServiceGroup services in registration order (DaemonBuilder.build). Shutdown walks this list from the bottom.
#
Service
Target
Registered when
1
TelegramPollerService
ClawGateway
always
2
OutboxDispatcher
ClawGateway
always
3
SchedulerService
ClawGateway
always
4
ApprovalExpiryService
ClawGateway
always
5
ScheduledLearningService
ClawGateway
CLAW_LEARNING_ENABLED is on
6
SandboxLifecycleService
ClawGateway
CLAW_EXEC_ENABLED is on
7
MCPSessionLifecycleService
clawd
at least one enabled MCP server
8
CoderService
ClawGateway
Coder is enabled, or durable Coder reservations need recovery
9
LaneAdmissionShutdownService
ClawGateway
always, and always last
Boundary
MCP: client only
ClawMCP is the only target that implements MCP. It turns each remote tool into an
ordinary Tool, so the policy gate, approvals, policy_version,
redaction and audit have no MCP-specific code. The daemon uses remote tools and exposes none
of its own.
Ctrl/Cmd + wheel zooms. Drag or scroll pans once zoomed in. Double-click fits.
Loading…
A remote tool looks like any other tool.MCPTool conforms to
Tool. The gate, approval state machine and fingerprint work with that
protocol. The word MCP appears nowhere in ClawTools, ClawAgent,
ApprovalFSM or PolicyFingerprint. ClawGateway
mentions it only to route and render the /mcp status command.
The client
Discovers tools from owner-configured Streamable HTTP servers once, at boot. The catalog stays fixed while the process runs.
Names each tool mcp__<server>__<tool>. A name collision takes the lowest free numeric suffix, assigned in config order.
Starts every remote tool at ask. Owner config may lower a named tool to safe, and the trifecta gate can still force approval.
Skips an unreachable or misbehaving server with a redacted reason. The daemon still boots, and clawd doctor exits non-zero.
Folds each server's endpoint, auth header name, static headers and remote tool names into policy_version, so a change voids parked approvals.
Disconnects every session at shutdown while the tool client is still open to send DELETE.
Speaks Streamable HTTP only. The stdio transport, OAuth 2.1 client auth and live catalog refresh are deferred.
Never
Runs an MCP server or exposes swift-claw's own tools.
Advertises client capabilities: the SDK Client is built with none, so no server can request sampling, elicitation or roots.
Gives the model an MCP admin, credential or catalog tool. Tokens change only through clawd mcp set-token and clear-token, under the instance lock.
Lets Telegram manage MCP: /mcp reports boot status only.
Accepts dangerous for a remote tool. Config loading rejects it.
Treats server text as trusted: those results are ingestedUntrusted and taint the session.
Exception
Group and forum mode
Off by default. When CLAW_GROUP_CHATS lists chat IDs, clawd also
serves those rooms, and each forum topic gets its own session. The spec scopes this to a
supervised event on a separate installation. It is not a multi-user mode for the owner's
personal daemon.
Ctrl/Cmd + wheel zooms. Drag or scroll pans once zoomed in. Double-click fits.
Loading…
One decision point, then the session key.AccessControl is
the only component that turns CLAW_GROUP_CHATS into a mode. Consumers that
hold only a session ID read the mode back from the key. clawd reads the list
in two other places: to refuse boot without a bot @username, and to count group chats in
doctor.
What changes in a group topic. relaxed marks a DM safeguard that group mode weakens; narrower marks a capability it removes.
Aspect
Direct message (default)
Group topic (opt-in)
Admission
The sender's numeric user ID must be on the allowlist.
The chat ID must be listed. Members need no allowlist entry. relaxed
Refusal
A stranger gets a reply telling them to ask the owner.
Unlisted groups, channels and unknown chat kinds get silence.
Session key
tg:dm:<chat>
tg:topic:<chat>:<thread or general>, one per forum topic
What starts a run
Every message.
An @mention, a bot command or a reply to the bot. Other text is stored without a run. narrower
Stored provenance
Voice transcripts and photos are stored untrusted and taint the session.
Every line is stored trusted, voice transcripts included, and prefixed with the speaker's name. Attendee text never taints. relaxed
Recall
Searches across sessions.
Restricted to the topic's own session. narrower
Trifecta gate
A tainted session with private data needs approval for arbitrary-destination egress.
Allows. The argument scans for secrets and private substrings still run. relaxed
Approvals
Ask-tier and dangerous tools park for the owner's tap.
Ask-tier and enabled dangerous tools run without parking. Only coder_submit parks, and any current member may decide after a fresh getChatMember check. relaxed
Memory and prompt files
memory_write and privileged prompt-file writes need approval.
Both are refused. narrower
Owner commands
All available.
/remember/memory/schedule/learning/pause/resume/runnow/cancel are refused. narrower
Durable memory in context
Assembled. High-sensitivity items are held back once the session is tainted.
Assembled. Attendee text never taints, so it never triggers the hold-back. The owner's memory is in the room. relaxed
Delivery
The owner's DM, with streaming rich drafts.
A reply in the originating topic, with a typing indicator only. A Telegram 429 holds that chat while other chats keep sending.
Daily budget
One RunBudget for the whole daemon. Every topic and the owner's DM share it.
Run it on a separate installation
Group lines reach the model unfenced, the trifecta gate allows, and the owner's durable memory is assembled into the room. The spec accepts that only for a supervised event on its own state root, with a workspace and memory that hold nothing private.
Boot guard
RunComposition refuses to boot when CLAW_GROUP_CHATS is set but getMe returned no bot @username. Without it, addressing can never match and the bot would stay silent in every room.