Historical plan. The user-approved 2026-10-18 change retires endpoint activation and migrates to native profiles. Follow Native Execution Engines and its implementation plan for current delivery and validation. Do not reopen legacy network activation from this historical plan.
This document breaks the future Codex app-server, OpenClaw/ACP, Hermes, and other external-agent runner work into small implementation slices. It extends Runner Adapters without changing the current runtime boundary:
- Pi remains the only enabled runner today.
- OpenAI, Claude, Gemini, and compatible model endpoints remain provider presets when Ling only needs a model API.
- External agent runtimes become runner adapters only after they expose sessions, tool events, artifacts, cancellation, approval semantics, and resume handles.
Borrow these patterns, not the whole implementation:
- ACP protocol implementations negotiate protocol version and capabilities before using optional messages. Ling should not infer compatibility from package versions alone.
- OpenClaw treats ACP as an explicit external-harness path. It requires backend plugin
setup, allowlist policy, and
/acp doctorbefore using ACP agents. - OpenClaw documents separate routes for native Codex configuration and ACP harnesses.
Ling should keep
codex-app-serverdedicated unless a Codex target is explicitly ACP. - Hermes exposes multiple integration surfaces: ACP for editor-native clients, TUI gateway JSON-RPC for custom hosts that need full Hermes features, and an OpenAI-compatible API server for model-like clients.
- Hermes ACP maps internal tools into editor-facing tool kinds and keeps the exposed tool surface curated. Ling should do the same for external runners.
- OpenAI Agents SDK should stay a deferred dedicated-adapter decision until Ling's own sandbox policy and hydration observability gates are implemented. Treat SDK primitives as pattern inputs first; implement a sidecar only after conformance can prove the boundary.
References:
- Agent Client Protocol
- OpenClaw ACP Agents
- OpenClaw Agent Runtimes
- Hermes ACP Editor Integration
- Hermes Programmatic Integration
- Do not implement real ACP, Codex app-server, or Hermes adapters in the registry slice.
- Do not add runner ids for plain model providers such as
claude,gemini, oropenai. - Do not expose Ling secrets, MCP tools, or workspace write access directly to external agents.
- Do not let external runtime session state become the source of truth for Ling work units, attempts, approvals, artifacts, or teammate memory.
Keep the implementation modular. Adapter-specific code should not accumulate in
packages/daemon/src/index.ts, server route handlers, or the generic runner registry.
Recommended daemon layout:
packages/daemon/src/runtime/
runners.ts existing registry and resolution
runner-readiness.ts shared readiness/doctor contracts
external-agent/
session-handles.ts opaque backend session metadata
event-normalizer.ts backend event -> Ling event mapping
approval-bridge.ts side-effect proposal -> Ling approval request
artifact-importer.ts remote/local artifact import + redaction
workspace-boundary.ts cwd/path/tool boundary checks
tool-surface.ts curated tool exposure and MCP bridge policy
acp/
protocol.ts ACP wire types used by Ling
client.ts process/http client lifecycle
capabilities.ts capability negotiation and policy decisions
doctor.ts ACP endpoint readiness checks
adapters/
codex-app-server.ts dedicated Codex app-server adapter
openclaw-acp.ts OpenClaw via ACP client bridge
hermes-decision.ts decision record helpers, no runner until chosen
packages/daemon/test/test-support/
runner-conformance.ts shared adapter conformance helpers
fake-acp-server.ts ACP conformance harness
fake-codex-app-server.ts Codex app-server conformance harness
Recommended contract package additions only when the daemon API needs to expose them:
packages/contracts/src/
runtime.ts existing runner status fields
runner-readiness.ts readiness result schema if exposed over HTTP
Recommended web additions only after readiness data exists:
apps/web/src/features/settings/
RunnerReadinessPanel.tsx disabled reason, doctor result, conformance status
These snippets are design-level interfaces. Use the existing RunnerAdapter and
RegisteredRunner names when implementing; do not create a parallel runner abstraction.
export type RunnerReadinessStatus = "ready" | "blocked" | "degraded" | "unknown";
export interface RunnerReadinessReport {
readonly runnerId: string;
readonly status: RunnerReadinessStatus;
readonly checkedAt: string;
readonly featureFlagEnabled: boolean;
readonly endpointReachable?: boolean;
readonly capabilityNegotiated?: boolean;
readonly toolExecutionConformance: RunnerToolExecutionConformanceView;
readonly blockingReasons: readonly RunnerReadinessBlocker[];
}
export interface RunnerToolExecutionConformanceView {
readonly status: "verified" | "blocked";
readonly adapterRevision: string;
readonly receiptAdapterRevision: string;
readonly curatedTools: readonly string[];
readonly toolSideEffects: Readonly<Record<string, ExternalToolSideEffect>>;
readonly allowedSideEffects: readonly ExternalToolSideEffect[];
readonly nativeSideEffectMode: "disabled" | "daemon_brokered" | "unverified";
readonly toolSurfaceCurated: boolean;
readonly approvalBridgeVerified: boolean;
readonly sandboxBindingVerified: boolean;
readonly evidenceCommitVerified: boolean;
readonly abortPropagationVerified: boolean;
readonly attemptFencingVerified: boolean;
readonly workspaceBoundaryVerified: boolean;
readonly artifactImportVerified: boolean;
}
export interface RunnerReadinessBlocker {
readonly code:
| "feature_flag_disabled"
| "missing_endpoint"
| "endpoint_unreachable"
| "missing_capability_negotiation"
| "tool_conformance_not_passed"
| "tool_conformance_revision_mismatch"
| "tool_surface_not_curated"
| "tool_side_effect_mode_unverified"
| "approval_bridge_not_verified"
| "evidence_commit_not_verified"
| "abort_propagation_not_verified"
| "attempt_fencing_not_verified"
| "workspace_boundary_not_verified"
| "artifact_import_not_verified"
| "sandbox_profile_missing"
| "sandbox_backend_unverified"
| "sandbox_artifact_import_unverified"
| "sandbox_network_policy_unverified"
| "sandbox_policy_unverified"
| "sandbox_os_unsupported"
| "sandbox_execution_wrapper_missing"
| "adapter_not_implemented";
readonly message: string;
}export interface ExternalAgentSessionHandle {
readonly runnerId: string;
readonly backend: "codex-app-server" | "openclaw-acp" | "hermes-acp" | "custom";
readonly backendSessionId: string;
readonly createdAt: string;
readonly expiresAt?: string;
readonly metadata?: Record<string, unknown>;
}export interface ExternalToolProposal {
readonly backendToolCallId: string;
readonly toolName: string;
readonly input: unknown;
readonly sideEffect:
| "none"
| "workspace_read"
| "workspace_write"
| "shell"
| "network"
| "provider_or_secret"
| "external_delivery";
}A future runner can be shown in Settings while disabled, but it cannot be selected until all selection gates pass:
- feature flag is enabled;
- endpoint or launch command is configured;
- doctor/readiness probe succeeds;
- protocol/capability negotiation succeeds;
- runner conformance suite passes;
- approval bridge is verified for side-effectful tools;
- workspace boundary enforcement is verified;
- artifacts are imported into Ling artifact roots, not trusted by remote path alone.
If any gate fails, RunnerRegistry.resolve must still fail closed or fall back according
to the existing explicit/implicit fallback policy.
External agent runners receive a curated tool surface. They do not receive raw Ling internals.
| Capability | Default | Requirement before enabling |
|---|---|---|
| workspace read | allowed after path-boundary check | file reads must stay under bound workspace roots |
| workspace write | disabled | approval bridge, patch preview, and post-write evidence |
| shell | disabled | runtime permission request and command evidence |
| MCP tools | disabled | explicit MCP bridge allowlist and per-tool approval profile |
| provider secret access | forbidden | external agent must never receive raw secret values |
| external message delivery | forbidden | delivery stays owned by Ling channels/automations |
| memory/persona mutation | forbidden | external memory can return candidates only |
Use a dedicated adapter when Ling talks to a Codex server that exposes native Codex session, stream, approval, and artifact semantics. Do not route this through ACP unless the selected Codex target explicitly speaks ACP and loses no required Ling controls.
OpenAI Agents SDK is not a provider preset and is not the current default runner. It may become a dedicated adapter only if Ling wraps it behind a sidecar or equivalent boundary that exposes session lifecycle, tool events, approval interception, artifacts, cancellation, workspace constraints, and resume handles.
The design record is OpenAI Agents SDK Adapter Decision Record. Do not add a runner id until that record is refreshed against current SDK APIs and the short-term runtime hardening slices have landed.
Use openclaw-acp when OpenClaw exposes an ACP-compatible endpoint and can report
capabilities before execution. The adapter is a client bridge:
Ling run -> RunnerRegistry -> openclaw-acp adapter -> ACP client -> OpenClaw session
OpenClaw must not directly mutate Ling work state, provider secrets, teammate memory, or channel delivery.
Hermes should stay out of the runner registry until the integration surface is chosen:
- context/memory overlay only: no runner id;
- ACP server: possible ACP runner target with editor-style tool/event semantics;
- TUI gateway JSON-RPC: possible dedicated adapter if Ling needs full Hermes features;
- OpenAI-compatible API server: provider-like path, not runner execution.
Create a decision record before adding any Hermes runner id.
Use this template before adding any non-Pi runner id or adapter module:
# External Runtime Decision: <runtime-name>
## Verdict
- Integration type: provider preset | dedicated runner adapter | ACP runner target |
context/memory only | rejected
- Runner id, if any: <id or none>
- Confidence: high | medium | low | unknown
## Evidence
- Runtime surface: model API | ACP endpoint | JSON-RPC gateway | local CLI | HTTP server
- Session lifecycle: none | opaque session id | resumable session | unknown
- Tool lifecycle: none | stream-only | proposed-before-side-effect | side-effect-after-fact
- Artifact lifecycle: none | remote paths | importable artifacts | unknown
- Cancellation: none | best-effort | confirmed | unknown
- Approval interception: none | partial | confirmed | unknown
- Workspace boundary: none | configured | enforced by runtime | enforced by Ling adapter
## Decision Boundary
- Why this is not just a provider preset:
- Why ACP is or is not the right bridge:
- Which Ling controls must remain authoritative:
## Required Gates
- Feature flag:
- Readiness/doctor probe:
- Conformance tests:
- Approval bridge tests:
- Workspace boundary tests:
- Artifact import/redaction tests:
## Rejected Options
- Option:
- Reason rejected:- Add this implementation plan.
- Add a short decision record template for future external agent runtimes.
- Acceptance: docs clearly separate provider presets, dedicated adapters, and ACP harnesses.
- Add
RunnerReadinessReportschema and daemon-only readiness service. - Add disabled runner readiness to
/runnersor a new/runners/:id/readinessroute. - Acceptance: disabled future runners report why they are blocked without becoming selectable.
- Move reusable checks from
runner-conformance.test.tsinto a helper that adapter tests can reuse. - Add fixtures for capability negotiation, side-effect approval, cancellation, artifact import, and secret redaction.
- Acceptance: Pi-compatible fake runner and future fake external runner can run the same conformance suite.
- Add ACP protocol type subset, client lifecycle, and fake ACP server for tests.
- Implement initialization/capability negotiation only; no real OpenClaw dependency.
- Acceptance: fake ACP endpoint can pass readiness and fail closed when capabilities are missing.
- Add shared
approval-bridge,artifact-importer,workspace-boundary, andtool-surfacemodules. - Acceptance: fake external runner cannot perform unapproved side effects and cannot expose raw secrets in events or artifacts.
- Implement
openclaw-acpusing the shared ACP client and bridges. - Keep it disabled unless feature flag, endpoint, readiness, and conformance all pass.
- Acceptance: selecting
openclaw-acpwithout passing gates fails closed; selecting it after fake conformance executes through normalized runner events.
- Implement dedicated
codex-app-serveradapter separately from ACP. - Reuse the shared readiness, approval, artifact, and conformance modules.
- Acceptance: Codex-specific code stays in its adapter module; registry and routes stay generic.
- Write a Hermes decision record choosing context-only, ACP, TUI gateway JSON-RPC, or provider-like API server.
- Do not implement a Hermes runner until the decision states which Ling controls are preserved.
- Acceptance: no
hermesrunner id exists without a concrete session protocol and conformance plan.
- Re-check the current SDK transport, sandbox, approval, snapshot, and tool APIs.
- Decide whether Ling should build a local sidecar, use a hosted sandbox bridge, or reject SDK integration for the current cycle.
- Acceptance: the decision record names the exact integration surface and required gates; no runtime code is added in this slice.
- Keep each adapter behind a feature flag and a conformance gate.
- Keep registry code generic; adapter-specific readiness belongs in adapter modules.
- Keep approval, artifact import, workspace boundary, and tool-surface policy shared.
- Prefer fake server tests before wiring a real external process.
- Treat external logs and artifacts as untrusted evidence until imported and redacted.
- Never silently promote external memory/persona/session state into Ling teammate memory.