feat(cell): uptake cell-harness 0.3.0, agent-protocol 0.2.0, celld v0.3.0 - #156
Open
coccyx wants to merge 10 commits into
Open
feat(cell): uptake cell-harness 0.3.0, agent-protocol 0.2.0, celld v0.3.0#156coccyx wants to merge 10 commits into
coccyx wants to merge 10 commits into
Conversation
* docs: cell-harness extraction + Pi adoption design (spike: pi-agent-core turn-stepping PASS) Design for splitting the generic celld agent harness out of cell/ into framework packages (@cribl/agent-protocol, cell-harness, cell-workspace) behind a CellPayload seam, so a second app can run a Pi coding agent in a celld worker without copying the APM cell. Spike (spike-pi-agent-core.mjs, 5/5 PASS on 0.84.2): pi-agent-core supports the DO alarm-per-turn model natively — shouldStopAfterTurn stops after one LLM call with history ending on toolResult, messages JSON round-trip, and a fresh Agent rehydrated from that JSON continue()s to the answer. Root entry pulls no node builtins. pi-coding-agent's tools are NOT worker-bundlable today (static node + TUI imports despite the Operations seam; no tools subpath export) — recommendation is worker-native tools over the @cloudflare/computer vfs + just-bash, borrowing its pure edit-diff module. Also proposes publishing the framework as a proper npm module to retire the .framework-sha pin (registry choice open). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * docs: record second-round decisions (GitHub Packages/@criblio, interactive-first, git write-back) Registry: GitHub Packages under criblio — scope must match the org, so packages publish as @criblio/*; @cribl/app-utils migration (rename vs npm alias) deferred to the publishing PR. Coding app v1 is interactive-first. Write-back is git: GitHub Data API backend first (tarball checkout carries no .git state), isomorphic-git — including a just-bash 'git' command wrapping it — as the upgrade path behind the same tool surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * docs: run_search/run_metrics_query are framework-level — move their defs to app-utils in step 4 Answering the review question on PR #148: the executors already live in app-utils/agent-tools (createRunSearchTool / createRunMetricsQueryTool); APM only wires them and adds render_trace. Step 4 also moves the shared tool definitions (agentToolDefs schemas) into app-utils so other payloads can offer the Cribl search tools without importing APM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * docs: design status — steps 1-5a built as the PR stack Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… pi-agent-core runner, rebuild on framework packages (#149 #150 #151 #152) Combined squash of the extraction stack (PRs #149-#152; #149/#150 were accidentally squash-merged into their stack bases rather than master, so this lands the full content — see the individual PRs for per-step detail and validation): - #149 CellPayload seam: DOs reach APM behavior through the payload interface (cell/src/payloads/apm.ts) - #150 wire protocol from @criblio/agent-protocol (both former mirrors deleted; LoopEvent parity type-test) - #151 pi-agent-core turn runner (shouldStopAfterTurn alarm-per-turn; scripted-stream tests) - #152 cell rebuilt on @criblio/cell-harness + cell-workspace; cell/src = APM payload + 16-line entry; DO class names/SQL/bindings frozen v1; esbuild bundle smoke verified Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT
…sha (#153) * feat: consume the framework from GitHub Packages — retire .framework-sha (extraction §6) The framework is now published to GitHub Packages under @criblio (framework PR #30): this switches every dependency to registry versions and deletes the pin machinery. - imports: @cribl/app-utils → @criblio/app-utils, @cribl/app-tooling → @criblio/app-tooling (54 files, mechanical) - package.json: registry versions (^0.5.0 app-utils, ^0.2.0 app-tooling, ^0.1.0 agent-protocol/cell-harness/cell-workspace) replacing file:../ links - .npmrc: @criblio scope routing (no auth line — that lives in ~/.npmrc locally / is written by the release-build action in CI) - ci.yml/release.yml: release-build action in registry mode (@7ef8eb4, registry-token: github.token); .framework-sha deleted - new update-lockfile workflow: regenerates package-lock.json on a branch in CI for machines without a registry token - CLAUDE.md: pin section replaced with GitHub Packages guidance (one-time ~/.npmrc auth, bump flow, framework-dev flow) package-lock.json is regenerated by the update-lockfile workflow on this branch (local machine has no registry token yet). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: temporary push trigger to bootstrap the lockfile regen on this branch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: regenerate package-lock.json (update-lockfile workflow) * chore: bootstrap regen must drop the stale lockfile (file: resolutions were preserved) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: regenerate package-lock.json (update-lockfile workflow) * chore: restore update-lockfile to its utility form (bootstrap trigger removed) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: preserve master's locked dep versions — resolve only @criblio fresh The full lockfile regen floated every semver range; a newer eslint-plugin-react-hooks brought new rules and 10 new lint errors, which is version drift, not part of this change. Re-seed from master's lockfile with the framework entries stripped so the CI regen resolves exactly the 5 @criblio packages from the registry and nothing else moves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: regenerate package-lock.json (update-lockfile workflow) * chore: trigger CI (bot lockfile pushes don't start workflows) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: bump framework packages to the built versions (0.5.1 / 0.1.1) The dist+d.ts builds from framework PRs #32/#33 — source-shipped packages broke Node consumers (type-stripping refuses node_modules). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ae/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: fix duplicate push key in bootstrap trigger (touch package.json to refire) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: refire lockfile regen * chore: widen bootstrap trigger paths so this push refires the regen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: regenerate package-lock.json (update-lockfile workflow) * chore: remove the bootstrap trigger — update-lockfile is dispatch-only from here Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Same-version deploys dedupe (deliberately no force), and 0.14.0 was already installed; this ships the extraction-era pack (payload seam, shared protocol, pi-agent-core runner, framework packages) to staging. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT
* fix(deps): react-router-dom ^7.18.2 — RSC-mode CSRF bypass (Dependabot #11) High-severity advisory: RSC Mode CSRF bypass allows action execution before the 400 response (react-router >=7.12.0 <7.18.2). This app doesn't use RSC mode, so exposure is theoretical, but the bump is a one-liner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT * chore: regenerate package-lock.json (update-lockfile workflow) * chore: trigger CI (bot lockfile pushes don't start workflows) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158jez4WfWj6ttmaxh3HEbT --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Uptakes the framework's cell work since the extraction landed. Both bumps are additive at the type level — the app and cell typecheck, lint, and pass 440 tests unchanged — but two of the behaviors behind them matter operationally: - **Bounded watchdog retries** (cell-harness 0.3.0). The watchdog alarm re-ran a turn whose isolate died mid-run with no attempt counter, so a turn that kills its OWN isolate (a tool blowing celld's handler budget terminates the celld *process*) retried forever, restarting the node every 240s and taking every other session with it. Now capped at 3 attempts, counted in durable storage — the isolate dying is the event being counted, so the count has to outlive it. The new `watchdogAttempts` key defaults to absent, so existing DOs start with a clean slate. - **Image input on interactive messages.** APM's UI doesn't attach images, so this is dormant here: `WireLoopEvent.userMessage` gained an optional `imageCount`, and the UI imports that type straight from @criblio/agent-protocol rather than mirroring it, so it widens with no edit. The new `agent_images` table is created with CREATE TABLE IF NOT EXISTS in the session DO's constructor, which runs on every rehydration — the 30 investigations already on the node gain it without a migration step. `createTools` also gained an optional `sql` handle (PayloadSqlHandle) for storage-backed payloads. APM's payload is API-backed and ignores it; the param is optional, so apmPayload needs no change. Not uptaken here: LLM_VISION. Declaring vision on a model that isn't multi-modal is a hard API error on most providers, and the APM cell's pinned model is text-only, so it stays off (the framework default). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three changes to the cell's fleet root, all riding one instance replacement (user_data changes, and user_data_replace_on_change is on). The plan is exactly `2 to add, 2 to destroy`: the instance and its EIP association. Bucket, IAM, security group and the EIP itself are no-ops — the sslip.io URL and every replicated cell SQLite survive, as they did on the last two replacements. **celld v0.2.0 → v0.3.0.** v0.3.0 adds a replicated write-behind log and moves the default write-ack posture from `bucket` to `fleet`. On a single-node fleet that is not the regression it looks like: with no peers celld behaves exactly like sync-to-bucket (no record, no shipper, bucket-proven acks) and starts using peers the moment one joins. It's pinned via the new CELLD_DURABILITY rather than left implicit, so a future celld default change can't move this node's durability without appearing in a plan; `bucket` is the opt-out. CELLD_HANDLER_BUDGET_S is pinned at celld's own default for visibility — blowing that budget kills the celld PROCESS, so every session on the node dies, which is the failure cell-harness 0.3.0's bounded watchdog stops retrying into. Rolling back is not symmetric: a v0.2.x binary cannot read v0.3.0's replicated log, so starting one on a node that has run v0.3.0 can lose acknowledged writes unless the shutdown log shows `node-log close: sealed epoch`. Documented in the README next to the version pin. **Module source: sibling checkout → pinned git ref.** The path source assumed a framework clone next to this repo, which was true when the app consumed the framework via `file:../`. That ended when it moved to GitHub Packages, so `terraform init` on a fresh machine or in CI had a dependency nothing else in the repo still had. The framework repo is public, so the https source needs no credentials. **required_secret_keys: three of six.** Boot aborted on any missing SSM parameter; now only the ones with no working degraded mode do. UI_BEARER, because bearerOk() treats unset as closed and a node without it serves 401 to the app — up and unusable is worse than not up. TICKET_SECRET, which gates the WS transport. LLM_API_KEY, because an investigator that can't reach a model has nothing to offer. The rest degrade honestly and shouldn't cost the node: WEBHOOK_BEARER only guards /alerts/fire, and the coordinator PULLS from $vt_results now (verified: pollTriggers authenticates with the Cribl creds, not the webhook bearer), while CRIBL_CLIENT_ID/SECRET are checked at use and report themselves in the transcript. All six exist in SSM today, so this changes nothing about the current node — it decides what happens to the next one. Also pins the agent-loop config as variable defaults instead of runbook `-var` flags. All four values render into user_data, so an apply that forgot one would replace the node into stub mode (empty llm_base_url ⇒ no agent loop) with no error at all. Verified: terraform fmt/validate clean; `init` resolves the pinned module over https with no token; plan against live state shows the 2-add/2-destroy shape above and the module's required_secret_keys subset validation passing (that check runs at PLAN time, not during validate); the rendered user_data passes `bash -n` and carries CELLD_VERSION=v0.3.0, CELLD_DURABILITY=fleet, CELLD_HANDLER_BUDGET_S=300 and REQUIRED_KEYS; and the v0.3.0 aarch64 release asset resolves 200. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Documents the operational surface the previous two commits changed, and stops eslint walking Terraform's module cache. - DEPLOY.md: step 2 is `terraform apply -var bucket_name=…` and nothing else now that the agent-loop config lives in variables.tf defaults; the celld-bump note points at `var.celld_version` and carries the v0.3.0 rules (roll-forward-only off 0.3.0). The CI/CD sketch needs a GitHub Packages auth line — the cell's deps are @criblio packages since .framework-sha retired — and downloads celld v0.3.0 to match the node's binary. - cell/README.md: document LLM_VISION and TURN_BUDGET. Vision stays off deliberately: the pinned model is text-only, pi-ai silently drops images for an undeclared model, and most providers hard-fail image parts sent to a text-only one. - eslint.config.js: ignore `**/.terraform/`. `cell/infra` now pins the celld-fleet module by git ref, so `terraform init` drops a full framework clone in there and lint failed on vendored source (react-refresh/only-export-components, two files). It's gitignored, but eslint doesn't read .gitignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records what shipped, the deploy sequencing (bundle first on the old node, instance replacement second), the verification evidence, and the traps — content-hash version IDs vs the current.json pointer, the one-way v0.3.0 downgrade, and celld 0.2.0 having no version endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Takes into APM the framework changes that landed while Kidder was being built: the two npm packages, the celld 0.3.0 runtime, and the infra + docs changes needed to consume them.
Stacked on #155 (
refactor/cell-infra-fleet-module) — the diff here shows only the uptake chunk. Merge #155 first.Commits
5dea567—chore(deps): cell-harness 0.3.0, agent-protocol 0.2.0cd73be7—feat(cell/infra): celld v0.3.0, pinned module ref, optional secrets839ec0e—docs(cell): DEPLOY/README for celld 0.3.0 + harness 0.3.0 varsa session log commit1. Deps — every change is additive, so no APM source edits
Verified rather than assumed:
EnvextendsCellEnvCellEnvis a name-identical superset of what APM declared — the node's existing/etc/celld/envstill binds. No SSM/Terraform change.createTools(env, sql?)sql.imageCount?on tool frames@criblio/agent-protocolrather than mirroring them, so the type widens for free.agent_imagestableCREATE TABLE IF NOT EXISTSin the session DO constructor ⇒ existing DOs gain it on rehydration. No migration.CoordinatorDO/InvestigationDOunchanged ⇒ durable state stays addressable across the deploy.The headline behavior change is the bounded watchdog:
MAX_WATCHDOG_ATTEMPTS = 3, counted in durable storage underwatchdogAttempts, written before the turn (the isolate dying is the event being counted). Previously unbounded — a turn whose tool overran celld's handler budget killed the process and got retried forever, restarting the node every ~240s.2. Infra
v0.2.0→v0.3.0.CELLD_DURABILITYpinned tofleet. v0.3.0 added a replicated write-behind log and moved the default write-ack posturebucket→fleet. That reads like a regression on a single-node fleet but isn't: with no peers celld behaves exactly like sync-to-bucket (bucket-proven acks) and starts using peers the moment one joins. Pinned rather than left to the default so a future celld default change can't move this node's durability without showing up in a plan.CELLD_HANDLER_BUDGET_Spinned at the default, 300. Exceeding it kills the celld process, not just the isolate, so every session on the node dies — this is the budget the bounded watchdog stops retrying against. Pinned for visibility.file:../when it moved to GitHub Packages, so nothing guarantees a checkout sits beside this one andterraform initon a fresh machine has to resolve without one. The repo is public, so https needs no credentials.required_secret_keys = [UI_BEARER, TICKET_SECRET, LLM_API_KEY]— only these three abort the boot.WEBHOOK_BEARERonly guardsPOST /alerts/fire, and the coordinator pulls firing alerts from$vt_resultsnow, so autonomous investigation survives its absence;CRIBL_CLIENT_ID/SECRETare checked at use. Skipped keys land in/etc/celld/missing-secrets.-varflags intovariables.tfdefaults. All of it renders intouser_dataunderuser_data_replace_on_change = true, so an apply that forgot a flag would replace the node into stub mode (emptyllm_base_url⇒ no agent loop) with no error.bucket_nameis the only flag now.eslint.config.jsignores**/.terraform/— the pinned git module meansterraform initdrops a full framework clone there and lint failed on vendored source. It's gitignored, but eslint doesn't read.gitignore.Test plan
Local (all green on the tip):
Validate on staging — already done for the bundle
The new bundle is deployed and verified on the live node; sequenced deliberately one variable at a time so a bad bundle would still have a working node to roll back on.
celld deploy→ version9d5358e345bf0c41(was7bd185e1a91dd9d7), withdeploy/current.jsonconfirmed pointing at it before the restart. celld version IDs are content hashes andcurrent.jsonis a separate pointer, so the bucket can hold a newer bundle than the pointer selects — that exact trap left the node on old code all of the previous day.watchdogAttemptspresent in the newindex.js, absent from the old.fresh=false;journalctl -p warning→ No entries;Started celld= 1 (no crash-loop).inv-ed5faf29-…): 69 frames, 19 real tool calls against live staging, found 393 erroroteldemo.PaymentService/Chargespans at sub-ms latency.Anyone can re-check right now:
curl -s https://54-71-34-177.sslip.io/healthz # {"ok":true,"disabled":false}Known limitations
terraform applyhas NOT been run. The celld 0.2.0 → 0.3.0 binary upgrade replaces the instance, so it needs a human at the confirmation prompt. Plan verified, blast radius from the plan JSON:module.fleet.aws_instance.cell(replace onuser_data) +module.fleet.aws_eip_association.cell(replace oninstance_id), 10 no-ops —2 to add, 0 to change, 2 to destroy. The EIP is a separate resource socell_urlstayshttps://54-71-34-177.sslip.ioand durable state survives in the bucket. Pre-flight done:user_datarendered throughtemplatefileandbash -n'd, and the celld v0.3.0 aarch64 asset returns 200 (a 404 would brick the replacement node). The live node therefore still runs celld 0.2.0 with neither new env var; the bundle is already in place, so that apply is the only remaining step.failedon "hit the 12-turn cap without concluding". Not a regression — the identical string is in the old bundle, and autonomous mode is hard-capped atMAX_TURNS = 12regardless ofTURN_BUDGET(sessionDO.js:832:interactive ? row.turn_budget : MAX_TURNS). Worth revisiting on its own.node-log close: sealed epoch. Documented invariables.tfandcell/infra/README.md.Session log: docs/sessions/2026-08-22-uptake-cell-harness-0.3.0.md
🤖 Generated with Claude Code