Skip to content

creatornader/agent-bridge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

135 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Agent Bridge

npm version test license

Agent Bridge lets AI agents message each other and hand off work across tools, sessions, and machines. Messages wait while an agent is offline and are still there after it restarts.

Use it through MCP, a CLI, a Node library, or an authenticated HTTPS gateway. Start locally with SQLite. Use PostgreSQL when agents need to communicate across machines.

It supports two operating modes:

Mode Store Use case
Local SQLite WAL One machine, no service or account
Gateway PostgreSQL plus a local SQLite edge store Cross-machine messaging, offline sends, claims, retries, and presence

PostgreSQL is the authority in gateway mode. SQLite holds local-only messages or gateway edge state. Supabase can host PostgreSQL, but Agent Bridge does not depend on its client APIs. The direct Supabase runtime adapter was removed in 0.6.0.

Quick start

Install the published package and run a two-principal local proof. Local mode needs no account, daemon, or external database.

npm install --global @creatornader/agent-bridge
agent-bridge init --provider local
agent-bridge demo

Install Agent Bridge into a supported client after the demo succeeds:

agent-bridge clients install codex --identity codex
agent-bridge clients install claude-code --identity claude-code
agent-bridge clients install claude-desktop --identity claude-desktop

Restart the client, then run agent-bridge doctor --json if the MCP server does not attach. The troubleshooting guide covers executable paths, client configuration, and recovery steps.

Run the gateway with Compose

The repository includes a loopback-only Compose stack for development and evaluation. It starts PostgreSQL 16, runs the ordered migrations, creates a restricted runtime login, and starts the authenticated gateway.

