Skip to content

[Enhancement]: Epic: serve harnesses' native interfaces through one adapter contract #547

Closed as duplicate

Description

@cv

Current scope (2026-11-06): This closed issue preserves the historical specification and research. #577 now scopes GTC DC P0 to OpenClaw and Hermes. Deep Agents Code adapter, cold resume, mounted skills and qualification are deferred until after GTC DC in #566; historical dcode P0 references below do not restore that delivery commitment. Shared Fabric lifecycle and transport requirements remain applicable. LangChain Deep Agents is unaffected.

Closed on 2026-11-02. GTC DC P0 work is tracked in #577, and deferred work, including #565 and #550 to #560, in #566. The P0 specification below still applies; #578 (cold resume) and #579 (OpenClaw's and Hermes's terminals) extend it.

Affected area: Harness adapter; Schemas or configuration; Consumer integration; Documentation or examples

How to read this issue. This description is the specification. It consolidates the decisions, clarifications and reviews that were posted as comments; those comments are hidden as outdated, and the change log records what changed. To change the specification, edit this description and add a change-log line. Sub-issues hold only their adapter's details.

Problem or opportunity

Consumers that run agents for people, such as NVIDIA NemoClaw, want to offer each harness's own interfaces: an API, a browser dashboard with chat, or a terminal UI. Fabric's adapters configure and supervise the harness, so they are the only place that can turn those interfaces on safely. Answered separately, each adapter would get a different shape for settings, credentials, ports and health, and each consumer would need adapter-specific code to reach them. This epic defines one contract and leaves each adapter's specifics to its sub-issue.

Sub-issue Priority Harness Interfaces Adapter work Effort
#541 P0 OpenClaw Control UI (browser dashboard and chat) A setting on the managed runtime's Gateway, plus its per-start token file; already shown working with a one-line change Small
#540 P0 Hermes API server, dashboard, browser chat Runtime-bound Hermes API server and dashboard children; the service mode is deferred (#566) Medium
#546 P0 Deep Agents Code Terminal UI, optionally ACP A new adapter around dcode's agent server Large
#550 Negotiable Kilo Code Terminal UI, ACP Report the kilo serve the adapter already runs, so kilo attach opens the terminal UI on it Small
#551 Negotiable Codex Terminal UI Open the terminal UI on the runtime's app-server or a shared app-server daemon Medium
#552 Negotiable OpenCode Web UI, terminal UI Depends on what OpenCode v2 provides; otherwise a service mode Medium
#553 Negotiable Qwen Code Terminal UI, ACP Attach to the runtime's session, through qwen serve if available Medium
#554 Negotiable Droid Terminal UI Attach to the runtime's session, through droid daemon if the SDK uses it Medium
#555 Negotiable Claude Code Terminal UI Open the terminal UI on the runtime's session, for example as a background session Medium
#556 Negotiable Pi Terminal UI Open the terminal UI; sharing needs a session file instead of memory Medium
#557 Negotiable Cline Terminal UI, ACP Open the terminal UI with the runtime's configuration, attached if the core allows Medium
#558 Negotiable Kiro Terminal UI Open the terminal UI with the runtime's configuration, on its session if Kiro allows Medium
#559 Negotiable Strands Interactive CLI Open the interactive CLI with the runtime's configuration, on its session if Strands allows Medium
#560 Negotiable OpenHands Agent server API, browser UI A service mode running openhands-agent-server Medium to large
#565 After P0 All Discovery and shared code Generalize from the P0 adapters Medium
#566 Deferred All – Deferred contract items –

