ExecutionAdapter — that normalizes the runtime’s execution model into a standard request/result contract. Bridge handles prompt assembly, tool translation, output normalization, and callback delivery. Your adapter’s job: accept a request, run it on the runtime, return a result.
This guide walks through writing a new adapter from scratch.
The ExecutionAdapter Interface
Every adapter implements this interface. It lives inpackages/bridge/src/execution-adapter.ts and is the foundational contract for all gateway communication.
Method-by-Method Breakdown
| Method | Required | Purpose |
|---|---|---|
provider | Yes | Unique string ID. Used for adapter routing, logging, and the BRIDGE_ADAPTERS env var. |
initialize() | Yes | Called once at startup. Load API keys, validate configuration, connect to external services. |
shutdown() | Yes | Called on process exit. Cancel in-flight tasks, close connections, release resources. |
checkHealth() | Yes | Called periodically by Bridge and Convex. Return "healthy", "degraded", or "offline". |
capabilities() | Yes | Return a GatewayCapabilityProfile declaring what the runtime supports. Used for routing decisions. |
execute() | Yes | The core method. Receives a normalized ExecutionRequest, runs it, returns an ExecutionResult. |
cancel() | Yes | Attempt to cancel a running task by its handle. Return true if cancellation was initiated. |
onEvent() | Optional | Register a listener for real-time execution events (streaming text, tool calls). |
offEvent() | Optional | Remove a previously registered event listener. |
startEventRelay() | Optional | For adapters with their own event infrastructure (e.g. WebSocket relays). |
stopEventRelay() | Optional | Tear down the adapter-managed event relay. |
Adapter Lifecycle
An adapter goes through a predictable lifecycle managed by Bridge:Registration
packages/bridge/src/adapter-loader.ts with a case in the loadSingleAdapter() switch. Bridge discovers it via the GATEWAY_PROVIDER or BRIDGE_ADAPTERS environment variable.Initialization
adapter.initialize(config) at startup, passing environment variables as the config object. The adapter validates its configuration (API keys, endpoints) and sets up internal state.Health Checks
/api/health endpoint that calls adapter.checkHealth(). Convex polls this endpoint to determine whether the gateway can accept work. Return "degraded" (not "offline") if you can partially function.Dispatch
adapter.execute(request) with a fully assembled ExecutionRequest. The adapter translates this to its native format, runs the task, and returns an ExecutionResult.Event Streaming
ExecutionEvent objects to registered listeners. Bridge forwards these as SSE events to connected Forge Console clients.Dispatch Flow
When a.lobsterX workflow step is dispatched, it flows through the following path:
End-to-End Dispatch Flow
Resolves the DAG, runs governance gates, and POSTs a dispatch payload to Bridge.
Validates the request, authenticates the token, and calls assembleExecutionRequest() to build a normalized ExecutionRequest from the dispatch payload.
Translates the ExecutionRequest to the runtime’s native format. Calls the runtime. Normalizes the response into an ExecutionResult.
Normalizes the output, then POSTs the result to Convex via the callback URL. Retries 3x with exponential backoff.
Records the step result, creates token events for cost attribution, and fires a completion event to the interpreter to advance the DAG.
ExecutionResult.Self-Registration and Capabilities
Adapters declare their capabilities at runtime through thecapabilities() method. There is no static capability map — capabilities are self-registered by each adapter and stored in the gatewayRegistry table in Convex.
The GatewayCapabilityProfile
How Capabilities Are Checked
On the Convex side, the routing engine usesgatewaySupports() from convex/gateways/capabilities.ts to check whether a gateway supports a specific capability:
capabilities() return value feeds into Layer 1.
Implementing a Minimal Adapter
Here is a step-by-step walkthrough for adding a new adapter. We will use a hypothetical “Acme AI” runtime as the example.Step 1: Create the Package
package.json:
pnpm-workspace.yaml if it is not already covered by a glob pattern.
Step 2: Implement the Adapter
Createsrc/provider.ts:
src/index.ts (barrel export):
Step 3: Register in the Adapter Loader
Add your adapter to the switch inpackages/bridge/src/adapter-loader.ts:
Step 4: Add to the Runtime Providers List
Add"acme-ai" to the RUNTIME_PROVIDERS array in packages/shared/src/runtime-types.ts:
Step 5: Configure and Run
Set the environment variable and start Bridge:Existing Adapters
Forge ships with five adapters. Use these as reference implementations when building your own.| Adapter | Package | Runtime | Capabilities | Cost Tier | Best For |
|---|---|---|---|---|---|
| OpenClaw | adapter-openclaw | Stateful daemon with WebSocket relay | streaming, session_resume, file_io, sandboxing, subagents, management | Medium | Full-featured agent execution with persistent state, workspace files, and agent CRUD |
| Claude Agent SDK | adapter-claude-sdk | Anthropic Claude Agent SDK | streaming, session_resume, file_io, mcp_servers, subagents, cost_tracking | High | Claude-native execution with tool use, subagent orchestration, and conversation capture |
| Vercel AI | adapter-vercel-ai | Vercel AI SDK (multi-provider) | streaming, mcp_servers, cost_tracking, structured_output | Low | Multi-provider model routing (Anthropic, OpenAI, Google), structured output, high-concurrency serverless |
| Gloo AI | adapter-gloo-ai | Gloo AI Completions v2 | streaming, cost_tracking | Low | Intelligent model routing, grounded completions (RAG), tradition-aware responses |
| Hermes Agent | adapter-hermes-agent | Hermes Agent runtime | streaming, file_io, mcp_servers, management, native_tools | Medium | Full management interface with rich native tool set (terminal, browser, vision, memory) |
Interfaces Implemented
Most adapters implement onlyExecutionAdapter. Stateful runtimes that manage agent configurations additionally implement management sub-interfaces:
| Interface | Purpose | Implemented by |
|---|---|---|
ExecutionAdapter | Core execution contract | All adapters |
RuntimeManager | Agent sync, skill sync, model discovery | OpenClaw |
AgentManager | Agent CRUD, workspace files, channel bindings | OpenClaw |
SkillManager | Skill CRUD, runtime status, dependency installation | OpenClaw |
SystemManager | Config reload, model catalog, security posture | OpenClaw |
isAgentManager(), isSkillManager(), isSystemManager()) and exposes the corresponding gateway API routes only when the loaded adapter supports them. If your runtime manages agent state, implement the relevant sub-interfaces.
Optional: Management Sub-Interfaces
If your runtime stores agent configurations and supports management operations, you can implement theRuntimeManager interface alongside ExecutionAdapter:
Testing
Adapters are tested with Vitest. Tests go insrc/__tests__/provider.test.ts within the adapter package.
What to Test
| Area | What to verify |
|---|---|
| Identity | provider returns the correct string |
| Capabilities | capabilities() returns the expected profile |
| Health | checkHealth() returns "degraded" without config, "healthy" with config |
| Cancellation | cancel() returns false for unknown tasks, true for active tasks |
| Shutdown | shutdown() aborts all active controllers and clears state |
| Event listeners | onEvent()/offEvent() correctly register and unregister listeners |
| Error isolation | A throwing event listener does not break emitEvent() for other listeners |
Example Test
Checklist
Before shipping a new adapter:Implementation Checklist
Implementation Checklist
- Implements all required
ExecutionAdaptermethods -
providerstring is unique and added toRUNTIME_PROVIDERS -
capabilities()accurately reflects what the runtime supports -
initialize()validates configuration and logs startup -
shutdown()cancels all active tasks and cleans up -
checkHealth()returns meaningful status based on actual connectivity -
execute()handles timeouts viaAbortController -
execute()returnsNormalizedUsagewith token counts when available -
cancel()handles both known and unknown task handles - Error paths return
status: "error"with a descriptiveerrorfield - Event listeners are isolated — a throwing listener never breaks execution
Integration Checklist
Integration Checklist
- Adapter registered in
packages/bridge/src/adapter-loader.ts - Provider name added to
RUNTIME_PROVIDERSinpackages/shared/src/runtime-types.ts - Barrel export in
src/index.ts - Package added to
pnpm-workspace.yamlif needed -
@tangogroup/bridgelisted as a dependency inpackage.json
Testing Checklist
Testing Checklist
- Unit tests for identity, capabilities, health, cancellation, shutdown, event listeners
- Tests pass with
pnpm test - Verified end-to-end with
BRIDGE_ADAPTERS=acme-ai pnpm dev