mkdir -m 700 .secrets
node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('hex'))" > .secrets/postgres_password
node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('hex'))" > .secrets/runtime_password
chmod 600 .secrets/*
docker compose up --build --wait gateway
curl --fail http://127.0.0.1:8787/readyz

The checked-in stack publishes the gateway and PostgreSQL only on loopback. It is not a production TLS deployment. Read the deployment guide before exposing a gateway, changing network bindings, upgrading the schema, or relying on the named volume for persistent data.

The repository also maintains a Fly.io reference that does not depend on a specific database host. It lives at deploy/fly.toml. It deliberately omits an app name and release command. Check the local contract before creating or changing any Fly resource:

npm run preflight:fly -- --json

Before migrating an existing PostgreSQL database, run the read-only production preflight with schema-owner authority:

AGENT_BRIDGE_DATABASE_URL="..." npm run preflight:postgres:production -- --json --require-ssl

The command checks the PostgreSQL major, database and role authority, existing migration ledger, derived-role collisions, and the legacy shared_context import shape inside a read-only transaction. authority.roles reports migration capability: the schema owner may be a true superuser or a managed-provider administrator with CREATEROLE. authority.native_dr reports source-backup capability. The source normally connects as the schema owner, which must be a true superuser or hold BYPASSRLS, so CREATEROLE alone can run migrations but cannot create a native backup. A restore target uses its superuser session to validate a suspended restored schema-owner shell. The migration suite tests both authority forms on PostgreSQL 15 through 18. The command does not print the connection URL or apply DDL. --require-ssl rejects an unencrypted public database connection. Passing the preflight does not replace a backup or authorize migration. When a session pooler terminates TLS, the check inspects the client socket instead of the pooler's separate database connection.

After an operator supplies an existing app, npm run preflight:fly -- --app <app> --json adds read-only account, app, machine, config, and secret-name observations. The command does not deploy, migrate, scale, restart, or print environment-variable values. The Fly production procedure keeps database migration and runtime-role bootstrap outside the gateway process. The Fly gateway expects two secrets: the restricted runtime database URL and a base64-encoded PostgreSQL CA bundle. It decodes the CA in memory and verifies both the certificate chain and database hostname. It never needs a CA file inside the image.

Build the production image with its source revision, then confirm the running gateway before moving clients or starting the full proof:

fly deploy --config deploy/fly.toml --app <app> \
  --build-arg AGENT_BRIDGE_BUILD_REVISION="$(git rev-parse HEAD)"
AGENT_BRIDGE_URL="https://<app>.fly.dev" AGENT_BRIDGE_TOKEN="..." \
  npm run verify:gateway:release -- \
  --version "$(node -p "require('./package.json').version")" \
  --revision "$(git rev-parse HEAD)"

The authenticated check compares the running package and Git revision with the intended release. It also requires protocol 2.1, request authority, and row isolation. It prints no credential. A missing or different revision stops the rollout.

The manual gateway production proof workflow tests an existing gateway after this deployment gate passes. GitHub must run it from main in the protected agent-bridge-production-proof environment. Separate sender and receiver jobs prove offline outbox replay, exact idempotency, claim, and acknowledgment. The workflow then restarts the single Fly machine, requires a later successful machine start event, and verifies the message and settled delivery with a fresh receiver instance and a new SQLite edge file. Use a new proof workspace for each release run. Owner provisioning refuses a second agent with the same principal in one workspace, and revoked proof credentials cannot be rotated. Its versioned receipts contain only identifiers, times, principal and workspace labels, the gateway origin, checks, and SHA-256 host evidence. They omit credentials, database URLs, message content, and SQLite files.

What v2 provides

  • Immutable messages with UUIDv7 IDs and opaque server cursors.
  • Indexed source, thread, timestamp, workspace, and delivery queries.
  • Read receipts separated from executable delivery state.
  • Atomic claim, lease renewal, acknowledgment, negative acknowledgment, retry, and dead-letter operations.
  • Immutable publisher-owned delivery policy with mailbox and leased modes, strict urgent/high/info claim priority, publisher cancel and requeue controls, authorized audit pagination, and fenced leases.
  • Append-only delivery transition history.
  • Idempotency conflict detection. Exact replay deduplicates; changed content under the same key fails.
  • Scoped credentials that bind a remote workspace and agent principal.
  • SQLite outbox and inbox cache for gateway clients.
  • Leased instance-keyed presence with capabilities.
  • MCP, CLI, HTTPS, and Node library access, plus host adapters for Codex, Claude Code, Claude Desktop, OpenClaw, and generic MCP clients.
  • Canonical portable archives for messages and read receipts across local SQLite and shared PostgreSQL stores.
  • Native SQLite and PostgreSQL backup, verification, and restore.
  • Existing post_context, get_context, ack_context, post, and get behavior during migration.

The accepted protocol and storage decisions live in docs/architecture-v2.md.

The canonical v2 operation registry generates JSON Schema, OpenAPI 3.1.2, and the MCP manifest. Use GET /v2/capabilities, MCP capabilities, or CLI agent-bridge capabilities to discover the operations for that surface and provider. Capabilities distinguish the current, selected, and supported protocol versions.

Authenticated gateway capabilities also report implementationVersion. Production images report implementationRevision when the operator supplies the Git revision at build time. These fields identify deployed code. They do not change protocol version negotiation.

Protocol 2.1 uses a gateway-first rollout. An upgraded gateway continues to serve released 2.0 clients, including headerless requests and their direct or null delivery results. A 2.1 client probes before mutation and proceeds only when complete, consistent response headers select 2.1 and advertise 2.1 support. It rejects mutation against a headerless or 2.0 gateway instead of downgrading. Upgrade the gateway before installing or starting 2.1 clients.

Two gateway-only HTTP 2.1 operations establish a short-lived endpoint-migration challenge. Initial preflight uses a direct predecessor and successor. After a cutover journal exists, the same active successor credential can issue at the retained source URL and consume at the target URL. Both calls require active transaction-bound authority and use the canonical credential and operation rate buckets. PostgreSQL stores only a domain-separated SHA-256 commitment. A challenge expires within 60 seconds and does not authorize endpoint cutover. Authenticated HTTP 2.1 capabilities include the credential's grantedScopes.

The OpenAPI paths describe protocol 2.1. The embedded x-agent-bridge-protocol-2.0 and x-agent-bridge-schemas-2.0 vendor extensions contain frozen compatibility schema metadata for released 2.0 clients. They are not a second OpenAPI description. Gateway credentials enforce the operation scopes declared by the canonical registry. Local mode uses process identity. Provider-neutral artifacts report this difference instead of claiming one authorization model for every backend.

Architecture

flowchart TB
  subgraph execution["Agent execution"]
    harnesses["Agent harnesses<br/>product-specific or custom"]
    callers["Non-harness callers<br/>scripts, CI jobs, and application services"]
  end

  subgraph hosts["Host surfaces"]
    clis["Command-line hosts<br/>Codex CLI and Claude Code CLI"]
    desktops["Desktop hosts<br/>Codex in ChatGPT and Claude Desktop"]
    daemons["Daemons and custom MCP hosts"]
  end

  subgraph access["Integration and access"]
    adapter["Host adapter or MCP registration"]
    mcp["MCP over stdio: agent-bridge-mcp"]
    cli["CLI: agent-bridge"]
    library["Node library"]
    direct["Direct HTTPS client"]
  end

  harnesses --> clis
  harnesses --> desktops
  harnesses --> daemons
  clis --> adapter
  desktops --> adapter
  daemons --> adapter
  adapter --> mcp
  callers --> cli
  callers --> library
  callers --> direct

  mcp --> core["Agent Bridge client core<br/>principal plus optional consumer instance key"]
  cli --> core
  library --> core

  core --> local["Local mode<br/>SQLite authority"]
  core --> edge["Gateway client mode<br/>SQLite edge outbox and cache"]
  edge -->|"HTTPS protocol 2.1"| gateway["Agent Bridge gateway"]
  direct -->|"HTTPS protocol 2.1"| gateway
  gateway --> postgres["PostgreSQL authority"]
Loading

The diagram shows concepts, not exclusive product categories. A product may package a harness and a host together. A harness is the agent execution environment. A host is the CLI, desktop app, or daemon where it runs. A host adapter registers an access surface. Scripts are orthogonal callers: they can invoke the CLI, call HTTPS, or embed the Node library without becoming agent harnesses.

Adapter or integration Host coverage Access Installation status
codex Codex CLI and Codex in the ChatGPT desktop app through their shared profile MCP stdio Automated
claude-code Claude Code terminal harness MCP stdio Automated
claude-desktop Claude Desktop application MCP stdio Automated
openclaw OpenClaw host or daemon MCP stdio Manifest included; operator-managed
generic-mcp Any compatible stdio MCP host MCP stdio Manifest included; operator-managed
Scripts and services Shell jobs, CI, daemons, and application code CLI, HTTPS, or Node library Direct integration
Hermes, Pi, and other harnesses Depends on the host's supported interface No interface assumed No dedicated adapter or conformance claim yet

agent-bridge-mcp is a stdio server. The gateway exposes the Agent Bridge HTTPS API; it is not MCP over HTTP. A direct HTTPS client can bypass MCP, the CLI, and the local edge store, but then owns its own retry and offline behavior.

Realtime notifications or hooks may wake a client, but cursored reads remain authoritative. A missed notification does not lose a message.

A2A and application task semantics sit above Agent Bridge. MCP, CLI, HTTPS, and the Node library are access surfaces. Optional transports such as SLIM or NATS may provide wakeups or transport below the core, but authoritative recovery still uses cursored pull. agmsg remains a reference for adapters, interoperability, and client experience.

ADR-0001 defines this boundary and the distinct acknowledgment meanings. ADR-0003 defines the host layers and consumer instance-key contract.

Agent Bridge is not a workflow engine, scheduler, memory system, terminal manager, or new universal agent protocol. See Agent Bridge in the agent ecosystem for its relationship to MCP, A2A, agmsg, brokers, and agent runtimes.

Requirements

  • Node.js 22.23.1 or newer.
  • SQLite 3.51.3 or newer for local and edge storage. The supported Node version includes it.
  • Concurrent local and edge initialization may wait up to 15 seconds for schema work; normal database operations retain their configured busy timeout.
  • PostgreSQL 15, 16, 17, or 18 for gateway mode. New PostgreSQL majors fail the migration prerequisite and live readiness checks until their catalog digest is certified.

Install from source

Use the source install for development. The normal installation path is the published @creatornader/agent-bridge package shown in the quick start. The unscoped npm name belongs to another project, and npm rejects agentbridge as confusingly similar.

git clone https://github.com/creatornader/agent-bridge.git
cd agent-bridge
npm ci
npm run build
npm link

The package exposes three executables:

  • agent-bridge: CLI.
  • agent-bridge-mcp: stdio MCP server.
  • agent-bridge-gateway: HTTP gateway.

npm pack builds the package before creating a tarball. CI installs that tarball into an empty project, imports the provider-neutral library API, and runs the packaged CLI. Importing the package root does not start the MCP server.

Client identities and local commands

Initialize backend settings. init does not write an agent identity into the shared config.

agent-bridge init --provider local

Each client supplies its own identity:

AGENT_BRIDGE_AGENT=codex agent-bridge send \
  --target claude-code \
  --type context \
  "Bridge is ready"
AGENT_BRIDGE_AGENT=claude-code agent-bridge inbox --receipt-state unread

You can also pass --source to a standalone send. If the process already has AGENT_BRIDGE_AGENT, an explicit identity must match it.

The post, get, and --unacked-by forms remain available only for compatibility with older clients. New integrations should use send, caller-bound inboxes, and --receipt-state.

Portable archives

Portable archives move one workspace's immutable messages and read receipts between canonical local SQLite and PostgreSQL stores. Export writes a private file through a same-directory temporary file, file fsync, atomic publication, and directory fsync. The export audit completes only after durable publication. A failed export records a bounded abandonment code. Export and import paths must satisfy the current user's private-path policy. Export refuses to replace an existing file unless --force is present. Export accepts --request-id, embeds it as exportRequestId in the archive header, and returns it on success. Verification and import also return that export provenance. An import request ID is a separate destination operation identifier. Reusing an export request ID verifies a completed file or reconciles a matching file whose audit response was lost. A missing or mismatched file stays started and requires a new export request ID or owner reconciliation. Recovery artifacts use deterministic same-directory names derived from the export request ID, so a retry can report and clean only its own private temporary file or backup. Force replacement keeps that backup until the new file is durable. Errors report whether publication, backup cleanup, or audit state is known.

Portable v1 accepts only records in the current Agent Bridge domain. New API writes use lowercase UUIDs. Archives require lowercase UUIDs and UTC timestamps with six fractional digits. They do not trim strings, remove duplicates, apply defaults, or rewrite message content. Export rejects legacy or direct database rows outside these rules. Recover those rows through native database backup and restore tools.

install -d -m 700 "$HOME/.agent-bridge/archives"

agent-bridge archive export \
  --provider local \
  --workspace team \
  --request-id 00000000-0000-4000-8000-000000000001 \
  --db "$HOME/.agent-bridge/bridge.sqlite3" \
  --output "$HOME/.agent-bridge/archives/team.ndjson"

agent-bridge archive verify --file "$HOME/.agent-bridge/archives/team.ndjson"

# Import is a rollback-backed dry run unless --apply is explicit.
agent-bridge archive import \
  --provider local \
  --db /path/to/target.sqlite3 \
  --file "$HOME/.agent-bridge/archives/team.ndjson" \
  --workspace team \
  --dry-run

agent-bridge archive import \
  --provider local \
  --db /path/to/target.sqlite3 \
  --file "$HOME/.agent-bridge/archives/team.ndjson" \
  --workspace team \
  --request-id 00000000-0000-4000-8000-000000000001 \
  --apply

Local commands use --db, AGENT_BRIDGE_DB, the selected shared config, or the canonical local database path, in that order. PostgreSQL commands accept database authority only from AGENT_BRIDGE_ARCHIVE_DATABASE_URL and require a login registered for the archive boundary created by the current migrations. They do not fall back to the schema-owner, gateway runtime, owner-control, or client database settings.

The archive digest detects accidental changes to the canonical bytes. It is neither encryption nor authentication. Archives contain message content and routing metadata, so protect them as private data. They exclude deliveries, delivery events, presence, credentials, owner-control records, security events, and other control or security state. The library API is available from @creatornader/agent-bridge/archive. Import opens the archive once without following links. It verifies the file, replays messages, then replays receipts through the same descriptor. Every pass checks framing, order, counts, and the client-computed digest. Batches are limited by row count and a 4 MiB byte budget. --dry-run and --apply are mutually exclusive.

Native disaster recovery

Native DR preserves one complete local authority or shared PostgreSQL deployment. Use it for recovery, not for moving a workspace between providers. The framed .abdr bundle records its provider, schema contract, and entry hashes. Commands calculate and return a separate whole-file SHA-256 digest. The hashes detect changed bytes. The bundle is not encrypted or authenticated, and it may contain credentials and message content. Store and transfer it as private database material.

Local backup uses SQLite's online backup API, so the source may remain open. It accepts only the local authority database. Gateway edge caches and outboxes are rejected because PostgreSQL remains their authority. Restore requires a new target path and verifies the exact local schema contract before publication.

agent-bridge dr backup \
  --provider local \
  --source "$HOME/.agent-bridge/bridge.sqlite3" \
  --output "$HOME/.agent-bridge/archives/local.abdr" \
  --backup-id 00000000-0000-4000-8000-000000000010

agent-bridge dr verify \
  --provider local \
  --bundle "$HOME/.agent-bridge/archives/local.abdr"

agent-bridge dr restore \
  --provider local \
  --bundle "$HOME/.agent-bridge/archives/local.abdr" \
  --target /path/to/new-bridge.sqlite3 \
  --request-id 00000000-0000-4000-8000-000000000011

PostgreSQL backup takes a repeatable-read snapshot while holding the native DR fence. It stores the agent_bridge schema dump plus a canonical inventory of required roles, memberships, and default privileges. The backup excludes transient data from agent instances, rate-limit buckets, request authority, endpoint-migration challenge rows, and archive transaction authority. Restore turns claimed deliveries into immediately retryable work because a database restore cannot preserve a live lease.

PostgreSQL authority comes only from the process environment. Backup reads AGENT_BRIDGE_DR_SOURCE_DATABASE_URL; restore reads AGENT_BRIDGE_DR_TARGET_DATABASE_URL. URLs are not accepted as command arguments. pg_dump and pg_restore must match the database major exactly. Native DR supports PostgreSQL 15 through 18. If matching tools are not on PATH, pass their directory to PostgreSQL backup, verify, or restore with --tool-directory. Local verification does not use PostgreSQL tools and rejects that option.

AGENT_BRIDGE_DR_SOURCE_DATABASE_URL='postgresql://backup-admin@db/agent_bridge' \
  agent-bridge dr backup \
    --provider postgres \
    --output "$HOME/.agent-bridge/archives/postgres.abdr" \
    --backup-id 00000000-0000-4000-8000-000000000020

agent-bridge dr verify \
  --provider postgres \
  --bundle "$HOME/.agent-bridge/archives/postgres.abdr"

AGENT_BRIDGE_DR_TARGET_DATABASE_URL='postgresql://restore-admin@new-db/agent_bridge' \
  agent-bridge dr restore \
    --provider postgres \
    --bundle "$HOME/.agent-bridge/archives/postgres.abdr" \
    --request-id 00000000-0000-4000-8000-000000000021 \
    --accept-source-sql-risk

PostgreSQL restore executes SQL from the dump, so use --accept-source-sql-risk only for a bundle from a source you trust. The target must be a dedicated fresh database with the same database name and PostgreSQL major as the backup. Restore requires a superuser because it recreates object owners, role shells, memberships, and default privileges. It never enables login on restored external principals. Runtime principals lose their runtime membership. Active control and archive registrations receive a new revocation event, and their principals lose those memberships. Re-enable a principal and register it again only after inspecting the restored target. A restore may reuse a preexisting role only when it is the source schema owner, the target session user, and a true superuser. Every other role collision fails. If a restore fails after mutation begins, Agent Bridge disables new target connections and reports any residual roles or recovery paths. Inspect and clean those artifacts before retrying. Never run the source and restored target as active authorities at the same time.

Backup publication never replaces an existing output. Deterministic adjacent staging paths use the backup or request UUID. A retry with the same UUID reports retained recovery paths instead of overwriting them. File and directory synchronization is strongest on platforms that support directory fsync; Windows cannot provide the same directory durability proof. Successful Windows commands report directoryDurability: "unavailable" or cleanupDirectoryDurability: "unavailable" instead of claiming that proof. The library API is available from @creatornader/agent-bridge/dr.

Install host adapters

Agent identity and gateway credentials belong to each installed client, not the shared ~/.agent-bridge/config file.

agent-bridge clients install codex --identity codex
agent-bridge clients install claude-code --identity claude-code
agent-bridge clients install claude-desktop --identity claude-desktop

For gateway mode, use the owner enrollment workflow below for new credentials. The older token handoff remains available for credentials issued by external tooling:

AGENT_BRIDGE_CLIENT_TOKEN=<codex token> \
  agent-bridge clients install codex --identity codex
AGENT_BRIDGE_CLIENT_TOKEN=<claude-code token> \
  agent-bridge clients install claude-code --identity claude-code
AGENT_BRIDGE_CLIENT_TOKEN=<claude-desktop token> \
  agent-bridge clients install claude-desktop --identity claude-desktop

--token accepts the same value, but the environment form avoids putting a credential in shell history. Each install writes an owner-only backend file under ~/.agent-bridge/clients/. The host MCP registration receives that file path, the client identity, and a generated stable instance key. Tokens are not copied into the shared config, and one client cannot reuse a token bound to another principal.

The codex adapter uses the Codex profile shared by the Codex CLI and the Codex surface in the ChatGPT desktop app; it does not install two independent registrations. Claude Code installation uses its native MCP command. Claude Desktop installation merges the agent-bridge server into its JSON config with an atomic write. Restart the client after installation.

Inspect an existing registration before making Agent Bridge its lifecycle owner:

agent-bridge clients inspect codex --identity codex --instance <stable-key> \
  --backend-config <absolute-client-backend-path>
agent-bridge clients adopt codex --identity codex --instance <stable-key> \
  --backend-config <absolute-client-backend-path>
agent-bridge clients adopt codex --identity codex --instance <stable-key> \
  --backend-config <absolute-client-backend-path> --apply

inspect is local, read-only, and does not return backend values or credentials. It classifies the requested registration as absent, unmanaged, managed, or drifted. adopt is plan-only unless --apply is explicit, and application writes only owner-private, credential-free management metadata after the identity, instance, backend path, scope, launch contract, and registration locator match exactly. Adoption never rewrites the host registration or backend file. Enrollment-based first-time provisioning still refuses registration and backend-file collisions. Claude Code inspection accepts the installed --scope; Claude Desktop accepts an explicit validated --command and, when needed, --config-path; its default contract is absolute Node plus the absolute server entry. Other adapters reject these runtime-specific options. Managed metadata binds Desktop to that normalized config path and Codex to its active profile config. Claude Code local and project adoption bind to the current directory because the native CLI exposes no stronger locator, so run those operations from the project context that later lifecycle commands should use. Backend files and their immediate parents must satisfy the owner-only no-link policy. Registration state is independent of connectivity health, and applied adoption re-inspects before reporting success.

Once adopted, repair, update, and uninstall locate the registration from owner-private managed metadata. The runtime and stable instance select that record. --identity must match the recorded identity and the immutable request. These commands reject backend-path, scope, and host config locator flags, so a caller cannot redirect a managed mutation.

agent-bridge clients repair codex --identity codex --instance <stable-key>
agent-bridge clients repair codex --identity codex --instance <stable-key> --apply
agent-bridge clients update codex --identity codex --instance <stable-key> \
  --command agent-bridge-mcp --apply
agent-bridge clients repair codex --identity codex --instance <stable-key> \
  --apply --resume <operation-uuid>
agent-bridge clients uninstall codex --identity codex --instance <stable-key> --apply
agent-bridge clients rollback <update-operation-uuid> --identity codex
agent-bridge clients rollback <update-operation-uuid> --identity codex --apply
agent-bridge clients resume <operation-uuid>
agent-bridge clients migrate stage codex --identity codex --instance <stable-key> \\
  --enrollment-file <private-path>
agent-bridge clients migrate cutover <stage-operation-uuid> --exclusive-edge
agent-bridge clients migrate cutover <stage-operation-uuid> --exclusive-edge --apply
agent-bridge clients migrate finalize <cutover-operation-uuid> --exclusive-edge --apply

These commands return a plan by default. A managed registration that is already exact and has a private backend file produces no operation. Repair restores the recorded launch contract. Update validates and records a new credential-free launch contract before it creates a journal. A native --command is one executable contract. Bare commands cannot contain arguments or URL-like selectors. Absolute paths must resolve to executable files. Native adapters remove, verify absence, add, and verify the target registration. Claude Desktop replaces only mcpServers.agent-bridge, preserves unrelated JSON values in memory, publishes through a private operation-scoped temporary file, and verifies the new entry. Node cannot provide an OS transaction with an uncooperative same-user Desktop writer. The pre-rename identity checks make this an advisory race boundary rather than a guarantee.

Uninstall is forward-only. It removes the managed registration and verifies its absence, deletes the private backend file, then deletes the management metadata. It does not repair an unsafe backend before deletion. If the backend is not already private, uninstall stops without changing the registration. It does not restore a registration, backend, or metadata file after a later step fails. Desktop removes only mcpServers.agent-bridge and preserves other JSON values. Backend and metadata removal check the file and private parent identities before unlink. POSIX then syncs the parent directory. Windows verifies the result but records unavailable directory durability. Node cannot make pathname-based deletion atomic against an uncooperative same-user writer, so that boundary remains advisory.

Rollback is explicit and update-only. A committed v4 update retains a bounded credential-free inverse contract: its prior managed metadata, prior exact registration contract, and digests for the forward metadata and registration state. clients rollback uses only the source operation UUID and matching identity. It first returns a plan. With --apply, it creates a separate reverse journal after it proves the source is same-host, the source is a committed v4 update, and the current state still matches the recorded forward state. Native reversal removes the forward entry, adds the prior entry, then writes prior metadata. Claude Desktop replaces only its Agent Bridge entry and writes prior metadata last. A reverse journal can resume with clients resume; a second rollback fails because the forward state is no longer present. Repair remains monotonic. Uninstall recovery requires re-enrollment.

--resume must name an unfinished operation for the same action, runtime, instance, and identity. It uses the recorded request instead of new flags. --recover-lock requires --apply and only removes an old same-host lock after the process-death proof succeeds. Never delete a lock manually.

clients resume <operation-uuid> takes only recorded v3, supported v4, v5 migration-stage, or v6 endpoint-migration operation authority and an optional --recover-lock. It does not accept a replacement action, runtime, instance, identity, command, backend path, scope, or config locator. Use it for every generic resume. It is also required when an uninstall has already deleted its final metadata file, because the action-specific command has no metadata record left to assert.

Repair and uninstall create v3 journals. New update and reverse rollback journals use v4. Existing v2 journals remain inspectable. A non-terminal v2 journal lacks the required identity-bound request, so it is blocked rather than resumed by these commands.

Managed-client operations now have a crash-safe local substrate under the owner-only ~/.agent-bridge/operations/ directory. agent-bridge clients operations lists safe operation summaries, and appending an operation UUID inspects one manifest. Output never includes artifact contents or backend values. Update requests retain only a normalized launch command, arguments, scope, and the fixed Agent Bridge environment key names. Each immutable ordered plan names registration, backend, and management-metadata targets with non-sensitive locators, no-replace before and after artifacts, and expected digests. Registration artifacts contain only bounded Agent Bridge fields and the full nonsecret target contract. An unknown environment key, malformed field, unsafe argument, or wrong scope stops before the journal is created. Begin holds the runtime-plus-instance lock and refuses another unfinished operation. Any corrupt or blocked journal fences new mutations and resume until an operator resolves it. Resume is same-host only. Intent is durable before an external write; observed-applied is durable only after the after-state is verified. On restart, the exact pending step is retryable only at its before-state, advances only at its after-state, and otherwise blocks as ambiguous. Sensitive file access checks and pins its immediate directory. Stale-lock recovery separately pins the operation root and locks directory for the full recovery sequence. Creation and destructive cleanup also keep the operation root and target directories pinned through their writes. The full metadata record is rechecked after the lock and before every non-metadata step.

On Windows, a held mutation lock can reuse a native ACL result only for the same directory path identity. File checks, every new or resumed lock, and each passive inspection check the ACL again. POSIX mode checks remain per-access. This cache does not change local mode's trust in the current OS user.

The journal states are prepared, snapshotted, in-progress, applied, cleaning, and committed; inspection separately reports resumable, classification-required, blocked, or complete. committed means every write was verified and every artifact was removed. Cleanup records durable intent per artifact, pins and verifies it, unlinks it, and syncs the directory on POSIX; Windows explicitly records unavailable directory durability. A missing artifact is safe to resume only when cleanup intent predated the attempt. Inspection accepts only two exact crash residues: that authorized absence, or the verified after artifact for the current intent-recorded step. A terminal manifest removes request, step, digest, locator, and artifact metadata. A v4 update retains only its explicit credential-free inverse contract. A committed v5 stage retains its credential-free stage contract. A committed v6 phase retains its credential-free cutover contract. Completed records remain readable across hosts. Repair, update, rollback, uninstall, and endpoint migration use this journal. Agent Bridge cannot promise physical erasure on SSDs or journaled filesystems, so future commands must minimize credential-bearing snapshots and rotate credentials when retained copies contained them. Inspection changes no registration, backend, metadata, or snapshot file.

The v1 host-adapter manifests are under clients/. Their compatibility field is named runtime, but its value identifies the installation target, not a unique process. OpenClaw and generic MCP manifests declare the required environment variables and command but leave config mutation to the operator because their host config shapes vary.

The lifecycle ownership boundary and deferred endpoint-migration constraints are recorded in ADR 0004.

clients migrate stage <runtime> --identity <name> --instance <key> --enrollment-file <path> --apply prepares a private gateway successor from a rotation enrollment. It does not change the active registration or backend. Staging records endpoint digests, credential IDs, the source edge path and scope key, and the predecessor grace cutoff without recording tokens or raw gateway URLs.

clients migrate cutover <stage-operation-uuid> --exclusive-edge --apply consumes a committed v5 stage. Before it creates a journal, it verifies both live gateways under HTTP 2.1, including the recorded workspace, principal, credential ID, immutable gateway authority ID, and the successor credential's status:read and messages:write grants. The endpoints then exchange a direct predecessor-to-successor route challenge. The durable journal retains the normalized source gateway URL without a token. Every later route challenge uses the successor credential at both URLs, and it drains the source edge through the target gateway. The source edge changes to draining, receives an exclusive lease, and must reach an exact zero-work outbox before any host registration changes. The command removes and adds native registrations, or replaces the Claude Desktop entry, then changes managed metadata last. It rechecks the authority and lease before every host write.

Every cutover phase requires --exclusive-edge. Agent Bridge can enumerate managed registrations that share an edge file, including hardlink aliases, but it cannot see unmanaged publishers. The flag records the operator's assertion for that remaining cohort. The forward phase also requires an active source edge and an active, empty target edge.

clients migrate finalize <cutover-operation-uuid> --exclusive-edge --apply works only after the predecessor grace cutoff and retires the retained source edge. To return to an earlier endpoint, first rotate a new owner credential for that endpoint, then run an ordinary forward cutover with it as the successor. Use clients resume <operation-uuid> after a crash. Resume waits for a live drain lease to expire before another worker can take it. Dry inspection refuses an edge with live WAL sidecars rather than opening it in a mode that could miss uncheckpointed outbox state. Moving to an independent database needs a separate owner-mediated process. The source backend must name an absolute, normalized edge database file. Relative and in-memory edge paths cannot identify one durable outbox and are rejected.

SKILL.md provides concise runtime-neutral operating instructions for agents. llms.txt gives tools and model crawlers a compact map of the package, modes, commands, and identity rules.

Gateway setup

Apply migrations to a PostgreSQL database:

PGUSER=schema_owner PGPASSWORD='<password>' \
AGENT_BRIDGE_DATABASE_URL=postgresql://host/database \
  agent-bridge migrate

The migration sequence creates or refreshes a database-specific restricted role. Get its name, then create a login that inherits it. Keep the schema-owner URL out of the gateway process:

create role agent_bridge_gateway login password '<generated password>';
do $grant$
declare
  runtime_role text := 'agent_bridge_runtime_' || substr(md5(current_database()), 1, 16);
begin
  execute format('grant %I to agent_bridge_gateway', runtime_role);
end
$grant$;

The database-derived suffix prevents a gateway login for one database from inheriting access to another Agent Bridge database on the same PostgreSQL cluster. Do not grant the login another database's runtime role.

Migration 014 provides the database owner control plane. The schema-owner connection registers provisioning and inventory logins with register_control_member. It can remove them with revoke_control_member. Do not grant the database-specific operator, auditor, or owner roles directly. Operators call the narrow control_provision, control_rotate_credential, and control_revoke_credential functions with a fresh request UUID. Operators and auditors can call control_credential_inventory; it never exposes credential digest material. Inventory accepts an optional workspace, an optional (created_at, credential_id) cursor, and a row limit. The default is 100 rows and the maximum is 1,000.

Each mutation takes a transaction lock for its request UUID before checking the request ledger. An identical concurrent call returns the stored result. Reusing the UUID with changed content fails. Provisioning reuses an existing workspace only when its name matches, so one workspace can contain several principals. Rotation supports credentials without an expiry and a null grace cutoff for immediate replacement. A rotation request includes the expected workspace and principal. The function checks both while it locks the predecessor, then returns the stored canonical identity. The database derives the audit actor from session_user.

This temporary operator workflow creates an eligible login and registers it through the protected SQL boundary. The owner CLI then provisions a token-bound codex principal. It generates the raw token locally, writes it to a private enrollment file before contacting PostgreSQL, and sends only the SHA-256 hash to the database.

OPERATOR_LOGIN="agent_bridge_operator_$(date +%s)"
OPERATOR_PASSWORD="$(node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('hex'))")"
MEMBERSHIP_REQUEST="$(node -e "process.stdout.write(require('node:crypto').randomUUID())")"
PROVISION_REQUEST="$(node -e "process.stdout.write(require('node:crypto').randomUUID())")"
export AGENT_BRIDGE_OPERATOR_LOGIN="$OPERATOR_LOGIN"
export AGENT_BRIDGE_OPERATOR_PASSWORD="$OPERATOR_PASSWORD"
export AGENT_BRIDGE_MEMBERSHIP_REQUEST="$MEMBERSHIP_REQUEST"
export AGENT_BRIDGE_PROVISION_REQUEST="$PROVISION_REQUEST"

psql -v ON_ERROR_STOP=1 "$AGENT_BRIDGE_DATABASE_URL" <<'SQL'
\getenv operator_login AGENT_BRIDGE_OPERATOR_LOGIN
\getenv operator_password AGENT_BRIDGE_OPERATOR_PASSWORD
\getenv membership_request AGENT_BRIDGE_MEMBERSHIP_REQUEST
SELECT format(
  'CREATE ROLE %I LOGIN PASSWORD %L',
  :'operator_login',
  :'operator_password'
) \gexec
SELECT * FROM agent_bridge.register_control_member(
  :'membership_request'::uuid,
  :'operator_login'::name,
  'operator'
);
SQL

export AGENT_BRIDGE_OPERATOR_DATABASE_URL="postgresql://${OPERATOR_LOGIN}:${OPERATOR_PASSWORD}@host/database"
export AGENT_BRIDGE_URL="https://bridge.example.com"
ENROLLMENT_FILE="${AGENT_BRIDGE_ENROLLMENT_DIR:-$HOME/.agent-bridge/enrollments}/${PROVISION_REQUEST}.json"

agent-bridge owner provision \
  --request-id "$PROVISION_REQUEST" \
  --workspace team \
  --workspace-name Team \
  --identity codex \
  --runtime codex \
  --instance codex-machine \
  --scope-set release-a-full

agent-bridge clients install codex --enrollment-file "$ENROLLMENT_FILE"

REVOKE_REQUEST="$(node -e "process.stdout.write(require('node:crypto').randomUUID())")"
export AGENT_BRIDGE_REVOKE_REQUEST="$REVOKE_REQUEST"
psql -v ON_ERROR_STOP=1 "$AGENT_BRIDGE_DATABASE_URL" <<'SQL'
\getenv operator_login AGENT_BRIDGE_OPERATOR_LOGIN
\getenv revoke_request AGENT_BRIDGE_REVOKE_REQUEST
SELECT * FROM agent_bridge.revoke_control_member(
  :'revoke_request'::uuid,
  :'operator_login'::name,
  'operator'
);
SELECT format('DROP ROLE %I', :'operator_login') \gexec
SQL
unset OPERATOR_PASSWORD AGENT_BRIDGE_OPERATOR_PASSWORD
unset AGENT_BRIDGE_OPERATOR_LOGIN AGENT_BRIDGE_MEMBERSHIP_REQUEST
unset AGENT_BRIDGE_PROVISION_REQUEST AGENT_BRIDGE_REVOKE_REQUEST
unset AGENT_BRIDGE_OPERATOR_DATABASE_URL

AGENT_BRIDGE_OPERATOR_DATABASE_URL is the owner CLI's only database authority. It does not fall back to the schema migration URL, runtime URL, shared client config, or active client identity. owner inventory returns at most 100 rows by default and 1,000 when requested. Its opaque cursor is bound to the selected workspace.

Provision and rotation enrollment files stay under $AGENT_BRIDGE_ENROLLMENT_DIR or ~/.agent-bridge/enrollments. The CLI requires an owner-only root and private path components. It refuses symlinks, path escapes, and directory replacement during an operation. Every file has a monotonic revision. A per-file exclusive lock covers the read, state transition, database call, and client installation side effects. Each transition compares the revision, state, request, operation, and token with the current file before atomic replacement.

If provisioning fails, rerun the same operation with --resume "$ENROLLMENT_FILE". A process crash can leave the adjacent lock file behind. Do not delete it manually. After at least 60 seconds, use --recover-lock with --resume or the client install command. Recovery succeeds only when the lock belongs to this host and user and its recorded process no longer exists. The stored request UUID, inputs, instance, and raw token are reused exactly.

clients install <runtime> --enrollment-file <path> derives the gateway URL, workspace, principal, instance, and token from the file. It rejects conflicting flags or environment values. Provisioning registers the host MCP server. Rotation updates only that instance's existing private backend file and never repeats host registration. The backend must contain the predecessor credential ID, principal, and instance metadata written by an enrollment-based provision. Rotation fails closed for older backend files without this metadata. It also verifies the exact live MCP registration before replacing the token. The installer removes the enrollment file only after installation succeeds. A failed delete returns enrollmentStatus: "consumed-file-retained" and the consumed file path. An already missing file returns consumed-file-missing. If unlink succeeds but the directory identity check or fsync fails, the result is consumed-deletion-durability-unknown; it never claims that the path was retained. lockReleaseStatus separately reports released, retained, or durability-unknown. A lock-release failure after successful installation does not replace the installation result with a generic error. The consumed artifact no longer contains the raw token. A retry validates the live backend and MCP registration before deleting a retained consumed file.

--token and AGENT_BRIDGE_CLIENT_TOKEN remain available for the older manual installation flow, but they cannot be combined with --enrollment-file. The final SQL block revokes and drops the temporary operator after the client installation has stored the credential.

Rotate the credential for one installed instance with an explicit grace cutoff or --invalidate-immediately, then consume the resulting enrollment file:

ROTATE_REQUEST="$(node -e "process.stdout.write(require('node:crypto').randomUUID())")"
ROTATION_FILE="${AGENT_BRIDGE_ENROLLMENT_DIR:-$HOME/.agent-bridge/enrollments}/${ROTATE_REQUEST}.json"
agent-bridge owner rotate \
  --request-id "$ROTATE_REQUEST" \
  --credential-id "<current credential UUID>" \
  --workspace team \
  --identity codex \
  --runtime codex \
  --instance codex-machine \
  --scope-set release-a-full \
  --invalidate-immediately
agent-bridge clients install codex --enrollment-file "$ROTATION_FILE"

Control membership is an offline SQL administration boundary. The gateway and MCP server do not expose it. Runtime readiness compares PostgreSQL membership with the append-only registry. It fails for an unregistered operator or auditor, an external owner holder, a missing registered grant, any extra role inherited by a registered member, or any role that inherits a registered member. Protected operations recheck the live registry and direct membership after taking a member-global transaction lock, followed by capability locks in operator-before-auditor order. Registration and revocation use the same order. A stale session that remains in a revoked role cannot continue operating. The schema owner is the only bootstrap holder of all three control roles and the only role with ordinary execution authority on the registration functions. PostgreSQL 16 and newer may represent its effective admin, inherit, and set authority with a bootstrap administrator row plus a schema-owner row. A native DR restore may recreate the same authority as one combined bootstrap-superuser grant. Readiness validates either physical form and its effective authority. True superusers and managed-provider schema owners remain inside this trusted database-administration boundary. A separate NOLOGIN backup-reader role grants only table and sequence read access to the schema owner. Runtime, control, archive, and external principals cannot inherit it. Native backups require the schema owner to be a true superuser or hold BYPASSRLS. A target restore superuser can validate the intentionally suspended schema-owner shell. CREATEROLE alone remains sufficient for migrations. Readiness checks the running PostgreSQL major on every call, so upgrading an already-migrated database to an uncertified major disables the runtime.

Direct credential inserts default to empty scopes rather than full access. Issued identity, scope, label, expiry, and lineage fields cannot be edited directly. Existing migration-013 rows retain their values and remain revocable during upgrade. Runtime readiness compares the live owner-control catalog with the protected migration-014 attestation. Migration 014 refuses to create that baseline if critical workspace, agent, credential, index, trigger, function, ownership, or row-isolation dependencies have already drifted. Unrelated additive schema objects do not change readiness. The migration also blocks unsafe direct and default privileges on protected objects. Global and workspace inventory use fixed-origin expression indexes for their keyset ordering.

Start the gateway:

PGUSER=agent_bridge_gateway PGPASSWORD='<password>' \
AGENT_BRIDGE_RUNTIME_DATABASE_URL=postgresql://host/database \
AGENT_BRIDGE_HOST=127.0.0.1 \
AGENT_BRIDGE_PORT=8787 \
agent-bridge-gateway

Put the gateway behind TLS for any non-loopback deployment.

Configure a client backend without storing its identity:

AGENT_BRIDGE_PROVIDER=gateway
AGENT_BRIDGE_URL=https://bridge.example.com
AGENT_BRIDGE_WORKSPACE=team

These shared settings are enough before clients install. The secure flow reads the principal-bound token and exact client settings from an enrollment file. The compatibility flow accepts AGENT_BRIDGE_CLIENT_TOKEN. Both write a private client backend and register AGENT_BRIDGE_AGENT, AGENT_BRIDGE_INSTANCE, and that file's path with the host. Gateway initialization checks readiness, token validity, and that the token-bound principal matches that process identity.

Offline gateway behavior

Gateway clients use ~/.agent-bridge/edge.sqlite3 by default.

When a send cannot reach the gateway:

  1. The validated message is written to the SQLite outbox with a stable idempotency key.
  2. The CLI or MCP result reports disposition: "queued" and authoritative: false.
  3. Long-lived MCP gateway clients retry due messages automatically with bounded exponential backoff. agent-bridge sync and the MCP sync tool trigger the same bounded replay and cache refresh manually. A permanently blocked message remains visible in diagnostics but does not stop later messages.
  4. An ambiguous retry is safe because the gateway enforces idempotency and rejects changed content under the same key.

Pass --queue-only to agent-bridge send when you want to write to the gateway outbox without making a network request. The message stays under the configured gateway, workspace, and principal scope. Run agent-bridge sync to publish it later.

Normal inbox and pending reads fall back to the local cache when the gateway is unreachable. Cached reads report source: "cache", stale: true, and degraded: true. When a read requested an unacknowledged filter, cached rows are candidates rather than proof of unread state and report acknowledgements: "unknown". Claims, lease changes, delivery settlement, presence, and read-receipt writes still require the gateway because replaying those operations after a lease or identity change is unsafe.

CLI

agent-bridge --version
agent-bridge --help
agent-bridge init --provider local
agent-bridge doctor
agent-bridge status
agent-bridge pending
agent-bridge send --type request --target worker "Run the task"
agent-bridge send --queue-only --type request --target worker "Queue the task"
agent-bridge post --category goal-update --project agent-bridge "Gateway is ready"
agent-bridge inbox --limit 20
agent-bridge sent --limit 20
agent-bridge history --mailbox all --receipt-state any
agent-bridge get --since 24h --unacked-by codex
agent-bridge history --thread-id release-1
agent-bridge acknowledge --ids <message-uuid>
agent-bridge claim --lease-ms 30000
agent-bridge claim --message-id <message-uuid> --lease-ms 30000
agent-bridge send --target worker --delivery-policy '{"mode":"leased","maxAttempts":3,"retryBaseDelayMs":1000,"retryMaxDelayMs":60000,"retryJitterRatio":0.2}' "work"
agent-bridge deliveries --state dead
agent-bridge delivery-events --delivery-id <uuid>
agent-bridge cancel --delivery-id <uuid>
agent-bridge requeue --delivery-id <uuid>
agent-bridge extend --delivery-id <uuid> --lease-token <uuid>
agent-bridge ack --delivery-id <uuid> --lease-token <uuid>
agent-bridge nack --delivery-id <uuid> --lease-token <uuid> --error "retry later"
agent-bridge join --instance desktop-1 --runtime codex --capability mcp
agent-bridge presence
agent-bridge sync
agent-bridge watch

-h and --help print help before Agent Bridge loads backend configuration or dispatches a command. They are safe to use after a command name, including archive and dr commands.

--project adds an optional immutable label to a message. It never selects a workspace or changes the active identity. Omit it on reads to include every project and unlabeled messages in the credential-bound workspace. Pass it to get, inbox, history, pending, or watch for an exact label match. Gateway callers may use --workspace only as an assertion. The CLI rejects a mismatch before sending a request. Local mode permits a per-command workspace override.

History defaults to mailbox inbox: broadcasts plus messages targeted to the configured principal, exactly as prior releases did. sent selects messages whose source is that principal, and all is the union. --receipt-state any|unread|read is caller-relative and valid only with inbox. The deprecated --unacked-by option remains an identity assertion. The CLI rejects a mismatch before opening storage or contacting a gateway. Server surfaces reject it before querying message storage. Cursors bind workspace, principal, mailbox, and normalized filters. Version 1 cursors are temporarily accepted, while all new cursors are version 2.

Publishers set deliveryPolicy on the message. Leased policy uses maxAttempts, retryBaseDelayMs, retryMaxDelayMs, retryJitterRatio, and optional notBefore. Consumers cannot override those values. Consumer-side maxAttempts on claim and retryPolicy on nack remain validated but ignored for one compatibility release. New code should omit both fields.

Claims normally select the next due delivery by priority. HTTP 2.1, MCP, and CLI callers may supply messageId or --message-id to atomically select only that due, recipient-visible delivery. The filter also scopes lease-expiry and exhaustion maintenance performed by that claim. Released HTTP 2.0 request shapes remain frozen and reject the field.

agent-bridge pending is a cheap shell gate for agent startup. It exits 0 when unread candidates or due delivery work are visible, 1 only for an authoritative empty result, and 2 when an empty remote state cannot be confirmed. Its JSON result separates unread context from executable delivery work and labels the state as available, empty, or unknown.

watch runs until interrupted unless --polls sets an explicit bound. Empty polls use capped backoff and jitter. Gateway network failures retry when the error is transient. Authentication and validation errors stop the watcher. Each exact project filter gets its own local watch checkpoint. Switching between a filtered and unfiltered watch cannot advance the other checkpoint.

Unknown flags and missing option values fail before a message can be sent. This prevents a misspelled target flag from becoming a broadcast.

MCP tools

Both providers expose the v1 compatibility tools:

  • post_context
  • get_context
  • ack_context

They also expose:

  • send
  • history
  • claim
  • extend
  • acknowledge
  • negative_acknowledge
  • list_deliveries
  • list_delivery_events
  • cancel_delivery
  • requeue_delivery
  • heartbeat
  • presence

The acknowledgment names depend on the interface. MCP ack_context and CLI acknowledge write read receipts. MCP acknowledge and CLI ack settle claimed deliveries. A receipt never settles delivery work, and delivery settlement never creates a receipt.

Identity and security

  • ~/.agent-bridge/config contains backend settings only. A stored AGENT_BRIDGE_AGENT value is ignored.
  • Workspace is the tenant boundary. Principal is the durable agent identity and message source.
  • AGENT_BRIDGE_INSTANCE is an optional caller-supplied stable consumer key. Supported installers generate and persist one for each installed client. Direct clients should do the same when they need separate consumer state or presence.
  • Unless AGENT_BRIDGE_CURSOR sets an explicit path, a present instance key selects the cursor path. The key also selects the delivery lease owner and presence row. Processes that share a key also share those resources. The gateway does not bind the key to an installer registration. It is not a PID, unique live process, or conversation ID.
  • When the key and explicit cursor path are both omitted, cursor storage uses default. Delivery leases fall back to the principal, and presence operations reject the request because presence requires an instance key.
  • Presence is instance-keyed in the released protocol. Per-process presence requires an additive identity instead of a silent change to lease ownership or cursor paths.
  • Session and thread identifiers are message and application context. They do not select credentials, workspace, principal, or delivery ownership.
  • Local clients bind identity through their process environment or an explicit CLI send argument.
  • Gateway tokens bind workspace and principal. The API ignores caller-supplied source and workspace fields.
  • Gateway clients use separate owner-only backend files and principal-bound tokens.
  • The gateway uses AGENT_BRIDGE_RUNTIME_DATABASE_URL. Migration and provisioning commands use the separate schema-owner AGENT_BRIDGE_DATABASE_URL.
  • Remote HTTP must use TLS. Plain HTTP is accepted only on loopback.
  • Local config, database, WAL, and shared-memory files use owner-only permissions where the platform supports POSIX modes.
  • The private agent_bridge schema is denied to Supabase anon and authenticated Data API roles.
  • Expired instance-presence rows are pruned during heartbeat and listing. Each agent is capped at 128 active instance keys, and each workspace is capped at 4,096.
  • Errors expose stable codes without database URLs, tokens, or provider response bodies.
  • Delivery work is at least once, not exactly once. Consumers must make external side effects idempotent.

Upgrading historical Supabase deployments

Agent Bridge 0.6.0 removed the direct legacy-supabase runtime provider. Old provider names and key-only configurations now fail with a migration message instead of opening PostgREST. Configure local or gateway mode before upgrading a running client.

When gateway migrations run in the same PostgreSQL database as public.shared_context, migration 006 imports legacy rows and receipts into the private v2 schema. It preserves valid envelope UUIDs, keeps the existing synthetic UUID mapping for ordinary numeric IDs, assigns deterministic UUIDs to larger IDs, maps projects to workspaces, rejects ID collisions, and verifies the imported count. It does not create executable deliveries for historical targeted rows because an upgrade must not replay old work.

Migration 008 adds the optional project label without changing migration 006. A schema owner can preview correction of migration 006 rows into the canonical agent-bridge workspace with agent-bridge reconcile-legacy-projects. The command defaults to a dry run. Pass --apply to make the change in one transaction. The canonical workspace must already exist. Reconciliation preserves message IDs, timestamps, receipts, and total row counts. It creates no deliveries and is safe to repeat. It refuses changed source data, invalid labels, project or idempotency conflicts, and imported messages that already have a delivery.

The original v1 schema remains in sql/setup.sql as a migration fixture. Ordered gateway migrations live in sql/migrations/. The retirement decision is recorded in ADR-0005.

Health and operations

status is a passive snapshot. It does not synchronize or probe a remote provider. An unprobed remote reports status: "unknown", connected: false, and remoteReachable: null. Passive status still exits 0. doctor evaluates named checks and exits 0 for ok, 2 for degraded, and 1 for failed. Both return JSON with:

  • Provider and actual schema version.
  • Bound workspace, agent, and consumer instance key.
  • Endpoint or database path.
  • Cursor and queue state.
  • Pending, claimed, retrying, and dead delivery counts, plus due versus scheduled work, expired leases, oldest-due time, and bounded queue lag.
  • Gateway reachability, outbox due/scheduled/leased/blocked depth, cache size, blocked age/attempt/error, next retry, last outbound/inbound sync, and last attempt.
  • Explicit check results. Blocked outbox rows, expired leases, dead deliveries, or a known-unreachable gateway prevent a healthy result.

Local edge health and remote reachability are separate. A healthy offline gateway client after a doctor probe reports localHealthy: true, remoteReachable: false, connected: false, and status: "degraded" while retaining usable cached reads and queued sends. Fatal local edge errors report localHealthy: false without hiding the queue snapshot. Blocked age is null for rows created by an older client before transition timestamps existed.

Client-local outbox and synchronization fields appear only in CLI status output. Authenticated HTTP /v2/status reports gateway delivery diagnostics and does not advertise client-local state.

In-process SyncingBridgeStore diagnostics also expose the current loop state and a sanitized loop error. Standalone CLI health commands omit those fields because their short-lived diagnostic runtime is not the long-lived MCP client.

The gateway exposes two unauthenticated probes. /healthz reports HTTP process liveness without querying PostgreSQL. /readyz checks database and schema readiness. /v2/status and /metrics require a valid credential.

Each gateway operation checks the scopes in the canonical registry. capabilities needs an active credential but no named scope. For requests with bodies, the gateway validates media type, size, and JSON before it opens the request transaction. It then audits scope denials and applies both a credential-wide token bucket and an operation bucket before domain work begins. Missing policy state or a failed denial audit closes the request with security_unavailable. Rate denials return the same rounded delay in Retry-After and error.details.retryAfterSeconds.

Migration 012 makes PostgreSQL the authority for each production gateway request that passes credential preflight. Node hashes the bearer credential before PostgreSQL receives it. The gateway checks out one connection, opens one explicit transaction, matches the credential ID and hash inside a narrow security-definer function, and derives the workspace, principal, and scopes from current database state. Security accounting and domain work share that transaction and backend. Delivery claim, cancel, and requeue reuse it without nested BEGIN. Runtime logins cannot read credential hashes, agent records, workspace records, or request-authority records.

Migration 017 adds one immutable PostgreSQL authority UUID. The released request-authority opener keeps its return shape. Production gateway requests use a bound opener that returns the authority UUID with the database-derived credential. Authenticated gateway /v2/status replies include gatewayAuthorityId and credentialId when transaction-bound request authority is active. Authenticated HTTP 2.1 capabilities return grantedScopes for that credential. Migration 018 uses the same bound authority to issue and consume an expiring challenge for a direct credential successor. Migration 019 also permits the same active successor credential at both endpoints after a cutover journal begins. Neither challenge proves a database migration or authorizes endpoint cutover.

Migration 013 enables and forces row-level security on messages, receipts, deliveries, delivery events, and presence. Policies read workspace and principal from the transaction-bound request authority. The runtime role cannot set that authority through session variables, inherit a table-owner role, or bypass RLS. Separate no-login roles own domain tables, read request context, and write delivery audit events. Delivery identity and publisher bindings are immutable. A trigger lets recipients perform recipient lifecycle actions and reserves cancel and requeue for publishers.

The migration records a catalog attestation after it creates the policies, constraints, triggers, and security functions. /readyz fails if the current catalog no longer matches that owner-created baseline. Gateway capabilities report rowIsolation: true only when request authority is enabled and the migration, catalog attestation, ownership, policy, privilege, and function checks all pass. An owner-approved schema change must update the attestation in its migration.

RLS does not replace operation checks. The service still enforces lease tokens, valid state transitions, and target-to-delivery membership. The database binds each delivery to its publisher and message, but it does not prove that the recipient appears in the message target list. PostgreSQL superusers and roles with BYPASSRLS remain outside this boundary; do not use either for the gateway login.

Credential replacement links are immutable and stay within one workspace and principal. A replacement grace period can shorten the predecessor's lifetime but cannot extend ordinary expiry or override revocation. Replacement, revocation, scope denial, and rate denial events use explicit append-only columns. They do not store tokens, hashes, authorization headers, request bodies, message content, arbitrary metadata, URLs, IP addresses, or database errors.

Before dropping an Agent Bridge database, remove its gateway login and database-specific runtime role. Run this while connected to that database so current_database() still identifies the right role:

drop role if exists agent_bridge_gateway;
do $cleanup$
declare
  suffix text := substr(md5(current_database()), 1, 16);
  role_name text;
begin
  foreach role_name in array array[
    'agent_bridge_runtime_' || suffix,
    'agent_bridge_data_owner_' || suffix,
    'agent_bridge_context_reader_' || suffix,
    'agent_bridge_event_writer_' || suffix
  ] loop
    if exists (select 1 from pg_roles where rolname=role_name) then
      execute format('reassign owned by %I to %I', role_name, current_user);
      execute format('drop owned by %I', role_name);
      execute format('drop role %I', role_name);
    end if;
  end loop;
end
$cleanup$;

The runtime role owns no schema objects. PostgreSQL keeps roles after a database is dropped, so skipping this step leaves an unused cluster role behind.

Development

npm run typecheck
npm test
npm run test:postgres:preflight
npm run build
npm pack

The PostgreSQL preflight starts a fresh Docker container for each supported major, assigns a free loopback port, and runs the live contract. Normal completion removes each container. A later run removes labeled preflight containers older than two hours, which bounds residue after a process or machine stops before cleanup can run. Use npm run test:postgres:preflight -- --major 16 for one major, or npm run test:postgres:preflight:full for the full suite on every major. The command requires a running Docker engine and never reuses a database from an earlier run.

Set AGENT_BRIDGE_TEST_DATABASE_URL when an existing disposable PostgreSQL database is more convenient. CI runs one PostgreSQL 16 contract before starting the 15 through 18 jobs. Each version then runs the live contract and matching native DR proof. The package job builds and installs the npm tarball separately.

CI runs Node 22 and 24 on Linux, macOS, and Windows. One native Windows contract checks private paths, managed uninstall path identity, and recovery before the two Windows jobs start. Before applying a Windows DACL, private paths must be owned by the current account SID or the active token's default owner SID. The final owner and sole protected FullControl rule must use the account SID. Verification-only paths must already satisfy that final policy. Node file identity checks and native Windows reparse attributes must also agree before and after policy validation; symlinks, junctions, and other reparse objects fail closed. CI applies and verifies this policy on native Windows runners. Windows ACL race and directory durability behavior has not yet been proved on a dedicated Windows host.

Documentation

  • ROADMAP.md: released work, unreleased implementation, and the remaining program.
  • SECURITY.md: supported versions, release integrity, private vulnerability reporting, and security boundaries.
  • docs/ecosystem.md: product boundary, adjacent systems, and interoperability direction.
  • docs/troubleshooting.md: MCP startup, identity, backend, and client recovery.
  • docs/deployment.md: Compose development setup, production constraints, upgrades, backup, and rollback.
  • ADR-0001: protocol layers and acknowledgment semantics.
  • ADR-0002: canonical contracts, generated artifacts, discovery, and version negotiation.
  • ADR-0003: host layers and consumer instance-key semantics.
  • ADR-0004: managed client ownership and endpoint migration.
  • ADR-0005: direct Supabase runtime retirement and historical migration support.
  • docs/architecture-v2.md: protocol and storage decisions.
  • SKILL.md: runtime-neutral agent operating instructions.
  • llms.txt: compact machine-readable project map.
  • docs/postmortems/2026-07-08-wrapper-source-drift.md: wrapper and source-identity incident.
  • CLAUDE.md: repository rules, architecture constraints, and documentation ownership.
  • CHANGELOG.md: released and pending changes.

License

Apache-2.0

About

Let AI agents message each other and hand off work across tools, sessions, and machines.

Topics

Resources

License

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors