Codencer v2 supports a self-hostable remote planner path without moving execution off the local machine.
If you want cloud tenancy, cloud-scoped runtime control, or provider installations instead of the raw relay/runtime path, start with CLOUD_SELF_HOST.md.
Planner / Chat
-> Relay planner API or relay MCP
-> Relay daemon
-> Connector outbound websocket
-> Local Codencer daemon
-> Local adapters
Execution still stays local. The relay is transport, auth, and audit. The connector is an outbound bridge. The daemon remains the local system of record.
- Local daemon API:
/api/v1 - Local daemon compatibility/admin MCP surface:
/mcp/call - Relay planner API:
/api/v2 - Relay MCP:
/mcp - Relay MCP compatibility path:
/mcp/call - Relay connector websocket:
/ws/connectors
The local daemon is not the public remote MCP server.
The practical cold-start flow is:
mkdir -p .codencer/relay
./bin/codencer-relayd planner-token create \
--config .codencer/relay/config.json \
--write-config \
--name operator \
--scope '*'That command creates or updates a local relay config file with a high-entropy static planner bearer token.
Minimal relay config example:
{
"host": "127.0.0.1",
"port": 8090,
"db_path": ".codencer/relay/relay.db",
"planner_tokens": [
{
"name": "operator",
"token": "<generated-by-planner-token-create>",
"scopes": ["*"]
}
],
"proxy_timeout_seconds": 300,
"allowed_origins": ["http://127.0.0.1:8090"]
}Run the daemon near the repo you want to serve:
./bin/orchestratord --repo-root /path/to/repoOr use the existing convenience flow:
make startVerify the daemon’s stable identity and local manifest-backed metadata:
./bin/orchestratorctl instance --jsonOr inspect the daemon directly:
curl http://127.0.0.1:8085/api/v1/instanceThe daemon writes a repo-local manifest under .codencer/instance.json.
Run the relay with the config you created:
./bin/codencer-relayd --config .codencer/relay/config.jsonThe relay is the public remote control plane. Do not expose the daemon directly.
Operator status/admin endpoints live on the relay too:
GET /api/v2/statusGET /api/v2/connectorsGET /api/v2/audit?limit=N
Local helper commands are available too:
./bin/codencer-relayd status --config .codencer/relay/config.json
./bin/codencer-relayd connectors --config .codencer/relay/config.json
./bin/codencer-relayd instances --config .codencer/relay/config.json./bin/codencer-relayd enrollment-token create \
--config .codencer/relay/config.json \
--label local-dev \
--expires-in-seconds 600 \
--json./bin/codencer-connectord enroll \
--relay-url <relay> \
--daemon-url <local-daemon> \
--enrollment-token <token>The connector persists:
relay_urlconnector_idmachine_idprivate_keyinstances[]allowlist entriesstatus.jsonsession snapshot
Legacy bootstrap compatibility:
enrollment_secretis still accepted if configured on the relay as a bootstrap-only fallback- new self-host setups should prefer one-time enrollment tokens
Enrollment seeds one shared instance from the daemon URL you enrolled against.
Important rules:
- discovery roots do not auto-share repos
- connector config is the allowlist
- only
share: trueinstances are advertised
Inspect and manage the allowlist explicitly before running the connector:
./bin/codencer-connectord discover --config .codencer/connector/config.json
./bin/codencer-connectord list
./bin/codencer-connectord share --daemon-url http://127.0.0.1:8085
./bin/codencer-connectord share --instance-id <instance-id>
./bin/codencer-connectord unshare --instance-id <instance-id>
./bin/codencer-connectord configunshare marks an instance as share=false and keeps the record in local config, so operators can see both known-shared and known-unshared repos.
share --instance-id is only valid when discovery or existing connector metadata can resolve that id back to a healthy local daemon. share --daemon-url is the self-sufficient operator path.
You can also inspect the relay-side view of shared instances with:
./bin/codencer-relayd connectors --config .codencer/relay/config.json
./bin/codencer-relayd audit --config .codencer/relay/config.json --limit 20./bin/codencer-connectord runThe connector opens an outbound authenticated websocket session to the relay and advertises only the explicitly shared local instances.
Check connector state locally at any time:
./bin/codencer-connectord status --jsonUse either:
- relay planner API under
/api/v2 - relay MCP at
/mcp
The relay is the remote planner surface. The daemon-local /mcp/call endpoint is only a local compatibility/admin bridge.
For the frozen planner/client compatibility matrix, generic HTTP/MCP examples, and client-specific packaging notes, see mcp/integrations.md and mcp/relay_tools.md.
Current MCP transport posture:
- canonical endpoint:
/mcp - compatibility alias:
/mcp/call - POST JSON-RPC is supported for straightforward planner integrations
- Streamable HTTP compatibility is implemented on
/mcpwithGET,POST, andDELETE,MCP-Protocol-Version, andMCP-Session-Id - the current relay is still request/response-first and does not emit long-lived unsolicited server notifications
Typical remote sequence:
- list instances
- start run
- submit task
- wait for step or poll step/result
- inspect result
- inspect validations
- inspect logs
- inspect artifacts
Remote artifact access is ID-based:
- artifact content is fetched by
artifact_id - there is no arbitrary path browsing tool
- large binary transport is intentionally bounded
Supported remote actions include:
- approve gate
- reject gate
- abort run
- retry step
- disable or enable a connector from the relay admin surface
Current limitations remain explicit:
- abort is best-effort unless the adapter actually confirms stop, and the caller only gets a successful abort when the active step reaches
cancelled - large binary artifact transfer is intentionally bounded
Current routing behavior:
- relay step/gate/artifact lookups first use stored route hints
- if a hint is missing, the relay probes only authorized online shared instances
- successful probes are persisted as route hints for later lookups
- ambiguous matches still fail closed
The connector only proxies a narrow allowlist:
- run create/list/read
- run abort
- run gate listing
- step submit/read/result/validations/artifacts/logs
- step retry
- step wait
- gate approve/reject
- instance read
- artifact content read
The relay and connector do not expose:
- raw shell
- arbitrary filesystem browsing
- generic network tunneling
Once the daemon and relay are already running, use the repo smoke helper for the happy path:
PLANNER_TOKEN=<planner-token> make self-host-smokeThe smoke flow:
- reads the local daemon instance identity
- creates a one-time relay enrollment token through
codencer-relayd enrollment-token create - enrolls and runs a temporary connector
- waits for instance advertisement
- starts a run through the relay
- submits a real
TaskSpec-compatible task - waits for the step
- fetches result, validations, logs, gates, and artifacts
Optional smoke scenario coverage:
Default proof from make self-host-smoke:
- connector enrollment and websocket session establishment
- relay instance visibility for the enrolled daemon
- run create, task submit, wait, result, validations, logs, gates, and artifact fetch over relay HTTP
- relay audit visibility when
auditis enabled
Optional proof paths:
PLANNER_TOKEN=<planner-token> SMOKE_SCENARIOS=status,audit,share-control,mcp,mcp-sdk make self-host-smoke
PLANNER_TOKEN=<planner-token> make self-host-smoke-allshare-controlnow provesunshareremoves relay visibility and blocks routing, thenshare --instance-idrestores visibility before the main relay flow runs again.mcpproves manual relay MCP initialize, SSE stream bootstrap, compatibility POST alias use, tool calls, and session delete.mcp-sdkproves official Go SDK interoperability against relay/mcp.multi-instanceproves one connector can advertise two local daemons and that explicit instance targeting reaches only the selected daemon.
Still outside smoke proof:
- cold bootstrap of the daemon and relay themselves
- real non-simulation adapter execution
- WSL/Windows/Antigravity topology behavior
- hard guarantees for gate, retry, or abort semantics beyond the statuses captured by the script
If you want the standalone SDK proof path, build and run the helper directly:
make build-mcp-sdk-smoke
./bin/mcp-sdk-smoke --endpoint http://127.0.0.1:8090/mcp --token <planner-token> --instance-id <instance-id>If you need the Windows-side agent-broker binary too, build it separately with:
make build-brokerThe practical default is:
- daemon, connector, repos, worktrees, and artifacts in WSL/Linux
- agent-broker and IDE on Windows when needed
- relay wherever the operator wants to host the remote control plane
This is recommended operator topology, not an automated smoke proof. See WSL / Windows / Antigravity Topology for the trust boundaries and placement guidance.
- Self-host mode is implemented in this repo and uses your own relay config, sqlite state, and tokens.
- A future default or managed relay can speak the same connector session model, but self-host does not depend on that future service.