Skip to content

Backend request: Qoder Cloud Agents as a runner backend (first hosted REST+SSE harness, no local CLI) #236

Description

@hellomypastor

Problem

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.
  • Wait for the optional persistent-adapter work in Should HarnessRouter support persistent harness runtimes instead of spawning a CLI process per turn? #25. Not needed — Qoder fits the existing per-turn model as-is, and this backend should not depend on that roadmap.

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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions