[evergreen] Flue-native agent type — opencomputer (gateway / package / CLI / docs)#489
[evergreen] Flue-native agent type — opencomputer (gateway / package / CLI / docs)#489ZIJ wants to merge 17 commits into
Conversation
Long-lived integration branch for the Flue-native agent type in opencomputer (L2 gateway, L4 CLI, L5 @opencomputer/flue package, docs). Spec: oc-bg-agents .agents/design/013 + 014; plan: work/flue-native-buildout.md. Lane PRs target this branch (base=flue-native); Igor merges the evergreen -> main as a unit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the serveOC/brain-box package with the Flue CF-native shape (design 013 §4/§5). A stock `flue build --target cloudflare` app opts into OC via: - useOcGateway(ctx) + DEFAULT_MODEL (claude-haiku-4.5, prompt-caching-safe): point managed anthropic at env.OC_GATEWAY. Called INSIDE the defineAgent initializer (top-level is tree-shaken — 1a). - route: the HTTP-transport opt-in every OC agent must export. - ocSandbox(env): a durable OC-fleet SandboxApi (files/exec over the OC sandbox HTTP API; stat/mkdir/rm via shell, mirroring cloudflareSandbox). - ocRepoTools(env): publish_pull_request (repo plane, used by W10). - ./app: default hosting app (flue() + /health + observe→OC_INGEST). ./wire: telemetry-only side-effect for apps with their own app.ts. Builds + typechecks clean against @flue/runtime@1.0.0-beta.9. Verified end-to-end: the re-scaffolded oc-flue-starter `flue build --target cloudflare`s with zero hand-editing and the wiring (useOcGateway/registerProvider, ocSandbox, /health, haiku model) is present in the bundle (not tree-shaken). TOKEN SEAM — OPEN, needs orchestrator decision (see gateway.ts): Flue's registerProvider takes only a static apiKey and its getApiKey(providerId) callback gets no request context, and the provider registry is isolate-global (shared across co-located DO instances) — so a per-SESSION token/header/baseUrl via registerProvider RACES. Wired the buildable env shape; robust per-session attribution needs the upstream `headers(ctx)` ask. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Productionizes the 1a spike (spike/oc-gateway, #486) into the flue-native gateway per buildout contract #1 / design 013 §4. Thin OC Worker over OpenRouter: verify a per-session token → epoch-fence + on-path budget gate → inject the org's OR key → cache-safe + usage-accounted forward → sub-meter. Org spend stays on the org's single OR key → the existing OpenRouter→Autumn cron (nothing pushed to Autumn); the gateway only adds per-session sub-metering + enforcement. What changed vs the spike: - Token: HS256 → EdDSA (Ed25519). The minter holds the private key; the gateway holds only GATEWAY_TOKEN_PUBLIC_KEY (base64url raw). Alg pinned to EdDSA (rejects none/HS256 swaps). - Lease-epoch fence (close the freeze-flags): the SessionBudget DO tracks a monotonic max_epoch; a token with a stale `ep` → 401 token_superseded; a newer epoch supersedes older in-flight tokens. DO-serialized. - Org OR key from the credential store, not the KV/SPIKE stand-in: a dedicated internal sessions-api seam (mirrors the edge's dedicated-secret key hand-off), cached per-org in-isolate (60s). TEST_OR_KEY override for the acceptance run. The L3 route (resolveManagedSecret) is flagged for sessions-api. - Prompt-caching safety: strip cache_control for caching-unsafe models (claude-3-haiku → Bedrock 400s) via an env-extensible denylist. - Kept: usage:{include:true}, 402 budget_exceeded, metering by token.sub, the µ$ counter + /add idempotency on the OR generation id. Tests: 23 green (vitest) — EdDSA/alg-pin/tamper/expired, SessionBudget fence + budget + idempotency, cache_control strip, cost extraction, and the full on-path flow through the real handler. Live acceptance (2026-07-05, real OpenRouter via wrangler dev --local, throwaway $1-capped OR key minted from the provisioning key and deleted after): a real claude-haiku-4.5 turn completed gateway → OpenRouter (200, no key in the response, token != key); per-session budget refused on-path (402); cache_control stripped so claude-3-haiku succeeds via the gateway where a direct OR call 400s; epoch fence returns 401 token_superseded. Seam to confirm with the orchestrator (W1): EdDSA claim set + the mint side (default: per-turn token via getApiKey). L3 seam to build: the internal org-OR-key route. Not merged — Igor merges opencomputer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oken, org+agt enforcement) Reshapes the productionized gateway to the RESOLVED token seam (2026-07-05, option b) and the co-location refinement, and lands a consistent, compiling tree. The prior commit (0b5f580) captured a mid-reshape snapshot (new token.ts + old index.ts) that does not compile; this commit completes the reshape. buildout contract #1 / design 013 §4. Token seam (was per-session {sub,bud}; now per-DEPLOY): - token.ts: claims are {org, agt, iat, exp, ep?} — no sub:session, no bud. EdDSA (Ed25519), alg-pinned; the minter (W7) holds the private key, the gateway only the public key. verifyDeployToken/mintDeployToken/DeployClaims. - Session identity rides the X-OC-Session request header (Flue's static apiKey can't carry per-session data). It is BEST-EFFORT: Flue's provider registry is isolate-global and CF co-locates many session-DOs of one script per isolate, so per-session data injected via registerProvider races. Therefore hard enforcement is at the race-free org+agt grain. Enforcement (co-location refinement): - SpendCounter DO (renamed from SessionBudget; generic keyed µ$ counter + gate). HARD 402 at the org+agt grain (keyed agt:${org}:${agt}, from the token). Best-effort per-session tracking keyed sess:${X-OC-Session} — recorded for visibility, NEVER gated (a race must not wrongly 402 a legit session). Budget looked up server-side (provisioned or the gateway default), never carried in the token. Exact per-session enforcement is deferred to an upstream Flue per-request resolver (tracked ask, off the critical path). - DeployLease DO (new): per-(org,agt) lease-epoch floor. A token below the floor is fenced (401 token_superseded); the floor auto-rises on a higher-epoch token (rotation) and via POST /admin/lease/bump (revoke without redeploy). - Admin routes (guarded by GATEWAY_ADMIN_SECRET): /admin/agent/budget (provision org+agt cap), /admin/lease/bump (revoke). Unchanged from the spike: org OR key from the dedicated sessions-api seam (orgkey.ts; TEST_OR_KEY override), usage:{include:true}, cache_control strip for caching-unsafe models, cost extraction (JSON+SSE), org spend stays on the existing OpenRouter→Autumn cron (gateway pushes NOTHING to Autumn). Tests: 31 green (vitest) — EdDSA/alg-pin/tamper/expired/missing-claims, SpendCounter gate+provision+idempotency, DeployLease fence+bump, org+agt hard enforcement, co-location (two sessions share the org+agt cap), per-session tracked-but-never-gated, cache_control strip, admin provision. Live acceptance (2026-07-06, real OpenRouter via wrangler dev, throwaway $1-capped OR key minted from the provisioning key and DELETEd after): a real claude-haiku-4.5 turn completed gateway → OpenRouter (200, model claude-4.5-haiku, cost echoed; no OR key / deploy token in the response); org+agt budget refused on-path (402 budget_exceeded, spent 0.000088 > budget 0.00001); a stale lease epoch was fenced (401 token_superseded); no-token and bad-admin-secret → 401. opencomputer = PR-only; do NOT merge (Igor merges). Base = flue-native, into #489. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
W6 live bring-up hit three defects W4's bundle-grep acceptance missed.
Fixed at the package layer so the scaffolded starter needs no workarounds.
1. DEFAULT_MODEL dot->dash. `anthropic/claude-haiku-4.5` is absent from
pi-ai's model catalog, so pi-ai can't derive max output tokens and
defaults max_tokens=1 -> empty completions. Use the catalog id
`anthropic/claude-haiku-4-5` (dashes; OpenRouter routes it too).
2. ctx.env empty on the CF build. On `flue build --target cloudflare`
the real Worker bindings live on the ambient `cloudflare:workers`
env, not the per-agent `ctx.env` Flue threads in (that is empty for
OC bindings), so OC_GATEWAY was unset -> the anthropic provider never
registered ("Unknown model specifier"). New cf-env.ts reads the
ambient env via a guarded lazy import (falls back to the passed env
for local dev / node); useOcGateway + ocSandbox read through it.
3. Default `@opencomputer/flue/app` 500s. app.ts mounted flue() from
`@flue/runtime/routing`; the generated CF entry seeds the runtime via
configureFlueRuntime from `@flue/runtime/internal` and its no-app.ts
path builds the app with createDefaultFlueApp() from that same entry.
Compose via createDefaultFlueApp() so flue()'s module-scoped
runtimeConfig is the instance the build configures (+ keep /health and
installOcObserver) -> no "flue() route invoked before runtime was
configured".
Verified: scaffolded starter `flue build --target cloudflare` + wrangler
dev -> GET /health 200; POST /agents turn 202 -> model call reaches the
gateway with model=claude-haiku-4-5, max_tokens=64000 (not 1); full turn
settles completed with a non-empty agent message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… turns health panel Extends the existing Sessions UI (runtime-agnostic; renders flue and brain-box sessions alike): - Sessions list: Runtime badge (flue accented) + Spend column, from the session's agent_snapshot.runtime + usage. Adds agent_snapshot to the web SessionSchema and a display-only flue label to lib/runtimes (kept OUT of the create picker). - Conversation viewer: distinct rendering for agent.thinking, tool results (exec.completed AND flue's tool.result), turn.failed, and a body-spill affordance (content_ref/body_truncated). Keeps the Conversation/All level filter + live SSE tail. - Submission health: new turns panel (GET /v3/sessions/:id/turns) — per-turn state, yield_reason, timing, usage, error. Recovery stays cancel + steer + webhook redeliver (no fake retry — v3 has no turn re-drive endpoint). - Per-session spend: Spend/Tokens/Events metric cards on detail, derived defensively from the opaque usage object (flue meters at the gateway → renders '—'). - Data layer: getSessionTurns/getSessionResult wrappers + schemas; lib/usage helpers; preview mocks extended so the new surfaces render with no backend. Tier-2 note: /v3 has no streaming-parts/delta mode — tiering is the level field (internal/progress/user); 'Tier-2 fidelity' = faithful internal-level trace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Rework the flue branch of `oc agent deploy` for the WfP Durable-Object model
(design 013 §6). Replaces the old oc-flue-build -> dist-oc tar.gz -> R2 presigned
PUT -> {framework_artifact_digest} flow with: credscan (kept) -> `flue build
--target cloudflare` -> POST the built Worker module + generated wrangler to the CP,
which owns compose/mint/WfP-upload/canary-verify/activate. The CLI holds no CF creds
or signing keys.
deployFlue now POSTs /v3/agents/<id>/deployments with input.type=inline +
flue_module={filename,contentB64}, flue_wrangler=<generated wrangler.json object>,
flue_agent_name=<agent.toml name>; activate=!--no-activate. Reuses pollDeployment
(verifying -> ready|failed). Rollback/revisions/status verbs unchanged.
- module filename is read from the generated wrangler's `main` (robust: it must
equal the CP's metadata.main_module), not hardcoded
- output discovery handles `flue build`'s real nested dist/<app>/ layout (and a flat
dist/); wrangler.json is strict JSON, parsed + forwarded verbatim
- removed the tar/digest/R2 machinery (bundle, net/http, io/fs)
- test rewritten: TestDeployFlueDoEndToEnd drives the real deployFlue against a fake
`flue` build + fake CP and asserts the well-formed DO body + verifying->ready poll;
credscan block test kept. go build/vet/test green, gofmt clean.
NOTE for the orchestrator: real `flue build --target cloudflare` emits a MULTI-module
worker (main index.js + no_bundle + assets/*.js). The current single `flue_module`
contract carries only the entry; the CP-side upload must accept the assets/ tree (a
module map) for the tenant to actually boot. Flagged — CP/contract owns this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… update)
The oc-deploy contract changed to keep the API host byte-free: no base64 module
bytes in the JSON. deployFlue now tar.gz's the WHOLE `flue build --target cloudflare`
output dir (entry module + the no_bundle assets/ tree), stages it in R2 via the
presigned PUT (POST /v3/agents/:id/artifacts), and POSTs the deployment with
{ flue_bundle_digest: "sha256:<hex of the tar.gz>", flue_wrangler, flue_agent_name }.
- restores the tar/R2 machinery (bundle.Pack/Digest, presigned PUT, already_uploaded
skip); drops flue_module/contentB64 entirely
- the tar is rooted at the wrangler's dir so wrangler.main (index.js) + assets/ sit at
the bundle root for the off-host runner's WfP upload
- staging the whole dir resolves the earlier multi-module finding (index.js + the
assets/*.js chunk tree travel together)
- matches the CP contract on sessions-api flue-w7-deploy (core/flue-do-deploy.ts
enqueueFlueDoDeploy: byte-free enqueue; digest = sha256 of the tar.gz)
- test rewritten to assert the byte-exact whole-dir upload chain + the byte-free DO
body (flue_bundle_digest, no flue_module). go build/vet/test green, gofmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… seam) The per-deploy OC_SESSION_TOKEN is a secret surfaced on the CF ambient env only once the request ALS is entered — not at defineAgent-init (DO-construction scope), where useOcGateway ran and read it falsy -> "No API key for provider: anthropic". Move the apiKey bind into the exported `route` middleware (run scope, the same phase ocSandbox reads the token and succeeds), once per isolate. The token is per-deploy (identical for every session), so the isolate-scoped provider registry holds one static value with no cross-session race — no upstream per-request resolver needed. useOcGateway still registers the baseUrl at init so the model specifier resolves. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Validated end-to-end (live agent turn via the OC gateway). Correction to the run-scope bind: the per-deploy token secret is NOT on the CF ambient env — it lives on the per-REQUEST env (c.env), the same source ocSandbox reads. The route middleware reads OC_SESSION_TOKEN from c.env by DIRECT property access (never spread c.env — the CF env proxy throws on spread) and OC_GATEWAY from the ambient snapshot, binding the provider apiKey once per isolate before the turn model call. Also reconciles the token-seam header comment to the real mechanism and drops a duplicate doc comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Evergreen integration branch for the Flue-native agent type — opencomputer lanes (L2 gateway, L4 CLI, L5
@opencomputer/flue, docs).oc-bg-agents/.agents/design/013-flue-native-agent-type.md+014-agent-session-contract.md. Plan/status:work/flue-native-buildout.md.mainas a unit when the slice is complete.flue-native(notmain).spike/oc-gatewayspike(L2): thin per-session gateway over OpenRouter — Contract #1 [DO NOT MERGE] #486, which stays a DO-NOT-MERGE reference); the@opencomputer/fluescaffolding (defaultapp.ts,ocSandbox,registerProviderinsidedefineAgent,export const route, a prompt-caching-safe default model, publish/repo tools); the CLI deploy path; the re-scaffolded starter + docs.🤖 Draft evergreen PR.