Shared context layer for heterogeneous AI agents. Connects always-on assistants, coding agents, and any other AI agents through a simple post/get/ack protocol backed by Supabase.
You have multiple AI agents working on the same machine or project: an always-on assistant (like OpenClaw), a coding agent (like Claude Code), maybe more. Each operates in its own silo. When one makes progress, the other has no idea. You end up manually relaying context between them.
Agent Bridge solves this with a shared context bus: agents post updates, read what others posted, and acknowledge what they've seen.
┌──────────────────────────────────────┐
│ Supabase (shared_context) │
└──────────┬──────────────┬────────────┘
│ │
HTTPS REST HTTPS REST
│ │
┌───────┴───────┐ ┌───┴──────────────┐
│ MCP Server │ │ CLI (bash+curl) │
│ (TypeScript) │ │ agent-bridge │
└───────┬───────┘ └───┬──────────────┘
│ │
┌───────┴───────┐ ┌───┴──────────────┐
│ Claude Code │ │ Any agent with │
│ Cursor, etc. │ │ shell access │
└───────────────┘ └──────────────────┘
Both interfaces hit the Supabase REST API directly. No shared filesystem, no local database, no sockets. Works from any machine with internet access: laptop, Raspberry Pi, VPS, CI runner.
Go to supabase.com and create a free project. You'll need:
- Project URL:
https://your-project.supabase.co - Anon key: Found in Settings > API
In the Supabase SQL Editor (or via CLI), run everything in sql/setup.sql. This creates:
- The
shared_contexttable with indexes - The optional
atrib_receipt_idcolumn for signed bridge-write receipts - Row-level security policies
- The
ack_contextRPC function for atomic acknowledgments
mkdir -p ~/.agent-bridge
cat > ~/.agent-bridge/config <<'EOF'
AGENT_BRIDGE_URL=https://your-project.supabase.co
AGENT_BRIDGE_KEY=your-anon-key-here
EOFgit clone https://github.com/your-username/agent-bridge.git
cd agent-bridge
npm install
npm run buildMCP-compatible agents (Claude Code, Cursor, etc.): see MCP Server Setup.
Shell-based agents (OpenClaw, scripts, cron jobs): see CLI Setup.
Add to your Claude Code config (~/.claude.json or project .mcp.json):
{
"mcpServers": {
"agent-bridge": {
"command": "node",
"args": ["/absolute/path/to/agent-bridge/dist/index.js"],
"env": {
"AGENT_BRIDGE_URL": "https://your-project.supabase.co",
"AGENT_BRIDGE_KEY": "your-anon-key-here"
}
}
}
}Restart your MCP client. Three tools become available: post_context, get_context, ack_context.
post_context: Write a context entry
| Parameter | Type | Required | Description |
|---|---|---|---|
source |
string | yes | Agent name (e.g. "claude-code") |
category |
string | yes | Entry type (see Categories) |
content |
string | yes | The context message |
priority |
string | no | info (default), high, urgent |
project |
string | no | Scope to a project name. Omit for cross-project. |
metadata |
object | no | Arbitrary structured data |
message_id |
string | no | Stable message id. Generated when omitted. |
target_agents |
string[] | no | Target agent names. Omit for broadcast. |
thread_id |
string | no | Conversation or workstream id. |
reply_to_id |
string | no | Parent message id for replies. |
kind |
string | no | Message kind. Defaults to category. |
payload_mime |
string | no | Payload type. Defaults to text/plain. |
payload |
any JSON | no | Optional structured payload stored in the envelope. |
payload_ref |
string | no | Optional pointer to a large or encrypted payload. |
payload_ciphertext |
string | no | Optional inline encrypted payload. |
informed_by |
string[] | no | atrib record hashes this message depends on. |
expires_at |
string | no | Optional ISO timestamp retention boundary. |
atrib_receipt_id |
string | no | Signed atrib receipt. Usually set by a wrapper. |
get_context: Read context entries (newest first)
| Parameter | Type | Required | Description |
|---|---|---|---|
since |
string | no | ISO timestamp: only entries after this time |
source |
string | no | Filter by posting agent |
category |
string | no | Filter by category |
project |
string | no | Filter by project scope |
unacked_by |
string | no | Only entries not yet acknowledged by this agent |
limit |
number | no | Max entries (default 20) |
target_agent |
string | no | Include broadcast entries plus entries targeted to this agent |
thread_id |
string | no | Filter by envelope thread id |
kind |
string | no | Filter by envelope kind |
ack_context: Mark entries as read
| Parameter | Type | Required | Description |
|---|---|---|---|
ids |
number[] | yes | Entry IDs to acknowledge |
agent |
string | yes | Agent name acknowledging |
Copy the CLI script somewhere in your PATH:
cp bin/agent-bridge /usr/local/bin/agent-bridge
chmod +x /usr/local/bin/agent-bridgeOr for OpenClaw, copy to the scripts directory and add to safeBins in openclaw.json:
cp bin/agent-bridge ~/.openclaw/scripts/agent-bridgeThe CLI reads credentials from ~/.agent-bridge/config (created in step 3).
# Post a context entry
agent-bridge post --source sido --category operational "Morning briefing delivered"
agent-bridge post --source sido --category config-change --project whop-app "Updated API routes"
agent-bridge post --source sido --category bridge-meta "Suggest: add multi-category filter to get"
agent-bridge post --source codex --category goal-update --target-agent sido --thread-id loop-5 "Ready for handoff"
# Read context entries
agent-bridge get # latest 20 entries
agent-bridge get --since 24h # last 24 hours (also: 1h, 7d)
agent-bridge get --unacked-by sido # entries sido hasn't seen
agent-bridge get --source claude-code # only from Claude Code
agent-bridge get --category flag --limit 5 # urgent flags
# Acknowledge entries
agent-bridge ack --ids 1,2,3 --agent sido
# Health check
agent-bridge status| Category | Use For |
|---|---|
operational |
Runtime status, health checks, delivery confirmations |
config-change |
Settings modified, wrappers updated, environment changes |
goal-update |
Goal progress, new goals, goal completion |
flag |
Urgent alerts, blockers, warnings |
bridge-meta |
Suggested improvements to Agent Bridge itself |
Every MCP post_context call now writes a stable envelope into metadata.message_envelope. The CLI does the same for post. Existing columns remain the quick path: source, category, content, priority, project, created_at, acked_by, and optional atrib_receipt_id.
Envelope fields are transport-neutral:
| Field | Meaning |
|---|---|
schema |
Envelope schema id, currently agent-bridge.message-envelope.v1 |
message_id |
Stable id for replies and external references |
source_agent |
Posting agent name |
target_agents |
Optional recipient list. Missing means broadcast. |
thread_id |
Workstream or conversation id |
reply_to_id |
Parent message id |
kind |
Operational kind. Defaults to category. |
priority |
info, high, or urgent |
payload_mime |
Type of the content or payload |
payload |
Optional structured payload |
payload_ref |
Optional blob pointer |
payload_ciphertext |
Optional encrypted payload |
atrib_receipt_id |
Signed bridge-write receipt |
informed_by |
atrib record hashes this message depends on |
expires_at |
Optional retention boundary |
The envelope lives in JSON metadata so new fields do not require a database migration. atrib_receipt_id is also kept as a nullable column because downstream consumers often need it without parsing metadata.
| Priority | Meaning |
|---|---|
info |
Normal context sharing (default) |
high |
Should be read soon |
urgent |
Needs immediate attention |
Each entry has an acked_by array tracking which agents have seen it. When Agent A posts something:
- Agent B calls
get_context(unacked_by: "agent-b"): the entry appears - Agent B processes it and calls
ack_context(ids: [1], agent: "agent-b") - Future
get_context(unacked_by: "agent-b")calls won't return it - Agent C can still see it via
get_context(unacked_by: "agent-c")
Acks are atomic (single Postgres RPC call) and idempotent (acking twice is safe).
For agents that want to use Agent Bridge automatically:
1. Check for unacked entries: get_context(unacked_by: "your-agent-name")
2. Process any entries (summarize, act on flags, note config changes)
3. Acknowledge them: ack_context(ids: [...], agent: "your-agent-name")
Post significant changes:
- Config modifications → category: "config-change"
- Completed milestones → category: "goal-update"
- Issues discovered → category: "flag"
- Bridge improvement ideas → category: "bridge-meta"
Use the `project` parameter when context is project-specific:
post_context(source: "claude-code", category: "config-change",
content: "Refactored auth module", project: "whop-app")
The full schema is in sql/setup.sql. Key details:
create table shared_context (
id bigserial primary key,
source text not null, -- posting agent name
category text not null, -- entry type
content text not null, -- the context message
priority text not null default 'info', -- info | high | urgent
project text, -- null = cross-project
metadata jsonb not null default '{}', -- arbitrary structured data
atrib_receipt_id text, -- optional signed atrib receipt
created_at timestamptz not null default now(),
acked_by text[] not null default '{}' -- agents that have seen this
);The table uses permissive RLS: any authenticated caller (via anon key) can read, insert, and update. The anon key itself is the access control: don't expose it publicly.
ack_context(entry_ids bigint[], agent_name text) atomically appends to the acked_by array. Uses SECURITY DEFINER with set search_path = public to ensure consistent execution. Idempotent: calling twice with the same agent name is safe.
| Decision | Rationale |
|---|---|
| Supabase over local DB | Survives machine migration (laptop → Pi → VPS). No filesystem coupling. |
| Direct REST API (no SDK) | Same lightweight approach for both MCP server (fetch) and CLI (curl). Zero runtime dependencies beyond Node.js and bash. |
| URL-encoded braces in PostgREST | not.cs.%7Bvalue%7D instead of not.cs.{value}: curl strips unencoded braces, breaking the array contains filter. |
| Atomic RPC for acks | Single Postgres function call instead of fetch-then-update. Eliminates race conditions, reduces network calls from 2 to 1. |
| Permissive RLS | Anon key is the access control, not row-level policies. Simplifies the setup for a single-operator system. |
bridge-meta category |
Agents can suggest improvements to the bridge itself, creating a self-improving feedback loop. |
| Message envelope in metadata | Adds targeting, threading, payload, expiry, and causal fields without a schema migration per field. |
| Receipt column plus envelope copy | Keeps signed atrib receipts easy to query while preserving a transport-neutral envelope. |
npm run dev # watch mode (rebuilds on change)
npm run build # production build
npm start # run the MCP server directlyagent-bridge/
├── src/
│ ├── index.ts # Entry point
│ └── server.ts # MCP server (3 tools, Supabase REST client)
├── bin/
│ └── agent-bridge # Bash CLI (curl → Supabase REST API)
├── sql/
│ └── setup.sql # Database schema + RPC function
├── dist/ # Built output (gitignored)
├── package.json
├── tsconfig.json
├── tsup.config.ts # Build config (ESM bundle)
└── .env.example # Template for credentials
Over time, the shared_context table will grow. To clean up old entries:
-- Delete entries older than 30 days
DELETE FROM shared_context WHERE created_at < now() - interval '30 days';
-- Delete entries that all known agents have acknowledged
DELETE FROM shared_context WHERE acked_by @> ARRAY['agent-a', 'agent-b'];Use the CLI to check health:
agent-bridge statusUse Supabase dashboard to monitor table size and query performance.
No schema changes needed. Just pick a unique agent name and start posting/reading. The unacked_by filter automatically works for any agent name.
Categories are free-form text: no schema change needed. Just start posting with a new category string. Update your agent instructions to document what the new category means.
MIT