The adapter package represents adapter configuration, capability metadata, and command/resume plans. CLIAdapter.Execute builds provider-specific non-interactive argv, installs a session-scoped MCP config, and runs it inside the container runtime. It returns bounded stdout/stderr, exit code, timestamps, timeout/cancellation state, and truncation metadata. The coordinator dispatches persisted assignments through the supervisor; successful non-empty diffs are submitted as ordinary pending proposals, never applied by the execution process. Process completion is not task acceptance.
Adapter entries live under adapters in .roundtable/config.yaml. Supported fields include image, command, supports_resume, resume_pattern, supports_mcp, supports_readonly_workspace, captures_session_id, provider_hosts, and credential_env. image is mandatory for execution and must contain the matching CLI; use an immutable image digest in production. Provider hosts are exact DNS names, not wildcards or IP literals. Credential variables are explicit API-key environment names only. Defaults for built-in names are fully enumerated in Configuration Reference.
| Adapter | Default command | Resume argv pattern | MCP | Read-only workspace metadata | Captures session ID |
|---|---|---|---|---|---|
codex |
codex |
disabled (provider home is disposable) | yes | yes | yes |
claude |
claude |
disabled (provider home is disposable) | yes | yes | yes |
gemini |
gemini |
disabled (provider home is disposable) | yes | yes | yes |
opencode |
opencode |
none | yes | yes | no |
generic |
sh |
none | no | yes | no |
Capability values are persisted in adapter_capabilities and exposed by API diagnostics/configuration surfaces. They are declarations, not runtime enforcement. In particular, supports_readonly_workspace: true does not itself create a sandbox or mount the project read-only.
Before a successful execution’s workspace is exported, the runtime pauses the container so surviving child processes cannot race patch collection. The export uses a rooted extractor that rejects traversal, symlinks, hard links, and special files; the paused container is forcibly removed during deterministic cleanup.
CLIAdapter.Execute requires a caller-supplied Docker/Podman runtime and configured adapter image. The runner does not fall back to executing an adapter binary on the host. It uses an argv vector (no shell parsing), a read-only source mount at /roundtable/source, and separate tmpfs-backed named volumes for the writable workspace (1 GiB maximum) and temporary storage (512 MiB maximum). Workspace output is copied back only after the agent process exits, using a rooted extractor that rejects traversal, symlinks, hard links, and special files. The container runs as UID/GID 1000 with dropped Linux capabilities, no-new-privileges, the runtime’s default seccomp profile, read-only root, 256 process limit, 2 GiB memory, 2 CPUs, and --network none. The Docker/Podman daemon socket and host home directories are never mounted. The source/workspace/temp directories must resolve beneath a dedicated execution root and must not overlap. Captured stdout and stderr are each capped at 1 MiB by default (maximum 16 MiB); truncation is reported. The configured adapter image must include a POSIX shell and sleep for the supervised container lifecycle.
For codex, claude, gemini, and opencode, execution writes a session MCP manifest in the private host-side session temp directory, then seeds that generated file into the container’s bounded tmpfs /tmp volume before launching the provider CLI. The in-container file is mode 0400; only the generated manifest is copied, not an arbitrary host directory. The CLI loads it through isolated CODEX_HOME, Claude --mcp-config, Gemini’s system settings file, or OPENCODE_CONFIG, respectively. Provider argv uses each CLI’s non-interactive interface (Codex exec, Claude -p, Gemini -p, OpenCode run) and retains the full prompt as one argument. The manifest starts only roundtable mcp stdio --unix-socket /run/roundtable-agent-mcp.sock; the capability token is not written into the file and is supplied only through the explicit container environment. Agent images must include the roundtable executable on PATH in addition to their provider CLI. The bridge uses a session-pinned capability and a single mounted Unix socket; it does not expose the admin MCP endpoint. This manifest plumbing does not itself grant provider API egress, and provider CLI behavior must still be verified against the image’s pinned version.
The ExecutionSupervisor provisions a revision-pinned source/workspace pair filtered to the task’s operator-authored readable_paths, issues a server-bound capability, records lifecycle metadata, handles cancellation/timeouts and startup reconciliation, collects a private patch artifact, submits non-empty patches through the scoped proposal.create tool, revokes the capability, and cleans the workspace. Empty scopes fail closed. Sensitive descendants such as credential/config/private-key files are excluded even under an allowed directory. Proposals remain subject to claims, review, policy, human approval where required, and the transaction manager. It does not apply patches or mark tasks accepted. The configured container image and CLI are operator-trusted inputs.
The container has no network interface route (--network none). When an exact provider_hosts policy is configured, its local loopback CONNECT proxy forwards through one mounted per-session Unix socket to a host gateway. The gateway accepts only CONNECT to port 443, enforces exact host allowlisting, resolves and pins public addresses, and rejects private, loopback, link-local, shared, documentation, and special-use address ranges before dialing. Before forwarding any TLS bytes upstream, it parses a bounded ClientHello and requires a visible SNI exactly matching the authorized CONNECT hostname; missing SNI and Encrypted ClientHello are rejected. This narrows virtual-host domain-fronting but does not inspect encrypted HTTP paths or requests after TLS establishment. The container cannot select arbitrary proxy destinations through inherited host proxy variables; its provider child receives only the supervised loopback proxy configuration. This control depends on the configured container runtime and OS network/filesystem isolation. A tested Colima VirtioFS configuration cannot bind-mount host Unix sockets (operation not supported); Docker Desktop passed the strict socket and provider-egress integrations on the current macOS host. Any runtime with the same limitation fails closed. Do not weaken network isolation to work around a runtime that cannot mount the required per-session sockets. In default test mode, only explicitly recognized host restrictions may skip integration tests; ROUNDTABLE_REQUIRE_CONTAINER_TESTS=1 turns unavailable runtime prerequisites into failures. DNS rebinding is mitigated by dialing the validated resolved address directly. Containerization does not eliminate kernel/runtime escape risk. An allowed provider can receive agent-authorized task data, so this is not an anti-exfiltration guarantee. See Configuration Reference for credential and host policy.
Agent role capabilities are represented by short-lived random bearer tokens stored by digest in the in-memory authority. roundtable start serves coordinator/admin MCP at /mcp; each execution receives a pinned role-filtered MCP handler over its own mounted Unix socket. Authorization is checked on every tool dispatch, including revocation, and the container receives no admin endpoint or daemon socket. The supervisor is connected to assigned-task scheduling. Startup reconciliation stops orphan containers and removes managed session resources. Runtime integration tests remain required to prove enforcement on the selected Docker/Podman installation. The coordinator/admin endpoint remains intended for trusted local operator/harness access, not external agents.
Session records are created/updated through the sessions CLI/API/MCP-related workflows and store adapter name, provider/model, external session ID, resume command, working directory, MCP socket, state, timestamps, and metadata. If sessions register receives no explicit resume command and the configured adapter has a nonempty pattern plus external session ID, the CLI replaces `` and persists the result.
roundtable resume produces a database-derived briefing and reconciles stale claims. It does not execute an external CLI. The HTTP session-creation API does not fabricate a resume command. External session identifiers, command lines, working directories, and metadata should be protected as operational data and scrubbed from public logs.
The Go registry creates one CLIAdapter for every entry in the loaded configuration map, including custom names. Lookup is an exact, case-sensitive name match and returns a found/not-found result; it does not fall back to generic. Registry names are returned alphabetically. This package does not independently discover installed executables or probe their supported flags. Capability reporting copies the configured booleans; captures_session_id also enables machine-readable output and provider-specific session parsing during Execute. Codex thread.started.thread_id, Claude result session_id, and Gemini JSON session_id are recognized. Truncated output, malformed output, unsupported providers, and invalid identifiers produce no captured ID. Pin adapter images by immutable digest and verify them with the corresponding provider image before enabling capture in production; CLI flags are version-sensitive.
Start requires nonblank session ID, agent ID, run ID, and working directory. It checks these strings with whitespace trimming but retains the original values in the returned record. It does not validate database associations, filesystem existence, executable availability, provider compatibility, or MCP connectivity. Built-in CLI configurations capture IDs for audit but set supports_resume: false, because disposable provider-home state is not retained.
The returned LaunchSession contains a session record with status starting, a UTC RFC3339 last-seen timestamp, provider/model, external session ID, working directory, socket, and a resume command when a pattern and external ID are available. Metadata contains mode: start, role, and task ID. Neither this record nor an event is saved by the adapter itself.
The start command array uses the trimmed configured command as one array element, falling back to the adapter name when that string is empty. A value such as codex --some-option is not split into an executable and arguments. Built-in adapters construct provider-specific non-interactive argv; the prompt remains one argument and is never interpreted by a shell. generic does not add provider flags and cannot inject a provider-specific MCP manifest. These argv forms are unit-tested, but compatibility with an image’s pinned CLI version still requires runtime verification.
Resume first rejects adapters whose supports_resume flag is false. Built-in container adapters set it false. For a separately configured adapter that opts into resume, the method requires a valid external session ID plus the same identifiers/directory as start, then returns a provider-specific argv plan with the briefing passed as a single final argument. This plan method does not itself persist state or execute a process. Roundtable’s disposable-worker policy does not retain provider-home state, so enabling resume metadata alone does not make a later resume operational.
Resume command construction splits the operator-controlled resume_pattern into argv fields and requires exactly one standalone `` argument. IDs are restricted to 1-128 ASCII letters, digits, underscore, or hyphen, then substituted as one argv value; no shell is invoked. A nonblank resume briefing is appended as one argument, never interpolated into a shell string. Missing/malformed patterns and unsafe IDs fail explicitly. This basic argv grammar does not interpret shell quotes, so patterns needing whitespace-bearing fixed arguments are unsupported.
Stop returns success without inspecting the session ID or stopping any process. The start/resume methods do not use their context to perform cancellable work. Statuses such as starting and resuming describe returned plans, not observed provider process state.
Keep configured executable paths, external IDs, and resume patterns under trusted operator control. Do not execute a saved command by passing it to a shell without an explicit, separately reviewed trust boundary. The current whitespace splitting is not a safe general-purpose command-line parser. Capability declarations must not be used as proof of sandboxing, authenticated MCP access, session capture, process liveness, or compatibility with the installed CLI version.
The API diagnostic action in api/internal/httpapi/diagnostics.go requires shared human authorization, a nonblank Idempotency-Key, a resolvable workspace, and an existing agent ID. The workspace is checked but not used to establish that the agent belongs to it or to set the probe’s working directory. This is a synchronous executable probe with a persisted result, not a provider-session launch. It returns HTTP 202 only after probing and saving; there is no background diagnostic job merely implied by that status.
The configured agent command is trimmed, falling back to its adapter name if empty. It is split with strings.Fields, so quotes are not interpreted. The first field is executed directly, remaining fields are preserved, and --version is appended. Unlike start planning, this path actually calls exec.CommandContext. It uses a three-second context derived from the request, combined stdout/stderr, inherited process environment, and the API process’s working directory. It does not set the workspace directory, strip credentials from the environment, resolve a provider-specific version flag, or install filesystem isolation.
An empty command produces failed check executable. Execution failure produces failed check executable_version with generic detail; successful exit produces passed detail containing the sanitized excerpt. A command that rejects --version can fail even when installed. Conversely, successful exit does not prove model availability, provider authentication, resume capability, session capture, safe command behavior, or compatibility with planned --model/--prompt flags. Configure commands as trusted executable configuration; calling something with --version does not guarantee it has no side effects.
mcp_surface passes when the static registry contains at least one advertised tool. It does not connect to a socket, perform a handshake, execute a tool, or exclude unimplemented registry names. read_only_boundary is reported passed as descriptive text because the diagnostic does not create a session or explicitly grant repository write access; it is not the outcome of an attempted write or sandbox verification. Overall status is failed if any check has exact status failed, otherwise passed.
Combined output is placed in the response’s stdout; stderr is not separately populated. Redaction removes entire lines containing case-insensitive token=, password=, or secret= and trims the remainder. Other secret formats, bearer values, JSON credential keys, paths, and sensitive text are not comprehensively scrubbed. The returned excerpt is capped at 2,000 bytes by slicing, which can split a UTF-8 character. Full subprocess output is collected before truncation, so that cap is not an execution-time memory limit. Remediation hints are fixed text, not a complete failure diagnosis.
The report receives ID diag-<start Unix nanoseconds>, agent ID, and RFC3339Nano start/finish timestamps. It is stored as a test_runs row with command agent-diagnostic, empty task association, and the full report JSON in summary_md; it is not written to a separate diagnostic log artifact. Persistence failure returns diagnostic_persist_failed after the probe already executed.
After saving, the API appends agent.diagnostic.completed under literal run ID diagnostics, with agent ID, diagnostic run ID, and status in its payload. Event failure returns diagnostic_audit_failed even though the test row already exists. That event is not automatically associated with the requested workspace’s run. A failed check can still produce a successful 202 response; inspect report status/checks rather than HTTP status alone. Empty-body requests are reevaluated on retries under the current idempotency middleware, so a repeated request can execute another probe and create another report. See API Error and Retry Semantics.
Provider-home persistence and automatic external-session relaunch/reconciliation remain gaps. External agents still cannot write the authoritative repository; only the transaction manager applies approved proposals. Containerization is not a complete anti-exfiltration guarantee: an allowed provider can receive task context and patch content.
Actual compatibility with provider-pinned CLI images and authenticated provider sessions still requires live image-based verification and remains tracked in TODO.md. Adapter resume planning is distinct from orchestrator-driven process lifecycle; see Sessions and Resume.
Workspace agent list/detail endpoints validate the workspace but read the global agent registry. Agent detail and active-session counts are not workspace-bound. Active sessions count exact statuses active or running for the agent, without heartbeat-age or process checks. Stored status is returned as health, with empty status mapped to unknown; no executable probe occurs during these reads.
Response capability fields are hardcoded by exact adapter name, not queried from the runtime adapter registry: codex, claude, and gemini advertise resume and MCP; opencode advertises MCP only; other names advertise neither. Every response advertises read_only_workspace: true, concurrency limit 1, and policy weight 1. These labels do not enforce filesystem permissions or scheduling limits; the weight can differ from the role-based HTTP vote weight. Optional executable-version and last-heartbeat fields are not populated by this projection. Custom adapter configuration does not automatically change these capability values.
Listing loads all agents and queries context for each before pagination. Default limit is 50, allowed limits are 1 through 200. Current cursor mismatch: input uses the base64url decimal-offset decoder, but next_cursor is emitted as a plain decimal string. Passing it back verbatim is not a reliable continuation protocol. This documents the existing implementation rather than promising correct pagination. List failures use agent_list_failed; context failures use agent_context_failed; detail lookup failures use agent_not_found.
Enable/disable requires human authorization and a nonblank trimmed Idempotency-Key. It operates on the global agent row, with the URL workspace supplying audit context rather than restricting agent ownership. Disabling an enabled agent counts active/running sessions globally and returns 409 agent_has_active_sessions if any exist. Although the error describes drain semantics, the request does not set a draining state, enqueue a deferred disable, terminate a session, or wait for completion. Retry after sessions finish. An already-disabled row bypasses that active-session check.
The store writes enabled state and corresponding stored status plus audit in a SQL transaction; enabling maps status to idle, disabling to disabled. This does not launch/stop a provider CLI, cancel a scheduled turn, release claims, or update YAML configuration. The session-count precheck is outside the write transaction, so a concurrent session can start between check and mutation. A successful write followed by response-context failure can return agent_context_failed after the change is already persisted. Store errors return agent_state_conflict. Runtime agent synchronization can later overwrite database state from its configured agent list; these HTTP changes are not durable configuration edits.