- Go 94.4%
- Shell 5.6%
| cmd | ||
| internal/agentdispatcher | ||
| dispatcher-smoke.sh | ||
| go.mod | ||
| go.sum | ||
| identity-placement-smoke.sh | ||
| identity-routing-smoke.sh | ||
| README.md | ||
| topology.toml | ||
key.store agent-dispatcher
Unified intake service for inbound operations events, role routing, and resident identity steers.
This directory is the formal dispatcher codebase for key.store. Webhook topology
is source-controlled in topology.toml; Nix passes that file to the API service
and local callers can inspect the active, secret-free view at GET /topology.
Inbound webhook paths
POST /webhook/redminePOST /webhook/forgejoPOST /webhook/zulipPOST /webhook/forwardemailPOST /webhook/smsPOST /webhook/stablePOST /webhook/minecraftPOST /webhook/grafanaGET /healthzGET /topology(loopback only)
All webhook traffic is expected to come through https://dispatch.key.store.
Communications routing
- Forward Email aliases can post directly to
/webhook/forwardemail; the dispatcher dedupes onidentity + message-id. - Email intake (
agent-dispatcher mail) can still poll IMAP, sharing the same dedupe path so webhook-first and mailbox-fetch flows do not double-enqueue. - Mail intake first enqueues a resident-thread steer. The receiving identity must absorb the steer before any side effect; mail intake has no SMS sender.
- Redmine should be searched before creating duplicate todos from mail, Forgejo, SMS, or wake notifications.
- Mail sender policy is fail-closed for execution. Trusted senders can steer the owning role; untrusted mail routes only to the configured review role identity, keeps the requested role in payload context, and must not trigger repository, secret, DNS, or production changes without later human approval.
- Zulip outgoing webhooks post to
/webhook/zulipthrough theAgent Dispatcheroutgoing-webhook bot. Channel messages should mention@Agent Dispatcherplus a role alias such as@ios; direct messages to the bot can route through the receiving role identity. - Zulip replies are relayed from completed resident-thread output. The identity absorbs the steer first, then the supervisor posts the final assistant answer back to the originating stream/topic or direct message as the mapped Zulip identity.
- SMS/calls (
/webhook/sms) and physical mail (stable) can be delivered via signed webhooks. - SMS-compatible sources default to the
assistantrole andassistantidentity, andstabledefaults to thecoorole andmanageridentity, unless an explicit mention overrides routing. - SMS does not use a completion relay. The assistant must decide in the resident thread whether the inbound text needs a response and call
communications_sms_sendbefore ending the turn when it should text back. - Forgejo webhooks route explicit role mentions and identity-specific issue assignments or pull request review requests. Assigned/review-requested identities wake their resident role thread even when the Forgejo body has no
@role/...or alias mention. - Grafana Alerting posts to
/webhook/grafanathrough the provisionedkey.store dispatchercontact point. Grafana signs the raw body with HMAC-SHA256 and includes the configured timestamp header; labels or annotations can route withkey_store_role,role_id,key_store_identity, or role aliases such asteam=infra. Alerts default to the infra identity. - The resident-agent shape is one long-running Codex thread per identity. Incoming notifications become steers into that identity thread; the dispatcher no longer exposes a separate task executor path.
Resident Steer Execution
- Resident identity threads use the configured role model, reasoning effort, sandbox, and approval policy.
- Queue state lives in forge-local PostgreSQL via
AGENT_DISPATCHER_DATABASE_URL; legacy sqlite/task imports are no longer part of the live path. - Mail-to-work links live beside the steer queue:
mail_redmine_linksrecords JMAP/RFC message ids, dispatcher event keys, steer ids, and Redmine issue ids without duplicating message bodies. - Resident environments receive role-scoped Redmine, Forgejo, JMAP, Zulip, communications, physical-mail, and MCP credentials so primary-session tools can read/update the same event the dispatcher routed from.
- Resident environments project
org/organization.tomlagent runtime policy into the app-server thread. The supervisor prewarms every mapped identity for an identity-mode role; thread persistence, app-server clients, identity-bound credentials, and multi-identity work roots are scoped by role and identity so identities that share a role, such asagentandassistant, do not replace each other or inherit each other's tokens. Role-specific environment variables still override model and provider defaults for recovery changes. - Resident environments also project the per-identity workspace placement from
org/organization.toml. Every identity has an explicit host/surface/profile/tooling contract: iOS on macOS Namespace, Android on the Android Namespace image, Windows on the Windows lane, assistant on HQ, and the remaining identities on appropriate Linux Namespace or forge surfaces. - Minecraft and office presence are primary-session MCP tools. The dispatcher no longer requires a dedicated Minecraft sub-agent.
MCP and search posture
dispatch.key.store/mcpis the shared Authentik-backed MCP hub for contributor clients and the per-identity loopback bearer hub for forge-resident agents.- The public plugin metadata should mention the broad tool surface so Codex tool search can discover the hub for mail, Redmine, Forgejo, SMS, office/Minecraft, and observability work.
- Stalwart stores message data, blobs, lookup, and full-text search in PostgreSQL. The
stalwartandagent_dispatcherdatabases are part of the daily PostgreSQL backup set. - The mail MCP namespace exposes JMAP-native mailbox state, search, snippets, fetch, thread context, draft/submit/send, calendar event search/create, contact search, and readiness tools rather than inventing a separate message-body index first.
- Meeting-backed calendar events use the Stalwart/JMAP calendar create tool with
meet.key.storeJitsi URLs embedded in the event metadata; Stalwart remains the source of truth for calendar state and scheduling mail. - JMAP search, fetch, and thread results are enriched with dispatcher steer links and stored Redmine issue links. Use
mail_jmap_link_redmine_issueafter an identity decides that an email needs durable Redmine follow-up.
Helper CLIs
key-store-redmine whoamiredmine_issue_searchis available through MCP for duplicate checks before creating durable work.key-store-redmine issue show <id>key-store-redmine issue create --subject "..." --description "..."key-store-redmine issue note <id> --message "..."or note text on stdinkey-store-redmine issue update <id> --status-id <n> [--assigned-to-id <n>] [--notes "..."]key-store-zulip send --message "..."for office-channel updates.communications_sms_send,communications_sms_messages,communications_phone_numbers, andcommunications_voice_callsare available through MCP for assistant-owned communications.stable_mail_itemsis available through MCP for COO-owned physical-mail lookup.
Provider scripts
Scripts/communications-fetch-events.shScripts/communications-send-sms.shScripts/communications-provision-webhook.shScripts/stable-fetch-mail.shScripts/stable-provision-webhook.sh
These scripts assume communications and Stable API keys are managed with agenix:
secrets/communications/api-key.agesecrets/stable/api-key.age
Current provider defaults:
- SMS/voice provider API:
https://api.openphone.com - Stable API:
https://api.usestable.com - Stable webhook endpoints are configured in the Stable Dashboard; the local helper validates API access and prints the target/events for manual dashboard setup.
Smoke checks
services/redmine/redmine-smoke.shservices/redmine/redmine-api-smoke.shservices/agent-dispatcher/dispatcher-smoke.shservices/agent-dispatcher/identity-placement-smoke.shservices/mcp-hub/mcp-hub-smoke.sh
dispatcher-smoke.sh proves the dispatcher HTTP surface, including the Zulip
webhook path. identity-placement-smoke.sh fails if forge regains a dedicated
iOS supervisor or stops excluding ios and assistant from the forge-owned
supervisor.
HQ assistant resident supervisor
nixos/keystore-hq.nixruns theassistantresident supervisor on HQ. It keeps the Codex app-server and communications-capable assistant runtime on the failsafe node while using an SSH tunnel to forge PostgreSQL for the shared steer queue.- Forge excludes
assistant, so signed SMS and HQ watchdog steers have one canonical assistant consumer instead of competing forge and HQ sessions.
macOS iOS resident supervisor
Scripts/run-ios-macos-agent-supervisor.shruns thetech_lead_apple/iossupervisor locally on macOS. It starts a local Codex app-server and uses an SSH tunnel to forge PostgreSQL only for queue access whenAGENT_DISPATCHER_DATABASE_URLis not set.Scripts/install-ios-macos-agent-launchagent.shinstalls the no-sudo per-user LaunchAgent for the local Mac.Scripts/install-ios-macos-agent-launchd.shinstalls the system LaunchDaemon variant for managed macOS hosts where root installation is available.