You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Qoder Cloud Agents is a hosted agent runtime (create a session, send a user.message, follow an SSE stream of agent.thinking / agent.message / agent.tool_use / agent.tool_result / agent.artifact_delivered / session.status_* events). It runs the same agent loop as the qodercli CLI, but server-side, with MCP servers, skills and a fixed built-in toolset (Bash, Read, Write, Edit, Glob, Grep, WebFetch, WebSearch, ImageSearch, ImageGen, DeliverArtifacts) configured on the Agent object. It cannot be a HarnessRouter backend today for the same reason as #229: the backend list is fixed, and a custom harness is a configuration over a built-in backend.
It is also a new shape for this repository. Every existing backend — including dsh, whose {"m","p"} driver is the closest analog — is a local subprocess that the runner spawns, and three mechanisms rest on that: cancellation is a process-group kill, credential isolation is the loopback relay in front of a local process's outbound calls, and produced files are the git diff of the checkpointed workspace. Qoder has no local process making outbound model calls: the network session is the backend. We want to be explicit about how each of those three is preserved rather than have it discovered in review.
Proposal
Add qoder as a runner backend with a runner/qoder_driver.py, following the dsh_driver.py outer contract exactly — one process per turn, __hr_init first, one {"m": <event>, "p": <payload>} line per SSE event on stdout, __hr_result last, server.py unaware of the wire — and dropping its inner layer (no local runtime child, no relay). A new _qoder_to_claude normaliser maps the SSE vocabulary onto the same claude-stream-json shapes every other normaliser produces.
How the three subprocess-dependent mechanisms carry over:
Cancellation. The process-group kill still ends the local driver, but on its own it would leave the remote session running and billing (the failure mode named in Should HarnessRouter support persistent harness runtimes instead of spawning a CLI process per turn? #25). The driver installs a SIGTERM handler that calls Qoder's session cancel endpoint before exiting, so UHP POST /v1/responses/{id}/cancel reaches the remote runtime. Cancel gets no weaker than today.
Credentials. Qoder does not accept a caller-supplied provider or API key — every session runs on Qoder's own model access and billing (confirmed with the Qoder team). So there is no provider routing to plumb and no relay to build; the only credential is the Qoder PAT/SAT, passed to the driver in-process and never written under the workspace. providers for this backend is just Qoder itself.
Produced files.agent.artifact_delivered has no filesystem side. The driver downloads each delivered artifact into the checkpointed workspace (under .harness/) as it arrives, so the existing git-diff collection and the artifact verification rule in docs/harness-verification.md work unchanged.
Tool permissions: agent.tool_use carries a pending confirmation the caller must answer with user.tool_confirmation before the call executes. In a headless turn the driver auto-allows, and replies deny for any tool in the harness's disabled list. Because the deny lands before execution, this is a genuine tool_enforcement: "hard" per UHP §4.3 — a stronger tier than the instruction-doc approach most backends use. The confirmation loop is resolved entirely inside the driver; nothing new reaches the UHP surface, consistent with tasks.md §1.4 keeping tool execution as observability only. (This is deliberately not the interactive-approval / waiting state discussed in #25 — that remains a separate roadmap item; a headless auto-policy is all this backend needs.)
Other checklist points with a non-default answer: session.error is a typed event, so provider failure needs no prose regex; docker/entrypoint.sh gets no installer (there is no binary) and the checklist entry is recorded as N/A rather than skipped; whether harnessrouter skill bundles map onto Qoder's server-side Skill resource, or are N/A for this backend, is the one design point still open and we will state it either way in the PR.
Compatibility
Additive: one backend entry, one driver, one normaliser, the seventeen registration points from docs/harness-verification.md. No protocol change. No change to the one-process-per-turn boundary or the per-session uid isolation — the driver is an ordinary per-turn process that happens to spend its life in an HTTPS/SSE connection.
Alternatives
Wrap the local qodercli CLI instead, like every other backend. Rejected for now: the cloud service is the product being integrated, and the CLI path would not exercise Qoder's hosted MCP/skills/artifact semantics.
Run it behind a custom Responses server as systemone does. Heavier than a driver and hides the harness identity.
We can provide the driver PR following the "Adding a harness" checklist if the entry is welcome, then run the UHP conformance suite and the support-matrix column against a live Qoder instance; opening the issue first per CONTRIBUTING.
Problem
Qoder Cloud Agents is a hosted agent runtime (create a session, send a
user.message, follow an SSE stream ofagent.thinking/agent.message/agent.tool_use/agent.tool_result/agent.artifact_delivered/session.status_*events). It runs the same agent loop as theqodercliCLI, but server-side, with MCP servers, skills and a fixed built-in toolset (Bash, Read, Write, Edit, Glob, Grep, WebFetch, WebSearch, ImageSearch, ImageGen, DeliverArtifacts) configured on the Agent object. It cannot be a HarnessRouter backend today for the same reason as #229: the backend list is fixed, and a custom harness is a configuration over a built-in backend.It is also a new shape for this repository. Every existing backend — including
dsh, whose{"m","p"}driver is the closest analog — is a local subprocess that the runner spawns, and three mechanisms rest on that: cancellation is a process-group kill, credential isolation is the loopback relay in front of a local process's outbound calls, and produced files are the git diff of the checkpointed workspace. Qoder has no local process making outbound model calls: the network session is the backend. We want to be explicit about how each of those three is preserved rather than have it discovered in review.Proposal
Add
qoderas a runner backend with arunner/qoder_driver.py, following thedsh_driver.pyouter contract exactly — one process per turn,__hr_initfirst, one{"m": <event>, "p": <payload>}line per SSE event on stdout,__hr_resultlast,server.pyunaware of the wire — and dropping its inner layer (no local runtime child, no relay). A new_qoder_to_claudenormaliser maps the SSE vocabulary onto the same claude-stream-json shapes every other normaliser produces.How the three subprocess-dependent mechanisms carry over:
POST /v1/responses/{id}/cancelreaches the remote runtime. Cancel gets no weaker than today.providersfor this backend is just Qoder itself.agent.artifact_deliveredhas no filesystem side. The driver downloads each delivered artifact into the checkpointed workspace (under.harness/) as it arrives, so the existing git-diff collection and the artifact verification rule indocs/harness-verification.mdwork unchanged.Tool permissions:
agent.tool_usecarries a pending confirmation the caller must answer withuser.tool_confirmationbefore the call executes. In a headless turn the driver auto-allows, and repliesdenyfor any tool in the harness's disabled list. Because the deny lands before execution, this is a genuinetool_enforcement: "hard"per UHP §4.3 — a stronger tier than the instruction-doc approach most backends use. The confirmation loop is resolved entirely inside the driver; nothing new reaches the UHP surface, consistent withtasks.md§1.4 keeping tool execution as observability only. (This is deliberately not the interactive-approval /waitingstate discussed in #25 — that remains a separate roadmap item; a headless auto-policy is all this backend needs.)Other checklist points with a non-default answer:
session.erroris a typed event, so provider failure needs no prose regex;docker/entrypoint.shgets no installer (there is no binary) and the checklist entry is recorded as N/A rather than skipped; whether harnessrouter skill bundles map onto Qoder's server-side Skill resource, or are N/A for this backend, is the one design point still open and we will state it either way in the PR.Compatibility
Additive: one backend entry, one driver, one normaliser, the seventeen registration points from
docs/harness-verification.md. No protocol change. No change to the one-process-per-turn boundary or the per-session uid isolation — the driver is an ordinary per-turn process that happens to spend its life in an HTTPS/SSE connection.Alternatives
qodercliCLI instead, like every other backend. Rejected for now: the cloud service is the product being integrated, and the CLI path would not exercise Qoder's hosted MCP/skills/artifact semantics.systemonedoes. Heavier than a driver and hides the harness identity.We can provide the driver PR following the "Adding a harness" checklist if the entry is welcome, then run the UHP conformance suite and the support-matrix column against a live Qoder instance; opening the issue first per CONTRIBUTING.