Repository navigation
feat(operations): core primitive, resources, transfer events and ctx.operations (#895, 1/6) - #976
Merged
Merged
Conversation
…895) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) Six sequenced PRs (T0 antfly spike, primitive, UI, repair, installs, search, onboarding/runtime) with additive prepare commits and one atomic switch commit per flow. Five Codex review rounds; every finding resolved. Spec gains the team-wide sync route and the lesson-toggle exclusion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T0 for the operations plan. antfly 0.2.2 pulls into a staging dir with .part files, publishes atomically, resumes at byte level after SIGTERM or SIGKILL, and reports structured progress on stderr. The pull ignores ANTFLY_HOME, so runPull will pass --models-dir. SPEC D12 and PLAN 5.2 now carry the decided progress source, cancel rule and stub-binary stall test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The leaf types for the operations primitive, single-homed in the SDK and re-exported by @bakin/core/operations: fixed stage kinds, the canonical core resource order, complete OperationView, OperationContext (with holds for inner ownership assertions), outcomes and the HTTP wire shapes. pointOfNoReturn gains null for flows cancellable throughout (previews, model pulls per the T0 spike). OperationCancelledError lives in core. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te-view events (#895) createOperationRegistry takes its side effects by injection (publish, audit, log, bootId, now), so the app wires SSE and audit while tests build their own. It covers: - atomic fingerprint attach; validation failures unregister and reach every attached caller - cancel only before the point of no return, terminal after run settles - stages in plan order, items, bytes and attempts on two clocks (output is liveness, never progress) - complete views with a monotonic rev: milestones publish immediately, ticks coalesce to the throttle window - a stuck sweep that logs once per stage, one audit record per finished top-level operation, eviction (1h / 50) and the shutdown note The quiet threshold table and operationQuietState live in the SDK leaf module because the CLI and browser judge quiet from the same view. The operation contract types join the single-home architecture guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p context (#895) install-state, media-store and search-engine become O_EXCL lock files with holder metadata {pid, bootId, operationId, title, acquiredAt}. install-state stays at the existing ~/.bakin/install.lock; the others live under ~/.bakin/locks/, and plugin-private resources under locks/<pluginId>/. - acquireResources sorts into the canonical order, waits abortably with onWait and backoff, releases a lease obtained after an abort, and reclaims stale holders (dead pid, or this pid with no live holder). - tryAcquireResources never waits, for background work and 409 writers. - One AsyncLocalStorage ownership context (runHolding / heldInContext) makes acquisition reentrant within one operation's call chain. install-lock.ts becomes a thin facade over the resource: same exports and messages, same file, same context. So withInstallLock and assertInstallLockHeld keep working inside an operation that holds install-state, and the two APIs exclude each other. PR4 deletes the facade. An architecture test keeps the lock literals inside resources.ts; antfly's service lock is out of scope by construction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pp registry (#895) After the 202 an operation acquires its declared resources in canonical order. While blocked it shows waitingFor naming the holder and stays cancellable. After a wait, revalidate can end it with an outcome instead of running. run executes inside the ownership context, so op.holds and the legacy inner-writer assertion both pass, and the lease is released before the terminal view is published. runLocal runs the same machinery in-process and resolves with the terminal view. Inside an existing holder (an outer operation or withInstallLock) it is reentrant and never waits on itself. src/core/operations.ts is the process registry. It sends operation.updated in the plugin-event envelope (milestones via the replayable broadcast, ticks via the ephemeral one), audits with the channel derived from origin, and logs to the server log. The SIGTERM path notes "Server stopping" in every live operation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#895) downloadToFile gains onEvent and signal, still beside onProgress until the caller switch removes it. The events are attempt N of M, bytes, a stall noticed on a body read, and each fresh-request retry with its classified reason (stalled, deadline, http, network). An abort skips retry, removes the partial and rethrows the abort reason. Checksum mismatches still never retry. downloadText runs through the same attempt loop (follows redirects), ready to replace the node:http copy in whiskit. opTransfer maps events onto an operation stage and item. The tar helpers take a signal and stream stderr lines to onOutput. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ive (#895) The caller switch for the transfer primitive: - pack bins and model files report through onEvent instead of onProgress; onProgress is removed from the primitive. - Whiskit releases, plugin sources and artifact upgrade indexes use the primitive's downloadToFile/downloadText. src/core/whiskit/download.ts (node:http, no retry, no stall guard) is deleted. - The antfly engine streams to disk with the pin verified, instead of buffering the archive via arrayBuffer(). It keeps its domain error wording through the new typed DownloadChecksumError. Fixes a race the antfly suite exposed: an attempt that failed before its sink's async open completed could surface a late ENOENT as an unhandled rejection once the caller removed its temp dir. A regression test pins it (RED without the fix). operations-transfers.test.ts pins the result: installer modules never buffer a body, Bun.write a response or hand-roll node:http(s), the Whiskit copy stays gone, and onProgress stays gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…art (#895) GET /api/operations, GET /api/operations/:id[?children=1] and POST /api/operations/:id/cancel, each answering with the server boot id. A 404 or cancel response therefore lets a client tell expired (same boot) from restarted (different boot). Cancel maps the registry result to 202 cancelling, 409 not_cancellable / already_finished, or 404. startOperationResponse is the start every converted route will share: - 202 { operationId, bootId } within 500ms while the run is blocked or waiting on a resource (success-criterion harness) - 200 attached for an identical live request - OperationRejectedError thrown by validate keeps today's status and body operationOriginFromRequest reads the CLI's X-Bakin-Origin marker. The routes are declared in core-routes (polling GETs routine) and published in the generated OpenAPI and API reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plugins start long-running work as operations through ctx.operations.start and can await one. It is available on the full context and on the route handler's PluginContextLite. The rules live in core (toPluginOperationInput) so the host and the SDK harness behave identically: - kind and fingerprint are namespaced <pluginId>: and the owner is plugin:<id> - origin comes from the triggering request (web or cli), else system - any plugin may declare its own <pluginId>:<name> resources; the core resources are reserved for core plugins @makinbakin/sdk/testing runs a real registry with in-memory resources (createMemoryResourceAcquirer, via a new injectable acquireResources dep), so author tests never touch a real Bakin home. The isolation guard found that sortResources resolved the content dir just to validate names; name validation is now pure (assertResourceName). The origin rule moves into core and is shared by the host routes and the plugin API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gin guide (#895) New .claude/knowledge/operations.md covers the model, the registry, the resources (canonical order, stale reclaim, legacy-lock interop), runLocal, the HTTP surface, the transfer primitive, antfly model pulls and ctx.operations. CLAUDE.md gains the Key Patterns entry plus install.lock and locks/ in the ~/.bakin map. The plugin docs-site guide gains Long-Running Work. The install-lock passages in agent-packages, capability-packs and plugin-system now name the install-state resource; agent-packages had still described the long-gone packages/.lock path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
markhayden
force-pushed
the
feat/operations-progress
branch
from
October 16, 2026 15:54
3fe1d0e to
fd3b12a
Compare
This was referenced Oct 16, 2026
Merged
Merged
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.
Part of #895 (PR 1 of 6). Spec:
.claude/specs/operations-progress/SPEC.md(rev 3.1, approved). Plan:.claude/specs/operations-progress/PLAN.md(rev 6, approved after five Codex review rounds).This PR builds the core of operations: one progress system for every long-running flow. There is no UI yet, and no existing flow switches over yet. PRs 2–6 add the UI and switch the flows over one at a time.
What's in it
tasks/evidence-antfly-pull.md):inference pulldownloads into a separate staging folder, publishes the model atomically at the end, and resumes where it left off after SIGTERM/SIGKILL.ANTFLY_HOME, so it needs--models-dir.packages/sdk/src/types/operations.ts):packages/core/src/operations/registry.ts):resources.ts):~/.bakin/install.lock), then media-store, then search-engine;withInstallLock(now built on top of this) keeps working inside an operation.runLocalruns an operation in-process.src/core/operations.tsis the per-process registry, wired to SSE and the audit log.packages/core/src/net/download.ts):downloadTextandopTransferlive alongside it;src/core/whiskit/download.tsis deleted.GET /api/operations[/:id]andPOST /api/operations/:id/cancel, each answering with the server'sbootId;startOperationResponsehelper for converted routes (202 within 500ms);core-routesand the OpenAPI docs.ctx.operationsfor plugins:.claude/knowledge/operations.md, a CLAUDE.md Key Patterns entry plus the~/.bakinfolder map, the plugin guide's "Long-Running Work" section, and corrections to the install-lock descriptions in three knowledge docs.Fixed along the way
Commit strategy
Each commit is green on its own. Reverts go in reverse order:
844dd5faf(the caller switch) reverts as one unit;The lock file only gained extra holder fields, and older readers ignore them.
Verification
bun run test: 10,622 of 10,628 pass (18 skipped). The 6 failures are the compiled-binary suites, which fail on the author's Mac because macOS kills every Bun 1.3.13 compiled binary, even a trivial one. CI on Linux runs them.typecheck,lint(0 errors) andcheck:cyclespass.ui:conformance --quickpasses with the Bits checkout at its pinned commit.docs:validateanddocs:validate:routespass.GET /api/operationsreturns{ bootId, operations: [] }, and the 404s carry thebootId.UI conformance: no UI changes in this PR (the browser pattern comes in PR2).
🤖 Generated with Claude Code