{"slug": "signet-runtime-adapter-contract", "title": "Signet Runtime Adapter Contract", "summary": "Signet introduces a runtime adapter contract to decouple its cognitive maintenance layer from external harnesses like OpenClaw and Claude Code, defining canonical interfaces for providers, tools, and channels. The daemon-owned contract ensures harnesses implement a standard spec, with the runtime acting as a thin orchestration layer over the daemon API.", "body_md": "# Signet Runtime Adapter Contract\n\n## Context\n\nSignet currently runs as a cognitive maintenance layer on top of external runtimes — OpenClaw, Claude Code, OpenCode. These are first-class harness integrations and remain so. The problem is that Signet’s capability growth is gated on what each harness exposes: if a harness deprecates a plugin interface, changes its hook model, or simply doesn’t support a new lifecycle event, Signet loses the surface.\n\nThe deeper issue is definitional. Every harness integration has to answer the question “what does a Signet harness need to implement?” — right now there is no canonical answer. Each connector is a bespoke translation layer.\n\nThe solution is a daemon-owned adapter contract that defines what it means to run a harness on Signet. When the daemon adds a new lifecycle capability, each harness adapter has a clear spec to implement against. The daemon API is the source of truth for the integration contract.\n\nKey constraints:\n\n- All actions in the runtime are Daemon API calls first. The runtime is a thin orchestration layer over the daemon HTTP API, not a parallel implementation of daemon functionality.\n- Harnesses are thin clients. They translate platform-specific events into Daemon API calls using the same interfaces the reference runtime uses.\n- SDKs for TypeScript, Rust, and Python continue to be first-class. The runtime expands what the SDKs expose, not replaces them.\n\n## What the Runtime Is\n\nThe Signet runtime is a session execution loop that:\n\n- Assembles context (memory injection, system prompt, identity) via daemon\n- Sends a turn to a configured LLM provider\n- Dispatches tool calls through a registered tool registry\n- Manages the session lifecycle (start, prompt, end, compaction)\n- Records behavioral signals back to the daemon (FTS hits, continuity)\n\nEvery one of those steps is a daemon API call or a thin wrapper around one. The runtime doesn’t store state — the daemon does.\n\nThe runtime’s only owned concern is the execution loop: taking a user message, assembling what the agent needs to respond, calling the model, handling tools, and returning output. Everything else is delegated.\n\n## Core Interfaces\n\nThese interfaces define the integration contract. Implementing them is what it means to be a Signet harness.\n\n### Provider\n\n```\ninterface Provider {\n  id: string\n  complete(messages: Message[], opts?: CompletionOptions): Promise<CompletionResult>\n  stream(messages: Message[], opts?: CompletionOptions): AsyncIterable<CompletionChunk>\n  available(): Promise<boolean>\n}\n\ninterface CompletionOptions {\n  model?: string\n  maxTokens?: number\n  temperature?: number\n  tools?: ToolDefinition[]\n  systemPrompt?: string\n}\n\ninterface CompletionResult {\n  content: string\n  toolCalls?: ToolCall[]\n  stopReason: 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence'\n  usage: { inputTokens: number; outputTokens: number }\n}\n```\n\nImplementations: Anthropic, OpenAI, OpenAI-compatible (Ollama, local). The runtime ships a default Anthropic provider. Additional providers are registered at startup or loaded from config.\n\n### Tool\n\n```\ninterface Tool {\n  name: string\n  description: string\n  inputSchema: JsonSchema\n  runBeforeGeneration?: boolean  // opt-in to pre-generation research phase\n  execute(input: unknown, context: ToolContext): Promise<unknown>\n}\n\ninterface ToolContext {\n  sessionKey: string\n  project: string\n  daemonUrl: string  // all daemon calls go through context, never direct\n}\n```\n\nTools are registered in a `ToolRegistry`\n\n. The runtime dispatches tool calls\nfrom model responses through the registry. Built-in tools: `memory_search`\n\n,\n`memory_store`\n\n, `memory_get`\n\n, `memory_modify`\n\n, `memory_forget`\n\n— thin\nwrappers over daemon API endpoints, identical to what the MCP server exposes.\n\n### Channel\n\n```\ninterface Channel {\n  kind: string\n  receive(): Promise<UserTurn>\n  send(output: AgentOutput): Promise<void>\n  onClose(handler: () => void): void\n}\n\ninterface UserTurn {\n  content: string\n  attachments?: Attachment[]\n  metadata?: Record<string, unknown>\n}\n\ninterface AgentOutput {\n  content: string\n  toolResults?: ToolResult[]\n  streaming?: boolean\n}\n```\n\nFirst channel: CLI (stdin/stdout). Subsequent: HTTP (for harness adapter attachment). Channel is the only interface that varies between the reference runtime and a harness adapter — everything else (provider, tools, lifecycle) is identical.\n\n### RuntimeAdapter\n\nThis is the interface a harness implements to integrate with Signet. It maps platform-specific lifecycle events to daemon API calls.\n\n```\ninterface RuntimeAdapter {\n  harness: string  // 'openclaw' | 'claude-code' | 'opencode' | ...\n\n  onSessionStart(params: SessionStartParams): Promise<SessionStartResult>\n  onUserPromptSubmit(params: PromptSubmitParams): Promise<PromptSubmitResult>\n  onSessionEnd(params: SessionEndParams): Promise<void>\n  onPreCompaction(params: PreCompactionParams): Promise<PreCompactionResult>\n  onCompactionComplete(params: CompactionCompleteParams): Promise<void>\n}\n```\n\nAll `RuntimeAdapter`\n\nimplementations are thin clients: every method body is\na daemon API call. OpenClaw’s adapter implements this interface by calling\nthe same daemon endpoints via its plugin system; other adapters translate\ntheir own lifecycle hooks into the same calls.\n\nA harness adapter is a `RuntimeAdapter`\n\nimplementation. Nothing more.\n\n## Package Structure\n\nSignet does not maintain a monorepo-owned native runtime package. Runtime\ncoverage is provided by connector and plugin packages under `integrations/*`\n\n,\nwith daemon endpoints as the shared contract.\n\n## Session Lifecycle\n\nEach turn in the execution loop:\n\n```\n1. Channel.receive()\n     → get user message\n\n2. executor.preGeneration()   [research phase]\n     → query daemon for relevant memories (FTS + vector)\n     → execute tools with runBeforeGeneration: true\n     → results available to model before generation starts\n\n3. context.assemble()\n     → system prompt: identity + SOUL.md content + injected memories\n     → conversation history\n     → pre-generation tool results\n\n4. Provider.complete()\n     → call the model with assembled context\n\n5. executor.dispatch()        [tool loop]\n     → for each tool call in response: ToolRegistry.execute()\n     → append results to messages\n     → loop back to Provider.complete() until stop_reason = end_turn\n\n6. daemon POST /api/hooks/user-prompt-submit\n     → record FTS hits for behavioral signals (predictive scorer training)\n\n7. Channel.send()\n     → deliver output to user\n```\n\nSession start:\n\n```\ndaemon POST /api/hooks/session-start\n  → memories injected into first-turn system prompt\n  → continuity checkpoint loaded if session resumed\n```\n\nSession end:\n\n```\ndaemon POST /api/hooks/session-end\n  → triggers continuity scoring job\n  → memory extraction pipeline enqueued\n  → session checkpoint saved\n```\n\nThe pre-generation research phase (step 2) is the key architectural\ndifference from a naive chat loop. Tools that declare `runBeforeGeneration: true`\n\nexecute before the model sees the user message. The model gets\ngrounded context instead of generating then correcting.\n\n## Daemon API Dependency Map\n\nEvery runtime action maps to a daemon endpoint. This table defines the integration surface a harness adapter must cover for full parity:\n\n| Runtime Action | Daemon Endpoint | Phase |\n|---|---|---|\n| Session start context | POST /api/hooks/session-start | start |\n| Per-prompt context + FTS | POST /api/hooks/user-prompt-submit | turn |\n| Session end / extraction | POST /api/hooks/session-end | end |\n| Pre-compaction summary | POST /api/hooks/pre-compaction | cmpct |\n| Save compaction result | POST /api/hooks/compaction-complete | cmpct |\n| Memory search (tool) | POST /api/memory/recall | any |\n| Memory store (tool) | POST /api/hooks/remember | any |\n| Memory get (tool) | GET /api/memory/:id | any |\n| Memory modify (tool) | POST /api/memory/modify | any |\n| Memory forget (tool) | POST /api/memory/forget | any |\n| Secret injection | POST /api/secrets/exec | tool |\n| Daemon health check | GET /health | start |\n\nA harness adapter that covers all rows has full parity with the reference runtime. Partial coverage is valid — the delta is explicit and auditable.\n\n## SDK Surface Additions\n\nThe runtime expands SDK surface without breaking existing API.\n\n**TypeScript SDK (@signet/sdk)**\n\nNew exports:\n\n`RuntimeAdapter`\n\ninterface`createAdapter(harness, daemonUrl?)`\n\n— factory returning a pre-wired client implementing each lifecycle method as a daemon API call`RuntimeAdapterServer`\n\n— HTTP server exposing adapter lifecycle as endpoints (for harnesses that prefer HTTP over library import)\n\n**Rust SDK**\n\n`RuntimeAdapter`\n\ntrait`AdapterClient`\n\nstruct — pre-wired daemon HTTP client`RuntimeAdapterServer`\n\n— axum-based server for HTTP attachment\n\n**Python SDK**\n\n`RuntimeAdapter`\n\nabstract base class`AdapterClient`\n\n— aiohttp-based daemon client`RuntimeAdapterServer`\n\n— FastAPI-based server for HTTP attachment\n\nAny harness in any language can implement `RuntimeAdapter`\n\nusing the\nappropriate SDK, call the same daemon endpoints, and have full parity\nwith the reference runtime.\n\n## Harness Adapter Pattern\n\nAn adapter is a translation layer with no business logic. Example:\n\n``` js\n// @signetai/adapter-openclaw — full implementation\nimport { createAdapter } from '@signet/sdk'\n\nexport default function createPlugin(opts: { daemonUrl?: string }) {\n  const adapter = createAdapter('openclaw', opts.daemonUrl)\n\n  return {\n    onSessionStart:     (ctx) => adapter.onSessionStart({\n      sessionKey: ctx.session.id, project: ctx.workspace\n    }),\n    onUserPromptSubmit: (ctx) => adapter.onUserPromptSubmit({\n      sessionKey: ctx.session.id, prompt: ctx.prompt\n    }),\n    onSessionEnd:       (ctx) => adapter.onSessionEnd({\n      sessionKey: ctx.session.id\n    }),\n    onPreCompaction:    (ctx) => adapter.onPreCompaction({\n      sessionKey: ctx.session.id, messageCount: ctx.messages.length\n    }),\n    onCompactionComplete: (ctx) => adapter.onCompactionComplete({\n      sessionKey: ctx.session.id, summary: ctx.summary\n    }),\n  }\n}\n```\n\nWhen Signet adds a new capability, it adds a daemon endpoint and a new\n`RuntimeAdapter`\n\nmethod. Each harness adapter adds one translation. There is\nno duplicated business logic to reason about or diverge.\n\n## Build Sequence\n\n**Phase 1: daemon-owned contract**\n\n- Preserve the daemon-owned execution contract: memory, hooks, secrets, and session state stay daemon-side\n- Deliverable: supported harnesses share the same lifecycle semantics where their host APIs allow it\n\n**Phase 2: harness parity**\n\n- Keep Bun daemon, Rust daemon, and existing harness adapters aligned on the same daemon endpoints\n- Treat external harnesses as thin adapters over the same runtime contract\n- Deliverable: integration deltas are explicit and auditable\n\n**Phase 3: SDK adapter ergonomics**\n\n- Add or refine adapter helpers in the SDKs so external harnesses can implement the runtime contract with less boilerplate\n- Keep adapter logic translation-only; business logic stays in the daemon/runtime boundary\n- Deliverable: new harness integrations have a minimal documented path\n\n**Phase 4: optional runtime transport expansion**\n\n- If a supported harness exposes a stable local API, document it as an extension of the same runtime contract instead of a separate architecture\n- Deliverable: transport choices can evolve without changing the core daemon contract\n\n## What This Is Not\n\n- Not a replacement for the daemon. All state lives in the daemon. The runtime is stateless at the contract boundary.\n- Not a new memory system. Memory is still pipeline v2 + predictive scorer\n- knowledge graph, owned by the daemon.\n\n- Not breaking for existing harnesses. OpenClaw/Claude Code/OpenCode work through their own adapters and remain additive integrations.\n- Not a config system. Config lives in agent.yaml, read by the daemon.\n\n## Critical Files\n\nRuntime contract + adapters:\n\n`libs/sdk/`\n\n`integrations/openclaw/connector/`\n\n`integrations/claude-code/connector/`\n\n`integrations/opencode/connector/`\n\nDaemon surfaces:\n\n`platform/daemon/`\n\n`platform/daemon-rs/`\n\n## Open Questions\n\n-\n**Provider config**— provider selection and model live in agent.yaml. Keep the runtime-facing schema minimal and daemon-owned. -\n**Multi-provider routing**— route different task types to different providers when there is a concrete need, not as a prerequisite for the reference runtime. -\n**Streaming in adapters**— adapters should continue to translate lifecycle hooks cleanly without taking ownership of core runtime state. -\n**Tool sandboxing**— third-party tools run in-process by default. Consider stronger isolation only when the third-party tool surface grows. -\n**Session resume**— checkpoints stay daemon-owned; document resume behavior consistently across supported harnesses.", "url": "https://wpnews.pro/news/signet-runtime-adapter-contract", "canonical_source": "https://signetai.sh/docs/specs/approved/signet-runtime/", "published_at": "2026-06-20 17:49:17+00:00", "updated_at": "2026-06-20 18:11:54.785922+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["Signet", "OpenClaw", "Claude Code", "OpenCode", "Anthropic", "Ollama"], "alternates": {"html": "https://wpnews.pro/news/signet-runtime-adapter-contract", "markdown": "https://wpnews.pro/news/signet-runtime-adapter-contract.md", "text": "https://wpnews.pro/news/signet-runtime-adapter-contract.txt", "jsonld": "https://wpnews.pro/news/signet-runtime-adapter-contract.jsonld"}}