Skip to content

Claude Code as a cloud worker program; fix self-hosted prefetch; release 0.6.0 - #19

Merged
flujo-app merged 1 commit into
mainfrom
feat/claude-cloud-worker
Nov 4, 2026
Merged

flujo-app merged 1 commit into
mainfrom
feat/claude-cloud-worker

Conversation

@flujo-app

Copy link
Copy Markdown
Owner

Cloud lite workers can now run Claude Code instead of Codex, on a subscription or on a self-hosted model. Ships as 0.6.0. Plans that do not set workerProgram behave as before, and the Codex bootstrap script is byte-identical to 0.5.0.

Worker program

  • plan.workerProgram: 'codex' | 'claude', default codex, with no new default written into existing fleet bindings.
  • A plan refuses a provider without the API its program needs (anthropic for Claude Code, responses for Codex), and a broker made for the other program.

src/cloud-lite/claude-driver.mjs

  • ClaudeSessionDriver offers the same methods and notifications the runtime already uses for a Codex app-server (thread/start|resume|read, turn/start|steer|interrupt). So NativeOperatorStop, NativeWorkflowWake, traces and the board are shared code, not copies.
  • One Claude Code process per running turn (--session-id, then --resume). An idle session holds no process.
  • A message during a turn is written to that process. The turn stays open until every message written has come back, so a queued message cannot be lost or end the turn early. If one never comes back, the turn still ends.
  • A stop sends the interrupt, and ends the process if it does not answer in 10 seconds. Stop wins over a queued message. The receipt is issued only after the process has gone and the session reads back as idle.
  • Stream events map to item/completed (agentMessage, reasoning, toolCall, toolResult), thread/tokenUsage/updated and model/retry. Streaming deltas are skipped unless traceDeltas is on.
  • History lost after a restart gives the same error Codex gives, so the runtime's existing recovery applies.

Auth

  • Self-hosted: the existing claudeProviderEnvironment(). The key is a staged secret and reaches only the Claude Code process.
  • Subscription: ClaudeSubscriptionBroker holds a long-lived token from claude setup-token in a private file on the trusted host. There is no refresh token to protect; replacing the file rotates the token for the next turn. A refused token asks the host for the current one through the existing /auth path.
  • Agent commands and the board MCP server run through CLAUDE_CODE_SHELL_PREFIX, a small wrapper that removes the credentials. CLAUDE_CODE_SUBPROCESS_ENV_SCRUB was tried and rejected: it forces default permission checks, and commands with shell variables were then refused.
  • Limit, also in the docs: with danger-full-access, an agent command runs as the same user and could read the key from its parent process.

Bootstrap and sandbox

  • Installs Claude Code 2.1.311 by SHA-256, plus bubblewrap and socat. Only the selected program is installed.
  • danger-full-access maps to --dangerously-skip-permissions. workspace-write and read-only use Claude Code's operating system sandbox with no network, and refuse to run commands without it. boardTools: false and supervision: 'operator' keep working.
  • Automatic subagents, schedules, web tools, skills, auto memory and repository settings files are off in every mode.

Two bugs found in 0.5.0 by the live check (factory-selfhost deploy could not create a new model; earlier tests used a fake runner)

  • The staged Python sources lacked profiles/, so every new deployment was refused.
  • --prefetch and flag checks started an app containing the serving functions, which need a secret that only the later deployment creates. Helper runs now leave those functions out.

Tests and docs: test/cloud-lite-claude.test.mjs (18 tests, with a fake stream-json process in test/fixtures/fake-claude.mjs, added to the focused list), offline tests for both bugs, PUBLIC_SDK.md, and the ClaudeSubscriptionBroker export in the packed-package verifier.

Fixes #15

Live check on a self-hosted endpoint

runCloudLiteMachine with its coordinator, the real Claude Code 2.1.311 and a real vLLM endpoint (profile qwen, one H100), in a Linux container with /data as the Machine volume, driven through the operator HTTP interface a fleet uses:

Check Result
Agent starts idle with a registered session pass
First operator job completes (READY) pass
Second job resumes the same session pass
Operator stop during a long answer: verified receipt (threadStatus: idle, turnStatus: interrupted, matching command, thread and turn) pass
No worker process left after the stop pass
Trace has turns, items and token use pass
The key is in no file under the state and session folders pass
The model runs a shell command through the worker; the command sees no key (grep -c printed 0) pass
The model calls a board tool and reports its identity pass
After a machine restart the session is kept, and the model recalls earlier work pass

Cost: about $0.84 of GPU and CPU by Modal's billing report. The model was retired afterwards and its app, secret and volumes were checked gone. Retirement needed a second run while the GPU container was stopping; see the follow-up issue.

The same machine run also passed against a scripted local model, in all three sandbox modes, with the real binary. It showed that commands write only where allowed, have no network, and lose the key, and that the file tools stay inside the same folders.

Not run: a real Fly Machine (the deployment path is covered offline, and the generated bootstrap was run on a clean local container), and a real subscription token (offline tests only).

Local CI (AGENTS.md)

Same steps as .github/workflows/public-sdk.yml (focused list read from the workflow file, 26 files), on a clean git archive export of 5ea6f19 with fresh npm ci. Linux used an archive with the repository's own line endings.

Platform Node / npm prod audit (high) Focused tests Python settings tests Packed install + verify-public-sdk CLI help + factory-selfhost profiles Installed MCP stdio Evidence regressions Left in temp
Windows 10, portable Node, Python 3.14 22.17.0 / 10.9.2 0 vulnerabilities 218/218 9/9 ok ok 1/1 7/7 0
Windows 10, portable Node, Python 3.14 24.22.2 / 11.19.1 0 vulnerabilities 218/218 9/9 ok ok 1/1 7/7 0
Linux, Docker node:22.17.0-bookworm, Python 3.11 22.17.0 / 10.9.2 0 vulnerabilities 218/218 9/9 ok ok 1/1 7/7 0
Linux, Docker node:24.22.2-bookworm, Python 3.11 24.22.2 / 11.19.1 0 vulnerabilities 218/218 9/9 ok ok 1/1 7/7 0

…ase 0.6.0

plan.workerProgram chooses codex (default) or claude. ClaudeSessionDriver offers
Claude Code's stream-json interface through the same contract the runtime uses
for a Codex app-server, so operator commands, stop receipts, traces, workflows
and evidence are shared. The bootstrap installs one pinned release by checksum.

factory-selfhost deploy could not run on a new model: staged sources lacked the
profiles, and the helper run needed a secret only the deployment creates.

Fixes #15
@flujo-app flujo-app added the enhancement New feature or request label Nov 4, 2026
@flujo-app
flujo-app merged commit d0ae006 into main Nov 4, 2026
@flujo-app
flujo-app deleted the feat/claude-cloud-worker branch November 4, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cloud lite workers: Claude Code as a worker program

1 participant