Introduce "template" as a VM state#229
Conversation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds StateTemplate to the instance state machine. A Standby instance is auto-promoted to Template the first time it's forked from a snapshot, and ForkCount is bumped on each subsequent fork. Templates can't wake while ForkCount > 0; un-promote (Template -> Standby) and delete (Template -> Stopped) are both refused until forks drain. Fork bookkeeping lives on StoredMetadata (IsTemplate, ForkCount, ForkOfTemplate, plus a reserved HotPagesPath for the prefetch path). Deleting a fork decrements the parent template's ForkCount under the parent's lock; deletion of the fork's own data has already happened, so worst case is refcount drift that a future reconciliation pass fixes. The running-fork flow keeps skipping promotion: it restores the source back to Running afterward, and a template can't wake.
✱ Stainless preview builds for hypemanThis PR will update the Edit this comment to update it. It will appear in the SDK's changelogs. ✅ hypeman-typescript studio · code · diff
✅ hypeman-go studio · code · diff
✅ hypeman-openapi studio · code · diff
This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push. |
Drops the persisted ForkCount field from StoredMetadata and the decrement bookkeeping in DeleteInstance. Live forks of a template are now counted by scanning metadata for ForkOfTemplate matches via a new countTemplateForks helper. The fork-of-template field itself remains the single source of truth, so there's no drift to reconcile. Template promotion on fork only flips IsTemplate when not already set; deletion of a template still refuses when forks exist, but the count is computed from disk rather than read from a denormalized field.
Previously ForkInstance auto-promoted a Standby source to Template the
first time it was forked from a snapshot, and RestoreInstance auto-demoted
a Template before waking it. That implicit lifecycle blurred the rules: a
Standby and a "Standby that has been forked once" behaved differently,
and callers had to know that restoring a Template was a two-step
operation under the hood.
Replace it with explicit PromoteToTemplate / DemoteTemplate manager
methods (and matching POST /instances/{id}/promote-template and
/demote-template endpoints). Promotion is now Standby -> Template only;
demotion is Template -> Standby only and refuses while live forks
reference the template. ForkInstance only records the parent linkage if
the source is already a Template, and RestoreInstance no longer
auto-demotes — callers must demote first.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Silently continuing past an unreadable metadata file could undercount forks of a template, allowing DemoteTemplate or DeleteInstance to free a template whose pages are still mapped by a live fork. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Firetiger deploy monitoring skipped This PR didn't match the auto-monitor filter configured on your GitHub connection:
Reason: PR modifies instance state machine logic in packages/api/lib/instances, not the API endpoints (packages/api/cmd/api/) or Temporal workflows (packages/api/lib/temporal) specified in the filter. To monitor this PR anyway, reply with |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0d0ab12. Configure here.

Summary
Models "Template" as a state in the instance state machine. A Standby instance is auto-promoted to Template the first time it's forked from a snapshot.
ForkCountis bumped on each subsequent fork and decremented when a fork is deleted. Templates cannot wake whileForkCount > 0; un-promote (Template -> Standby) and delete (Template -> Stopped) are both refused until forks drain.RestoreInstanceon a Template with zero forks transparently un-promotes and then restores.Three new fields live on
StoredMetadata:IsTemplate,ForkCount,ForkOfTemplate. A fourth fieldHotPagesPathis reserved for the future UFFD prefetch path and is cleared on un-promote.State derivation in
query.goreturnsStateTemplatewhen there's a snapshot, no socket, andIsTemplate=true. Template is a derived state alongside Standby/Stopped — no extra files on disk, no separate index.The running-fork flow (
from_running=true) skips promotion: it restores the source back to Running afterward, and a template can't wake.Fork delete decrements the parent's
ForkCountunder the parent's lock after the fork's data is gone. Worst case on partial failure is drift (parent count higher than reality), fixed by a future reconciliation pass.Test plan
go build ./...cleango test ./lib/instances/...)templates_test.gocover: deriveState returns Template, Restore refused on Template with forks, Restore demotes Template with zero forks, Delete refused on Template with forks, Delete-fork decrements parent ForkCountNote
Medium Risk
Adds a new instance lifecycle state and new API endpoints that change fork/restore/delete behavior; mistakes could block restores/deletes or mis-handle template fork tracking.
Overview
Introduces a new
Templateinstance state (derived from a standby snapshot) and extends the state machine to allowStandby -> TemplateandTemplate -> Standby/Stoppedwhile disallowing waking templates directly.Adds lifecycle operations to explicitly manage templates: new API endpoints
POST /instances/{id}/promote-templateandPOST /instances/{id}/demote-template, plusinstances.ManagermethodsPromoteToTemplate/DemoteTemplatewith state/"live fork" guards.Updates forking semantics to allow forks from templates (treating template/standby as snapshot-based), record parent linkage via
ForkOfTemplate, and ensure forks do not inherit template-only metadata; deletion and demotion now refuse when a template has live forks, enforced via a newcountTemplateForksscan and covered by newtemplates_test.go.Reviewed by Cursor Bugbot for commit 0d0ab12. Bugbot is set up for automated code reviews on this repo. Configure here.