Wirken
Wirken is the enterprise gateway for autonomous agents: the switchboard between your team's messaging channels and the AI agents working on their behalf. Your people reach it from a browser or the chat platforms they already use, like Slack and Teams, and the agent on the other end reads files, calls APIs, and runs tools for them. Each channel gets its own line.
Wirken is built for the security team that has to answer for what those agents do. It assumes any agent can be turned against you, and boxes in what a compromised one can reach.
It ships as a single static Rust binary, so it runs wherever your controls require: a locked-down workstation, a server inside your network, or an air-gapped host. Its model connection is provider-agnostic: frontier APIs like OpenAI, Anthropic, and Gemini, local weights through Ollama, TEEs for encrypted processing, or Infomaniak's Swiss-hosted Apertus, all interchangeable. Each agent runs the model you assign it, capped by a per-agent spend budget with usage reporting. Frontier or local, on your hardware or a remote host, the data boundary is yours to draw. MIT licensed.
How it works
Wirken sits between your people and the agent. A message comes in on a channel, Wirken wakes an agent to handle it, and every file it reads, API it calls, and tool it runs passes through Wirken's controls first.
- The agent is untrusted. Wirken assumes any agent can be turned against you, whether by a prompt-injection payload hidden in a file or web page or by drift over a long session. The guarantees below do not depend on the model behaving.
- Credentials never reach the model. Tokens and keys stay in an encrypted vault. Wirken supplies them at the edge when it talks to a channel or an MCP tool; the agent and the LLM never see them.
- Every action is logged before it runs, and attributed. Each step is written to a signed, hash-chained audit log as a typed event, tagged with the person who triggered it, before it executes. The log streams to your SIEM.
- Least privilege by default. Each agent runs under a three-tier permission model, and any sub-agent it spawns is boxed by limits you set: which tools, which tier, how many rounds, how long.
- Shell commands run in a sandbox. Anything the agent executes runs in an ephemeral, network-less container, with gVisor as an option. If the sandbox is unavailable, execution is refused rather than run on the host.
Install and run
Prerequisites
- A supported OS: Linux (x86_64 or aarch64), macOS (Intel or Apple Silicon), or Windows 11 (x86_64).
- At least one model to talk to. Wirken connects to any of:
- Ollama (any local model it serves)
- Any OpenAI-compatible endpoint
- OpenAI
- Anthropic
- Google Gemini
- AWS Bedrock
- NVIDIA NIM (local or cloud)
- Tinfoil (TEE, encrypted processing)
- Privatemode (TEE, encrypted processing)
- Infomaniak (Swiss-hosted, including Apertus)
- Docker, if you want shell commands sandboxed (the default). Without it the agent simply cannot run shell commands; nothing else is affected.
Download the latest release binary:
curl -fsSL https://raw.githubusercontent.com/gebruder/wirken/main/install.sh | sh
wirken setup
wirken run
Pin the installer before piping. The committed install.sh has this SHA-256:
73e678196ea073608e902c8ab11a01ede07e0d37fddccaa20c43fa5d62bd52f5
Verify it yourself:
curl -fsSL https://raw.githubusercontent.com/gebruder/wirken/main/install.sh | sha256sum
The installer then fetches checksums.sha256 and checksums.sha256.sig from the release, verifies the signature with ssh-keygen -Y verify against a signing key embedded in the script, and verifies the binary's SHA-256 against the signed checksums. Every failure path is fail-closed: missing signature, missing checksum, mismatched digest, or a machine without sha256sum/shasum aborts install. The only override is WIRKEN_ALLOW_UNVERIFIED=1, which warns on stderr and is documented in docs/release-signing.md.
Prebuilt binaries are available for Linux (x86_64, aarch64), macOS (x86_64, Apple Silicon), and Windows 11 (x86_64). The Linux binaries are statically linked against musl with no glibc dependency. Windows users: the bash installer above does not apply; see docs/windows.md for the Windows install path and the feature-set differences (Signal adapter, orchestrator-push, service installer, and cron presets are Linux/macOS only).
wirken setup walks you through six steps:
wirken setup
────────────
Wirken is the switchboard between your messaging channels and an
AI agent you control. Credentials never reach the LLM. Every
action is logged in a signed, hash-chained audit log.
Setup walks through six steps: provider, channels, credentials,
service, sandbox, audit. About a minute.
Continue [Y/n]: y
... (six interactive steps) ...
Setup complete!
Provider: anthropic (claude-sonnet-4-6)
Channels: Telegram
Next steps:
wirken channel add <channel> Add another messaging channel
wirken credentials add <name> Add or rotate a key
wirken doctor Verify the install
wirken sessions list See active conversations
WebChat: http://localhost:18790
Start wirken: wirken run
wirken run starts wirken. It spawns adapter processes, accepts authenticated connections, routes messages to the agent, and serves a WebChat UI at http://localhost:18790:
wirken v1.13.0
──────
Provider: ollama/llama3.2
Ollama version: 0.19.0
Route: Telegram -> agent:default
WebChat: http://localhost:18790
Wirken running. Press Ctrl+C to stop.
All local services bind to 127.0.0.1. Wirken never instructs you to bind inference servers, WebChat, or any local endpoint to 0.0.0.0.
Install as a system service so wirken starts on login:
wirken setup --install-service
Running modes
- Interactive.
wirken runstarts the gateway in the foreground, spawns the channel adapters, and serves WebChat. Ctrl+C stops it. - Browser. That same
wirken runserves WebChat athttp://localhost:18790, bound to localhost, so your team can use Wirken without installing a chat app. - As a service.
wirken setup --install-serviceinstalls a systemd user unit on Linux or a launchd agent on macOS, so the gateway starts on login and runs headless.wirken setup --uninstall-serviceremoves it. - Scheduled. Preset agents run on a cron schedule with no human in the loop, under the same permission and audit controls, via
wirken preset schedule <name>(andunschedule). Service and scheduled modes are Linux and macOS only.
Uninstall
Removing the data directory is irreversible and destroys the signed, hash-chained audit log along with it. If any retention or compliance need applies, export the audit chain before you delete anything:
wirken audit log --format json > wirken-audit-export.json # one-shot JSON snapshot
cp ~/.wirken/audit.db wirken-audit-backup.db # or copy the raw hash-chained DB
Then uninstall in order. The service and cron steps call the wirken binary, so run them before removing it:
# 1. Stop and remove the system service. Removes the systemd user unit
# ~/.config/systemd/user/wirken.service on Linux, or the launchd agent
# ~/Library/LaunchAgents/app.ottenheimer.wirken.plist on macOS.
wirken setup --uninstall-service
# 2. Remove any scheduled preset cron entries (leaves your own cron lines
# intact). Repeat per installed preset, e.g. zirkel:
wirken preset unschedule zirkel
# 3. Remove the binary.
rm "${WIRKEN_INSTALL_DIR:-$HOME/.local/bin}/wirken"
# 4. Remove the data directory. Deletes the credential vault, the age-file
# device key, and the audit chain. Irreversible.
rm -rf ~/.wirken
Residue the steps above do not touch:
- Shell profile: if you exported
WIRKEN_VAULT_PASSPHRASEin~/.bashrc,~/.zshrc, or a similar file, delete that line. - OS keychain: the default release build keeps the vault device key in the age-file keychain under
~/.wirken/keychain/, which step 4 removes. Only a build with thekeychain-macosorkeychain-linuxfeature stores it in the OS keychain instead. In that case remove it by hand. On macOS, the generic-password items under servicedev.wirken.vault(accountdevice-key, plus one aux entry per auxiliary key, for examplealarm-log-hmac). On Linux, the Secret Service items with attributeapplication=wirken(the device key is labeledwirken-device-key). - Platform-side registrations: local deletion does not touch state you created at each messaging platform. Remove or revoke it there:
- iMessage (BlueBubbles): the adapter registers a callback webhook on your BlueBubbles server (
POST <bluebubbles_url>/api/v1/server/webhooks). Delete it in the BlueBubbles server's webhook settings. - Bots, apps, and tokens you created stay live until you remove them at the platform: the Slack app and its bot/app tokens (Slack admin), the Telegram bot token (BotFather; the adapter long-polls, so there is no webhook to clear), the WhatsApp Cloud API app and its configured webhook URL (Meta App Dashboard), the Discord bot application (Discord Developer Portal), the Teams bot registration (Azure), the Matrix bot account (its homeserver), the Signal linked device (unlink it on the phone), and the Google Chat app. Rotating these credentials is prudent regardless, since they were held in the vault.
- iMessage (BlueBubbles): the adapter registers a callback webhook on your BlueBubbles server (
Windows uses different paths and a smaller feature set (no service installer or cron presets); see docs/windows.md.
Architecture
Each channel runs as its own isolated process. The gateway is the only component that holds credentials and writes the audit log. The agent is stateless: it is woken for each message and rebuilt from its session log.
graph TD
Channels["Telegram · Discord · Slack · Teams · Matrix · WhatsApp · Signal · Google Chat · iMessage"]
Channels -- "UDS · Ed25519 · Cap'n Proto" --> Registry
subgraph Wirken
Registry[Adapter Registry] --> Router
Router --> Detect[Injection Detection]
Detect --> Factory[AgentFactory]
Factory --> Agent[Agent Runtime]
Agent --> Context[Context Engine]
Agent --> Permissions
Agent --> Skills
Agent --> Tools
Agent --> Vault --> Keychain
subgraph Execution
Tools --> Sandbox[Docker / gVisor / Wasm]
end
SessionLog["Session Log\n(per-session hash chain, attested)"]
end
Agent -- "UDS" --> McpProxy["MCP Proxy\n(separate process)"]
McpProxy -- "stdio · HTTP · OAuth2" --> McpServers[MCP Servers]
Agent -- HTTPS --> LLM[LLM Providers]
Agent -- "spawn_subagent" --> Factory
Detect -.-> SessionLog
Permissions -.-> SessionLog
Tools -.-> SessionLog
SessionLog -.-> SIEM[SIEM / Webhook]
- Channel adapters. Each channel runs as its own OS process. It authenticates to the gateway with a per-adapter Ed25519 challenge-response handshake over a Unix domain socket, and messages cross as Cap'n Proto (zero-copy, traversal-limited). An adapter can only send and receive for its own channel; it cannot invoke tools, read another channel's sessions, or reach another channel's credentials. Compromise one adapter and the blast radius is exactly that one channel, because the gateway's IPC boundary runs in a separate memory-safe process and blocks lateral movement.
- Channel isolation. Enforced at the process level today. A compile-time API (a sealed
Channeltrait andSessionHandle<C: Channel>) makes cross-channel mix-ups a build error, but it is not yet threaded through the production message path, where the channel is still a string field on the inbound frame. - MCP proxy. MCP servers run behind a separate proxy process over a Unix domain socket, and the agent never sees MCP credentials. Two independent boundaries scope them: process isolation gives every
mcp.jsonserver its own subprocess and environment, and vault ACLs key each secret by name (slack-token,github-pat) so a connector receives only the credentials it was configured for. Neither alone is enough; together they stop one connector from reading another's secrets. - Stateless agents. An agent keeps no state between turns. The
AgentFactorywakes it per message by replaying its session log, an append-only per-session hash chain of typed events (messages, tool calls, tool results, LLM metadata). A crash mid-turn is detected on wake and surfaced as a failure rather than silently re-run. A context engine trims each conversation under the model's token budget before every call, dropping old tool results before user or assistant text. - Sub-agents. An agent can delegate a bounded subtask through
spawn_subagent, under an operator-set ceiling: tool allowlist, max permission tier, max rounds, max runtime. Children run headless, with their own session logs and a hard depth cap of 4.
Security properties
- Session attestation. Per-agent Ed25519 identity signs the per-session hash chain after every turn.
wirken sessions verifyreplays the log offline and re-checks message hashes, deterministic tool results, and chain integrity. Tampered sessions break the chain. - Reproducible replay. Every LLM call is recorded as a typed session event with a SHA-256 hash of the exact messages and tools sent. The verifier recomputes these hashes from the log and flags any divergence.
- Per-channel process isolation. Each channel adapter runs in its own OS process with a distinct ed25519 identity. Type-level channel separation (
SessionHandle<Telegram>vsSessionHandle<Discord>) exists in the IPC crate and is regression-tested but not yet used in the production message path. - Out-of-process credential isolation. MCP credentials (bearer tokens, OAuth2 client secrets) live in a separate proxy process. The agent process never sees them. The vault is XChaCha20-Poly1305, keyed from the OS keychain;
secrecy+zeroizemake logging a secret a compile error. - Capability-attenuated multi-agent. The LLM cannot widen a child agent's permissions. The operator sets the ceiling; the harness intersects, clamps, and enforces. Children run headless with no interactive approvals.
A handful of operator-controlled escape hatches relax specific
defaults: WIRKEN_ALLOW_UNSIGNED_ORG_CONFIG=1,
WIRKEN_ALLOW_UNSIGNED_SKILLS=1, sandbox.json mode off,
WIRKEN_WEBCHAT_ALLOW_NO_ORIGIN=1, and
WIRKEN_AUDIT_VERIFY_EVERY_FLUSHES=N (the last raises the
continuous-verify cadence; values above 10,000 effectively disable
the in-process verifier and emit a warn). Each is documented under
"Documented escape hatches" in the security-properties doc. Persistent flips need a
systemd EnvironmentFile, shell rc export, or wrapper script so the
operator's intent is durable across reboots.
Full OWASP and NIST AI RMF mappings: docs/security-properties.md
Enterprise deployment
Wirken gives organizations the controls they need to deploy AI agents without bypassing existing security, compliance, and audit requirements.
- Full attribution. Every inbound message records the platform sender id, channel, session, and agent. Permission decisions are scoped per agent, not per user. Typed session events record what action ran, when, and on which target.
- Tamper-evident audit trail. All actions logged as typed session events before execution. Per-session SHA-256 hash chain detects modification or deletion. Per-agent Ed25519 attestation signs the chain head after every turn.
wirken sessions verifyreplays the log offline and re-checks hashes. SIEM forwarding sends events to Datadog, Splunk HEC, Microsoft Sentinel, or any webhook in real time. A separate OpenTelemetry projection ships GenAI semconv spans over OTLP/HTTP+JSON to any OTLP-compatible backend; Microsoft documents a direct OTLP contract for non-SDK Agent 365 integration, which this projection implements. The per-release verification gate is described indocs/integrations/agent365.md. - Crash recovery. Agents are stateless between turns. The harness replays the session log on wake. Incomplete tool rounds are detected and surfaced as failures rather than silently re-executed.
- Graduated permissions. Three-tier model. Workspace file access and web search are always allowed. Shell exec and external file access require first-use approval. Destructive operations, credential access, and network requests always require explicit approval. Approvals expire after 30 days. Skill installs are gated by registry-anchored signature verification, not by the tier system; the gate runs at
wirken skills installtime and again at everySkillLoader::load_filecall, withWIRKEN_ALLOW_UNSIGNED_SKILLS=1as the documented opt-out. - Capability-attenuated multi-agent. Parent agents delegate to children via
spawn_subagentunder operator-configured ceilings (tool allowlist, max permission tier, max rounds, max runtime). Children run headless with isolated session logs. Hard depth cap of 4. - Sandboxed execution. Shell exec runs in an ephemeral Docker container by default (exec-only mode as of 0.7.5). Containers drop all Linux capabilities, set
no-new-privileges, use Docker's default seccomp profile, use a read-only root filesystem with a 64 MB tmpfs at/tmp, pin memory and PIDs, and run as a non-root user with no network. gVisor (runsc) is opt-in for kernel attack surface reduction. If Docker is not reachable, the ToolRegistry logs a distinct warning and refusesexec(fail-closed) for the agent's lifetime rather than falling back to host execution; if runsc is requested but not registered, the warning names that dependency specifically. Sandbox provisioning is lazy, so there is no startup cost when unused. Operators who setsandbox.jsontomode: offopt into runningexecat the wirken UID with no container; the gateway warns at startup, and the host shell can then read or rewrite trust files under the data directory. See docs/security-properties.md for the full posture. - Egress allowlist scope. The skill-set
egress.domainslist applies to the built-inweb_searchandgenerate_imagetools. It does not apply to theexecshell sink (curl/wget run as the host shell), to MCP server children (which open their own outbound connections), or to the LLM HTTP client (which talks to the configured provider directly). Operators wanting hard egress control should run wirken inside a network namespace, restricted-egress container, or with OS-level firewall rules. Seedocs/egress.mdfor the full scope and mitigation guidance, andcrates/agent/src/egress.rsfor the source. - Context management. A per-model context engine trims conversations under token budgets before every LLM call, preferring to drop old tool results before touching user or assistant text. Structured compaction events are written to the session log and projected back into the prompt so the model knows what was trimmed.
- Prompt injection detection. Inbound messages are scanned for role-switching attempts, instruction overrides, base64-encoded commands, tool-call injection, and system prompt extraction. Detected threats are flagged in the session log and forwarded to SIEM; messages are not blocked.
- Confidential inference. Tinfoil and Privatemode providers run LLMs inside TEEs (AMD SEV-SNP, Intel TDX). Tinfoil dispatch goes through the tinfoil-rs SDK so each session gates on AMD SEV-SNP hardware attestation plus Sigstore code-provenance verification, and chat traffic flows over a TLS connection pinned to the attested certificate. See the Tinfoil reference instance and the Privatemode reference instance for the end-to-end trust models and deployment recipes.
- Encrypted credentials. XChaCha20-Poly1305 vault keyed from the OS keychain. Per-credential expiry and rotation. No plaintext export. MCP credentials are isolated in a separate proxy process.
- Centralized policy.
wirken setup --org https://wirken.corp.example.compulls provider, SIEM, MCP, and permission config from a company endpoint. Developers get grab-and-go setup. IT manages one config. Policy refreshes on everywirken run.
Known limitations
Wirken contains the blast radius of a compromised agent; it does not make compromise impossible. The honest edges:
- Egress is not fully contained. The skill-set
egress.domainsallowlist covers the built-inweb_searchandgenerate_imagetools only. It does not constrain theexecshell (curl and wget run as the host shell), MCP server children, or the LLM client. Hard egress control needs a network namespace, a restricted-egress container, or OS firewall rules. See docs/egress.md. - The sandbox can be turned off. With
sandbox.jsonset tomode: off,execruns at the Wirken UID with no container, and the host shell can then read or rewrite trust files under the data directory. The gateway warns at startup when this is set. - Audit tamper-evidence assumes an out-of-band anchor. The hash chain and signature detect modification, but a same-UID attacker who rewrites both the log and the local public key is only caught if you verify against a key kept off the machine. See docs/security-properties.md.
- Injection detection flags, it does not block. Suspicious inbound messages are marked in the audit log and forwarded to your SIEM, but they are not stopped. The permission tiers and the sandbox are what limit what an injected agent can actually do.
- Type-level channel isolation is not in the hot path yet. Channel separation is enforced at the process level today. The compile-time
SessionHandle<Channel>API exists and is tested, but is not yet threaded through the production message path. - Escape hatches exist. A handful of environment flags relax defaults (unsigned org config or skills, WebChat origin checks, audit-verify cadence). Each is documented and warns when engaged.
Documentation
- Getting started
- Deploying for teams (shared inference, per-channel adapters, current limitations)
- Permissions and identity (what exists today, what is planned)
- CLI reference
- Configuration reference
- Channel setup (Telegram, Discord, Slack, Teams, Matrix, Signal, Google Chat, iMessage)
- Multi-agent setup
- Skills guide (markdown skills, Wasm skills, registry)
- Lyrik overview (security-assessment skill, what it is and why)
- MCP setup
- Security properties (OWASP and NIST AI RMF mappings)
- Enterprise deployment (org config, SIEM, sandbox)
- Microsoft Agent 365 integration (OpenTelemetry projection, wire contract, Tier A and Tier B verification framing)
- Migration from OpenClaw
- Troubleshooting
- Architecture
- Enforcement model (compile-time vs. runtime guarantees)
- Release process (version bump, tag, sign, publish, smoke test)
- Release signing (Ed25519 key, rotation, verification)
Contributing
Wirken is a Rust workspace. All crates compile and test independently:
cargo test # full test suite
cargo test -p wirken-vault # test one crate
cargo build -p wirken-cli # build the binary
Building from source needs a recent stable Rust toolchain and the Cap'n Proto compiler. Install capnp via your package manager (apt-get install -y capnproto on Ubuntu, brew install capnp on macOS, choco install capnproto -y on Windows), then cargo install --path crates/cli.
The architecture is documented in docs/architecture.md.
Adapter contributions are especially welcome. Each adapter is an independent crate (crates/adapter-<channel>/) that implements the same IPC contract: connect to the gateway UDS, perform Ed25519 handshake, convert platform messages to/from Cap'n Proto frames. See any existing adapter for the pattern (Telegram is the simplest; Teams shows the HTTP webhook variant).
The name
Wirken: German, to work, to weave, to have effect. Named for Gebruder Ottenheimer, a weaving mill in Wurttemberg, 1862-1937.
License
MIT
No comments yet
Be the first to share your take.