Skip to content

[feature] Generalize the versioning engine across agents, guardrails, orchestrations, and workflows #877

Description

@arantespp

Summary

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.

Motivating cases

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.

Suggested sequencing

  1. Fix [bug] Orchestration graphs are not versioned — editing an orchestration mutates in-flight runs (orchestrations) #872 with a simple run-level JSONB snapshot on OrchestrationRun (small, ships fast, no new tables).
  2. Extract the generic archive layer; migrate agents + guardrails onto it (behavior-preserving).
  3. Add OrchestrationVersion + WorkflowVersion on 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.
  4. Release/canary semantics per resource, on demand.

Related: #872

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions