cd /news/ai-agents/signet-runtime-adapter-contract · home topics ai-agents article
[ARTICLE · art-35024] src=signetai.sh ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Signet Runtime Adapter Contract

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.

read9 min views1 publishedJun 20, 2026
Signet Runtime Adapter Contract
Image: Signetai (auto-discovered)

Context #

Signet 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.

The 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.

The 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.

Key constraints:

  • 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.
  • Harnesses are thin clients. They translate platform-specific events into Daemon API calls using the same interfaces the reference runtime uses.
  • SDKs for TypeScript, Rust, and Python continue to be first-class. The runtime expands what the SDKs expose, not replaces them.

What the Runtime Is #

The Signet runtime is a session execution loop that:

  • Assembles context (memory injection, system prompt, identity) via daemon
  • Sends a turn to a configured LLM provider
  • Dispatches tool calls through a registered tool registry
  • Manages the session lifecycle (start, prompt, end, compaction)
  • Records behavioral signals back to the daemon (FTS hits, continuity)

Every one of those steps is a daemon API call or a thin wrapper around one. The runtime doesn’t store state — the daemon does.

The 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.

Core Interfaces #

These interfaces define the integration contract. Implementing them is what it means to be a Signet harness.

Provider

interface Provider {
  id: string
  complete(messages: Message[], opts?: CompletionOptions): Promise<CompletionResult>
  stream(messages: Message[], opts?: CompletionOptions): AsyncIterable<CompletionChunk>
  available(): Promise<boolean>
}

interface CompletionOptions {
  model?: string
  maxTokens?: number
  temperature?: number
  tools?: ToolDefinition[]
  systemPrompt?: string
}

interface CompletionResult {
  content: string
  toolCalls?: ToolCall[]
  stopReason: 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence'
  usage: { inputTokens: number; outputTokens: number }
}

Implementations: Anthropic, OpenAI, OpenAI-compatible (Ollama, local). The runtime ships a default Anthropic provider. Additional providers are registered at startup or loaded from config.

Tool

interface Tool {
  name: string
  description: string
  inputSchema: JsonSchema
  runBeforeGeneration?: boolean  // opt-in to pre-generation research phase
  execute(input: unknown, context: ToolContext): Promise<unknown>
}

interface ToolContext {
  sessionKey: string
  project: string
  daemonUrl: string  // all daemon calls go through context, never direct
}

Tools are registered in a ToolRegistry

. The runtime dispatches tool calls from model responses through the registry. Built-in tools: memory_search

, memory_store

, memory_get

, memory_modify

, memory_forget

— thin wrappers over daemon API endpoints, identical to what the MCP server exposes.

Channel

interface Channel {
  kind: string
  receive(): Promise<UserTurn>
  send(output: AgentOutput): Promise<void>
  onClose(handler: () => void): void
}

interface UserTurn {
  content: string
  attachments?: Attachment[]
  metadata?: Record<string, unknown>
}

interface AgentOutput {
  content: string
  toolResults?: ToolResult[]
  streaming?: boolean
}

First 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.

RuntimeAdapter

This is the interface a harness implements to integrate with Signet. It maps platform-specific lifecycle events to daemon API calls.

interface RuntimeAdapter {
  harness: string  // 'openclaw' | 'claude-code' | 'opencode' | ...

  onSessionStart(params: SessionStartParams): Promise<SessionStartResult>
  onUserPromptSubmit(params: PromptSubmitParams): Promise<PromptSubmitResult>
  onSessionEnd(params: SessionEndParams): Promise<void>
  onPreCompaction(params: PreCompactionParams): Promise<PreCompactionResult>
  onCompactionComplete(params: CompactionCompleteParams): Promise<void>
}

All RuntimeAdapter

implementations are thin clients: every method body is a daemon API call. OpenClaw’s adapter implements this interface by calling the same daemon endpoints via its plugin system; other adapters translate their own lifecycle hooks into the same calls.

A harness adapter is a RuntimeAdapter

implementation. Nothing more.

Package Structure #

