diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/README.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/README.md new file mode 100644 index 0000000000..e17f35d489 --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/README.md @@ -0,0 +1,77 @@ +# Runner self-hosting cleanup + +Status: DESIGNED v2 after owner review round 1, awaiting second-round review +Date: 2026-07-13 +Target branch: `big-agents` + +This project makes the agent runner understandable from its deployment configuration alone. It replaces shared and overlapping environment variables with one runner-owned provider contract, removes automatic subscription credential upload, and documents explicit operator-owned subscription mounts. A capability endpoint, a bootstrap manifest, and fail-loud mount semantics are deferred open issues, not release work. + +## Outcome + +A self-hosted operator should be able to answer these questions by reading one Compose service or one Helm values block: + +1. Where is the runner? +2. Which sandbox providers can it use? +3. Which provider is the default? +4. Which credentials belong to the runner itself? +5. Which files are intentionally copied into a run? +6. Which combinations of sandbox, harness, and authentication are supported? + +The normal deployment uses one runner service. That runner can support both local and Daytona runs at the same time. Harness choice remains per agent. Sandbox choice remains per run, constrained by the providers the operator enabled. + +## Decisions + +- The runner is a deployment-level service, not one sidecar per harness or sandbox provider. +- The operator enables sandbox providers explicitly. Unset means local only, not every provider known to the binary. +- The enabled-provider list is also the deployment security posture: `daytona` only means no user code ever runs inside the runner container. That is the right posture for any multi-tenant or exposed deployment. +- API and web get provider availability from the same environment value, set once per deployment by the hosting templates. A discovery endpoint is deferred (RSH-7). +- Daytona configuration belongs under the runner namespace. Code-evaluator Daytona configuration is separate. +- The runner never discovers an operator's home directory or automatically uploads Pi or Claude OAuth state. +- Subscription authentication is an explicit local self-hosting setup: a read-write Compose volume mount taught in a tutorial. The harness runs directly out of the mount so it can refresh its own OAuth token; there is no per-run copy and no bootstrap manifest in version 1 (RSH-4). +- Runtime customization (extra binaries, certificates, dependencies) happens through operator-built images and the shipped Daytona snapshot scripts, not runner configuration. +- Remote subscription authentication is unsupported until the relevant provider explicitly approves the integration and Agenta defines a safe product contract. +- Pi installation is detected and repaired by the runtime. There is no deployment-wide "Pi is installed" boolean. +- Mount failure behavior stays best-effort in version 1, with one structured warning when a durable mount degrades. The fail-loud contract is deferred (RSH-11). +- The runner receives a narrow environment and no static Agenta API key. Per-run caller credentials authorize callbacks and trace export. It does not inherit a shared application environment or database and cryptographic secrets. + +## Reading order + +- [Context](./context.md) explains today's architecture and why the current variables are confusing. +- [Research](./research.md) records repository evidence and the review of PRs #5274, #5285, and #5286. +- [Interface](./interface.md) defines the target environment, Helm, provider-availability, subscription-mount, and failure contracts. +- [Plan](./plan.md) splits the cleanup into reviewable implementation PRs. +- [QA](./qa.md) defines the environment, harness, and credential matrix. +- [Documentation plan](./documentation-plan.md) maps the final public docs through Diátaxis, with per-page dispositions for the existing self-host tree. +- [Open issues](./open-issues.md) parks work that should not block the cleanup. +- [Status](./status.md) is the owner review checklist and progress log. + +## In scope + +- Runner provider selection and configuration. +- Runner-specific Daytona names and validation. +- Runner-to-Services routing and authentication. +- Local subscription setup through explicit read-write mounts. +- Removal of automatic Daytona OAuth upload. +- Removal of deployment-wide runtime facts and hidden policy switches. +- A structured warning when a durable mount degrades. +- Compose, Helm, Railway, self-hosting docs, a `hosting/README.md` layout map, and the local 8280 QA deployment. +- One shared provider-availability value read by API and runner. + +## Out of scope + +- Implementing the Docker or E2B providers. +- Sharing a personal subscription between users or tenants. +- Remote subscription support. +- A runner capability-discovery endpoint (RSH-7). +- A declarative bootstrap-asset manifest, scripts, VPN hooks, or network overlays (RSH-4). +- Fail-loud required-mount semantics (RSH-11). +- Reworking code-evaluator execution in the same change. +- Backward-compatible aliases for unpublished pre-production variables. +- A custom-role migration bridge. Default roles and tests still need the final mount permissions. + +## Related work + +- [PR #5274](https://github.com/Agenta-AI/agenta/pull/5274) added mount and snapshot changes that exposed the immediate configuration bugs. +- [PR #5285](https://github.com/Agenta-AI/agenta/pull/5285) proposes Docker and a sandbox allowlist. This project adopts its capability-based provider direction but replaces the permissive cross-layer allowlist contract. +- [PR #5286](https://github.com/Agenta-AI/agenta/pull/5286) adds useful runner authentication and provider-key narrowing. This project rejects its broad Helm `commonEnv` injection and its compatibility escape hatch. +- [Runner self-hosting explainer](../runner-selfhosting-explainer/README.md) contains the detailed as-built explanation that led to this cleanup. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/context.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/context.md new file mode 100644 index 0000000000..d0b925d0c9 --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/context.md @@ -0,0 +1,99 @@ +# Context + +## The current architecture + +The agent runner is a long-lived Node service. Services sends it a run request, the runner chooses a sandbox provider, and a sandbox-agent daemon launches the selected harness. + +~~~text +Services API + -> runner HTTP service + -> sandbox provider: local or Daytona + -> sandbox-agent daemon + -> harness: Pi or Claude +~~~ + +The two choices are independent: + +- The harness belongs to the agent configuration. +- The sandbox provider belongs to the run, with a deployment default. + +One runner process can therefore execute Pi locally, Claude locally, Pi in Daytona, and Claude in Daytona. We do not need separate deployment services for those combinations. Additional runner replicas are a scaling and ownership concern, not a harness-selection requirement. + +## Why the current setup is hard to explain + +Configuration grew in several directions at once: + +- The backend locates the runner with `AGENTA_RUNNER_INTERNAL_URL`. +- The runner defaults a sandbox with `SANDBOX_AGENT_PROVIDER`. +- Daytona uses ambient `DAYTONA_*` variables shared with code evaluation. +- Agent-only overrides such as `DAYTONA_SNAPSHOT_AGENT` sit beside shared defaults. +- `AGENTA_AGENT_SANDBOX_PI_INSTALLED` asks the operator to describe a runtime fact that the runner can detect. +- `AGENTA_SESSION_HARNESS_MOUNTS` exposes an internal implementation choice as a deployment policy. +- Self-managed Pi authentication causes the runner to discover its own Pi login and upload it into Daytona. +- The API, Services, runner, and browser each carry pieces of provider availability. + +This makes a Compose file look like several partially overlapping products. An operator cannot tell which variables are read by the runner, which belong to the code evaluator, which describe policy, and which are only workarounds for one snapshot. + +## The live failure that exposed the boundary + +The local deployment on port 8280 had two runner-like services: + +- the normal Compose runner, which had the Daytona API key; +- a subscription sidecar, which held local harness login state but had no Daytona API key. + +Services was pointed at the subscription sidecar. A request that selected Daytona reached that runner and failed with: + +~~~text +Authentication credentials not found. Set DAYTONA_API_KEY, or both +DAYTONA_JWT_TOKEN and DAYTONA_ORGANIZATION_ID. +~~~ + +Nothing was wrong with the user's Daytona account. The request reached a runner deployment that was not configured for Daytona. The setup made it easy to split capabilities accidentally. + +The target local QA deployment is one trusted development runner with both providers enabled and explicit read-write local subscription mounts. This is a development convenience, not a production multi-tenant topology. A local harness shares the runner container and can inspect other same-user processes through `/proc`. + +## Trust boundaries + +### Local provider + +The daemon and harness are child processes inside the runner container. The working directory is a starting directory, not a confinement boundary. The harness can read files and environment visible to the container user. + +Consequences: + +- The runner container must receive only secrets that a local harness is allowed to reach. +- Helm must not inject database, auth, cryptographic, or bucket-wide store credentials into the runner. +- Subscription files mounted into the runner are available to trusted local agents. This is opt-in and single-tenant. + +### Daytona provider + +The runner stays in the deployment. The daemon and harness execute in a Daytona sandbox. Only material the runner explicitly sends should cross that boundary: + +- the resolved model credential for the run; +- workspace and instruction files; +- scoped durable-mount credentials; +- explicitly configured runtime files; +- runner-owned support binaries and configuration. + +The runner's Daytona organization credential provisions the sandbox. It is infrastructure control-plane material and must never enter the sandbox environment. + +## Product principles + +### Configuration describes intent + +Operators configure facts they own: enabled providers, a default, credentials, lifecycle values, and mounted subscription inputs. The runner detects runtime facts such as whether Pi is already installed. + +### One owner per value + +A Daytona API key used to provision agent sandboxes belongs to the runner. A Daytona key used by code evaluation belongs to the code evaluator. A shared ambient variable is not a simplification when the two consumers have different images, snapshots, permissions, and lifecycles. + +### Explicit data movement + +The runner should never infer that a missing model API key means "copy whatever login I happen to have." Runtime authentication is a declared mode, and subscription state is a declared deployment mount. + +### Fail before side effects + +Unknown providers, disabled providers, missing provider credentials, and contradictory defaults fail at startup or before sandbox creation. There is no silent provider fallback. Mount behavior stays best-effort in version 1, with a structured warning on durable-to-ephemeral degradation (the fail-loud contract is RSH-11). + +### Pre-production cleanup + +These configuration surfaces are not a stable public API. The implementation should remove obsolete variables directly instead of carrying aliases, deprecation branches, or insecure compatibility modes. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/documentation-plan.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/documentation-plan.md new file mode 100644 index 0000000000..bfb34f63ff --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/documentation-plan.md @@ -0,0 +1,248 @@ +# Documentation plan + +Agenta is an agent studio. Agents are the product a person self-hosts Agenta to run, not a +feature bolted onto a platform. The self-host section is structured around that fact: agent +execution is a first-class area next to deployment and access control, not a subfolder. + +The pages land with the phase 1 rename PR. They depend only on the final variable names in +[interface.md](./interface.md). Troubleshooting notes whose error text comes from later phases +land as that code lands. + +Each page is one Diátaxis type. A reader who wants to learn, to do a task, to look up a value, +or to understand a model reaches a page written for exactly that need. + +## Target sidebar + +``` +Self-host +├─ Overview (explanation: what you self-host, editions, license scope) +├─ Get started +│ ├─ Quick start (tutorial: self-host locally with Docker, end to end) +│ └─ Use your own subscription (tutorial: local Pi / Claude / Codex login) +├─ Agent execution +│ ├─ How agents run (explanation: runner topology, harness and sandbox axes) +│ ├─ Sandbox isolation and security (explanation: local is not a boundary; posture switch) +│ ├─ Run agents in a cloud sandbox (Daytona) (how-to) +│ ├─ Customize the agent runtime (how-to) +│ └─ Runner and sandbox configuration (reference) +├─ Deploy to production +│ ├─ Deploy on a remote server +│ ├─ Set up SSL +│ ├─ Deploy on Kubernetes +│ └─ Deploy on Railway +├─ Access control +│ ├─ Configure SSO +│ ├─ Restrict organization creation +│ └─ Dynamic access controls +├─ Architecture +│ ├─ System architecture +│ └─ Networking +├─ Configuration reference (all environment variables) +├─ Upgrade and migrate +│ ├─ Upgrade Agenta +│ ├─ Enable the runner and store +│ ├─ Multi-organization migration +│ ├─ OSS to EE +│ └─ v0.100.3 migration +└─ FAQ +``` + +## Structure decisions + +These are the calls where the shape was open. Each states the option chosen and why. + +- **Agent execution is a top-level group, not an `agents/` subfolder.** Agents are the reason + most readers self-host, so their concepts, how-tos, and reference sit alongside deployment and + access control at the same level. +- **The runner reference is its own page inside Agent execution, not a section of the main + configuration page.** Option A was a section of the configuration reference; option B a + dedicated page. The runner surface (routing, providers, Daytona, lifecycle, callbacks) is large + enough to stand alone, and it belongs next to the agent concepts a reader is already on. The + main configuration page keeps a one-line pointer. +- **Security and trust is its own concept page, not a warning box or an FAQ entry.** It is the + first question a self-hosting community asks. A dedicated concept page answers it once; the + how-tos link to it instead of repeating warnings. +- **Cloud sandbox how-tos are one page per provider, with the provider in the title.** Today that + is Daytona. When E2B or another cloud arrives, it becomes a sibling page in the same group. No + existing page is renamed. The local-versus-cloud axis lives in the two concept pages above. +- **Troubleshooting is distributed, not a single page.** Each how-to and the runner reference end + with a short symptom-cause-check section keyed to the structured errors they cause. A reader + hits the fix on the page they are already reading. +- **"Use your own subscription" is a tutorial in Get started, next to Quick start.** These two are + the only self-host tutorials: set the platform up, then opt into a personal subscription. The + page links forward to the Agent execution concepts for context. +- **Dynamic access controls moves next to SSO and organization restriction.** All three answer + "who can use this deployment and as what," so they form one group. +- **No "run your first agent" tutorial and no "connect an external runner" how-to.** Running an + agent is product usage and belongs in the product docs. Relocating the whole runner is not a + supported self-host path. + +## Get started + +### Quick start (tutorial) + +Keep the existing quick start as the end-to-end local setup path: prerequisites, a clean OSS +Compose stack on port 80 or a custom port, first login, and the upgrade pointer. Add one line at +the end that points to the Agent execution overview for readers who want to know where agent code +runs. This page is the "self-host locally" tutorial. It does not teach product usage. + +### Use your own subscription (tutorial) + +The opt-in path for a trusted personal deployment. Covers the three supported logins by name: Pi +login, Claude Code OAuth, and the ChatGPT/Codex login. + +1. Find the supported login state for the harness on the operator's machine. +2. Add the read-write volume mount in the Compose file and point the harness config variable at it. +3. Run a self-managed agent and confirm it uses the mounted login. +4. Remove the mount and confirm the run fails with the explicit missing-configuration error. + +Safety callouts: one personal subscription serves one operator, not every user of a shared +deployment; local agents can read files visible to the runner container; Daytona does not support +this by design. Link the isolation claim to the security concept page. + +## Agent execution + +### How agents run (explanation) + +Build the mental model that six partial pages tell today. Explain: + +- Services calls one runner service. +- Harness and sandbox provider are independent axes. One runner can serve local and Daytona runs + at the same time. +- Sandboxes are where agent code runs. Today that is the local runner container or a Daytona cloud + sandbox. Other clouds are future providers on the same axis. +- Managed model keys are per-run data. Runner provider credentials are deployment infrastructure. +- Session, agent, and transcript mounts give runs different persistence. A run with no session and + no workflow artifact is the only ephemeral case. + +No full variable table and no copy-paste deployment recipe. Link to the runner reference and the +Daytona how-to. + +### Sandbox isolation and security (explanation) + +Answer the trust question directly: + +- Local runs share the runner container. They are not an isolation boundary. They are a + convenience for a single trusted operator. +- Daytona runs cross an explicit file and credential boundary. Use Daytona when a deployment is + multi-user or exposed. +- The enabled-provider list is the posture switch. A deployment that enables `daytona` only lets + no user code reach the runner's process environment. +- What crosses into a Daytona sandbox and what never does: managed model keys cross; the runner's + Daytona credential and any personal subscription mount never do. +- A personal subscription mount is single-tenant by definition. + +### Run agents in a cloud sandbox (Daytona) (how-to) + +Configure remote sandbox execution with managed model credentials. + +Cover: the Daytona API key; enabling `local,daytona` or Daytona alone; choosing the default; the +`AGENTA_RUNNER_DAYTONA_*` variables; snapshot versus image; the snapshot build recipe in +`services/runner/sandbox-images/daytona/`; public reachability for durable storage; a smoke test. +Branch on the unsupported combinations (a personal subscription on Daytona) with the exact error. +Do not reuse code-evaluator Daytona variables. Do not describe one snapshot serving both the +runner and the code evaluator. End with a short troubleshooting section. + +### Customize the agent runtime (how-to) + +Add binaries, certificates, and dependencies (for example chromium or the gh CLI). Fix the scope +confusion in the page it replaces: a reader who wants extra tools inside agent runs needs the +sandbox image or snapshot, not the runner service image. + +Cover three mechanisms, ordered by what readers ask for most: + +- Extra dependencies for Daytona runs: build a custom snapshot with the shipped scripts. +- Extra dependencies for local runs and a pinned runner build: build a custom runner service image + from `services/runner/docker/Dockerfile.gh`. +- Extra project folders for local runs: an operator-owned Compose volume mount. + +### Runner and sandbox configuration (reference) + +Exact names, types, defaults, readers, secret classification, and conflicts. Generate or check the +table against the typed runner configuration schema so it cannot drift. + +Group by: Services-to-runner routing; runner server; enabled and default sandbox providers (note +that the API reads the same value); Daytona; session lifecycle and warm sessions; callback API; +intentionally internal debug settings. + +Each variable states: semantic role; consumer; default; whether empty is valid; whether it is +secret; Compose location; Helm values path; conflicts and startup validation. End with the +symptom-cause-check troubleshooting entries: requested provider disabled; runner unreachable or +unauthorized; missing local subscription mount; remote subscription unsupported; snapshot or +harness missing; Daytona authentication error; store unreachable from Daytona; mount degradation +warning. Copy error text from the shipped structured errors. Add entries whose errors land in +later phases as that code lands. + +## Existing page dispositions + +New URLs are preserved with `slug` where a page only changes folder, so bookmarks keep working. +Deleted and genuinely renamed pages get redirects (next section). + +| Page | Disposition | +|---|---| +| 01-quick-start | KEEP as the Quick start tutorial. Add a pointer to the Agent execution overview. | +| 02-configuration | TOUCH. Replace the agent-runner and agent-Daytona sections with a pointer to the runner reference. Keep the code-evaluator `daytona` section but fix its cross-reference to the removed shared snapshot. Delete the "Slice 1 / Slice 2" roadmap language. | +| 03-upgrading | MOVE into Upgrade and migrate as "Upgrade Agenta". Content unchanged. | +| 04-dynamic-access-controls | MOVE into Access control. Content unchanged. | +| 99-faq | TOUCH. Move the "which options are community projects" answer to the Overview; keep version-pinning and image questions. | +| guides/01-deploy-remotely | MOVE into Deploy to production. Content unchanged. | +| guides/02-using-ssl | MOVE into Deploy to production. Content unchanged. | +| guides/03-deploy-to-kubernetes | MOVE into Deploy to production and TOUCH. Fix the "What gets deployed" list to include the runner and store and the consolidated worker pair. | +| guides/04-deploy-on-railway | MOVE into Deploy to production and TOUCH. Rename the two stale variables in its runner table. Fix its worker list. | +| guides/05-configure-sso | MOVE into Access control. Fix the one deprecated variable (`SUPERTOKENS_EMAIL_DISABLED` to `AGENTA_ACCESS_EMAIL_DISABLED`). | +| guides/06-restrict-organization-creation | MOVE into Access control. Content unchanged. | +| guides/07-deploy-the-agent-runner | DELETE. Per-platform snippets move to the runner reference; the topology narrative moves to How agents run. The external-runner stub is dropped, not migrated. Redirect to How agents run. | +| guides/08-custom-agent-runner-images | DELETE. Superseded by Customize the agent runtime. Redirect there. | +| guides/09-agent-daytona-sandboxes | DELETE. Superseded by the Daytona how-to and the runner reference. Drop the archived-sandbox changelog prose. Redirect to the Daytona how-to. | +| infrastructure/01-architecture | MOVE into Architecture and TOUCH. Slim the runner subsection to a link to How agents run. Fix the stale variable in the sandbox matrix. | +| infrastructure/02-networking | MOVE into Architecture and TOUCH. Reconcile the store tunnel profile name across the networking page, the Daytona how-to, and the configuration reference (one name only). | +| upgrades/multi-org-migration | KEEP in Upgrade and migrate. Content unchanged. | +| upgrades/oss-to-ee-switch | KEEP in Upgrade and migrate. Content unchanged. | +| upgrades/runner-and-store | TOUCH. Replace the internal branch-name prerequisite with a version number. | +| upgrades/v0.100.3-migration | KEEP in Upgrade and migrate. Content unchanged. | + +New pages: Overview, Use your own subscription, How agents run, Sandbox isolation and security, +Run agents in a cloud sandbox (Daytona), Runner and sandbox configuration. Customize the agent +runtime replaces guide 08. + +### Overview (new, explanation) + +The landing page for the section. State what a person self-hosts: the agent studio and its +supporting services. State the editions (OSS and EE) and the open-source license and what it +covers. State the community-project support expectation moved from the FAQ. Link to Quick start as +the next step and to Agent execution for the model of where agents run. Keep it short. + +## Variable and cross-page corrections + +Apply on every touched page: + +- Update Helm snippets to the `agentRunner.providers.*` values paths. Remove + `agenta.sandboxLocalAllowed` and `agentRunner.daytona.installPi`. +- `SANDBOX_AGENT_LOG_LEVEL` becomes `AGENTA_RUNNER_LOG_LEVEL`. +- `AGENTA_RUNNER_TIMEOUT_SECONDS` and the `AGENTA_RUNNER_DAYTONA_SESSION_*` pair are already + canonical and stay. +- `AGENTA_AGENT_MCPS_ENABLED` is out of scope for this cleanup. +- The API-side gate `AGENTA_SANDBOX_LOCAL_ALLOWED` becomes the same enabled/default provider pair + read by the runner ([interface.md section 4](./interface.md)). + +## Redirects + +- `self-host/guides/deploy-the-agent-runner` to How agents run. +- `self-host/guides/custom-agent-runner-images` to Customize the agent runtime. +- `self-host/guides/agent-daytona-sandboxes` to the Daytona how-to. +- Old deploy, SSL, SSO, org-restriction, architecture, networking, upgrading, and + dynamic-access-controls URLs stay valid through `slug`, so no redirect is needed for the folder + moves. + +## Acceptance + +- A new reader can state what they self-host and the license scope after the Overview. +- A new reader can explain the runner topology after How agents run and the trust posture after + Sandbox isolation and security. +- A trusted local operator completes the subscription tutorial with no hidden files. +- A Daytona operator never mounts or uploads a subscription credential. +- Compose comments are enough to discover the opt-in local subscription path. +- The runner reference contains no variable absent from code. +- Search finds no removed variable name in public docs or `hosting/`. +- Every deleted page redirects to its successor and every moved page keeps its URL. +- No page mixes two Diátaxis types. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/interface.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/interface.md new file mode 100644 index 0000000000..b143fcb91a --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/interface.md @@ -0,0 +1,290 @@ +# Target interfaces + +This document classifies every field by semantic role and ownership. Names follow what a value is, not the feature that first needed it. + +## 1. Deployment topology + +A normal installation has one logical runner service. It may have multiple replicas later, but every replica in one runner deployment uses the same provider configuration. + +Services locates the runner. The runner owns sandbox execution. The code evaluator is a separate consumer with separate provider configuration. + +~~~text +Services + AGENTA_RUNNER_INTERNAL_URL + AGENTA_RUNNER_TOKEN + | + v +Agent runner + enabled sandbox providers + default sandbox provider + local provider + Daytona provider credentials and lifecycle + | + +--> local daemon and harness + | + +--> Daytona sandbox, daemon, and harness + +Code evaluator + separate code-execution provider configuration +~~~ + +## 2. Canonical environment variables + +### Caller-to-runner routing + +| Variable | Role | Owner and reader | Sensitive | Change cadence | +|---|---|---|---:|---| +| `AGENTA_RUNNER_INTERNAL_URL` | routing locator | deployment operator; Services reads it | no | deployment | +| `AGENTA_RUNNER_TOKEN` | protocol credential | deployment operator; Services sends it and runner verifies it | yes | deployment or rotation | + +The shared token is the same protocol credential at both ends, so one name is correct. It does not belong to a sandbox or harness. + +### Runner process + +| Variable | Role | Default | Sensitive | +|---|---|---|---:| +| `AGENTA_RUNNER_HOST` | server binding | `127.0.0.1`; hosting sets a private interface | no | +| `AGENTA_RUNNER_PORT` | server binding | `8765` | no | +| `AGENTA_RUNNER_CONCURRENCY_LIMIT` | capacity configuration | current safe default | no | +| `AGENTA_RUNNER_LOG_LEVEL` | logging configuration | current default | no | +| `AGENTA_RUNNER_REPLICA_ID` | replica identity | generated when absent | no | +| `AGENTA_API_INTERNAL_URL` | callback routing locator | deployment-specific | no | + +`AGENTA_API_INTERNAL_URL` keeps its standard name because it identifies the API, not the runner. The runner does not receive a static `AGENTA_API_KEY`. Session coordination, mount signing, and trace export use the caller credential already carried on each run request. Removing the static exporter fallback prevents a local harness from stealing a reusable platform credential through /proc. + +### Sandbox provider registry + +| Variable | Role | Default | +|---|---|---| +| `AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS` | deployment capability configuration | `local` | +| `AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER` | routing default | `local` | + +Rules: + +1. Values are normalized lowercase provider ids separated by commas. +2. Unset enabled providers means exactly `local`. +3. An explicitly empty list is invalid. +4. Unknown and duplicate ids are invalid. +5. The default must be enabled. +6. A request may select any enabled provider. +7. A request for a known but disabled provider fails before mounts, files, secrets, or sandboxes are created. +8. There is no fallback from an unavailable requested provider to another provider. +9. Adding a provider to a future runner release never enables it in an existing deployment. + +This is capability configuration, not a user authorization list. Per-project entitlement or policy can restrict the deployment set later, but cannot expand it. + +The API reads the same two variables. Hosting templates set both services from one operator-facing entry in the env file, so the operator configures the value once. See section 4. + +### Deployment postures + +The enabled-provider list is also the security posture switch: + +- **Trusted self-host (default):** `local` only. Local harnesses share the runner container; that is documented as a convenience for a single trusted operator, not an isolation boundary. Subscription mounts are an opt-in for this posture. +- **Multi-tenant or exposed:** `daytona` only. No harness process ever runs inside the runner container, so user code cannot read the runner's process environment or its Daytona provisioning credential. Setting the list is the entire posture change. + +### Runner-owned Daytona configuration + +| Variable | Role | Required when Daytona is enabled | Sensitive | +|---|---|---:|---:| +| `AGENTA_RUNNER_DAYTONA_API_KEY` | infrastructure credential | yes, unless an explicit supported token pair is configured later | yes | +| `AGENTA_RUNNER_DAYTONA_API_URL` | external service locator | no | no | +| `AGENTA_RUNNER_DAYTONA_TARGET` | region or target routing | no | no | +| `AGENTA_RUNNER_DAYTONA_SNAPSHOT` | runtime artifact reference | no, runner uses its pinned default | no | +| `AGENTA_RUNNER_DAYTONA_IMAGE` | runtime artifact override | no | no | +| `AGENTA_RUNNER_DAYTONA_AUTOSTOP_MINUTES` | sandbox lifecycle configuration | no | no | +| `AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES` | sandbox lifecycle configuration | no | no | +| `AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS` | warm-session policy | no | no | +| `AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM` | warm-pool capacity | no | no | + +Snapshot and image are mutually exclusive. Invalid positive durations fail startup. Empty strings are treated as absent only at the hosting parser boundary, then the typed runner config contains either a value or no value. + +The runner constructs a Daytona client explicitly from this object. It does not rely on the Daytona SDK reading ambient `DAYTONA_*` values. + +### Removed variables + +The cleanup deletes these names and their tests, examples, Helm values, and docs: + +- `SANDBOX_AGENT_PROVIDER` +- `SANDBOX_AGENT_LOG_LEVEL` (becomes `AGENTA_RUNNER_LOG_LEVEL`) +- `DAYTONA_API_KEY` +- `DAYTONA_API_URL` +- `DAYTONA_TARGET` +- `DAYTONA_SNAPSHOT` +- `DAYTONA_SNAPSHOT_AGENT` +- `DAYTONA_IMAGE` +- `DAYTONA_AUTOSTOP` +- `DAYTONA_AUTODELETE` +- `AGENTA_AGENT_SANDBOX_DAYTONA_SNAPSHOT` +- `AGENTA_AGENT_SANDBOX_PI_INSTALLED` +- `AGENTA_SESSION_HARNESS_MOUNTS` +- `AGENTA_SANDBOX_LOCAL_ALLOWED` +- `AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS` + +There are no aliases. Code-evaluator Daytona values get a separate namespace in its own follow-up rather than continuing to use the removed shared names. + +## 3. Startup validation and readiness + +The runner parses configuration once before listening. + +It fails startup when: + +- the enabled-provider list is empty or invalid; +- the default is not enabled; +- Daytona is enabled without a provisioning credential; +- snapshot and image are both set; +- lifecycle values are invalid; +- the runner token is required by hosting but missing on one side. + +Startup logs one redacted resolved configuration summary: + +~~~text +runner providers enabled=[local,daytona] default=local +runner daytona target=eu artifact=snapshot:agenta-agent-runner-v3 +~~~ + +No credential value or local source path is logged. + +## 4. Provider availability for API and web + +Version 1 uses one shared value instead of a discovery endpoint. + +The API already gates sandbox availability from its own environment today (`AGENTA_SANDBOX_LOCAL_ALLOWED` in `api/oss/src/utils/env.py`). The cleanup renames that existing gate rather than adding a new mechanism: + +- The API reads `AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS` and `AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER` with the same parsing rules as the runner. +- Compose, Helm, and Railway set both services from one operator-facing entry, so the operator configures the value once and drift requires deliberate effort. +- The web filters the sandbox picker from the API's existing configuration surface. +- The runner remains the final authority. A request for a disabled provider fails before side effects with an explicit "provider not enabled on this deployment" error, so even a drifted API produces an honest failure instead of a wrong run. +- `/health` remains a cheap unauthenticated process-health check. + +A runner `GET /capabilities` endpoint is deferred to [open issue RSH-7](./open-issues.md). It becomes worthwhile when deployments have multiple heterogeneous runners, not before. + +## 5. Run protocol authentication + +`credentialMode` is required and has three values: + +| Value | Meaning | Credential source | +|---|---|---| +| `env` | Agenta resolved a managed credential | per-run secret map | +| `runtime_provided` | the harness authenticates from explicitly prepared runtime state | local subscription mount | +| `none` | the run intentionally has no model credential | no credential | + +The target contract requires the provider and deployment metadata used for model-key narrowing; missing or unknown values fail validation, and the legacy "infer from hasApiKey" branch does not exist. Because the flip breaks any caller that omits the fields, it lands only after the caller audit in [plan.md phase 2](./plan.md) confirms every in-repo run path already sends them. The `AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS` escape hatch is deleted regardless. + +For `runtime_provided`: + +- local Pi and local Claude read explicitly mounted subscription state (section 6); +- Daytona Pi and Daytona Claude return a specific unsupported-combination error in version 1; +- the runner never searches its own home directory. + +## 6. Local subscription mounts and runtime customization + +Version 1 has no bootstrap manifest, no `AGENTA_RUNNER_BOOTSTRAP_CONFIG`, and no validation engine. The operator moves files the same way they customize every other service they run: through the Compose file and images they own. + +The two problems this replaces the manifest for: + +1. The runner must stop discovering its own Pi login and uploading it to Daytona. `shouldUploadOwnLogin` and `uploadPiAuthToSandbox` are deleted with their tests. +2. A local operator needs a declared way to use their own Pi, Claude, or Codex subscription. + +### Local subscription mounts + +The operator mounts credential state read-write into the runner container and points the harness config variable at it. The Compose files ship commented examples: + +~~~yaml +runner: + # Opt-in: use your own harness subscription for local runs. + # volumes: + # - ~/.pi:/agenta/harness/pi:rw + # environment: + # - PI_CODING_AGENT_DIR=/agenta/harness/pi +~~~ + +Rules: + +- Mounts are read-write, and the harness runs directly out of the mount. There is no per-run copy of the credential state. + + A subscription login is an OAuth login: the harness refreshes its access token mid-run and writes the new one back to its own config directory. A read-only mount plus a per-run copy discarded that refresh, so as soon as the provider rotated the refresh token the next run failed and the operator had to log in again by hand. Letting the harness own its token lifecycle, exactly as it does on a normal local install, is the only shape that survives rotation. + + The tradeoff: harness writes land in the operator's login directory (refreshed tokens, and any skills or system prompt a run installs), and concurrent local subscription runs share that directory the same way two local harness sessions do. This path is single-trusted-operator only, so both are acceptable. +- A `runtime_provided` local run without the matching mount fails with an error naming the missing configuration. +- Mounted subscription state never leaves the runner container. Daytona runs never receive it. +- One personal subscription belongs to one operator. The docs state this is a single-tenant convenience. + +### Runtime customization + +Extra binaries, certificates, and system dependencies are image concerns, not run-time file copies: + +- Local: build a custom runner image from `services/runner/docker/Dockerfile.gh` (documented how-to). +- Daytona: build and upload a custom snapshot with the existing scripts in `services/runner/sandbox-images/daytona/`, then set `AGENTA_RUNNER_DAYTONA_SNAPSHOT` (documented how-to). +- Extra project folders for local runs: an operator-owned Compose volume mount (documented how-to). + +A declarative bootstrap-asset manifest, hooks, plugins, and VPN setup remain future work ([RSH-4](./open-issues.md)) and start from real operator demand. + +## 7. Harness installation + +Harness availability is an image/runtime contract, not operator truth. + +- Pi uses a runner-pinned version. +- The runner probes the expected Pi executable. +- If a Daytona custom image or snapshot lacks Pi, the runner installs the pinned version before the session and logs the repair. +- If installation fails, the run fails with the missing executable and attempted version. +- The published runner image and published Daytona snapshot include Pi to avoid the repair path. +- Claude installation follows its existing licensing-safe first-use path and is also probed. + +There is no "installed" environment flag. + +## 8. Mount contract + +The run identity determines what persistence the run is supposed to have: + +| Run shape | Expected storage | +|---|---| +| no session id, no workflow artifact | ephemeral cwd is valid | +| session id present | session cwd mount | +| workflow artifact present | agent mount | +| resumable harness session | harness transcript mounts | + +Version 1 keeps the current best-effort behavior. Changing mount failure semantics is a behavior rewrite, not configuration cleanup, and it stays out of the release. Version 1 changes only two things: + +1. When a durable mount degrades to an ephemeral directory, the runner emits one structured warning naming the mount kind and the cause, so degradation frequency becomes measurable instead of silent. +2. `AGENTA_SESSION_HARNESS_MOUNTS` is removed as a public switch; transcript mounts derive from the session contract. + +The fail-loud contract (a required mount failure fails the run, no silent durable-to-ephemeral downgrade) is deferred to [RSH-11](./open-issues.md) and should be informed by the warning-log data. + +## 9. Hosting shape + +### Docker Compose + +The runner service enumerates only runner variables. It has no shared `env_file`. The examples include commented, opt-in read-write volumes for subscription inputs (section 6). + +One runner service can enable `local,daytona`; there is no second subscription runner in the default stack. + +### Helm + +Target values: + +~~~yaml +agentRunner: + enabled: true + providers: + enabled: [local] + default: local + daytona: + apiKeySecretRef: + name: agenta-runner-daytona + key: api-key + apiUrl: https://app.daytona.io/api + target: eu + snapshot: agenta-agent-runner + autostopMinutes: 15 + autodeleteMinutes: 30 + auth: + tokenSecretRef: + name: agenta-runner + key: token +~~~ + +The runner deployment must not include `agenta.commonEnv`. A dedicated helper renders only its narrow environment and provider secret references. Callback authorization arrives per run rather than through a pod-wide API key. + +### Railway + +Railway uses the same canonical environment names and one runner service. Unsupported local volume-based subscription setup is documented honestly rather than approximated with hidden copies. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/open-issues.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/open-issues.md new file mode 100644 index 0000000000..5172f7de41 --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/open-issues.md @@ -0,0 +1,73 @@ +# Open issues + +These items are intentionally deferred. They should become focused design projects or tracked implementation issues rather than expanding the cleanup. + +## RSH-1: Docker sandbox provider + +Source: PR #5285. + +Build the Docker provider on the typed provider registry and lifecycle-capability interface. Resolve Docker socket trust, per-session container identity, label-driven garbage collection, Linux host networking, durable mounts, and multi-replica ownership. + +Does not block: configuration cleanup, local provider, Daytona provider. + +## RSH-2: Code-evaluator Daytona namespace + +The code evaluator still needs its own explicit Daytona configuration and snapshot/image contract. It must not inherit the agent runner's credential or artifact default. + +Proposed owner: code-execution runtime. + +Does not block: removing shared names from the agent runner if the evaluator gets a coordinated rename in its own PR. + +## RSH-3: Provider-approved remote subscription authentication + +Obtain written guidance from OpenAI and Anthropic for third-party clients, storage, remote sandbox execution, multi-device use, and organization deployments. Only then design an explicit remote-auth product contract. + +No implementation or tutorial should precede that decision. + +## RSH-4: Declarative bootstrap assets, hooks, and network plugins + +Version 1 ships no bootstrap manifest at all: local subscription state arrives through an operator-owned read-write Compose mount, and runtime customization happens through images and snapshots. If operator demand shows a real need for declarative per-run file assets (typed manifest, validation, Daytona upload of non-auth files), design it then, together with hooks: repository checkout, VPN enrollment, certificate rotation, custom initialization. + +A future design must define: + +- trust and execution user; +- ordering and idempotency; +- timeout and cancellation; +- secrets and log redaction; +- local versus remote parity; +- network policy interaction; +- image-build alternatives. + +## RSH-5: Secret-manager bootstrap sources + +Version 1 reads mounted files. Helm users may want Kubernetes Secret references and external secret-store integrations without materializing a broad shared environment. + +Add typed source variants only after the file contract is stable. + +## RSH-6: Multi-runner scheduling and ownership + +Multiple replicas need provider-aware capacity, session ownership, warm-sandbox adoption, and capability aggregation. A single logical runner deployment is enough for the current cleanup. + +## RSH-7: Runner capability discovery endpoint + +Version 1 has no `GET /capabilities` endpoint. API and runner read the same enabled-provider value from one deployment entry, and the runner stays the final authority. Build the endpoint (with caching, versioning, and platform exposure) when deployments have multiple heterogeneous runners and one shared value stops being true, not before. + +## RSH-8: Local isolation provider + +Trusted local is intentionally unconfined. Docker, Landlock, bubblewrap, or another provider can offer isolation later. Do not relabel the current local provider as secure. + +## RSH-9: Mount viewer UX + +PR #5274 did not add mount-viewer UI. The product still needs an explicit UX for session files, agent files, nesting, empty states, and mount failures. That work is separate from making runtime mounts reliable. + +## RSH-10: Custom mount-role documentation + +The product is pre-production, so this cleanup does not add a compatibility bridge for old custom permission lists. Before public RBAC customization ships, document the final mount permissions and add a role-validation diagnostic. + +## RSH-11: Fail-loud required mounts + +Version 1 keeps best-effort mount behavior and adds one structured warning when a durable mount degrades to an ephemeral directory. The deferred contract: a session, workflow-artifact, or resumable-session run whose required mount cannot be signed or mounted fails the run instead of silently continuing on ephemeral storage. Use the warning-log data to size the problem and to catch race conditions before making failures hard. + +## RSH-12: Runner secret narrowing in shared-env deployments + +A deployment that delivers one shared environment file to every container exposes the runner token and the Daytona key to services that do not need them. Daytona-only deployments tolerate this for now because no user code runs in the runner container. The follow-up hardening: a dedicated runner secret, no shared environment file on the runner, and FUSE privilege removal where local mounts are not needed. Run it as its own project after the release. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/plan.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/plan.md new file mode 100644 index 0000000000..36d4cb0718 --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/plan.md @@ -0,0 +1,150 @@ +# Implementation plan + +The design lands as a short sequence of narrow PRs sized for the MVP release. The release needs stable public names, a clean hosting layout, and honest docs. It does not need new subsystems. Behavior rewrites (capability discovery, bootstrap manifests, fail-loud mounts) are deferred to open issues. + +## Phase 0: repair local QA and the merged snapshot regression + +Goal: restore a trustworthy 8280 test environment before larger refactors. + +### Changes + +- Fix the Compose empty-string snapshot regression from PR #5274. +- Remove the dead `AGENTA_AGENT_SANDBOX_DAYTONA_SNAPSHOT` line. +- Point the 8280 deployment at one trusted development runner. +- Give that runner the Daytona provisioning credential. +- Keep local Pi and Claude login inputs read-write and explicit (the harness owns its token lifecycle). +- Remove or stop the two ad hoc runner services after the unified runner passes the matrix. +- Record the exact local setup without committing personal credentials. + +### Acceptance + +- One runner health endpoint is used by Services. +- Local Pi subscription and local Claude subscription work. +- Daytona Pi and Daytona Claude work with managed API keys. +- A Daytona request no longer reaches a runner that lacks Daytona credentials. +- No subscription file is uploaded to Daytona. + +This phase may use the current names temporarily to unblock QA, but it must not add new compatibility variables. + +## Phase 1: canonical names and typed runner configuration + +Goal: one parse-and-validate boundary inside the runner, and the final public names everywhere. This is the open interface we cannot change after release, so it is the center of gravity. + +### Changes + +- Add a `RunnerConfig` module with typed server, providers, Daytona, callback, and lifecycle sections. +- Rename provider and Daytona variables to the contract in [interface.md](./interface.md). +- Delete old variables and compatibility branches. +- Default enabled providers to local only. +- Validate configuration before the HTTP server listens. +- Make the default provider come from typed config instead of scattered `process.env` reads. +- Pass explicit Daytona configuration to the SDK client. +- Remove `AGENTA_AGENT_SANDBOX_PI_INSTALLED`; probe and repair Pi. +- Rename the API-side gate: `AGENTA_SANDBOX_LOCAL_ALLOWED` becomes the same `AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS` / `AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER` pair, parsed with the same rules ([interface.md section 4](./interface.md)). Compose, Helm, and Railway feed API and runner from one operator-facing entry. +- Update unit tests to cover unset, explicit empty string, invalid ids, default mismatch, missing credentials, and mutually exclusive artifact configuration. + +### Files and surfaces + +- `services/runner/src/config/*` or one equivalent focused module. +- `provider.ts`, `run-plan.ts`, `server.ts`, session identity and pool readers. +- `api/oss/src/utils/env.py` and the SDK/services readers of the local-allowed gate. +- Compose files and environment examples in OSS and EE. +- Helm schema, values, runner template, secrets, and tests. +- Railway runner scripts. +- Runner README and Docker image documentation. + +### Acceptance + +- All provider selection reads the typed object. +- No old agent-runner Daytona name remains in code, hosting, or docs. +- Enabling Daytona without a key fails startup. +- Adding an unknown provider id fails startup. +- The default must be enabled. +- A request for a disabled provider fails before side effects with an explicit error. +- Existing provider tests include the Compose empty-string case. + +## Phase 2: runner environment narrowing + +Goal: reconcile the #5286 audit fixes with the runner trust boundary, without a strictness flip that can break the release. + +### Changes + +- Keep the shared runner token and constant-time verification. +- Keep clear-then-apply for managed credentials and provider-specific key narrowing. +- Delete `AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS`. +- Remove `agenta.commonEnv` from the Helm runner deployment if #5286 lands it; add a dedicated runner environment helper that injects only approved fields. +- Remove the static `AGENTA_API_KEY` exporter fallback and use the request-scoped caller credential for callbacks and traces. +- Audit every in-repo caller (playground, workflows, evaluations, SDK local) for `credentialMode`, provider, and deployment. If all send them, make the fields required and delete the inference branch in this phase. If any caller omits them, fix the caller here and land the strictness flip immediately after the release. +- Assert in rendered-chart tests that database, auth, cryptographic, Redis, store-master, and unrelated provider secrets are absent from the runner deployment. + +### Acceptance + +- A local OpenAI run cannot read an Anthropic key from its harness environment. +- A local harness cannot read Postgres, Agenta cryptographic, or bucket-wide store credentials from the runner process. +- Managed credentials remain per run. +- Daytona infrastructure credentials are blanked from every harness environment. +- The caller audit result is recorded in status.md with the decision it produced. + +## Phase 3: subscription cleanup + +Goal: replace automatic credential discovery with explicit operator mounts. This is a deletion plus documentation, not a new subsystem. + +### Changes + +- Remove `shouldUploadOwnLogin`, `uploadPiAuthToSandbox`, and their fallback tests. +- Reject Daytona `runtime_provided` authentication with an actionable error. +- Local `runtime_provided` runs read the operator's mounted subscription state; the runner copies it to a per-run directory so harness writes never touch the source. +- A local `runtime_provided` run without the matching mount fails with an error naming the missing configuration. +- Add commented Compose examples for Pi and Claude subscription source mounts. +- Remove `AGENTA_SESSION_HARNESS_MOUNTS`; transcript mounts derive from the session contract. +- Add one structured warning log when a durable mount degrades to an ephemeral directory ([interface.md section 8](./interface.md)). No other mount behavior changes. + +### Acceptance + +- No code reads an operator home directory to find credentials. +- Local Pi and Claude subscription runs work only with an explicit mount. +- A Daytona run never receives subscription state. +- Harness writes affect only the per-run copy. +- Mount degradation appears in logs with kind and cause. + +## Phase 4: hosting layout and public documentation + +Goal: make the hosting tree and the docs self-explanatory. Docs depend only on the phase 1 names, so this phase starts as soon as phase 1 is merged and lands with the release. + +### Changes + +- Add `hosting/README.md` mapping the layout: Compose variants per edition and image mode, Helm, Railway, the runner image built from `services/runner/docker/`, the Daytona snapshot recipe in `services/runner/sandbox-images/daytona/`. +- Add a commented entry for every canonical runner variable to every `env.*.example`. +- Rewrite the self-host docs per [documentation-plan.md](./documentation-plan.md). +- Add a removed-name gate: a grep check that no removed variable name survives under `hosting/` or `docs/`. + +### Acceptance + +- Every documented variable appears in the runtime and hosting examples. +- Every runtime variable is either documented or intentionally internal. +- Tutorial commands pass against a clean Compose deployment. +- Daytona guides use managed API keys only. +- The docs do not imply local isolation or remote subscription support. +- Search finds no removed variable in `hosting/` or public docs. + +## Deferred (open issues, not release work) + +- Runner `GET /capabilities` and platform capability discovery: [RSH-7](./open-issues.md). One shared env value replaces it in version 1. +- Declarative bootstrap-asset manifest, hooks, plugins, VPN setup: [RSH-4](./open-issues.md). Read-only mounts and custom images replace it in version 1. +- Fail-loud required mounts: [RSH-11](./open-issues.md). The degradation warning log gathers the data first. +- The credentialMode strictness flip, if the phase 2 caller audit finds an omitting caller. + +## Proposed PR stack + +1. `fix(runner): repair agent snapshot selection and local QA routing` +2. `refactor(runner): canonical runner configuration names and typed config` +3. `fix(hosting): narrow the runner deployment environment` +4. `docs(self-host): agent runner docs and hosting layout` + +PRs 2, 3, and 4 touch mostly disjoint files and can be built in parallel. PR 4 merges last so docs match the merged names. + +## Landing order with open PRs + +- PR #5274 is already merged. Phase 0 addresses its immediate follow-ups. +- PR #5285 is docs-only. This design supersedes its allowlist contract but keeps its capability-based provider direction for future Docker work. +- PR #5286 can land independently if needed. Preserve its runner token and key narrowing. Remove broad `commonEnv` and the inherit-all flag in phase 2. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/qa.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/qa.md new file mode 100644 index 0000000000..7ccdce2372 --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/qa.md @@ -0,0 +1,155 @@ +# QA plan + +The cleanup is complete only when one deployment can exercise the supported combinations and unsupported combinations fail for the intended reason. + +## 1. Primary matrix + +| Sandbox | Harness | Authentication | Expected | +|---|---|---|---| +| local | Pi | managed OpenAI or other provider key | pass | +| local | Pi | explicit Pi subscription mount | pass | +| local | Claude | managed Anthropic or supported custom-provider key | pass | +| local | Claude | explicit Claude subscription mount | pass | +| Daytona | Pi | managed provider key | pass | +| Daytona | Claude | managed provider key | pass | +| Daytona | Pi | runtime-provided subscription | reject before sandbox creation | +| Daytona | Claude | runtime-provided subscription | reject before sandbox creation | + +Run the passing cells with: + +- a stateless prompt; +- one tool call; +- a session follow-up; +- a file written to the session cwd and read on the next turn; +- a workflow artifact that exposes the agent mount. + +## 2. Provider configuration tests + +### Parser unit tests + +- enabled providers unset gives `local`; +- explicit `local,daytona` preserves order-independent set equality; +- leading and trailing whitespace normalize; +- duplicate ids fail; +- unknown ids fail; +- explicit empty string fails; +- default unset gives local; +- default outside the enabled set fails; +- Daytona enabled without provisioning credentials fails; +- Daytona absent ignores optional Daytona tuning; +- snapshot plus image fails; +- invalid zero, negative, non-numeric, and fractional lifecycle values fail; +- Compose-substituted empty optional values become absent. + +The API-side parser (`AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS` read by the API) gets the same parser unit cases so both readers agree on every input. + +### Execution-gate tests + +- explicit enabled provider passes; +- explicit disabled provider fails before temp directory creation with the explicit "provider not enabled on this deployment" error; +- omitted provider uses the configured default; +- no silent fallback occurs; +- keepalive does not create or park an environment for a rejected provider; +- provider factory repeats the hard gate for callers that bypass run-plan construction; +- the web picker shows exactly the configured enabled set (API-derived); +- a deliberately drifted API value still ends in the runner's honest rejection, not a wrong run. + +## 3. Credential-boundary tests + +### Managed credentials + +- `credentialMode=env` clears ambient model-provider variables, then applies only resolved secrets; +- Daytona API key is always blank inside the harness; +- local OpenAI run cannot read Anthropic variables; +- local Anthropic run cannot read OpenAI variables; +- Daytona sandbox receives only the selected model credential and scoped operational data. + +### Runtime-provided credentials + +- caller audit outcome is enforced: if the strictness flip landed, provider, deployment, and credential mode are required and no `hasApiKey` inference remains; if it was deferred, the audit record in status.md names the omitting caller and the follow-up; +- no inherit-all environment mode remains; +- local runtime-provided auth requires the matching subscription mount and fails with an error naming the missing configuration when absent; +- Daytona runtime auth fails with a stable unsupported-combination code; +- no function scans `HOME`, `PI_CODING_AGENT_DIR`, or `CLAUDE_CONFIG_DIR` for implicit upload; +- no Pi `auth.json` is written to a Daytona sandbox during managed runs. + +### Rendered deployment assertions + +Render OSS and EE Helm charts and inspect the runner container environment. Assert the absence of: + +- `POSTGRES_PASSWORD`; +- `POSTGRES_URI_*`; +- `AGENTA_AUTH_KEY`; +- `AGENTA_CRYPT_KEY`; +- `AGENTA_API_KEY`; +- Redis passwords and URIs; +- bucket-wide `AGENTA_STORE_ACCESS_KEY` and `AGENTA_STORE_SECRET_KEY`; +- unrelated LLM provider credentials; +- `agenta.commonEnv` expansion. + +Assert the presence of only configured runner values, runner token reference, API locator, and provider-specific secret references. Callback and trace tests assert that the per-run caller credential is used and never promoted to process-wide state. + +## 4. Subscription mount tests + +- every runtime-provided run runs directly out of the operator's mount (no per-run config copy); +- Pi receives `PI_CODING_AGENT_DIR` pointing at the mount; +- Claude receives `CLAUDE_CONFIG_DIR` pointing at the mount; +- the operator source is mounted read-write and a token the harness refreshes persists to it; +- teardown does NOT delete the mount; +- concurrent local subscription runs share the mount (accepted: single trusted operator); +- subscription state never uploads to a Daytona sandbox; +- content and credential paths do not appear in logs or traces. + +## 5. Harness installation tests + +- published local image contains the pinned Pi version; +- published Daytona snapshot contains the pinned Pi version; +- custom Daytona image missing Pi triggers one pinned install; +- successful install continues the run; +- failed install returns the version and executable location without credentials; +- stale `AGENTA_AGENT_SANDBOX_PI_INSTALLED` has no effect because the variable is absent from the contract; +- Claude first-use install follows the existing license-safe path. + +## 6. Mount tests + +Version 1 keeps best-effort behavior, so QA asserts observability instead of hard failure: + +| Case | Expected | +|---|---| +| sessionless run, store unavailable | ephemeral run passes, no warning | +| session run, durable mount degrades to ephemeral | run continues and one structured warning names mount kind and cause | +| transport endpoint disconnected once | one remount, then continue if healthy | +| successful session turn | file survives a cold next turn | +| `AGENTA_SESSION_HARNESS_MOUNTS` set in the environment | no effect; variable absent from the contract | + +The fail-loud cases (session, artifact, and transcript mount failures failing the run) move to RSH-11 with this table as their starting point. + +## 7. Local 8280 acceptance + +Before deleting the extra runners: + +1. Record the currently selected `AGENTA_RUNNER_INTERNAL_URL`. +2. Confirm which service contains the Daytona credential. +3. Confirm which read-write subscription inputs exist. +4. Configure the normal runner with enabled providers `local,daytona`. +5. Add local-only Pi and Claude read-write subscription mounts. +6. Run the primary matrix. +7. Confirm the Daytona sandbox count returns to zero. +8. Stop the legacy subscription sidecars. +9. Repeat one local subscription cell and both Daytona managed-key cells. +10. Record the final Compose service and environment shape. + +Personal credentials and generated per-run copies must never be committed. + +## 8. CI commands + +Implementation PRs should run the narrow suites first, then the area suite required by repository instructions: + +- runner unit and acceptance tests under `services/runner`; +- Services and SDK agent tests for wire changes; +- API tests for the provider-availability configuration; +- web package tests for the picker; +- Helm lint and rendered-template assertions; +- Compose config rendering for OSS/EE and dev/GH variants; +- agent-workflows QA against the supported matrix; +- one replay regression for each real failure worth pinning. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/research.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/research.md new file mode 100644 index 0000000000..035900f6c4 --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/research.md @@ -0,0 +1,151 @@ +# Research + +Date: 2026-07-13 + +## Repository evidence + +### Provider resolution + +The runner currently resolves the sandbox from the request, then `SANDBOX_AGENT_PROVIDER`, then `local`. The provider factory recognizes local and Daytona and keeps E2B as planned work. + +The target keeps per-run selection but moves the default and enabled set into one runner-owned configuration object. The runner remains the authoritative execution gate. + +### Daytona snapshot resolution + +`services/runner/src/engines/sandbox_agent/provider.ts` resolves the agent snapshot with: + +~~~ts +process.env.DAYTONA_SNAPSHOT_AGENT ?? process.env.DAYTONA_SNAPSHOT +~~~ + +Compose always injects `DAYTONA_SNAPSHOT_AGENT` and substitutes an empty string when the operator does not set it. An empty string is not nullish, so the configured shared snapshot is discarded. The existing unit test deletes the variable and misses the Compose case. + +PR #5274 also introduced `AGENTA_AGENT_SANDBOX_DAYTONA_SNAPSHOT` in one OSS SSL Compose file. No runtime reads it. + +The larger fix is not another fallback expression. The target has one agent-runner snapshot variable, `AGENTA_RUNNER_DAYTONA_SNAPSHOT`, and no shared code-evaluator default. + +### Automatic Pi OAuth upload + +For a self-managed run, `shouldUploadOwnLogin` treats `credentialMode=runtime_provided` as permission to upload local login state. `uploadPiAuthToSandbox` copies the runner's entire Pi `auth.json` and settings into Daytona. It does not select only the credential needed by the run. + +That couples three unrelated decisions: + +1. the workflow uses runtime-provided authentication; +2. the runner happens to have Pi state; +3. the state should be copied to a third-party remote sandbox. + +The target removes this inference. Local subscription use requires an explicit read-write mount. Daytona runtime-provided subscription authentication is unsupported in version 1. + +### Runtime-fact and policy booleans + +`AGENTA_AGENT_SANDBOX_PI_INSTALLED` changes whether the runner installs Pi in Daytona. It is easy for the flag and snapshot contents to disagree. The runner already controls the pinned Pi version and can probe the executable. + +`AGENTA_SESSION_HARNESS_MOUNTS` turns harness transcript mounts on and off globally. These mounts exist to make a durable session work. Their presence should derive from the session contract, not from a public deployment switch. + +Both variables are removed. + +### Best-effort mounts + +Mount signing and mounting can currently degrade to an ephemeral directory. On Daytona, a store endpoint that is unreachable from the sandbox can also produce a plain directory at the expected durable path. This gives a successful run whose files or harness state later disappear. + +Request identity defines what persistence a run is supposed to have: + +- sessionless run: ephemeral is valid; +- session id present: the session cwd; +- workflow artifact present: the agent mount; +- continuation that needs harness-native state: transcript mounts. + +Version 1 keeps best-effort behavior and logs one structured warning when a durable mount degrades, so the degradation rate becomes measurable. Failing the run instead is RSH-11. + +## PR #5285 assessment + +[PR #5285](https://github.com/Agenta-AI/agenta/pull/5285) contains two useful directions: + +- model providers as a typed capability axis, with future Docker and E2B implementations; +- capability-based lifecycle behavior instead of adding `isDocker` beside `isDaytona`. + +Its proposed `AGENTA_RUNNER_SANDBOXES_ALLOWLIST` contract should not be implemented as written: + +- Unset or empty means all known providers, so adding a provider changes existing deployments. +- It is read independently by API, Services, runner, and web, which creates four sources that can drift. +- It preserves `AGENTA_SANDBOX_LOCAL_ALLOWED` as an alias even though the product is pre-production. +- "Allowlist" frames normal runtime capability configuration as an access-control exception. +- The singular default remains in the unrelated `SANDBOX_AGENT_*` namespace. + +This project instead uses: + +- `AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS`; +- `AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER`; +- local-only as the unset default; +- runner authority plus capability discovery for upstream UX; +- no compatibility alias. + +The Docker implementation stays a separate project. Its discriminated provider and lifecycle-capability model is a dependency for future extensibility. + +## PR #5286 assessment + +[PR #5286](https://github.com/Agenta-AI/agenta/pull/5286) contains two changes to keep: + +- `AGENTA_RUNNER_TOKEN` on both the caller and runner; +- narrowing inherited model-provider keys using the declared provider and deployment. + +Two parts need revision: + +### No provider-key escape hatch + +`AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS` restores the insecure behavior for callers that omit provider metadata. The wire contract already has `provider`, `deployment`, and `credentialMode`. Because the feature is pre-production, those fields should become required where needed and unknown values should fail. There should be no public "inherit everything" mode and no un-migrated-caller branch. + +### No Helm common environment + +The PR includes `agenta.commonEnv` in the runner deployment. That block contains application license, Postgres password and URIs, Agenta auth and cryptographic keys, Redis credentials, store master credentials, and unrelated service configuration. + +A local harness can inspect the runner process environment. Injecting `commonEnv` therefore exposes the whole application trust domain to every local agent. The runner needs a narrow set instead: + +- its own server and provider configuration; +- the Services-to-runner shared token; +- an Agenta API locator; +- scoped per-run credentials delivered in the request. + +The current run request already carries the caller credential used for session coordination, mount signing, and trace export. The target removes the process-wide `AGENTA_API_KEY` fallback rather than introducing another reusable runner secret. + +If #5286 lands first, the cleanup must remove `commonEnv` from the runner template while preserving the runner token and least-privilege model-key logic. + +## PR #5274 assessment + +[PR #5274](https://github.com/Agenta-AI/agenta/pull/5274) made valuable changes to mount identity, mount permissions, workflow-artifact validation, geesefs recovery, snapshot contents, and generated clients. + +Follow-up work from its review: + +- fix the empty-string snapshot regression immediately; +- remove the dead snapshot variable; +- do not describe the PR as mount-viewer UI work, because it contains generated web clients only; +- keep the new mount permissions without a compatibility bridge for unpublished custom roles; +- make the session and agent mount contract fail loudly. + +## Subscription terms and support boundary + +Official OpenAI material says ChatGPT-plan Codex use starts through supported Codex clients and that users may not share account credentials or make an account available to another person. It does not document Pi as an approved client or authorize copying a Codex OAuth file into a third-party sandbox. + +This is not a legal conclusion. It is an engineering support decision under uncertainty: + +- Agenta will not automatically copy OpenAI or Anthropic subscription state to Daytona. +- The first tutorial covers a user mounting their own credential into their own trusted local self-hosted runner. +- Remote subscription support remains closed until the provider confirms the intended client, storage, and execution model in writing. +- API-key authentication remains the supported Daytona path. + +Sources: + +- [OpenAI Terms of Use](https://openai.com/policies/terms-of-use/) +- [OpenAI Account Sharing Policy](https://help.openai.com/en/articles/10471989-openai-account-sharing-policy) +- [Using Codex with your ChatGPT plan](https://help.openai.com/en/articles/11369540-using-codex-with-chatgpt) + +## Current support matrix + +| Sandbox | Harness | Managed API key | Local subscription | +|---|---|---:|---:| +| local | Pi | supported | supported through an explicit mount | +| local | Claude | supported | supported through an explicit mount | +| Daytona | Pi | supported | unsupported | +| Daytona | Claude | supported | unsupported | + +"Local subscription" means the operator's own credential in a trusted self-hosted runner. It is not a mechanism for sharing one subscription across Agenta users. diff --git a/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/status.md b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/status.md new file mode 100644 index 0000000000..76f1819ab1 --- /dev/null +++ b/docs/design/agent-workflows/projects/runner-selfhosting-cleanup/status.md @@ -0,0 +1,99 @@ +# Status + +State: DESIGNED v2 +Next action: owner second-round review on PR #5288 +Last updated: 2026-07-13 + +## Review round 1 (2026-07-13) + +Owner review plus a second reviewing agent converged on a re-scope for the MVP release. All four owner comments were adopted: + +- Cut the runner `GET /capabilities` endpoint. API and runner read the same enabled-provider value from one deployment entry (the API already had this pattern via `AGENTA_SANDBOX_LOCAL_ALLOWED`). Deferred as RSH-7. +- Cut the declarative bootstrap manifest. Local subscriptions use an operator-owned read-write Compose mount; runtime customization uses custom images and the existing Daytona snapshot scripts. Deferred as RSH-4. +- Cut the fail-loud mount rework. Version 1 keeps best-effort behavior plus one structured degradation warning. Deferred as RSH-11. +- Confirmed customization is owned by the operator through scripts and images, not runner environment machinery. + +Round 1 also added: + +- The credentialMode strictness flip is now conditional on a caller audit (phase 2). +- A hosting-layout workstream: `hosting/README.md`, env.example coverage, removed-name grep gate (phase 4). +- Docs land with the phase 1 rename, not after all phases; the documentation plan now carries per-page dispositions from a full audit of the 20 existing self-host pages. +- `SANDBOX_AGENT_LOG_LEVEL` added to the rename list as `AGENTA_RUNNER_LOG_LEVEL`. + +The managed-deployment migration is assessed in a private working note outside this repository; it is a small rename delta, not new infrastructure. + +## Owner decisions confirmed in round 1 + +- [x] `AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS` and `AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER`. +- [x] Local-only as the unset enabled-provider default. +- [x] Shared environment value instead of `GET /capabilities` (owner comment; endpoint deferred). +- [x] Runner-scoped `AGENTA_RUNNER_DAYTONA_*` namespace; separate code-evaluator configuration. +- [x] No environment aliases because the feature is pre-production. +- [x] Subscription mounts instead of a bootstrap manifest (owner comment; manifest deferred). +- [x] Local subscription auth only; remote subscription combinations rejected. +- [x] Best-effort mounts with a degradation warning (owner comment; fail-loud deferred). +- [x] Remove the static runner `AGENTA_API_KEY` fallback in favor of request-scoped callback credentials. +- [x] Preserve the #5286 runner token and key narrowing; remove `commonEnv` and the inherit-all escape hatch. +- [x] One trusted all-capabilities runner for local 8280 QA. + +## Owner decisions requested (round 2) + +- [ ] Approve the v2 re-scope as a whole (phases and 4-PR stack in [plan.md](./plan.md)). +- [ ] Approve the documentation dispositions and new page set in [documentation-plan.md](./documentation-plan.md). +- [ ] Approve the conditional credentialMode strictness flip (audit first, flip only if free). + +## Implementation state + +- [ ] Phase 0: local QA and snapshot repair. +- [ ] Phase 1: canonical names and typed runner configuration. +- [x] Phase 2: runner environment narrowing. +- [x] Phase 3: subscription cleanup. +- [ ] Phase 4: hosting layout and public documentation. + +## Phases 2 + 3 landed (2026-07-14) + +Shipped in `fix/runner-env-narrowing` (stacked on `refactor/runner-config-names`): + +- Deleted `shouldUploadOwnLogin` + `uploadPiAuthToSandbox` and their tests. The runner no longer + discovers its own Pi login and uploads it to a Daytona sandbox. +- `buildRunPlan` rejects Daytona + `runtime_provided`, and rejects a local `runtime_provided` run + whose subscription mount (`PI_CODING_AGENT_DIR` / `CLAUDE_CONFIG_DIR`) is unset, before any + sandbox side effect. +- Local `runtime_provided` Pi and Claude runs read and write the operator's read-write mounted + login DIRECTLY: no per-run copy. The harness refreshes its own OAuth token and the new token + persists to the mount, so a rotated refresh token no longer breaks the next run. Tradeoff: + concurrent local subscription runs share the harness config dir (single-trusted-operator path). +- Removed the static `AGENTA_API_KEY` exporter fallback in `tracing/otel.ts`; export auth rides + the per-run caller credential. +- One structured `mount degraded kind= cause=` warning at each durable-null site + (session cwd, agent mount, harness transcript). Failure behavior unchanged (RSH-11 still open). +- Helm: `AGENTA_RUNNER_TOKEN` wired via `agentRunner.auth.tokenSecretRef` in + `runner-deployment.yaml` + `_helpers.tpl` (both ends), values.yaml/schema examples, and a + rendered-chart secret-absence test (`hosting/kubernetes/helm/tests/`) wired into CI. +- Compose: commented opt-in read-write subscription mount examples on the OSS + EE runner service. + +No-ops on this base (deletions already satisfied, nothing to remove): `commonEnv` is absent from +the runner deployment (the #5286 include that would add it is not an ancestor of this base), +`AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS` has zero code occurrences, and +`AGENTA_SESSION_HARNESS_MOUNTS` was already removed from `services/runner/src` in phase 1. + +## Deferred to post-release: credentialMode strictness flip + +The interface's required-`credentialMode` contract (interface.md section 5) is NOT free on this +base, so the flip is deliberately deferred and this slice keeps the existing inference branch. + +The caller audit (research.md / the env-narrowing spec section 9) found one structural path that +omits the field: a **model-less agent template** in the SDK agent handler. When `_agent_model_ref` +returns `None` (`sdks/python/agenta/sdk/agents/handler.py`, model-less template branch), no +`resolved_connection` is threaded, so `wire_resolved_connection()` returns `{}` and the run request +carries no `credentialMode` / `provider` / `deployment`. Every model-configured caller sends all +three via `ResolvedConnection.to_wire()`. + +Post-release work to unblock the flip: thread a sentinel `ResolvedConnection` (credential_mode +`"none"`, or `"runtime_provided"` for a self-managed harness) when `model_ref is None` in +`handler.py`, confirm across the matrix, then make `credentialMode` required in `buildRunPlan` and +delete the inference branch. `AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS` is already deleted (no-op). + +## Review guidance + +The draft PR has inline decision threads on the most consequential choices. Resolve those before implementation. Small naming edits can land directly in this workspace; changes that alter credential movement, remote subscription support, or mount behavior should update the interface, plan, and QA documents together.