Add cloud-agent product plan and roadmap (internal)

Strategy, architecture, and phased roadmap for a Copilot-style cloud coding agent on AWS, plus the pricing model. Private-repo planning doc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Seto Elkahfi committed Jun 26, 2026 at 16:43 UTC c4eafd9389344575aa9ca6b2422de51302aefd63
1 file changed +419
docs/product/cloud-agent-plan.md
+419
new file mode 100644 index 0000000..fb8c6bd --- /dev/null +++ b/docs/product/cloud-agent-plan.md @@ -0,0 +1,419 @@ +# siGit Cloud Agent — product plan and roadmap + +Status: draft v1 (2026-06-26). Owner: product/eng. Audience: internal (private repo). + +A GitHub-Copilot-coding-agent-style product built into sigit.si and siGit Code +Cloud: you give it a task against a repo you host on sigit.si, an isolated AWS +sandbox runs the siGit Code agent loop against that repo, and it returns a branch +plus a proposed pull request. This document is the strategy, the architecture, and +the phased roadmap. + +--- + +## 1. What we are building (and what it is not) + +**siGit Cloud Agent** is an asynchronous, server-side coding agent. The user +describes a task ("add pagination to the repos list", "fix the failing +auth test", "upgrade Rails to 8.1"), points it at one of their sigit.si repos and +a base branch, and walks away. The platform provisions an ephemeral AWS sandbox, +clones the repo into it, runs siGit Code headless against the task with cloud +inference, lets it edit files and run build/test commands, then pushes a head +branch back to sigit.si and opens a pull request for human review. + +It is the cloud, autonomous sibling of the two things siGit already ships: + +- **siGit Code** (public Rust CLI / ACP agent) runs *locally / on the desktop*, + interactively, driven by the developer at their keyboard. +- **siGit Cloud Agent** runs *in our cloud*, asynchronously, driven by a task and + reviewed afterward through a PR. + +Reference points in the market: GitHub Copilot coding agent (assign an issue, it +opens a PR), OpenAI Codex cloud, Cursor background agents, Devin. The +differentiator for us is that we already own the whole vertical: the git host, the +agent, the inference, the account, and the billing all live inside siGit. We are +not bolting an agent onto someone else's platform; we are completing a platform we +already run. + +What it is **not**, in v1: it is not an autonomous merger (a human reviews and +merges), not a long-lived persistent dev environment (sandboxes are ephemeral per +run), and not a chat product (the chat tier already exists; this is the +task-to-PR product on top of it). + +--- + +## 2. Why siGit is unusually well positioned + +The expensive parts of a cloud-agent product already exist in this repo and the +sibling repos. The new work is mostly orchestration and isolation, not net-new +agent or inference plumbing. + +| Capability the product needs | Already exists | Where | +|---|---|---| +| A git host the agent can clone from and push to | yes | `git_http_controller` (smart-HTTP, bare repos on disk, `Repository#disk_path`) | +| The coding agent itself (edit/run/test loop, tool calling) | yes | `sigit` (public Rust CLI/ACP agent), `onde-cloud` tool-call mapping | +| Hosted inference with auth, identity masking, metering | yes | `Api::V1::ChatCompletionsController` → `OndeCloudService` → Onde Cloud | +| Accounts and a trust boundary that mints scoped tokens | yes | smbCloud auth, `Api::V1` sessions/me, `git_token` exchange | +| Subscription gating + monthly metering | yes | `Subscription`, `CloudUsage`, `User#entitled_to_cloud?` | + +What is **missing** and must be built (see roadmap): + +1. **An execution plane on AWS** (the sandbox machine) and the orchestration to + drive it. This is the heart of the project. +2. **A control plane in Rails**: an `AgentRun` resource, its lifecycle, log + streaming, and web UI. +3. **Pull requests** (and ideally issues). sigit.si has repos, blobs, commits, and + stars, but no `PullRequest` model today. The agent's output is a PR, so a + minimal PR/diff/review surface is a hard dependency. This can be scoped down to + a "compare and open PR" view for v1. +4. **Per-run scoped credentials**: short-lived git-push and inference tokens minted + server-side, budget-bounded, never long-lived in the sandbox. +5. **A new metering/pricing dimension**: agent runs consume sandbox compute *and* + inference tokens, so COGS has two drivers, not one. + +--- + +## 3. Architecture + +Two planes plus the existing inference path. Control plane is Rails (sigit-si); +execution plane is AWS; inference reuses the existing `/api/v1/chat/completions` +proxy unchanged. + +``` +┌─ Control plane (Rails, sigit-si) ───────────────────────────────────────────┐ +│ Web UI: repo "Agent" tab → run form, live transcript, diff, "Open PR" │ +│ AgentRunsController + Api::V1::AgentRunsController (create/show/cancel/log) │ +│ AgentRun model (lifecycle state machine) │ +│ AgentRunJob → provisions sandbox, monitors, collects result │ +│ Mints per-run scoped tokens (git push + inference), enforces caps/metering │ +└──────────────────────────────────────────────────────────────────────────────┘ + │ RunTask (aws-sdk) ▲ SSE/webhook: logs, status, diff + ▼ │ +┌─ Execution plane (AWS) ─────────────────────────────────────────────────────┐ +│ Ephemeral sandbox (Fargate task v1 → Firecracker microVM at scale) │ +│ ├─ clones repo from sigit.si over smart-HTTP (scoped git token) │ +│ ├─ runs siGit Code headless against the task │ +│ │ OPENAI_BASE_URL=https://sigit.si/api/v1 (per-run inference token) │ +│ ├─ edits files, runs build/test in restricted shell │ +│ └─ pushes head branch back to sigit.si (scoped git token) │ +│ Private subnet, egress allowlist (sigit.si + package registries only) │ +│ Hard caps: wall-clock, CPU/mem, token budget, max tool calls │ +└──────────────────────────────────────────────────────────────────────────────┘ + │ /v1/chat/completions (per-run token) + ▼ +┌─ Inference (unchanged) ─────────────────────────────────────────────────────┐ +│ Api::V1::ChatCompletionsController → OndeCloudService → Onde Cloud → upstream│ +│ Existing entitlement gate, allowance metering, and identity masking apply. │ +└──────────────────────────────────────────────────────────────────────────────┘ +``` + +### 3.1 Control plane (Rails) + +**`AgentRun` model** (new table). Belongs to `user` and `repository`. Fields: + +- `status`: `queued`, `provisioning`, `running`, `pushing`, `needs_input`, + `completed`, `failed`, `canceled` (state machine; one-way transitions logged). +- `task_prompt` (text), `base_branch`, `head_branch` (generated, e.g. + `agent/<run-id>-<slug>`), `pull_request_id` (nullable until pushed). +- `sandbox_ref` (ECS task ARN / microVM id), `region`. +- Budgets and accounting: `token_budget`, `tokens_used`, `wall_clock_limit_s`, + `started_at`, `finished_at`, `exit_reason`. +- `transcript_url` (S3 pointer for the full log), plus a tail kept in Postgres for + the live view. + +**Controllers**: a web `AgentRunsController` (HTML, Turbo) under the repo, and an +`Api::V1::AgentRunsController` so the CLI and desktop can trigger and follow runs. +Actions: `create`, `index`, `show`, `cancel`, `messages#create` (steer a running +agent), and a `logs` SSE endpoint that relays the live transcript (reuse the +`ActionController::Live` pattern already used for chat streaming). + +**`AgentRunJob`** (Active Job, on the existing DB-backed queue): transitions the +run to `provisioning`, calls AWS to start the sandbox, persists the sandbox ref, +then hands off to monitoring. Cancellation and timeout both tear the sandbox down. + +**Web UI**: a new "Agent" (or "Tasks") tab on the repository page. A run form +(task prompt, base branch, optional model tier). A live transcript panel (Turbo +Streams fed by the SSE relay). On completion, a diff view and an "Open pull +request" action. + +### 3.2 Execution plane (AWS) — the sandbox machine + +This is the core AWS decision and the riskiest surface, because the sandbox runs +build and test commands over user code. + +**Runtime choice.** + +- **v1: AWS Fargate (ECS) ephemeral tasks.** One task per run. Scales to zero, pay + per second, decent container isolation, no servers to manage, and `RunTask` is a + single SDK call from a Rails job. Fast to ship. This is the recommendation for + v1. +- **At scale: Firecracker microVMs.** For stronger isolation of untrusted code and + for snapshot/restore warm pools (sub-second starts), move the sandbox to + Firecracker microVMs on bare-metal EC2 (the model E2B / Modal / Codex-style + sandboxes use). More operational weight; defer until run volume and the threat + model justify it. gVisor or Kata on EC2 is a middle option if Fargate isolation + proves insufficient before we are ready for Firecracker. + +**The sandbox image.** A container that bundles headless siGit Code plus a base +toolchain (git, common language runtimes). The agent boots, reads the run spec +from an injected env/file, clones, works, and pushes. Per-language base images (or +a `.sigit/agent.yml` setup step, see Phase 3) keep cold builds fast. + +**Orchestration.** v1 keeps it simple: the Rails `AgentRunJob` calls ECS `RunTask` +directly via `aws-sdk-ecs`, passing the run spec as container overrides, and polls +task status (or receives EventBridge task-state-change events into a webhook). If +the lifecycle grows (retries, multi-step, fan-out), promote to Step Functions. +Avoid Step Functions on day one; it is premature. + +**Networking and isolation (load-bearing for safety).** + +- Sandbox runs in a **private subnet**. Egress through a NAT restricted by an + **allowlist**: sigit.si (git + inference) and an explicit set of package + registries (rubygems, npm, pypi, crates, etc.). Everything else is denied. This + is the primary control against data exfiltration, SSRF against internal + services, and crypto-mining abuse. +- **No inbound.** The sandbox is not reachable from the internet. +- Per-run IAM role scoped to only what the task needs; no broad account access + inside the sandbox. + +**Credentials (mint short-lived, never long-lived).** The sandbox receives: + +- a **git token scoped to the single repo and ideally the single head branch**, + valid for the run only (extends the existing `git_token` exchange); +- an **inference token** minted per run, carrying the user's entitlement and a + hard token budget, pointed at `https://sigit.si/api/v1`. + +Both expire when the run ends. The sandbox never holds `app_secret` or any +long-lived credential, mirroring the existing public-client rule. + +**Logs and artifacts.** The agent streams transcript chunks back to Rails (the SSE +relay) for the live view and writes the full transcript and build logs to S3 +(pointer stored on `AgentRun`). CloudWatch captures infra-level logs. + +### 3.3 Inference path (reused as-is) + +The sandboxed agent sets `OPENAI_BASE_URL=https://sigit.si/api/v1` and uses the +per-run inference token as its bearer. That flows through the **existing** +`Api::V1::ChatCompletionsController`: entitlement gate, allowance metering, and the +`SIGIT_IDENTITY_PROMPT` identity masking all apply with no change. This is a major +reason the project is tractable: the autonomous agent is just another client of an +inference endpoint we already operate and protect. + +This also gives a clean answer to the existing "inference token ↔ Onde Cloud auth" +open item: for cloud-agent runs the token is minted server-side with a budget, so +there is no public client holding credentials at all. + +### 3.4 Git and PR flow + +The agent pushes the head branch via the existing `git-receive-pack` endpoint. On +push, the platform creates (or links) the PR record. Because **no PR model exists +yet**, scope for v1: + +- minimal `PullRequest` model (base/head branch, repo, author, status, title, + body), a diff/compare view (we already render blobs and commits, so the diff + renderer is incremental), and "open / close / merge" actions for the reviewer; +- the agent fills in title and body from its summary. PR prose must stay neutral + and must never name the upstream model or provider (same rule as chat). + +Issues (assign-an-issue-to-the-agent) are a Phase 3 surface and depend on an issue +model that also does not exist yet. + +--- + +## 4. Safety, guardrails, and abuse + +Principal-engineer non-negotiables, because this executes code on our infra on +behalf of users: + +- **Hard caps per run**: wall-clock timeout, CPU/memory limits, inference token + budget (tied to `CloudUsage`/a new `AgentUsage`), max tool calls, max sandbox + lifetime. A run that blows any cap is killed and marked `failed` with a reason. +- **Network egress allowlist** (section 3.2). The single most important control. +- **Ephemeral, scoped credentials** only. Nothing long-lived in the sandbox. +- **Human-in-the-loop by default**: the agent proposes a PR; it does not merge. No + auto-merge in v1. +- **Concurrency limits per plan**: caps simultaneous runs per user to bound spend + and abuse. +- **Identity hygiene**: transcripts, PR titles/bodies, and error messages stay + neutral; never disclose the upstream model or provider (existing rule extends to + agent output). +- **Cancellation is real**: cancel tears down the sandbox and revokes the run's + tokens. +- **Idempotency**: run creation and sandbox start are idempotent so retries cannot + double-spend. + +A short threat-model doc is a Phase 0 deliverable (exfiltration, SSRF to internal +metadata endpoints, resource abuse / mining, secret leakage from the user's own +repo, prompt injection from repo contents steering the agent). + +--- + +## 5. Pricing and packaging + +Agent runs cost us **sandbox compute (Fargate per-second) + inference tokens**, so +metering must capture both and price above blended COGS. The numbers below are a +working model with the inputs we cannot yet read from this repo marked +`[FILL FROM PHASE 0]`. The metering schema and billing copy should be designed +against this formula now; the dollar figures get populated once the Phase 0 +dogfood produces measured per-run token counts. + +### 5.1 Unit economics: cost per run (COGS) + +``` +cost_per_run = sandbox_cost + inference_cost + +sandbox_cost = vcpu_count * $0.04048/vcpu-hr * hours + + mem_gb * $0.004445/gb-hr * hours (Fargate, us-east-1) + +inference_cost = tokens_per_run / 1_000_000 * blended_upstream_cost_per_mtok +``` + +Worked example, sandbox side (known, fixed): 1 vCPU + 2 GB for a 20-minute run: + +``` +(1 * 0.04048 + 2 * 0.004445) * (20/60) = $0.0165 per run +``` + +Sandbox compute is rounding error. **Inference dominates**, and it is the unknown: + +``` +tokens_per_run = [FILL FROM PHASE 0] (expect 200K – 2M+) +blended_upstream_cost_per_mtok = [FILL: Onde/upstream blended $/Mtok] + +inference_cost_per_run = (tokens_per_run / 1e6) * blended_cost_per_mtok +cost_per_run = 0.0165 + inference_cost_per_run +``` + +Conclusion that holds regardless of the blanks: **price on metered inference per +run; treat sandbox-minutes as a kill-switch guardrail, not a billing axis.** + +### 5.2 Packaging (allowance model, mirrors `Subscription::CLOUD_ALLOWANCE`) + +Agent runs are an included monthly bundle per plan, with overage or an add-on for +heavy use. Suggested `Subscription` constant shape: + +```ruby +# Included siGit Cloud Agent runs per billing period. Tunable pricing knob. +AGENT_RUNS = { "pro" => [FILL], "team" => [FILL] }.freeze # e.g. pro 20, team 75 +TRIAL_AGENT_RUNS = [FILL] # e.g. 2 +AGENT_OVERAGE_PER_RUN = [FILL] # $/run past the bundle +``` + +| Plan | Price (today) | Included agent runs/mo | Overage | Notes | +|---|---|---|---|---| +| Free | $0 | 0 | n/a | local siGit Code only, no cloud agent | +| Trial | $0 (14 days) | `[FILL: ~2]` | n/a | felt-value, hard run cap | +| Pro | $20/mo (existing) | `[FILL: ~20]` | `[FILL: $/run]` | bundle sized so COGS < ~X% of $20 | +| Team | (existing) | `[FILL: ~75]` | `[FILL: $/run]` | higher bundle + concurrency | + +Sizing rule for the bundle: pick included-runs so that **bundle COGS stays under a +target fraction of plan price** (e.g. included-runs * cost_per_run ≤ 40% of MRR), +leaving margin for the chat allowance those plans already include. With +`cost_per_run` from 5.1 unknown, the bundle count is the lever set last. + +### 5.3 Metering + +Add `AgentUsage` keyed by `(user, billing_period_key)` (parallel to `CloudUsage`), +recording per period: `runs_count`, `agent_seconds`, `tokens_used`. Runs decrement +the plan's run bundle; tokens already flow through the existing cloud allowance and +its 429 cap, so a single run can never escape the token budget. `GET +/api/v1/billing` gains `agent_runs_used` + `agent_runs_allowance` alongside the +existing cloud fields. + +### 5.4 Guardrails (worst-case cost containment) + +- **Per-run token budget** minted into the run token, independent of the monthly + allowance, so one pathological run is bounded. +- **Per-run wall-clock + sandbox-lifetime cap** (kills runaway compute). +- **Per-plan concurrency cap** bounds simultaneous spend. +- **Trial run cap** (`TRIAL_AGENT_RUNS`) prevents trial-driven upstream bills, + mirroring `TRIAL_ALLOWANCE`. + +### 5.5 What Phase 0 must measure to finalize this + +1. **`tokens_per_run`** distribution (p50/p90/p99) over 20–30 real dogfood runs. +2. **`blended_upstream_cost_per_mtok`** (from the Onde/upstream cost, internal). +3. Resulting **`cost_per_run`** distribution, then back-solve included-run bundles + per tier against the target-margin rule in 5.2. + +Until 1 and 2 are real, every dollar above is a placeholder by design. + +--- + +## 6. Roadmap (phased) + +Estimates are calendar weeks for a small team; adjust to staffing. Each phase ends +with a demoable, dogfoodable increment. + +### Phase 0 — Foundations and spikes (2–3 weeks) +- Decide sandbox runtime: **Fargate for v1** (documented path to Firecracker). +- Build the headless siGit Code container image; prove end to end **manually**: a + container clones a sigit.si repo, runs the agent against a task with inference via + `/api/v1`, edits files, runs a test, and pushes a branch. +- Per-run scoped token minting (git + inference) in Rails. +- Threat model + egress allowlist design. +- Add `aws-sdk-ecs`/`aws-sdk-core` to the Gemfile; stand up the VPC/subnet/NAT and + the task definition in IaC. +- **Exit:** one task goes from prompt to pushed branch by hand. + +### Phase 1 — Private alpha, single happy path (3–4 weeks) +- `AgentRun` model + state machine; web `AgentRunsController`; `AgentRunJob` calling + ECS `RunTask`. +- Repo "Agent" tab: run form, live transcript via SSE/Turbo, final diff view. +- Branch push + a minimal compare view (full PR model can lag one phase). +- Caps: wall-clock timeout, concurrency = 1, token budget tied to metering. +- Internal-only feature flag; dogfood on our own repos. +- **Exit:** team members trigger runs from the web and review the diff. + +### Phase 2 — Beta, productized (4–6 weeks) +- Minimal `PullRequest` model + diff/review UI so agent output is a real PR. +- Steerability: follow-up messages to a running agent, cancel, re-run. +- Metering + pricing surface: `AgentUsage`, plan caps, billing page copy. +- Hardening: enforce egress allowlist, isolation review, token scoping, per-plan + concurrency, abuse controls. +- Trigger surfaces: `sigit cloud run` in the CLI and a desktop entry point, both + hitting `Api::V1::AgentRunsController`. +- Cold-start work (prebaked base images; warm pool if needed). +- **Exit:** invite-only beta with real external users and real billing. + +### Phase 3 — GA and advanced (ongoing) +- Issues + assign-an-issue-to-the-agent (needs an issue model). +- Custom environments: repo-level `.sigit/agent.yml` (setup steps, allowed + commands, language matrix), the analog of Copilot's environment customization. +- Firecracker microVM sandboxes + snapshot warm pools for isolation and sub-second + starts at volume. +- Dependency/repo caching (S3/EFS layers) for faster, cheaper runs. +- Parallel runs, plan-then-execute, multi-file refactors at scale. +- Observability and an eval harness (task success rate, PR acceptance rate, cost + per accepted PR) to drive model-tier and prompt tuning. + +--- + +## 7. Key decisions and open dependencies + +- **PRs/issues do not exist on sigit.si yet.** A minimal PR surface is on the + critical path (Phase 2); issues gate the issue-to-PR flow (Phase 3). Confirm + scope early. +- **Sandbox isolation level.** Fargate for v1; reassess against the threat model + before opening to untrusted external users at volume; Firecracker is the scale + answer. +- **Inference token model.** Mint per-run, server-side, budget-bounded. This also + closes the standing "inference token ↔ Onde Cloud auth" gap for this path. +- **COGS visibility.** Must meter sandbox time *and* tokens from day one, or + pricing flies blind. +- **No AWS SDK in the app yet.** Adds a new infra dependency and IAM/VPC footprint + to own and secure. +- **Naming.** "siGit Cloud Agent" is a working name; align with the existing + brand rules (`siGit Code`, `siGit Code Cloud`, `Onde Cloud`) before anything + ships to users. + +--- + +## 8. North-star and success metrics + +- **North star:** accepted agent PRs per active paying user per month. +- **Quality:** task success rate (run produces a mergeable PR without human + fixes), PR acceptance rate, median time-to-PR. +- **Economics:** cost per accepted PR (sandbox + tokens), gross margin per plan. +- **Reliability/safety:** zero egress-allowlist escapes, zero credential leaks, + sandbox p95 cold start, run failure rate by cause. +```