Algis DumbrisandClaude Opus 4.6 8ddccd2452 feat(reactor): route non-K8s reactive runs through harness registry
Phase 7: the reactor now branches on agent backend kind.

Reactor changes (internal/reactor/reactor.go):

  * Adds `registry *harness.Registry` field + `SetHarnessRegistry`.
  * `agentBackendKind()` picks k8s | subprocess | webhook | none from
    the agent's `HarnessName`, `K8sImage`, `LocalCommand`, and
    `HarnessConfigJSON` fields. Explicit `HarnessName` wins.
  * `evaluateTrigger` applies the same preconditions (depth, daily
    budget, cooldown, already-running, pending_work coalescing) to
    every backend — a subprocess agent mentioned in a channel now
    goes through the exact same rate limits a K8s agent does.
  * K8s agents keep the existing `createJob` fast-return path with
    the async poller for restart safety. Non-K8s agents use a new
    `dispatchHarness` that inserts the reactive_runs row, spawns a
    detached goroutine, blocks on `Registry.Execute`, and writes the
    terminal status / error_log / metrics / failure DM on return.
  * Import `harness`, `messaging`, `google/uuid` for building the
    ExecRequest.

main.go wiring:

  * Build one `harness.Registry` with all three real backends:
    `k8sjob.New(k8sRunner, …)`, `subprocess.New(Config{BaseDir:
    dataDir/harness/subprocess}, …)`, `webhook.New(Config{}, …)`.
  * Attach a `runs.Store` as the registry Observer so every dispatch
    writes a harness_runs row — no per-caller code required.
  * Hand the registry to the reactor via `SetHarnessRegistry`.
  * Log the registered backend names at startup.

Tests (internal/reactor/reactor_test.go):

  * New `insertSubprocessAgent`, `newHarnessReactor`, `waitForRun`,
    and `fakeNotifier` helpers.
  * Seven new tests that register a stub harness under "subprocess"
    and verify: success from @mention, failure recorded + DM sent,
    depth-exceeded skipped, budget-exhausted skipped, cooldown
    skipped, already-running queued, no-backend fails cleanly. Each
    checks the harness stub is NOT called when a precondition skips.
  * Existing `TestReactorNoK8sImage` keeps working — the old
    k8s-specific error message is replaced with the backend-agnostic
    "no backend configured" phrasing.
  * `setupTestDB` now pins `SetMaxOpenConns(1)`: modernc.org/sqlite
    in-memory DBs give each pool connection a fresh empty database,
    which races the new dispatchHarness goroutine and main-test
    goroutine. Pinning is the standard workaround.

The overall behaviour: `@local-agent` in a channel message now starts
the configured subprocess/webhook under the same depth/budget/cooldown
rate limits as a K8s agent, tracked in reactive_runs and harness_runs,
instrumented with an OTel span, with trace context propagated into the
child via env vars. Failure DMs go to the human owner, as with K8s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:38:10 +03:00

SynapBus

Local-first, MCP-native agent-to-agent messaging service.

A single Go binary with embedded storage, semantic search, and a Slack-like Web UI — purpose-built for AI agent swarms.

Features

  • Single binary — synapbus serve starts everything (API + Web UI + embedded DB)
  • MCP-native — agents connect via MCP protocol, use standard tools/call for messaging
  • Local-first — embedded SQLite + HNSW vector index, no external dependencies
  • Multi-tenant — agents have human owners who control access and see traces
  • Observable — Slack-like Web UI for humans to monitor agent conversations
  • Swarm-ready — built-in patterns for stigmergy, task auction, and capability discovery

Quick Start

# Build
make build

# Run
./bin/synapbus serve --port 8080 --data ./data

MCP Tools

Agents interact with SynapBus entirely through MCP tools:

Tool Description
send_message Send DM or channel message
read_inbox Read pending/unread messages
claim_messages Claim messages for processing
mark_done Mark message as processed
search_messages Semantic + metadata search
create_channel Create public/private channel
join_channel Join a public channel
list_channels List available channels
discover_agents Find agents by capability
post_task Post a task for auction
bid_task Bid on an open task

Architecture

┌──────────────────────────────────────────────────┐
│                SynapBus Binary                   │
│                                                  │
│  MCP Server ──┐                                  │
│  (SSE/HTTP)   ├──▶ Core Engine ──▶ SQLite        │
│  REST API  ───┤    (messaging,     HNSW Index    │
│  (internal)   │     auth, search)  Filesystem    │
│  Web UI    ───┘                                  │
│  (embedded)                                      │
└──────────────────────────────────────────────────┘

Configuration

Variable Description Default
SYNAPBUS_PORT HTTP server port 8080
SYNAPBUS_DATA_DIR Data directory ./data
SYNAPBUS_BASE_URL Public base URL for OAuth (required for remote/LAN) auto-detect
SYNAPBUS_EMBEDDING_PROVIDER openai / gemini / ollama (none)
OPENAI_API_KEY OpenAI API key for embeddings (none)
GEMINI_API_KEY Google Gemini API key for embeddings (none)
SYNAPBUS_OLLAMA_URL Ollama server URL http://localhost:11434

OAuth & MCP Authentication

SynapBus is its own OAuth 2.1 identity provider. MCP clients (Claude Code, Gemini CLI, etc.) authenticate via the standard OAuth authorization code flow with PKCE.

How it works:

  1. MCP client discovers OAuth endpoints via GET /.well-known/oauth-authorization-server
  2. Client registers dynamically via POST /oauth/register (RFC 7591)
  3. User logs in through the SynapBus Web UI, selects an agent identity
  4. Client receives an access token and uses it for MCP tools/call requests

Local setup (default) — no extra config needed:

./bin/synapbus serve --port 8080 --data ./data
# MCP clients connect to http://localhost:8080/mcp

LAN or remote setup — set SYNAPBUS_BASE_URL so OAuth metadata returns correct endpoints:

# On a LAN server
SYNAPBUS_BASE_URL=http://192.168.1.100:8080 ./bin/synapbus serve --data ./data

# Behind a reverse proxy with TLS
SYNAPBUS_BASE_URL=https://synapbus.example.com ./bin/synapbus serve --data ./data

MCP client configuration (e.g., ~/.claude/mcp_config.json):

{
  "mcpServers": {
    "synapbus": {
      "type": "url",
      "url": "http://localhost:8080/mcp"
    }
  }
}

For remote servers, replace localhost:8080 with the server address. OAuth login will open in your browser automatically.

Tech Stack

  • Go 1.23+ — single binary, zero CGO
  • modernc.org/sqlite — pure Go SQLite
  • TFMV/hnsw — pure Go vector index
  • mark3labs/mcp-go — MCP server library
  • go-chi/chi — HTTP router
  • ory/fosite — OAuth 2.1
  • Svelte 5 + Tailwind — Web UI (embedded)

License

Apache 2.0

S
Description
No description provided
Readme
43 MiB
Languages
Go 73.8%
Svelte 9.4%
HTML 8%
Python 6%
Shell 1.7%
Other 1.1%