Not covered: the Deep Agents SDK adapter has no native interface (#546 covers Deep Agents Code); mini-swe-agent offers only an interactive terminal and a trajectory inspector; NOOA's nooa start-dev is a trace and evaluation viewer, not an agent console; and the remote-agent adapter's remote service owns its interfaces.

Status

The P0 specification below is confirmed by the epic's author, including #540's runtime-bound scope, and two specification reviews judged it implementable. An adversarial review then raised nine questions, which the epic's author settled on 2026-10-30; the requirements below include the answers, and Resolved questions records them. A Fabric maintainer can still object; until one does, implementers build against this text.

Reference implementation. NemoClaw's team builds the P0 adapters in cv/NeMo-Fabric to validate this specification and unblock NVIDIA/NemoClaw#14011. The Fabric team can review, cherry-pick or merge from it at its own pace. Gaps found while building it are settled by editing this description.

P0 specification

Applies to #541 (OpenClaw), #540 (Hermes) and #546 (Deep Agents Code). The IDs are for tests and reviews; a requirement marked with a question (Qn) was settled by that question's answer below.

Settings

  • S1. harness.settings.interfaces is an object of named interfaces. Absent means off; present means on unless enabled: false; enabled defaults to true.
  • S2. HTTP interfaces require port: an exact loopback port with no default, so the consumer always knows it. Before launching a child, start checks that nothing listens on its port; after the child passes health, start confirms that the child is still running and owns the listening socket. Otherwise start fails because the port is taken (Q5). The ownership check reads Linux procfs; on other platforms, only the check before launch applies. Adapters with derived ports state which ones they check and which they reserve (Q7).
  • S3. Two interfaces of one runtime can't share a port. Equal ports fail start; planning doesn't catch them, because JSON Schema can't compare sibling values.
  • S4. Terminal interfaces take no port.
  • S5. public_origins, for browser interfaces that support it (OpenClaw's dashboard in P0; not Hermes's), is a list of origins (scheme://host[:port]) accepted in addition to http://127.0.0.1:<port> and http://localhost:<port>. OpenClaw accepts any loopback origin regardless of this list, with its token still required, so a consumer doesn't need to list a different local forward port; public_origins matters for non-loopback origins (Q6).
  • S6. Adapters may add interface-specific fields.
  • S7. Names in P0: OpenClaw dashboard; Hermes api and dashboard; Deep Agents Code terminal.
  • S8. Each P0 descriptor lists exactly its supported names under settings_schema.properties.interfaces.properties, with additionalProperties: false there and on each interface object. Fabric's existing settings_schema validation then rejects unsupported names and fields during planning, and a consumer can check the image catalog's descriptor before deploying.
  • S9. There is no optional field in P0; an enabled interface that can't start always fails start ([Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566).

Lifecycle

  • L1. Interfaces live with a managed runtime, because consumers that drive agents through fabric-agent-ctl have no service operations. Services (prepare_service(), attach_service()) reject interfaces with a stable error before starting anything; service binding is [Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566.
  • L2. An interface can't start when its child process exits, doesn't pass its health endpoint within the startup timeout, or fails S2's port check. start then fails, stops the children it already started, and leaves no token files or markers (Q1, Q5).
  • L3. startup_timeout_seconds is one deadline for all of start: children start concurrently and must all pass health within it. Default 60, maximum 75, which leaves time for cleanup within core's 90-second limit on an adapter's start. Adapters without a startup timeout gain this setting; OpenClaw's existing one also became one deadline for all of start, with the same default and maximum (Q3).
  • L4. An interface that is the agent's own server is covered by the runtime check. A separate interface process never fails the runtime check, and Fabric doesn't restart it.
  • L5. stop first removes the runtime's token files and interface markers, then sends SIGTERM to each interface child's process group, waits up to 5 seconds, and sends SIGKILL to what remains; in-flight interface requests are cut off. Where the harness has its own shutdown drain, the adapter configures it below 5 seconds (Q4).
  • L6. A consumer-chosen port stays fixed across restarts; the adapter fails rather than choosing another.
  • L7. If core kills or loses the adapter host (a crash, or core's start or stop limit), nothing removes the runtime's token files and markers in P0, and processes the adapter didn't start as its children aren't tracked (A4). Each adapter's README documents this (Q2, Q8).

Paths

  • P1. <artifact root> is the runtime's Fabric artifact root: RuntimeContext's artifact directory, or exactly <base>/artifacts when it's unset, regardless of where an adapter keeps its other files; /sandbox/artifacts in NemoClaw.
  • P2. <adapter> is openclaw, hermes or deepagents-code.
  • P3. Tokens are at <artifact root>/.fabric/<adapter>/runtimes/<runtime-id>/interfaces/<name>.token for interfaces that have one: OpenClaw's dashboard and Hermes's api. Hermes's dashboard and Deep Agents Code's terminal have none.
  • P4. Deep Agents Code's terminal launcher is .../interfaces/terminal, with its marker .../interfaces/terminal.running. The launcher checks only the marker, so after L7 it can open a session against the old runtime's configuration until the sandbox is replaced (Q2).
  • P5. These paths stay valid after [Enhancement]: Interfaces: generalize discovery and shared adapter code from the P0 adapters #565, whose handle fields report the same locations.

Credentials and output

Access

  • A1. Interfaces listen on loopback only. Exposing them is the consumer's job, for example with OpenShell's forward service.
  • A2. Discovery is by convention: the consumer chooses the port, and finds the token at P3 using the runtime ID that fabric-agent-ctl start returns. Handle fields and a read-only status command come in [Enhancement]: Interfaces: generalize discovery and shared adapter code from the P0 adapters #565 (fabric-agent-ctl: read-only status command for the owned runtime #420).
  • A3. Terminal interfaces are reached with openshell sandbox exec -n SANDBOX --tty -- <launcher path>. openshell sandbox connect attaches to the sandbox's main process, which in NemoClaw is fabric-agent-ctl serve, not a shell; sandbox exec has no -t short form.
  • A4. Hermes's dashboard has no sign-in in P0. Hermes's dashboard (v0.22.4 and v0.22.5) embeds its session token in the page it serves, and that token only gates direct API calls; a non-loopback dashboard.public_url switches on Hermes's provider-based auth gate, which refuses to start without a configured provider. So the dashboard is reached only through a gateway-authorized forward, which is its access control, with no dashboard token and no public_origins; its README says anyone who can reach its port, including the agent's own tools, can use it. Exposed service URLs for it wait for [Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566. Hermes's API server keeps its Bearer token. The dashboard's gateway actions, such as restart and migrate, start detached Hermes processes that replace the Fabric-started gateway and can outlive stop; P0 documents this (Q8).

Configuration and behavior

  • C1. The adapter generates the harness's configuration from FabricConfig at each start. Every harness process that calls a model, including interface children, uses FabricConfig's endpoint and the key from its api_key_env, set in that process's configuration only (Q9). Changes made through an interface aren't supported and don't survive a restart; each adapter documents this.
  • C2. Conversations in an interface aren't promised to share threads with Fabric invocations; each adapter documents its behavior.
  • C3. Update checks, automatic updates and telemetry that an interface's harness makes on its own stay off unless configured.
  • C4. Each README documents what an environment needs for its interfaces (packages, built frontends, binaries) and the interface features that would let the agent bypass its guardrails, such as approvals, configuration edits or plugin installation; disabling them is [Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566.

Resolved questions

An adversarial review reproduced findings 1 to 8 against the pinned harnesses and Fabric core, and a TLA+ model of the lifecycle raised Q1. The epic's author settled all nine on 2026-10-30.

ID Question and evidence Decision Requirements
Q1 Does a failed start remove the token it already wrote? (model) Tokens and markers are written only after every child passes health, so a failed start leaves none. L2, T1
Q2 Who removes tokens and markers when core kills or loses the adapter host, and how does the separate-session launcher know the runtime is live? Core's stop reports success after a crash, the files stay, and a restart uses a new runtime ID. (findings 3, 4, 7) Accepted for P0 and documented: the files can stay, and the launcher, which checks only the marker, can open a session against the old configuration until the sandbox is replaced. L7, P4
Q3 Two children started one after the other, each healthy after 50 seconds, exceed core's 90-second start limit. (finding 4) One deadline for all of start; children start concurrently; default 60, maximum 75. L3
Q4 Hermes's gateway drains for 30 seconds with a request in flight, past core's 10-second stop limit, so api.token stays. (finding 7) stop removes files first, then SIGTERM, 5 seconds, SIGKILL; adapters set the harness's own drain below 5 seconds. L5
Q5 The health check passes against whatever already holds the port. (finding 5) Check the port is free before launch; after health, confirm the child runs and owns the listening socket. S2, L2
Q6 OpenClaw accepts any loopback origin even with an explicit allowedOrigins list. (finding 1) Accepted for P0 and documented; public_origins is for non-loopback origins. Exact-origin enforcement is to be raised with OpenClaw upstream. S5
Q7 OpenClaw's browser relay binds base+10, outside the documented reservation. (finding 2) Reserve the whole range from base to base+110; start checks only base and base+2. S2, #541
Q8 Hermes's dashboard gateway actions start detached processes that replace the Fabric-started gateway and outlive stop. (finding 6) Documented as a P0 limitation. A4, L7
Q9 Hermes's API-server child didn't use FabricConfig's endpoint and key. (finding 8) Every Hermes child that calls a model uses them, set in that child's configuration only, and a test against a local endpoint checks the requests' URL and key. C1, #540

Follow-up: exact-origin enforcement is requested upstream in openclaw/openclaw#190113 (Q6).

Earlier questions, answered for P0: credentials are regenerated at each start (T1), retained ones wait for state_dir (#566); interfaces are runtime-bound (L1); terminal interfaces are launched by path (A3) until #565 reports a command in the handle; conversations aren't promised as shared (C2).

State chart of the P0 lifecycle, before Q1 and Q2 were settled
---
title: "P0 interface lifecycle, scratch model of NeMo-Fabric issue 547"
---
stateDiagram-v2
  state "Legend" as legend {
    lg0: live
    lg1: ended
    lg2: Breaks MarkerOnlyWhileRunning or NoChildOutlivesItsRuntime or TokenOnlyWhileServing
    lg3: Path to the MarkerOnlyWhileRunning or NoChildOutlivesItsRuntime or TokenOnlyWhileServing bug
  }
  class lg0 teal
  class lg1 tan
  class lg2 breaks
  class lg3 bugPath
  s0: Not started
  s1: Starting
  s2: Adapter killed (token file left)
  s3: Start failed (token file left)
  s4: Adapter killed (children left running, token file left)
  s5: Running
  s6: Adapter killed (children left running, token file left, marker left)
  s7: Stopped
  s8: Adapter killed (token file left, marker left)
  [*] --> s0
  s0 --> s1: start writes the token
  s1 --> s2: the adapter is killed
  s1 --> s3: a child exits or times out
  s1 --> s1: a child launches or passes health
  s1 --> s4: the adapter is killed
  s4 --> s2: a leftover child exits
  s1 --> s5: every child passes health
  s5 --> s6: the adapter is killed
  s5 --> s7: stop
  s6 --> s8: a leftover child exits
  s5 --> s8: the adapter is killed
  classDef breaks fill:#ffecee,stroke:#e72e3b,stroke-width:2.5px,color:#cd2834
  classDef teal fill:#bfe3df,color:#000
  classDef tan fill:#e5d9ce,color:#000
  classDef bugPath stroke:#1680f3,stroke-width:2.5px
  class s0 teal
  class s0 bugPath
  class s1 teal
  class s1 bugPath
  class s2 tan
  class s2 breaks
  class s3 tan
  class s3 breaks
  class s3 bugPath
  class s4 tan
  class s4 breaks
  class s4 bugPath
  class s5 teal
  class s5 bugPath
  class s6 tan
  class s6 breaks
  class s6 bugPath
  class s7 tan
  class s8 tan
  class s8 breaks
Loading

The model and TLC configuration are in this comment. It models the text before the answers, not code: Q1 removes the failed-start path, and Q2 accepts the paths through "Adapter killed" for P0.

Pinned versions

Component P0 targets Fabric main (624e687) NemoClaw v1 image (Fabric 7aedb66) Review ran against
OpenClaw 2026.9.34 2026.9.34 2026.9.4 Both; findings 1 and 2 hold on both
Hermes v0.22.5, the latest release (bf0203a) v0.22.5 v0.22.4 (4d04303) v0.22.4
Deep Agents Code Set by #546 Not pinned Not packaged 0.2.2
OpenShell – – 0.1.6 (ac7f615c) –

A4 and the Hermes findings were established on v0.22.4, while Fabric main tests v0.22.5, 261 commits later. A spot check of v0.22.5 found the same agent.cron_drain_timeout (default 30) and agent.restart_drain_timeout settings, the POST /api/gateway/restart route, the browser-chat flag and APIServerAdapter; #540's reference implementation confirmed A4 on v0.22.5, where the loopback dashboard still embeds its session token.

Target contract (after P0)

The full contract the P0 adapters start from. #565 delivers discovery and shared code; #566 tracks the rest. Where this list differs from the P0 specification, the P0 specification wins for P0.

  1. Settings. Each interface is opt-in under harness.settings.interfaces.<name> (S1 to S9). Browser interfaces add public_origins, which the adapter maps to the harness's own setting, such as Hermes's dashboard.public_url, OpenClaw's allowedOrigins or OpenCode's --cors. An adapter rejects an interface it doesn't support, or one its lifecycle shape can't serve, during planning.
  2. Discovery ([Enhancement]: Interfaces: generalize discovery and shared adapter code from the P0 adapters #565). The descriptor declares the interfaces an adapter supports, with each one's kind (HTTP, terminal or stdio). The service or runtime handle reports each enabled interface's loopback port and path, and where its credential is, never the credential itself, so a consumer can forward any adapter's interfaces without adapter-specific code.
  3. Network. Interfaces listen on loopback only, and exposing them is the consumer's job. Each interface declares its authentication scheme (Bearer, Basic, custom header, cookie or verifier) and which exposure paths it works through ([Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566).
  4. Credentials. Two modes, declared per interface. Adapter-generated (P0: T1 to T3). Consumer-supplied verifier ([Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566): the consumer keeps the raw credential outside the agent's environment, and the harness, or a verifying proxy in the adapter, receives only its hash. Each adapter documents the interface features that would let an agent bypass its guardrails, and disables them in the adapter-generated mode ([Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566).
  5. State ([Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566). state_dir keeps the harness's state and the credential across runtimes and services, under a stable consumer-chosen key, with one owner at a time and documented stale-lock recovery, resume or a documented lack of it, documented concurrent writes, the harness version that wrote it, and cleanup that never deletes it.
  6. Configuration ownership. Generated from FabricConfig at each start (C1). A terminal command reported in a handle includes the adapter's configuration flags and environment. Fabric-owned settings (model and endpoint, keys, instructions, tools, MCP servers, skills, approval mode, telemetry, workspace) can't be changed through an interface; session choices stay within what FabricConfig declares; personal preferences are retained in state_dir. The handle or ready check reports a configuration digest and drift ([Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566).
  7. Lifecycle. The descriptor declares whether each interface lives with a runtime or a service (P0: runtime, L1). Startup failure, fixed ports, failure and stop as L2 to L6; optional interfaces, service binding, terminal clients honoring a state lock and exiting when the runtime stops, and serializing interface turns with Fabric invocations or reporting them in activity are [Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566.
  8. Health. P0 keeps separate interface processes out of the runtime check (L4). An interface check target with live, active and ready levels, declared in the descriptor, is [Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566.
  9. Conversations. Each adapter documents whether interface conversations share threads with Fabric invocations (C2); a shared default is [Enhancement]: Epic: deferred until after GTC DC: interface contract items and other harnesses #566.
  10. Outbound requests. As C3.
  11. Packaging. As C4, and the descriptor lists the binaries it runs.

Implementation strategy

Step Work Depends on Issue
A Agree the P0 specification and the scope changes to #540, #541 and #546 (done, including Q1 to Q9) – #547
B OpenClaw Control UI A #541
C Hermes, runtime-bound API server and dashboard children A #540
D Deep Agents Code: investigate terminal attachment first, then the adapter A #546
E Consumer integration and qualification, for example NemoClaw's images, settings, forwarding, token instructions and live tests B, C, D NVIDIA/NemoClaw#14011
F Generalize from B, C and D: descriptor interface names and kinds, the shared settings fragment, interfaces in RuntimeHandle, a read-only status command (#420), shared Python code B, C, D #565
G Deferred contract items and the negotiable adapters (#550 to #560) F #566

B, C and D run in parallel. GTC needs A to E.

Public contract and compatibility impact

  • Rust API: none expected, unless handle discovery needs typed fields.
  • Python API: none; consumers use the existing service and runtime calls.
  • Schemas or adapter contract: descriptor fields for supported interfaces, handle fields for enabled ones, and each adapter's interfaces settings.
  • Harness or consumer integrations: a consumer reads interface ports and credential locations from handles instead of adapter-specific knowledge.
  • Backward compatibility: additive; every interface defaults to off.

Alternatives considered

  • Each adapter on its own. It works, but consumers would integrate a different shape for each of the fourteen sub-issues, and later adapters would add more.
  • A generic native-configuration passthrough. It lets consumers turn interfaces on themselves, but it widens the contract and lets their configuration conflict with Fabric-owned settings (see [Enhancement]: OpenClaw adapter: browser dashboard and chat #541).
  • Consumer-side supervision. Consumers would duplicate configuration, credential, state and lifecycle handling outside Fabric and drift from it.

Acceptance criteria

Additional context

NemoClaw's counterparts: NVIDIA/NemoClaw#13969 (Hermes), NVIDIA/NemoClaw#13360 (OpenClaw's dashboard) and NVIDIA/NemoClaw#13973 (signing in to it). NemoClaw forwards loopback services through OpenShell and doesn't patch adapter-owned configuration (NVIDIA/NemoClaw#13318).

Background research

The research behind the contract, moved here from comments. It records what was found and proposed at the time; where it differs from the P0 specification, the specification wins.

Implementation notes from Fabric `main` at `4fdf65a`

Implementation notes from Fabric main at 4fdf65a: nothing named interfaces exists yet, but these pieces can carry the shared contract.

  • Settings validation: each descriptor's settings_schema is a JSON Schema for harness.settings. fabric-core validates harness.settings against it during planning (config.rs) and reports errors at the setting's path, such as harness.settings.port_range. An interfaces property in a descriptor is validated with no new core code.
  • Closest existing settings: OpenClaw's port_range (start and end, spanning at least 111 ports), agent_id, its timeouts, and channel_config, in a schema that rejects unknown properties.
  • Declaring interfaces: capabilities has service and a free-form metadata object, which OpenClaw already uses for health_checks and service_health_checks. Supported interfaces could be declared there or in a new typed field.
  • Reporting interfaces: ServiceHandle has connection, a "sanitized connection summary" that "never contains credential values", and metadata. Each enabled interface's loopback port, path and credential location fits there.
  • Gap: no shared schema fragments. Each descriptor is self-contained, with no shared $defs or $ref. Shared settings are copied into each descriptor and have drifted: insecure_http_model_hosts accepts IPv6 literals in Codex's descriptor but only hostnames in Kilo's, while their runtime handling lives in common code (model-endpoint.ts, relay_http.py). The interfaces schema should be defined once, for example as a fragment in the adapter contract that a build step or check keeps identical in every descriptor, rather than copied into fourteen.
Discovery design (field names are proposals; #565 settles them)

Design proposal for discovery. The field names are proposals, not decisions.

Two discovery questions
  1. Before deployment: which interfaces does an adapter support? Fabric resolves descriptors from *.fabric-adapter.json files and its bundled catalog. NVIDIA NemoClaw copies installed adapters' complete descriptors into its image label, and its compatibility check treats an absent claim as unknown, not supported. A typed interfaces declaration in descriptors would reach consumers' planning with no consumer changes.

  2. After start: where is an enabled interface, and how does a person sign in? Today:

    • ServiceHandle has connection and metadata, but services exist only through the SDK's prepare_service() and attach_service().
    • fabric-agent-ctl, which sandboxed consumers use, offers serve, check, start, invoke and stop: no service operations, and no way to read a runtime's handle after start.
    • RuntimeHandle carries identity, harness, adapter, service_id and environment, but no connection details.

    So a consumer that drives agents through fabric-agent-ctl can't reach a service-shaped interface such as [Enhancement]: Hermes adapter: API server, browser dashboard and chat #540 proposes, or rediscover an interface after start. This answers the open question on managed runtimes: either runtimes may enable interfaces, or fabric-agent-ctl gains service operations. Either way, RuntimeHandle needs to report interfaces, and fabric-agent-ctl needs a read-only command that returns the current handle (fabric-agent-ctl: read-only status command for the owned runtime #420).

Proposed shape

Descriptor: what an adapter supports. A typed top-level field rather than free-form capabilities.metadata, so its absence is meaningful:

"interfaces": {
  "dashboard": { "kind": "http", "browser": true, "credential": "bearer", "lifecycles": ["service", "runtime"], "shares_conversations": false },
  "terminal":  { "kind": "terminal", "lifecycles": ["runtime"], "shares_conversations": true }
}

Settings: what a consumer enables, using the one shared interfaces schema fragment from the comment above, validated through settings_schema:

harness:
  settings:
    interfaces:
      dashboard: { enabled: true, port: 19119, allowed_origins: ["https://example.test"] }

Handles: what is running. RuntimeHandle and ServiceHandle report each enabled interface, without secrets:

"interfaces": {
  "dashboard": { "kind": "http", "address": "127.0.0.1", "port": 19119, "path": "/",
                 "credential_file": "/sandbox/artifacts/.fabric/hermes/runtimes/RUNTIME_ID/interfaces/dashboard.token" },
  "terminal":  { "kind": "terminal", "argv": ["kilo", "attach", "http://127.0.0.1:4096"],
                 "credential_env_files": { "KILO_SERVER_PASSWORD": "/sandbox/artifacts/.fabric/kilo/runtimes/RUNTIME_ID/interfaces/terminal.token" } }
}

A terminal interface reports the command to run in the agent's environment, which answers the open question on terminal interfaces without a consumer knowing each harness's CLI.

Consumer flow

For NemoClaw:

  1. YAML sets harness.settings.interfaces.
  2. Planning checks it against the image's descriptor.
  3. fabric-agent-ctl start returns the handle with interfaces.
  4. NemoClaw keeps the non-secret fields.
  5. It tells the person how to forward each port with OpenShell and how to read the credential file.
  6. A read-only fabric-agent-ctl command returns the current handle on later reads.
Network and credentials

Network and credentials, from OpenShell's documentation and CLI at ac7f615c (the revision NVIDIA NemoClaw pins) and the harnesses' own authentication code.

Two ways to reach an interface
openshell forward service SANDBOX --target-port P --local PORT openshell service expose SANDBOX PORT [NAME]
Transport TCP over the gateway; WebSockets and any authentication scheme work HTTP, at a gateway-managed URL
Authorization Each connection against the caller's gateway identity and workspace "Service routes bypass control-plane RPC authorization"; the application's own authentication is the only check
Host the browser sends 127.0.0.1:PORT, which loopback Host checks accept <sandbox>--<name>.openshell.localhost:<port> on a loopback gateway, or HTTPS on a remote one
Authorization header Unchanged Stripped by default; --authorization-mode bearer-passthrough forwards one Bearer credential; Basic fails with 400

Consequences:

  • Origins. Hermes's dashboard accepts only loopback Hosts or its dashboard.public_url, against DNS rebinding; OpenClaw has allowedOrigins; OpenCode has --cors. Every browser interface needs a public-origin setting for exposed URLs, not just OpenClaw's ([Enhancement]: OpenClaw adapter: browser dashboard and chat #541).
  • Schemes. Kilo and OpenCode servers use HTTP Basic (KILO_SERVER_PASSWORD, OPENCODE_SERVER_PASSWORD), which exposed URLs reject; OpenHands's agent server uses an X-Session-API-Key header, which browsers can't add. Those interfaces work through forward service only, unless the application signs browsers in itself.
  • The agent can reach its interfaces. OpenShell's network policy doesn't cover sandbox-local loopback (policies/network-rules.mdx: "a workload can reach a service listening on 127.0.0.1").
Who a credential stops

Everything in a sandbox runs as one identity (run_as_user):

Who Path Credential
The person, through forward service Gateway-authorized Defense in depth
Anyone who reaches an exposed URL Not gateway-authorized The only protection
Other processes on a loopback gateway's host Local service URL Needed
The agent's own tools Same sandbox and identity: the loopback port and owner-only files (but not another process's environment; see the correction below) Doesn't stop a file-based credential

So with an adapter-generated token in a 0600 file, a prompt-injected agent can sign in to its own interface and, depending on the harness, approve its own tool calls, change its model or tools, or edit cron jobs and plugins: it escapes the harness's guardrails, though not OpenShell's policy.

OpenShell documents the mitigation: keep the raw token outside the sandbox and give the harness only its SHA-256 verifier. Codex's app-server supports it (--ws-auth capability-token --ws-token-sha256). Hermes, OpenClaw, OpenCode, Kilo, Qwen and OpenHands take a raw token or password; Deep Agents Code's agent server has no authentication.

Correction, after testing. In NVIDIA NemoClaw's Hermes sandbox, openshell sandbox exec runs as the harness's identity (uid 10001), but the harness processes run with no-new-privileges, a seccomp filter and Yama ptrace_scope 1: the /proc/<pid>/environ of fabric-agent-ctl serve, the Hermes adapter, and even a plain process started from another exec weren't readable. Owner-only files of that identity are readable. So a credential held only in a harness process's environment isn't readable by other processes, though the harness's own children inherit it unless it scrubs them; a credential in a file is readable by the agent's tools.

Proposed additions to the contract
  1. Credential modes. Adapter-generated, the current proposal, which the agent can use; and consumer-supplied verifier, where the consumer keeps the raw token outside the sandbox and the harness, or a small verifying proxy in the adapter, receives only its hash. Each descriptor declares the modes it supports, and the documentation states that the first doesn't protect against the agent.
  2. Declared scheme and exposure compatibility. Each interface declares its authentication scheme (Bearer, Basic, custom header, cookie or verifier) and which exposure paths it works through, so a consumer can choose forward service or an exposed URL without knowing each harness.
  3. One public_origins setting for browser interfaces, which the adapter maps to Hermes's dashboard.public_url, OpenClaw's allowedOrigins or OpenCode's --cors.
  4. Guardrail-sensitive features. Each adapter documents which interface features would let an agent bypass its guardrails, such as approvals, configuration edits or plugin installation, and disables them in the adapter-generated mode.
State and configuration ownership

State and configuration ownership, from NVIDIA NemoClaw's state documentation (which lists every pinned adapter's state locations) and the adapters' READMEs on Fabric main.

State today
Adapter Native state After a runtime restart
Hermes Home and session database under .fabric/hermes/runtimes/<runtime-id> A new directory; empty memory
Deep Agents SQLite checkpoints per runtime key A new thread ID; the old file doesn't resume
OpenClaw A private temporary directory Removed on stop
Pi, OpenCode, mini-SWE-agent In memory Lost
Claude, Codex Generated files under .fabric/<adapter>; native login and history not inventoried A new client or thread

No adapter reattaches a new runtime to saved native state, and consumers such as NemoClaw replace the sandbox on any configuration change. Interface state (sessions started in the interface, preferences, sign-ins made through it, and its credential) would therefore disappear on every restart, and mounting a volume doesn't help while directories are keyed by runtime ID.

Proposed for state_dir
  1. Stable key. Named by the consumer, for example by deployment and agent, rather than by runtime ID, so restarts and replaced sandboxes find it again; consumers put it on a volume when it must outlive the sandbox.
  2. Resume. A new runtime continues the retained sessions, or the adapter documents that it doesn't; otherwise an interface starts empty after every restart.
  3. One owner, with recovery. An exclusive lock, and a documented way to recover a stale lock after a crash or a killed sandbox.
  4. Concurrent writers. A Fabric invocation and a person may write the same session store at once. Server-shaped harnesses (OpenClaw's Gateway, kilo serve) arbitrate this; an in-process SDK plus a separate terminal process may not, for example two processes on Hermes's SQLite database or Pi's in-memory session. Each adapter documents its behavior.
  5. Layout. Generated configuration (regenerated each start), sessions (retained), the interface credential (retained or rotated, per the credential-lifetime question), personal preferences (retained) and logs.
  6. Versions. The adapter records the harness version that wrote the state and refuses or migrates it explicitly when Fabric's harness pin moves.
  7. Cleanup and sensitivity. Adapter cleanup never deletes a consumer-provided state_dir. Its contents include credentials and sign-ins and are as sensitive as a home directory.
Configuration ownership

Adapters already generate native configuration and disable ambient configuration: Claude's setting_sources defaults to []; OpenHands, OpenCode and Pi disable user and project configuration; Qwen uses an isolated configuration; Droid doesn't copy ambient settings; OpenClaw generates openclaw.json; Codex uses a private CODEX_HOME. Interfaces add three problems:

  1. Separate processes read ambient configuration. A terminal UI started by a person (pi, kiro-cli, cline -i, dcode) loads the user's configuration unless it's pointed at the adapter's, and can then run with a different model, tools or MCP servers. The terminal command a handle reports must include the adapter's configuration flags and environment, not only the executable.
  2. Interface edits diverge. Interfaces can switch models, add MCP servers, install plugins or edit skills. Those changes bypass the consumer's declared configuration, Fabric's usage accounting and Relay tracing, and are silently lost at the next start.
  3. Classes of settings:
Class Examples Rule
Fabric-owned Model and endpoint, keys, instructions, tools, MCP servers, skills, approval mode, telemetry, workspace Not changeable through an interface; the adapter locks or hides them where the harness allows, and regenerates them each start
Session choices Switching between declared models, reasoning effort Allowed only within what FabricConfig declares
Personal preferences Theme, key bindings, layout Allowed, and retained in state_dir

Drift. The handle or the ready check reports a digest of the generated configuration and whether the live configuration still matches it, so a consumer can warn that an agent no longer matches its declared configuration.

Lifecycle and health

Lifecycle and health, from Fabric's health-check guide, OpenClaw's service documentation and the runtime server README on main, and NVIDIA NemoClaw's Fabric provider.

Today
  • Managed runtimes own their harness processes; OpenClaw's managed Gateway stops with its runtime. prepare_service() services are Fabric-owned; attach_service() services are caller-owned and never stopped by Fabric; release is rejected while runtimes are connected; resuming after the Fabric process exits isn't supported yet.
  • fabric-agent-ctl serve: stop doesn't establish that descendants exited, the sandbox supervisor cleans up, fabric.unclean records unconfirmed cleanup, and SIGTERM during work reports runtime_busy. Fabric doesn't restart a failed harness.
  • Health: server, runtime and service scopes (service only through the local SDK, not fabric-agent-ctl); cumulative live, active and ready levels with adapter-specific evidence; passed, failed, unknown and unsupported; one three-second deadline; activity reported separately and counting only Fabric invocations. Droid, Kiro and Strands lack active and ready; mini-SWE-agent, NOOA and remote-agent advertise no levels.
  • NemoClaw's provider runs fabric-agent-ctl check --agent --runtime-id; failed means not running, so OpenTofu plans to replace the sandbox, while unknown and unsupported count as running.
Lifecycle proposals
  1. Binding. The descriptor declares whether each interface lives with a runtime (Kilo's server, terminal attach) or a service (OpenClaw's Gateway, [Enhancement]: Hermes adapter: API server, browser dashboard and chat #540's Hermes service). Consumers limited to fabric-agent-ctl can use runtime-bound interfaces until it gains service operations.
  2. Start. An explicitly enabled interface that can't start, for example because its port is taken, fails start with a stable error, unless the settings mark it optional.
  3. Ports. A consumer-chosen port stays fixed across restarts, so forwards keep working; the adapter fails rather than choosing another. Unchosen ports are adapter-selected and reported in the handle.
  4. Failure. When the interface is the agent's own server (OpenClaw's Gateway, kilo serve, Deep Agents Code's agent server, a Hermes service), its failure is the runtime's or service's failure. A separate interface process, such as feat(hermes): run Hermes as a native API server with retained state #327's dashboard child, isn't restarted, as elsewhere in Fabric: its health fails and the runtime keeps running.
  5. Stop and release. Stopping a runtime or releasing a service closes its interfaces. Terminal clients started by a person, for example through openshell sandbox exec --tty, aren't descendants of fabric-agent-ctl serve and can outlive the runtime, and a client with its own session store could keep writing state. Terminal clients should honor the state_dir lock: attach while a runtime holds it, never open the store independently, and exit when the runtime stops.
  6. Concurrent turns. A person in an interface and a Fabric invocation can address one session at once; activity doesn't count interface turns, and the health guide notes that activity "does not establish that an agent will accept a second concurrent prompt". Each adapter serializes the two or reports interface turns in activity, and documents which.
Health proposals
  1. An interface target, for example fabric-agent-ctl check --agent A --runtime-id R --interface dashboard --ready, so an interface failure doesn't fail the runtime check; otherwise a broken dashboard would make NemoClaw replace the sandbox. The exception is an interface that is the agent's own server, which the runtime check already covers.
  2. Levels: live: the process runs and its port is bound. active: its unauthenticated health endpoint answers (Hermes /health and /api/health, OpenClaw /healthz, LangGraph /ok). ready: an authenticated request with the stored credential succeeds and the configuration digest still matches. In the consumer-supplied verifier mode the adapter has no raw credential, so ready can only confirm that authentication is required.
  3. Boundary. Fabric checks loopback inside the agent's environment; the exposure path (an OpenShell forward or service URL) is the consumer's to check, and person-started terminal clients aren't Fabric's.
  4. Advertising. The descriptor lists each interface's supported levels beside health_checks and service_health_checks.

Change log

Duplicate check

  • I searched the existing issues and did not find a duplicate.

Activity

  1. 22 remaining items

  2. jyaunches commented on Nov 2, 2026

    @jyaunches

    Closing in favor of #577, agreed with the Fabric team on 2026-11-02. #577 tracks the GTC DC scope: OpenClaw, Hermes and Deep Agents Code, with cold resume first (#578), terminal UIs (#579) and skills on mounted volumes (#580). The P0 specification in this description still applies, extended by #578 and #579. #565 and #550 to #560 moved under #566, which tracks everything deferred until after GTC DC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

enhancementNew feature or requestgtc-dc-blockerBlocks GTC DC readiness or delivery.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions