# OpenLinker Agent Node Chinese documentation: [README.zh-CN.md](./README.zh-CN.md) Agent Node connects an Agent you already have to OpenLinker. It runs next to the existing backend, receives a task from OpenLinker Runtime, starts or calls the backend, and returns the answer. It can adapt: - a local HTTP service; - an operator-chosen command; - an A2A JSON-RPC Agent; - a non-interactive Codex or Claude Code process, through this repository's native adapters. Agent Node is a deployable bridge for existing backends, not a mandatory intermediary. If a new Go, TypeScript, or Python Agent can use an OpenLinker SDK Runtime Worker directly, it does not need Agent Node. A stable public HTTPS Agent or remote MCP server also does not need it. ## How it works 1. The `openlinker-go` Runtime Worker receives and safely records a task. 2. Agent Node passes the task to the selected local backend. 3. The backend returns its answer; Agent Node sends it back through the SDK. 4. If OpenLinker cancels the task, command and native provider adapters stop their process trees. The long-lived Agent Token stays inside Agent Node. A backend that needs to call another Agent receives a short-lived localhost helper (HTTP/command) or Attempt-scoped MCP delegation tools (native Codex/Claude) instead. ## Technical boundary Agent Node does not implement a Runtime client or state machine. The pinned Go SDK owns discovery and the token-only or mTLS security policy, Session identity, WebSocket/Pull switching, assignment confirmation, lease renewal, resume, cancellation, drain, the encrypted journal, and stable Event/Result replay. The same SDK behavior is available directly to any Go application through `NewRuntimeWorker`. This repository owns environment and CLI parsing, Adapter selection, localhost helper sessions, process-tree control, the public A2A compatibility listener shell (cards, authentication, and request limits), and the choice of SDK file-store directory. The SDK proxies that listener's A2A operations to Core, except for stateless Agent Card responses whose external URL must remain the AgentNode listener. Cancellation reaches an Adapter through the SDK handler context; command and native provider Adapters terminate their own process trees before returning. The public `pkg/adapters` packages also provide protocol leaves that Plugin can reuse at compile time; that does not require running a Node process. Neither the Node module nor its package graph depends on CLI or Plugin. Plugin keeps its independent deep execution policy and Browser/Viewer/Profile products. Agent Node connects only to the Core Runtime contract. It does not call Hosted service-listing, order, wallet, billing, or marketplace-operation APIs, and it does not provide an MCP Adapter. ```mermaid flowchart LR Core["OpenLinker Core"] <-->|"Runtime protocol"| SDK["openlinker-go RuntimeWorker"] SDK --> Handler["Agent Node Adapter"] Handler -->|"HTTP, command, A2A, Codex, or Claude"| Backend["Private Agent backend"] Backend -->|"run-scoped helper"| Handler SDK --- Store["SDK FileRuntimeStore"] A2AClient["Legacy A2A client"] --> Compat["AgentNode card/auth/limits"] Compat --> Proxy["openlinker-go RuntimeA2AProxy"] Proxy --> Core ``` ## Page-managed skill packages Native Codex/Claude adapters consume the same owner-selected package versions as Plugin hosts. Import SKILL.md and supporting UTF-8 files in OpenLinker, associate an exact version with the Agent, and connect a compatible Node. New Runs snapshot that selection; retries retain their original versions. HTTP, command and A2A bridges do not advertise this contract: remote backends need an explicit loader. The SDK delivers Core-owned snapshots separately from ordinary task metadata. After native session isolation chooses the workspace, Node verifies the payload and dependencies, prepares `.openlinker-skills///` there, and emits durable loaded/failed evidence. Only a repository rooted at the execution workspace with an internal gitdir receives a local exclude rule; a cache-local .gitignore protects nested or external worktrees without editing their metadata. No install scripts run, and skills grant no additional tools or credentials. Adding, removing or upgrading packages starts a fresh provider session while preserving Core conversation history. Unchanged versions resume normally. Full instructions appear on a new/recovered session; resumed turns include a short SKILL.md path index so the model can reread after compaction. Load evidence does not prove model use. Immutable cache versions remain available for older Runs; this version does not automatically garbage-collect them. Damaged same-UID cache entries are replaced with a separate verified recovery copy, leaving the original tree untouched and starting a fresh provider session. This is recovery from prior corruption, not protection against a still-running process with the Host UID. Prerequisite lookup uses the Provider PATH and configured native tool read roots; it does not execute commands or certify that all runtime dependencies will work. The reusable `pkg/adapters/skillpackages` leaf owns package validation, immutable files and prompt fragments. Plugin consumes it without depending on Node's host, Worker lifecycle or full execution policy. This source change requires a new Node release and an explicit rollout before existing installations gain support. ## Status and installation Agent Node is pre-1.0 and intended as a bridge for existing backends, not as the default way to build a new Agent. Pin the Core, Go SDK, and Agent Node versions together and review `CHANGELOG.md` before upgrading. Prebuilt binaries for Linux, macOS, and Windows, together with adjacent `.sha256` files, are available from [GitHub Releases](https://github.com/OpenLinker-ai/openlinker-agent-node/releases). Verify the checksum before installing a binary. Contributors can build from source with the commands below. ### Candidate build identity and test-only enrollment This source candidate adds `openlinker-agent-node --version`. It returns the same implementation identity used for Runtime enrollment before reading configuration, starting a Provider/listener, opening SDK state, or making a network request. No arguments starts the configured Worker; unsupported arguments fail without startup. The internal agent-host v1 commands described under native delegation also bypass Worker startup. Older published binaries do not necessarily have these entry points; check the selected artifact. Source builds report `openlinker-agent-node/dev`; packaged builds inject the exact `v...` tag or `sha-...` identity. The release builder is `node scripts/build-agent-node.mjs ` (Node.js 22 and Go are build tools, not requirements for running a downloaded binary). Stable v0.2.0 packaging additionally requires the executable Core-controlled upgrade gate, using real old/candidate binaries and immutable source commits. See [the exact compatibility scope and operator procedure](docs/controlled-upgrade.md). The MCP-capable candidate matrix requires Core schema 099 with explicit target features; this does not authorize arbitrary version replacement or automatic rollback. Canonical pre-1.0 test prereleases remain available, while unsupported stable/v1+ tags stay blocked. For a test deployment with no real users, the supported change-of-version path is explicit fresh enrollment: 1. Stop new test calls, confirm all old Attempts have settled and the SDK spool is empty, then stop the old test process. Retain its private data directory; do not erase state or run two Workers on one directory. 2. Obtain a new, unbound, active Agent credential through the existing Core registration/token flow. Use a new `OPENLINKER_NODE_ID` and a new private `OPENLINKER_AGENT_NODE_DATA_DIR`; do not reuse the old binding, identity, certificates, or keys. Discovery still selects token-only or mTLS enrollment. 3. Start the exact selected binary, verify its `--version` and new Core readiness, then verify a test task. A changed Node identity is intentional; this is not an upgrade of the old enrollment or an automatic rollback mechanism. An ordinary restart of an **active** Node retains the exact binary version, Node identity, credential and SDK DataDir. The SDK rotates the Runtime Session; do not create a fresh directory on each restart. This does not cover a revoked Node. Administrative drain recovery and the tested version pair instead use [Core-controlled operations](docs/controlled-upgrade.md). Replacing other enrolled versions without authorization is unsupported and can fail with `ContractMismatch`; never spoof the old version. Fresh test enrollment needs no upgrade extension. These are source-level compatibility and release-policy boundaries, not proof that this candidate has been published or deployed. Record real WebSocket and pull enrollment, task execution and same-DataDir restart results before declaring a target environment verified. See [RELEASE.md](./RELEASE.md). ## Quick start Prerequisites: - Go 1.26.4 or newer - an active Agent Token - a private, persistent data directory - a local backend Build and test: ```bash go test ./... go build ./cmd/openlinker-agent-node ``` Platform discovery decides whether the Runtime is token-only or requires mTLS. For mTLS, the SDK can create the private key inside the data directory and automatically enroll and renew a short-lived client certificate. Run a local HTTP backend: ```bash OPENLINKER_URL=https://openlinker.example \ OPENLINKER_AGENT_ID=22222222-2222-4222-8222-222222222222 \ OPENLINKER_AGENT_TOKEN=ol_agent_xxx \ OPENLINKER_AGENT_NODE_DATA_DIR=/var/lib/openlinker-agent-node \ OPENLINKER_AGENT_NODE_TRANSPORT=auto \ OPENLINKER_AGENT_NODE_ADAPTER=http \ OPENLINKER_AGENT_NODE_HTTP_URL=http://127.0.0.1:18080/run \ go run ./cmd/openlinker-agent-node ``` The SDK `FileRuntimeStore` takes a single-process lock on the data directory. Keep it on persistent local storage, back it up as sensitive state, and never share one directory between two node processes. The SDK's encrypted spool is bounded to 512 MiB and 10,000 records. At 80% usage the worker advertises capacity zero and stops accepting new Runs while it continues renewal, cancellation, upload, and cleanup for existing Attempts. Data writes cannot consume the final 16 MiB of logical or filesystem capacity, leaving space for journal/control progress. Corruption, authentication failure, a missing key, or exhausted capacity fails closed; unacknowledged Results are never expired or deleted. ## Required Agent Node configuration At startup, the Go SDK Worker reads the public connection manifest from `$OPENLINKER_URL/.well-known/openlinker.json` and discovers the dedicated Runtime origin. Discovery uses a separate five-second HTTP client, follows no redirects, reads at most 64 KiB, and sends neither the Agent Token nor a client certificate. The same manifest selects token-only or mTLS security. A missing, disabled, insecure, or malformed Runtime entry stops startup instead of falling back to the ordinary API origin. | Variable | Purpose | | --- | --- | | `OPENLINKER_URL` | OpenLinker platform origin used to discover the Runtime connection | | `OPENLINKER_NODE_ID` | Optional existing Runtime Node UUID; token-only discovery derives a stable token-scoped value when omitted | | `OPENLINKER_AGENT_ID` | Agent UUID; required for token-only Runtime | | `OPENLINKER_AGENT_TOKEN` | Long-lived Agent Token kept inside the node | | `OPENLINKER_AGENT_NODE_DATA_DIR` | Directory selected for the SDK `FileRuntimeStore` | | `OPENLINKER_AGENT_NODE_MTLS_CERT_FILE` | Optional external-PKI certificate used only when discovery requires mTLS | | `OPENLINKER_AGENT_NODE_MTLS_KEY_FILE` | Optional external-PKI private key; configure the complete cert/key/CA group | | `OPENLINKER_AGENT_NODE_MTLS_CA_FILE` | Optional external-PKI CA bundle | | `OPENLINKER_AGENT_NODE_MTLS_SERVER_NAME` | Optional certificate server-name override | | `OPENLINKER_AGENT_NODE_TRANSPORT` | `auto` (default), `ws`, or `pull`; all share one Runtime session | `OPENLINKER_RUNTIME_URL` is an advanced connection-address override for tests and private routing. Platform discovery still runs and remains authoritative for the security policy; an override cannot downgrade an mTLS manifest. Normal deployments should leave it unset. A Runtime URL without `OPENLINKER_URL` uses the legacy direct mode and requires a complete mTLS configuration. Useful tuning options are `OPENLINKER_AGENT_NODE_CAPACITY`, `OPENLINKER_AGENT_NODE_CLAIM_WAIT_SECONDS`, `OPENLINKER_AGENT_NODE_COMMAND_WAIT_SECONDS`, `OPENLINKER_AGENT_NODE_HEARTBEAT_SECONDS`, `OPENLINKER_AGENT_NODE_RETRY_MIN_MS`, and `OPENLINKER_AGENT_NODE_RETRY_MAX_MS`. Use `auto` for normal deployments. Use `ws` when operators prefer the node to wait for WebSocket recovery instead of serving through long-poll. Use `pull` only for networks where WebSocket is known to be unavailable. Transport changes are implemented by the SDK and reuse the current session identity, journal, encrypted spool, leases, and per-Run cancellation state. ## Backend envelope HTTP and command backends receive a run envelope. When the local helper is enabled, its URL and run-scoped credential are included under `agent_node`: ```json { "input": { "query": "..." }, "run_id": "run uuid", "metadata": {}, "agent_node": { "helper": { "base_url": "http://127.0.0.1:12345", "token": "run-scoped helper token", "endpoints": { "call_agent": "http://127.0.0.1:12345/a2a/call", "events": "http://127.0.0.1:12345/events" } } } } ``` The long-lived Agent Token and assignment-scoped invocation capability are not passed to the backend. ## Adapter modes ### `http` / `openclaw` POST the run envelope to a local HTTP service: ```bash OPENLINKER_AGENT_NODE_ADAPTER=openclaw OPENLINKER_AGENT_NODE_HTTP_URL=http://127.0.0.1:18080/run ``` ### `command` Write the envelope to an operator-configured command's stdin. Cancellation terminates the command process tree. ```bash OPENLINKER_AGENT_NODE_ADAPTER=command OPENLINKER_AGENT_NODE_COMMAND=/usr/local/bin/my-agent OPENLINKER_AGENT_NODE_ARGS='["run","--json"]' ``` ### `a2a` Forward the run to an A2A JSON-RPC Agent: ```bash OPENLINKER_AGENT_NODE_ADAPTER=a2a OPENLINKER_AGENT_NODE_A2A_BASE_URL=http://127.0.0.1:31225/rpc OPENLINKER_AGENT_NODE_A2A_METHOD=SendMessage ``` Set `OPENLINKER_AGENT_NODE_A2A_DIALECT=legacy` only for an upstream Agent that still expects slash-style methods such as `message/send`. ### `codex` Run Codex non-interactively. By default each conversation gets a private workspace under native session isolation (see [native session isolation](#native-session-isolation-without-docker)): ```bash OPENLINKER_AGENT_NODE_ADAPTER=codex OPENLINKER_AGENT_NODE_CODEX_BIN=codex OPENLINKER_AGENT_NODE_CODEX_WEB_SEARCH=true # Optional; defaults to $HOME/.local/state/openlinker-agent-node-sessions and # must be outside any git worktree. OPENLINKER_AGENT_NODE_SESSION_ROOT=/srv/openlinker/node-sessions ``` To bridge Codex in an existing workspace instead, turn isolation off explicitly. The model can then see that workspace's files, git status/history and project instructions: ```bash OPENLINKER_AGENT_NODE_ADAPTER=codex OPENLINKER_AGENT_NODE_SESSION_ISOLATION=off OPENLINKER_AGENT_NODE_CODEX_BIN=codex OPENLINKER_AGENT_NODE_CODEX_WORKSPACE=/srv/openlinker/codex-work OPENLINKER_AGENT_NODE_CODEX_SANDBOX=workspace-write OPENLINKER_AGENT_NODE_CODEX_WEB_SEARCH=true ``` `OPENLINKER_AGENT_NODE_CODEX_WEB_SEARCH` defaults to **true**, passing `web_search="live"` on new and resumed conversations. Set it to `false` to pass `web_search="disabled"`, which turns search off even when the host client enables it. This changes only the native search tool: approvals, sandbox, session reuse, delegation, and shell network access retain their configured policies. It does not enable Plugin Browser. The selected model/provider must also support search. Values are case-insensitive and whitespace-trimmed: true/false, 1/0, yes/no, on/off; unset or empty is true. Other values fail before startup without echoing their contents. Configuration is fixed for the Node process lifetime. Releases before this change defaulted to false, so upgrading a Node that never set the variable turns search on; set `false` first to keep it off. Releases through `v0.1.58-rc.3` did not wire this setting; changing the host Codex config or setting the variable cannot repair those binaries. Use a release containing this fix and follow the fresh-enrollment version-change procedure above. ### `claude` Native session isolation is the default, as for Codex: ```bash OPENLINKER_AGENT_NODE_ADAPTER=claude OPENLINKER_AGENT_NODE_CLAUDE_BIN=claude OPENLINKER_AGENT_NODE_CLAUDE_WEB_SEARCH=true # Optional; defaults to $HOME/.local/state/openlinker-agent-node-sessions and # must be outside any git worktree. OPENLINKER_AGENT_NODE_SESSION_ROOT=/srv/openlinker/node-sessions ``` Bridging Claude in an existing workspace requires an explicit opt-out; the model can then see that workspace's files, git status/history and CLAUDE.md: ```bash OPENLINKER_AGENT_NODE_ADAPTER=claude OPENLINKER_AGENT_NODE_SESSION_ISOLATION=off OPENLINKER_AGENT_NODE_CLAUDE_BIN=claude OPENLINKER_AGENT_NODE_CLAUDE_WORKSPACE=/srv/openlinker/claude-work OPENLINKER_AGENT_NODE_CLAUDE_PERMISSION=dontAsk OPENLINKER_AGENT_NODE_CLAUDE_WEB_SEARCH=true ``` `OPENLINKER_AGENT_NODE_CLAUDE_WEB_SEARCH` defaults to **true**: the bridge adds `WebSearch` and `WebFetch` to `--allowedTools`, because `dontAsk` refuses any tool that is not allowed. It adds exactly those two, once, on top of `OPENLINKER_AGENT_NODE_CLAUDE_ALLOWED_TOOLS`; with native session isolation the isolated tool list keeps its own WebSearch rule. Set `false` to pass `--disallowedTools WebSearch,WebFetch` instead. It does not change `dontAsk`, `--safe-mode`, managed policy, Browser or delegation, and it is not an OS sandbox or a general network-deny switch. Search still needs a model/account that offers it. Values are case-insensitive and whitespace-trimmed: true/false, 1/0, yes/no, on/off; unset or empty is true. Other values fail configuration before startup without echoing the value. Releases before this change defaulted to false and did not grant the two tools. Configuration is fixed for the Node lifetime, not hot-reloaded. The `v0.1.57-rc.1` binary did not read this setting; setting an environment variable cannot repair that binary. Use a release containing this fix and follow the fresh-enrollment version-change procedure above; do not relabel it as rc.1. Both native adapters use this repository's `pkg/adapters`; shared protocol parsing, private session storage and process mechanisms live in its public leaf packages. The complete Codex app-server turn lifecycle is shared through [`codexturn`](pkg/adapters/codexturn/README.md); Node and Plugin keep their own launch/tool/session policy and use the same cancellation and shutdown flow. On macOS, ordinary native Codex additionally retains identity-checked observed descendants across process-group changes and reparenting, with bounded cleanup after scoped interruption and EOF shutdown. This is not an arbitrary process-tree or hostile-code isolation guarantee; see the leaf's [ownership and test limits](pkg/adapters/codexturn/README.md#macos-native-cancellation-and-ownership). No SDK, credential, session-store or tool-policy configuration changes are required. The pinned SDK is `v0.2.0-rc8.0.20260914164420-63fc87d73406`. Startup checks the installed CLI version and required flags (tested baselines: Codex 0.153.0, Claude 2.1.259). Codex uses bounded JSONL final messages; Claude streams normalized progress. `CODEX_SESSION_REUSE` / `CLAUDE_SESSION_REUSE` and `*_SESSION_STORE` with the `OPENLINKER_AGENT_NODE_` prefix configure native session reuse. Core history is synchronized on resume. Legacy Node Codex maps are not imported: a new session is seeded from Core history. The old plaintext session-key output and model-visible localhost helper credentials have been removed. Native session reuse defaults to **true** under native isolation (the default), which rejects an explicit `false`. With `SESSION_ISOLATION=off` it defaults to **false**; enable it explicitly when preserving an existing workflow, and keep workspace and session-store paths stable for reuse. Separate directories alone do not isolate processes running under the same OS identity from local files or other sessions. With isolation off, Claude/Codex reuse is scoped to the Core-provided principal, Agent and current Core conversation, in addition to provider and workspace. Without that trusted context, calls still execute but do not read or write reuse maps. Existing unscoped maps are retained, but are not resumed: the first call under the new scope starts a session from Core history. Existing native-isolation maps continue to resume in their already scoped workspace/store. Defaults, store paths, history synchronization and missing-session recovery are unchanged. Successful Claude results provide two optional, long-lived diagnostics: - `claude_resume_session_id_sha256`: SHA256 of the exact ID passed to `--resume` in the successful invocation; omitted when that invocation did not resume. - `claude_session_id_sha256`: SHA256 of the exact nonempty `result.session_id` returned by Claude; omitted when unavailable, never backfilled from a request. They are lowercase hexadecimal SHA256 values of the UTF-8 IDs, not credentials or authorization inputs. Missing-session recovery clears evidence from the failed invocation; a successful fresh retry does not claim a successful resume. Missing fields do not prove matching sessions. These hashes remain correlatable Run diagnostics and follow result access/retention policy; raw session IDs are not added to the result. The existing `claude_session_reuse` field is emitted only when reuse is enabled and a trusted session key is available. To enable native delegation, set `OPENLINKER_AGENT_NODE_DELEGATION_TARGETS` to a JSON array of allowed Agent UUIDs. The default transport host is the running `openlinker-agent-node` executable itself. No OpenLinker CLI or Plugin install is required. `OPENLINKER_AGENT_NODE_DELEGATION_PROXY_BIN` is an optional explicit host override; it must implement the frozen `openlinker.agent-host.v1` contract. Node implements `plugin capabilities` and `plugin delegation-proxy --host codex|claude`. The `plugin` prefix is the v1 protocol name, not a dependency on Plugin. Node advertises `delegation_proxy` only and rejects Browser commands. These subprocess commands never start a Worker or read Node serving configuration. Check the installed binary with: ```bash openlinker-agent-node plugin capabilities # {"protocol":"openlinker.agent-host.v1","browser_proxy":false,"delegation_proxy":true} ``` `OPENLINKER_AGENT_NODE_DELEGATION_BROKER_ROOT` optionally selects a private socket directory. Delegation is disabled by default and requires the SDK/Core `delegated_run_read.v1` extension. `CLAUDE_ALLOWED_TOOLS` with the same Node prefix takes a JSON string array; `OPENLINKER_AGENT_NODE_TIMEOUT_MS` applies to both providers. Native providers receive scoped MCP tools, not helper tokens. **Claude delegation uses `--bare`**, which does not use OAuth/Keychain login. Startup requires `ANTHROPIC_API_KEY` or `ANTHROPIC_API_KEY_FILE`, before probing Claude or starting the SDK Worker. The file must be a nonempty regular file owned by the Node user, inaccessible to group/other users (for example mode `0600`), at most 64 KiB, and not a symlink. The file source requires POSIX; Windows users must use the direct key until DACL checks are implemented. Setting both sources is an error. Node resolves the key once and passes the value to Claude without the file path. The delegation proxy clears inherited API keys and platform tokens at startup and only transports MCP over its private socket. Restart Node after rotating the key. This checks configuration, not remote key validity. Ordinary Claude without delegation keeps its existing native authentication; the optional file source is also supported there. Codex delegation does not gain a Claude API-key requirement. ## Events and delegated Agent calls The localhost helper is enabled by default for `http`, `openclaw`, and `command`. Command backends also receive: ```text OPENLINKER_AGENT_NODE_HELPER_URL OPENLINKER_AGENT_NODE_HELPER_TOKEN OPENLINKER_AGENT_NODE_HELPER_CALL_AGENT_URL OPENLINKER_AGENT_NODE_HELPER_EVENTS_URL ``` Every delegated Agent call must provide an `idempotency_key`. Reuse the same key when retrying the same call intent; use a new key for a distinct intent, even when its request body is identical. ```bash curl -X POST "$OPENLINKER_AGENT_NODE_HELPER_CALL_AGENT_URL" \ -H "Authorization: Bearer $OPENLINKER_AGENT_NODE_HELPER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target_agent_id":"target-agent-uuid","idempotency_key":"invoice-42-review-v1","reason":"review","input":{"invoice_id":"42"}}' ``` ```bash curl -X POST "$OPENLINKER_AGENT_NODE_HELPER_EVENTS_URL" \ -H "Authorization: Bearer $OPENLINKER_AGENT_NODE_HELPER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"event_type":"run.message.delta","payload":{"text":"working"}}' ``` Programmatic adapters follow the same rule: `RunContext.CallAgent` rejects a call whose `CallAgentOptions.IdempotencyKey` is empty. ## Optional public A2A compatibility listener Agent Node can expose a local inbound A2A URL for compatibility. The listener serves the local Agent Card and validates `OPENLINKER_PUBLIC_A2A_TOKEN`; the Go SDK forwards every message, task, push-notification, stateful JSON-RPC, and SSE request to Core over the configured Agent Token and discovered security policy. REST and JSON-RPC Agent Card responses remain local so their external URL continues to name this listener. Core remains the sole authority for A2A task, run, stream, and push state. The listener is disabled by default: ```bash OPENLINKER_AGENT_NODE_PUBLIC_A2A=true OPENLINKER_AGENT_NODE_PUBLIC_A2A_HOST=127.0.0.1 OPENLINKER_AGENT_NODE_PUBLIC_A2A_PORT=19091 OPENLINKER_AGENT_NODE_PUBLIC_A2A_SLUG=my-agent OPENLINKER_AGENT_NODE_PUBLIC_A2A_NAME="My Agent" OPENLINKER_PUBLIC_A2A_TOKEN=optional-bearer-token ``` The compatibility listener caps each request body at 1 MiB and applies bounded header/body read timeouts. SSE responses have no listener-wide write deadline; their lifetime follows client cancellation and the Core stream. ## Security and operations - Treat the Agent Token, any mTLS private key, SDK-managed spool key, assignment payloads, and helper tokens as secrets. - Do not mount the runtime data directory into backend containers. - Keep command and native provider workspaces isolated and narrowly permissioned. - Stop is not a substitute for a confirmed drain. Fence admissions and verify Core settlement and empty SDK spool before an operational switch; stopping a process can cancel active adapters. Preserve state when shutdown cannot finish. - On SIGTERM or SIGINT the Node stops through the SDK Worker's Stop, which gives in-flight Runs the SDK's 10s grace before canceling adapters, then closes the SDK store. The whole shutdown is bounded at 15s; a second signal while it is still running exits immediately through the same timeout path. Give the service manager more than 15s (for example `docker stop -t 20`) for a clean exit. - Redact credentials, private URLs, customer payloads, and adapter logs before filing an issue. - Alert before the SDK spool reaches 80%. Free space or complete existing uploads; never delete `.record`, journal, identity, or key files by hand. See [SECURITY.md](./SECURITY.md), [SUPPORT.md](./SUPPORT.md), and [CONTRIBUTING.md](./CONTRIBUTING.md). ## License Apache-2.0. See [LICENSE](./LICENSE). ## Shared file mechanisms and Handler compatibility The `pkg/adapters/appfiles` leaf owns strict JSON decoding, private file and secret I/O, atomic replacement and cross-process app locks. Node consumes it for helper request decoding and native credential files; Plugin can reuse it without a Node process. Product defaults, persistent paths and lock lifetime remain with callers. The published `adapters.NewHandler` and `adapters.Handler` API remains available in `handler_compat.go` for source compatibility, with regression coverage. It is deprecated for new integrations: use `NewProvider` and compose the SDK handler in the host. Node itself uses `NativeAdapter`. ## Native session isolation without Docker Run `openlinker-agent-node --check-config` with the **same environment as the service** before starting it. The JSON reports the build version, configured search/isolation/session policy and capacity. It omits credentials, endpoints, identities and local paths. It performs configuration parsing only: no Worker, provider, network request, login or state write, and `provider_preflight` is `not_run`. A successful check does not establish filesystem enforcement or model/search availability. Normal startup logs the same policy before its existing provider/OS preflight checks. `native_isolation_disabled` means the Node-specific native boundary was turned off with `SESSION_ISOLATION=off`; ordinary Codex `read-only` is not a substitute for that boundary. `serial_host_auth_capacity_gt_one` recommends capacity 1 so assigned Runs do not spend their timeout waiting for the host-auth lock. These diagnostics do not change any setting. `http_adapter_url_missing`, `command_adapter_command_missing` and `a2a_adapter_base_url_missing` mean the selected adapter's target variable is unset, so every Run will fail; startup also logs this warning but still proceeds as before. Codex and Claude default to `SESSION_ISOLATION=native` with a private session root outside any git worktree (default `$HOME/.local/state/openlinker-agent-node-sessions`); set `off` explicitly to bridge a client in an existing workspace without isolation. Releases through `v0.1.58-rc.3` do not include `--check-config`. Explicit native-only `SESSION_READ_PATHS` / `SESSION_NETWORK_DOMAINS` values, including `[]` or `null`, are rejected while isolation is off. Remove those settings for ordinary mode or explicitly enable native isolation; an empty network list must not be mistaken for an enforced offline policy in off mode. **Experimental and on by default for Codex/Claude, trusted callers only; no per-session resource quotas.** Node reuses the installed Codex/Claude client's authentication. Native mode does not require another login or new API-key environment variables. The official client owns authentication. Claude defaults to sandboxed Bash; its in-process file tools require explicit opt-in and have a different security boundary. Codex disables in-process image viewing and host notification commands in this mode. One Node retains multiple conversations and persistent A-B-A continuation. Native clients default to serial admission per shared host HOME/provider to reduce shared-login refresh overlap. `HOST_AUTH_CONCURRENCY=client-managed` explicitly opts into parallel clients; independently launched desktop/terminal clients are not locked. See [host-auth native isolation](docs/native-session-isolation.md) for macOS/Linux setup, supported clients, migration, gateway behavior and verification limits. This changes source behavior; it does not upgrade installed binaries or switch running Agents automatically. Ordinary/delegation behavior remains unchanged. ## Owner-selected MCP tools Select registered MCP service tools on the Agent's Skill / MCP settings page. A compatible Host advertises `mcp_bindings.v1` after checking its local proxy and writable broker root. Core supplies a trusted selection snapshot on the next Run; the Host configures the installed Codex/Claude client with only those tools. No service credential is sent to the client. The same selection resumes the existing scoped conversation; changing or removing tools starts a fresh model session so stale tool configuration is not reused. Existing scope and native isolation defaults remain unchanged. Tool discovery and successful calls report separate receipts. A configured tool proxy that never loads fails the Run with `MCP_TOOLS_UNAVAILABLE`, rather than silently claiming tools are available. Calls use Core-authorized child Runs and may continue for up to 30 seconds per MCP request. A pending result includes a request key that can be repeated without executing the operation again. Removal revokes subsequent calls even from an already running parent. Owner-private associations are not public listings; callers may still discover tool names/results and exercise granted side effects. ### Existing-identity mTLS preparation With a compatible Core/SDK release, a stopped Node can run `openlinker-agent-node runtime-credential prepare --data-dir PATH --node-id ID --agent-id ID --worker-id ID` as the existing Runtime data owner with the original `OPENLINKER_NODE_ID` (or original `OPENLINKER_AGENT_TOKEN` for a token-derived Node ID) in its environment. The command checks that configuration against `--node-id` before writing a key, then delegates offline key/CSR preparation to the SDK. It starts no Provider or Worker and preserves the original Worker epoch and acknowledged spool. Core must separately authorize the exact key, credential, revision and drained Node before migration or early rotation. Default native isolation remains enabled. Keep Runtime keys outside Provider workspaces and validate that the sandbox cannot read them.