You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Resource versioning is hand-rolled per resource and has already been duplicated once: AgentVersion.ts's own doc comment says "Mirrors GuardrailVersion." With orchestrations needing versioning (#872) and workflows having the same in-flight-mutation exposure, we're past the rule-of-three — extract a shared versioning engine instead of writing a third and fourth copy.
Workflows (same bug class) — a Task lives in a workflow for weeks; tasksTransition/tasksDispatch read the live Workflow row, so editing states/transitions changes the state machine under mid-flight tasks. A task that entered on v3 should finish on v3.
Agents + guardrails — already versioned, with two parallel implementations to unify.
Design: three layers with different generality
1. Version archive — fully generalizable
Immutable (resource_id, version, config JSONB, created_by, public_id) rows written on config-changing writes, plus list/get/restore. AgentVersion and GuardrailVersion already share this shape. The snapshot builder is generic by construction: buildAgentConfigSnapshot (agentVersionSnapshot.ts) projects the wire mapper's output minus an exclusion set (NON_CONFIG_AGENT_FIELDS) — copy-as-value, no key rewriting, per .claude/rules/case-convention.md.
A shared factory with per-resource adapters:
makeVersionedResource({versionModel: db.OrchestrationVersion,nonConfigFields: ['id','project_id','version','created_at','updated_at'],applyConfig: (config)=>{/* snake→camel restore, per resource */},})
Only applyConfig (the restore direction, today archivedConfigToUpdateArgs) is genuinely per-resource. Keep per-resource version tables (orchestration_versions, workflow_versions) for real FK integrity — share the lib code, not the schema; no polymorphic FK table.
2. Release / traffic split — mechanism generic, meaning per-resource
bucketForKey / assignReleaseVersion (agentReleaseAssignment.ts) are already pure. What a "release" targets differs:
Resource
Release means
Canary key
Agent
version serving a generation
actor/session (exists today)
Orchestration
version new runs start on
% of new runs (optional)
Workflow
version new tasks are created into
— (no mid-life reassignment)
Extract the pure helpers now; add release semantics per resource lazily, when actually requested.
3. Pin-at-consumption — inherently per-resource
Each engine pins at its own consumption moment: agents per generation (resolveServedAgentVersion), orchestrations at start-orchestration-run, workflows at task creation, guardrails per evaluation record. This layer is the adapter each engine writes against the shared archive — not shared code.
Summary
Resource versioning is hand-rolled per resource and has already been duplicated once:
AgentVersion.ts's own doc comment says "MirrorsGuardrailVersion." With orchestrations needing versioning (#872) and workflows having the same in-flight-mutation exposure, we're past the rule-of-three — extract a shared versioning engine instead of writing a third and fourth copy.Motivating cases
Orchestrationrow; edits mutate in-flight runs.Tasklives in a workflow for weeks;tasksTransition/tasksDispatchread the liveWorkflowrow, so editing states/transitions changes the state machine under mid-flight tasks. A task that entered on v3 should finish on v3.Design: three layers with different generality
1. Version archive — fully generalizable
Immutable
(resource_id, version, config JSONB, created_by, public_id)rows written on config-changing writes, plus list/get/restore.AgentVersionandGuardrailVersionalready share this shape. The snapshot builder is generic by construction:buildAgentConfigSnapshot(agentVersionSnapshot.ts) projects the wire mapper's output minus an exclusion set (NON_CONFIG_AGENT_FIELDS) — copy-as-value, no key rewriting, per.claude/rules/case-convention.md.A shared factory with per-resource adapters:
Only
applyConfig(the restore direction, todayarchivedConfigToUpdateArgs) is genuinely per-resource. Keep per-resource version tables (orchestration_versions,workflow_versions) for real FK integrity — share the lib code, not the schema; no polymorphic FK table.2. Release / traffic split — mechanism generic, meaning per-resource
bucketForKey/assignReleaseVersion(agentReleaseAssignment.ts) are already pure. What a "release" targets differs:Extract the pure helpers now; add release semantics per resource lazily, when actually requested.
3. Pin-at-consumption — inherently per-resource
Each engine pins at its own consumption moment: agents per generation (
resolveServedAgentVersion), orchestrations atstart-orchestration-run, workflows at task creation, guardrails per evaluation record. This layer is the adapter each engine writes against the shared archive — not shared code.Suggested sequencing
OrchestrationRun(small, ships fast, no new tables).OrchestrationVersion+WorkflowVersionon the shared engine; pin runs/tasks at start/creation. [bug] Orchestration graphs are not versioned — editing an orchestration mutates in-flight runs (orchestrations) #872's snapshot becomes a version reference.Related: #872