返回 CodeWhale
DAYTONA_CLOUD_DISPATCH.md
根目录 / docs / DAYTONA_CLOUD_DISPATCH.md
1 # Daytona cloud-agent dispatch
2
3 Local `cw` / Codewhale can offload a coding agent to Daytona the way Cursor
4 sends a cloud agent: the remote job raises a branch and opens a PR against an
5 explicit forge. Local stays responsive; spend and push never happen silently.
6
7 The sandboxes are **Codewhale-operated infrastructure**, not a user-facing
8 product or provider: nothing in the CLI, TUI, job cards, or PR bodies carries
9 a provider brand, and there is no provider signup or key setup a user needs
10 to perform. Access ships with Codewhale membership (`codewhale login`) and
11 fails closed without it.
12
13 ## One obvious offload
14
15 ```sh
16 codewhale dispatch "open a PR that fixes the flake" --remote github
17 codewhale dispatch --confirm cloud_<id>
18 ```
19
20 Same action in the TUI:
21
22 ```
23 /dispatch open a PR that fixes the flake --remote github
24 /dispatch confirm cloud_<id>
25 ```
26
27 `codewhale cloud-agent` and `/cloud-agent` are aliases. `--confirm` /
28 `/dispatch confirm` is required. A proposal is written first; nothing creates
29 a sandbox or pushes a branch until that confirmation.
30
31 Cloud jobs are first-class on the existing jobs surface (`kind=cloud`):
32
33 ```
34 /jobs list
35 /dispatch list
36 /dispatch show <id>
37 /dispatch cancel <id>
38 codewhale dispatch --list
39 ```
40
41 `/jobs list` shows shell jobs and, when cloud jobs exist, appends the cloud
42 section; `codewhale dispatch --list` (and `/dispatch list`) shows the cloud
43 jobs alone. `cloud_*` ids route to cloud show/cancel from both surfaces.
44
45 ## What a confirmed job actually does
46
47 The runner (`crates/tui/src/dispatch_runner.rs`) drives one lifecycle:
48
49 ```
50 proposed → launching → running → openingpr → done
51 │ │
52 └── failed ───┘ (+ canceled from any active state)
53 ```
54
55 1. **launching → running** — create the sandbox (labeled with the job id and
56 forge) and wait until it accepts work. The job record keeps the sandbox
57 id.
58 2. **running** — clone the target forge repository inside the sandbox and run
59 **one** cloud agent turn through the same one-shot harness entry every
60 local non-interactive caller uses (`codewhale exec --auto "<prompt>"`).
61 There is no second engine: the sandbox runs the one `Engine::run_turn`
62 path, remotely.
63 3. **openingpr** — collect the agent's work product (`format-patch` against
64 the clone's default branch), apply it locally on a fresh shallow clone,
65 and push the branch with a **plain** push (`--force` is never passed, so a
66 moved branch fails closed instead of rewriting history).
67 4. **done** — open the PR on the target forge and record the URL:
68 - `github` — the `gh` CLI (`gh pr create`), reusing the repo's existing
69 gh seam and auth;
70 - `gitee` — Gitee API v5 `POST /repos/{owner}/{repo}/pulls` with a token
71 from the Codewhale service slot `gitee`;
72 - `cnb` — CNB OpenAPI `POST /{repo}/-/pulls` with a token from the
73 service slot `cnb`.
74 The PR body is truthful: what the agent did, the receipts Codewhale has
75 (job id, sandbox id, branch, base, head sha), and an explicit
76 `No-Issue: cloud dispatch cloud_<id>` line.
77 5. **teardown** — the sandbox is deleted on completion, failure, *and*
78 cancellation; the job note says whether teardown succeeded.
79
80 Every phase persists its transition, so `codewhale dispatch --show <id>` /
81 `/dispatch show <id>` stream real progress while the run is in flight.
82
83 ### Where the run happens
84
85 - The launcher selects `codewhale-cloud-agent` by default, or the validated
86 `CODEWHALE_DISPATCH_SNAPSHOT` override. Its single image definition and build
87 instructions live in
88 [`computer/snapshots/cloud-agent/`](../computer/snapshots/cloud-agent/).
89 That image pins its own Engine version; a newer source checkout does not
90 update it automatically.
91 - Current source sends the account machine token as `CODEWHALE_API_KEY` in
92 create-time environment. This is server-visible account identity, not an
93 inference-provider key. Source wiring for snapshot creation and toolbox
94 execution does not establish a working account-to-provider credential bridge
95 or an end-to-end Cloud Agent acceptance result; see the image's documented
96 limitations before operating it.
97 - The CLI stays attached: after `--confirm` it prints the launching card and
98 waits for the runner so a sandbox is never orphaned by an early exit
99 (Ctrl-C exits the wait; the job record survives, and `--cancel` tears the
100 sandbox down).
101 - The TUI detaches the runner so the session stays responsive; the job
102 record is the source of truth and `/dispatch cancel` works at any time.
103
104 ## Remotes
105
106 Forges are explicit: `github`, `cnb`, `gitee`.
107
108 CWC already treats a remote *named* `github` as authoritative GitHub and
109 `origin` as the CNB mirror when that URL is `cnb.cool`. Codewhale uses the
110 same rule:
111
112 | Remote name | URL host | Forge |
113 | --- | --- | --- |
114 | `github` | any | `github` |
115 | `cnb` | any | `cnb` |
116 | `gitee` | any | `gitee` |
117 | `origin` or other | `github.com` | `github` |
118 | `origin` or other | `cnb.cool` | `cnb` |
119 | `origin` or other | `gitee.com` | `gitee` |
120
121 If more than one forge is present, pass `--remote` / `--remote` on `/dispatch`.
122 Do not assume `origin` is GitHub.
123
124 ## Access (fail-closed, membership-first)
125
126 Cloud agents ship with the Codewhale account. The gate is sign-in:
127 `codewhale login`. Until then dispatch proposes but refuses to confirm, and
128 `codewhale dispatch --status` says exactly that. **There are no provider
129 setup steps for users** — no provider signup, no dashboard, no user-held
130 provider key.
131
132 Internally (Codewhale operators only), the sandbox credential is discovered
133 from the service-side slot exactly as the first landing defined it
134 (`DAYTONA_API_KEY` / CWC alias / the `daytona` secret slot, plus the
135 `DAYTONA_API_URL` origin override). It is never printed, never logged, never
136 written into a job record, and is not a user surface: there is no
137 `auth set-slot` command for it and no locale string mentions it.
138
139 Forge credentials follow the same rule: GitHub auth comes from the existing
140 `gh` CLI login; Gitee and CNB tokens live in the Codewhale service slots
141 `gitee` and `cnb` and are read only at PR-open time.
142
143 The dispatching host also needs the account machine token
144 (`CODEWHALE_API_KEY`, a `cwc_key_…` key from Account → API keys): it is
145 injected into the sandbox so the in-sandbox `codewhale` runs as the
146 account. Without it confirm refuses before any spend — a sandbox whose
147 agent has no identity is money for nothing.
148
149 ## Confirmation and fail-closed rules
150
151 - No `--confirm` / `/dispatch confirm`: write a `proposed` job, exit success,
152 do not create a sandbox, do not push.
153 - Confirm without membership/credentials: write a `refused` job, exit
154 failure, no sandbox.
155 - Confirm + credentials: the lifecycle above. Any phase that cannot honestly
156 complete records a `failed` job with a sanitized, truthful note — a PR URL
157 is never invented, and a missing forge token fails closed *after* the
158 branch push with an explicit "no pull request was opened" message.
159 - `codewhale dispatch` may propose; it never confirms itself.
160
161 ## Cancellation and cost transparency
162
163 - `--cancel <id>` / `/dispatch cancel <id>` / `/jobs cancel cloud_<id>` flip
164 the record to `canceled` and tear a live sandbox down immediately; a runner
165 in flight stops at its next checkpoint and tears down too.
166 - The status card and `--show` surface real receipts: sandbox id, PR URL when
167 opened, head sha, and a runtime figure in whole minutes. Runtime is
168 Codewhale's own bookkeeping (created → finished); it is not a provider
169 bill, and the card says so.
170
171 ## Network safety
172
173 Every credential-bearing outbound call (sandbox control plane, sandbox
174 toolbox, Gitee, CNB) goes through one origin guard: https only, no userinfo,
175 and no loopback / private / link-local / reserved / multicast / `.local` /
176 `.internal` targets. Explicit loopback origins are allowed only in debug
177 builds for local smoke testing. DNS-rebinding (a public name resolving to a
178 private address) is out of scope. Branch pushes are plain `git push` through
179 the machine's existing forge credentials; `--force` is never used.
180
181 ## Live status vs recording tests
182
183 The full lifecycle (create → wait ready → clone → harness turn → collect →
184 push → PR → teardown), cancellation teardown, PR shapes, host validation,
185 and the no-force push rule are pinned by offline tests against recording
186 launchers and local git fixtures. The live network paths (Daytona sandbox
187 create/execute/delete against a real account, `gh pr create`, and the
188 Gitee/CNB REST calls) follow the providers' published OpenAPI shapes and
189 still need one real-sandbox smoke test per forge before the receipts they
190 produce can be called verified — the PR body and job notes never claim more
191 than the receipts shown.
192
193 ## Leftover
194
195 - Live watch / log tail of a running sandbox.
196 - Auto-decide heuristics (Codewhale may propose; it must not confirm itself).
197 - Private-repo clones in the sandbox (needs a credential pass-through design
198 that does not widen secrets into the agent process).
199
199 lines MARKDOWN