Signet does not maintain a monorepo-owned native runtime package. Runtime coverage is provided by connector and plugin packages under integrations/*

, with daemon endpoints as the shared contract.

Session Lifecycle #

Each turn in the execution loop:

1. Channel.receive()
     → get user message

2. executor.preGeneration()   [research phase]
     → query daemon for relevant memories (FTS + vector)
     → execute tools with runBeforeGeneration: true
     → results available to model before generation starts

3. context.assemble()
     → system prompt: identity + SOUL.md content + injected memories
     → conversation history
     → pre-generation tool results

4. Provider.complete()
     → call the model with assembled context

5. executor.dispatch()        [tool loop]
     → for each tool call in response: ToolRegistry.execute()
     → append results to messages
     → loop back to Provider.complete() until stop_reason = end_turn

6. daemon POST /api/hooks/user-prompt-submit
     → record FTS hits for behavioral signals (predictive scorer training)

7. Channel.send()
     → deliver output to user

Session start:

daemon POST /api/hooks/session-start
  → memories injected into first-turn system prompt
  → continuity checkpoint loaded if session resumed

Session end:

daemon POST /api/hooks/session-end
  → triggers continuity scoring job
  → memory extraction pipeline enqueued
  → session checkpoint saved

The pre-generation research phase (step 2) is the key architectural difference from a naive chat loop. Tools that declare runBeforeGeneration: true

execute before the model sees the user message. The model gets grounded context instead of generating then correcting.

Daemon API Dependency Map #

Every runtime action maps to a daemon endpoint. This table defines the integration surface a harness adapter must cover for full parity:

Runtime Action Daemon Endpoint Phase
Session start context POST /api/hooks/session-start start
Per-prompt context + FTS POST /api/hooks/user-prompt-submit turn
Session end / extraction POST /api/hooks/session-end end
Pre-compaction summary POST /api/hooks/pre-compaction cmpct
Save compaction result POST /api/hooks/compaction-complete cmpct
Memory search (tool) POST /api/memory/recall any
Memory store (tool) POST /api/hooks/remember any
Memory get (tool) GET /api/memory/:id any
Memory modify (tool) POST /api/memory/modify any
Memory forget (tool) POST /api/memory/forget any
Secret injection POST /api/secrets/exec tool
Daemon health check GET /health start

A harness adapter that covers all rows has full parity with the reference runtime. Partial coverage is valid — the delta is explicit and auditable.

SDK Surface Additions #

The runtime expands SDK surface without breaking existing API.

TypeScript SDK (@signet/sdk)

New exports:

RuntimeAdapter

interfacecreateAdapter(harness, daemonUrl?)

— factory returning a pre-wired client implementing each lifecycle method as a daemon API callRuntimeAdapterServer

— HTTP server exposing adapter lifecycle as endpoints (for harnesses that prefer HTTP over library import)

Rust SDK

RuntimeAdapter

traitAdapterClient

struct — pre-wired daemon HTTP clientRuntimeAdapterServer

— axum-based server for HTTP attachment

Python SDK

RuntimeAdapter

abstract base classAdapterClient

— aiohttp-based daemon clientRuntimeAdapterServer

— FastAPI-based server for HTTP attachment

Any harness in any language can implement RuntimeAdapter

using the appropriate SDK, call the same daemon endpoints, and have full parity with the reference runtime.

Harness Adapter Pattern #

An adapter is a translation layer with no business logic. Example:

// @signetai/adapter-openclaw — full implementation
import { createAdapter } from '@signet/sdk'

export default function createPlugin(opts: { daemonUrl?: string }) {
  const adapter = createAdapter('openclaw', opts.daemonUrl)

  return {
    onSessionStart:     (ctx) => adapter.onSessionStart({
      sessionKey: ctx.session.id, project: ctx.workspace
    }),
    onUserPromptSubmit: (ctx) => adapter.onUserPromptSubmit({
      sessionKey: ctx.session.id, prompt: ctx.prompt
    }),
    onSessionEnd:       (ctx) => adapter.onSessionEnd({
      sessionKey: ctx.session.id
    }),
    onPreCompaction:    (ctx) => adapter.onPreCompaction({
      sessionKey: ctx.session.id, messageCount: ctx.messages.length
    }),
    onCompactionComplete: (ctx) => adapter.onCompactionComplete({
      sessionKey: ctx.session.id, summary: ctx.summary
    }),
  }
}

When Signet adds a new capability, it adds a daemon endpoint and a new RuntimeAdapter

method. Each harness adapter adds one translation. There is no duplicated business logic to reason about or diverge.

Build Sequence #

Phase 1: daemon-owned contract

  • Preserve the daemon-owned execution contract: memory, hooks, secrets, and session state stay daemon-side
  • Deliverable: supported harnesses share the same lifecycle semantics where their host APIs allow it

Phase 2: harness parity

  • Keep Bun daemon, Rust daemon, and existing harness adapters aligned on the same daemon endpoints
  • Treat external harnesses as thin adapters over the same runtime contract
  • Deliverable: integration deltas are explicit and auditable

Phase 3: SDK adapter ergonomics

  • Add or refine adapter helpers in the SDKs so external harnesses can implement the runtime contract with less boilerplate
  • Keep adapter logic translation-only; business logic stays in the daemon/runtime boundary
  • Deliverable: new harness integrations have a minimal documented path

Phase 4: optional runtime transport expansion

  • If a supported harness exposes a stable local API, document it as an extension of the same runtime contract instead of a separate architecture
  • Deliverable: transport choices can evolve without changing the core daemon contract

What This Is Not #

  • Not a replacement for the daemon. All state lives in the daemon. The runtime is stateless at the contract boundary.

  • Not a new memory system. Memory is still pipeline v2 + predictive scorer

  • knowledge graph, owned by the daemon.

  • Not breaking for existing harnesses. OpenClaw/Claude Code/OpenCode work through their own adapters and remain additive integrations.

  • Not a config system. Config lives in agent.yaml, read by the daemon.

Critical Files #

Runtime contract + adapters:

libs/sdk/

integrations/openclaw/connector/

integrations/claude-code/connector/

integrations/opencode/connector/

Daemon surfaces:

platform/daemon/

platform/daemon-rs/

Open Questions #

Provider config— provider selection and model live in agent.yaml. Keep the runtime-facing schema minimal and daemon-owned. - Multi-provider routing— route different task types to different providers when there is a concrete need, not as a prerequisite for the reference runtime. - Streaming in adapters— adapters should continue to translate lifecycle hooks cleanly without taking ownership of core runtime state. - Tool sandboxing— third-party tools run in-process by default. Consider stronger isolation only when the third-party tool surface grows. - Session resume— checkpoints stay daemon-owned; document resume behavior consistently across supported harnesses.

── more in #ai-agents 4 stories · sorted by recency
── more on @signet 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/signet-runtime-adapt…] indexed:0 read:9min 2026-06-20 ·