feat(agents): releases — a digest of what the model is given, cut on deploy, pinned to every trace, run and evaluation - #453
Conversation
…deploy, pinned to every trace, run and evaluation ActiveAgent::Release gives each agent class a manifest of what shapes a generation — provider and model, options minus credentials, the actions, the prompt templates on disk, the tools and delegations it declares — and a twelve-hex digest of it. Two deploys of the same agent share a digest; any change to those inputs is a new one, with no number to bump. Release.revision carries the deploy itself, configured or read from the conventional deploy variables. The instrumentation stamps agent.version and agent.revision on the generation's root span, and every trace gets service.version. The dashboard already versioned agents (AgentVersion, cut on edit) but nothing referenced a version: not a trace, a run, nor an evaluation run, so "which release produced this" was unanswerable. Agent#record_release! cuts a version carrying the digest and revision, idempotent per digest; AgentRelease does it for every record mirroring a host class; two rake tasks (agents:release, agents:versions) make it a deploy step. Traces are pinned at ingest to the version whose digest their root span names (a dashboard run's trace takes the latest); runs and evaluation runs take the version current when they start. One attribution fix this depends on: the registrar matched a trace to an agent on service, class and action — all three — so a trace from a class the host mirrors into the dashboard never matched its record and registered an observed per-action twin beside it (support-hub's agent count went 6 to 7 after two code-path asks). A non-observed record naming the class now wins for any action of it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Reviewed this alongside #452 and #454 as candidates for the next cut from
Both pass when and it passes once an extra query is added to that assertion's message, so the extra rows arrive on a timing boundary rather than in a fixed order. Nothing persists in What the two telemetry tests in
A teardown that reverses those — |
|
Follow-up with CI evidence from the 1.6.2 preview (#455), which carries this branch merged with #452, #454 and #398:
The lock errors are the same cause seen from the other side: on that stack the reporter's delivery thread gets its own connection rather than the test's pinned one, so instead of writing stray rows into another test's transaction it contends with it. Shutting the reporter down and removing the instrumentation in |
…t::Base The two real-generation tests installed the telemetry instrumentation on ActiveAgent::Base, where it stayed for the rest of the process. Any later test that enabled telemetry then traced its generations, the reporter's thread stored them, and the registrar registered observed agents that leaked into unrelated tests — DashboardEngineApiTest listed two agents where it created one, depending on the seed. The instrumentation now goes on the test's own agent class, and the test flushes and clears what it stored before the next one runs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary
Agents get releases, and every trace, run and evaluation run says which one it ran under — so "6/8" is a statement about a specific release of an agent, a trace can be filtered by the deploy that produced it, and a regression is attributable to the instruction or tool change that caused it. Cut on deploy by a rake task; shown in the dashboard's existing Versions tab.
Framework (
activeagent)ActiveAgent::Release, included inBase:release_manifestis everything about the class that shapes a generation — provider and model, generation options minus credentials (keys matchingkey|token|secret|password|credentialare dropped at any depth), the public actions, every prompt template under the agent's view prefixes (path → content digest), and the tools and delegations the class declares.release_digestis the first 12 hex of its SHA-256: two deploys of the same agent share a digest, any change to those inputs is a new one, nobody bumps a number. Memoized per class; a dev reload replaces the class, so it recomputes.reset_release!for hosts that edit templates at runtime.ActiveAgent::Release.revision— the deploy itself (a git SHA or release label). Set from an initializer (value or proc), else read fromSERVICE_VERSION,GIT_SHA,KAMAL_VERSION,SOURCE_VERSION,HEROKU_SLUG_COMMIT.agent.version(the digest) andagent.revision; every root span carriesservice.version(Telemetry.configuration.service_version, defaulting to the release revision).Dashboard (
actionagent)Agent#record_release!(digest:, manifest:, revision:, released_by:)cuts anAgentVersioncarryingrelease_digestandrevision, with the manifest underconfiguration_snapshot["release"]so the Versions tab diffs releases like edits. Idempotent per digest — a redeploy of an unchanged agent is not a new version — so it belongs in the deploy.AgentVersion#release?,.releases,Agent#latest_release.ActionAgent::AgentRelease.call(revision:)does that for every record naming a host class (agent_class_name— the host's sync owns the mirror; this reads it back), skipping and reporting a class that no longer resolves.rake action_agent:agents:release[REVISION](alsoREVISION=/RELEASED_BY=env) andrake action_agent:agents:versions.TelemetryTrace#attach_agent_version!at ingest — the trace'sagent.versionmeets the version with that digest; a dashboard run's trace (no digest) takes the agent's latest.AgentRunandEvaluationRuntake the agent's latest version when created.agent_version_idon all three,belongs_to :agent_version.AgentRegistrarmatched a trace to an agent only on service + class + action — all three — so a trace from a class the host mirrors (class name set, no service/action) never matched and registered an observed per-action twin beside the synced record. Seen on support-hub today: the agent count went 6 → 7 after two code-pathTicketAgentasks. A non-observed record naming the class now wins for any action of it; observed records are still matched on all three.add_agent_releases(guarded column by column; the install generator emits it for any install lacking it); dummy app migration + schema. Version JSON gainsrelease,release_digest,revision.Not in this PR
change_summary(Release 1a2b3c4d5e6f · <sha>: templates, tools), so releases show without a bundle change. A version filter on Traces and a release chip on trace/run/eval views would use the new fields — follow-up.AI::DashboardAgentSynccarries the hub's delegations as a flat roster on purpose); the release reads what the sync set.Tests
test/release_test.rb— manifest contents and exclusions, digest shape/stability/uniqueness, a template edit is a new digest, revision resolution,service_versionfallback. 5 runs.actionagent/test/agent_release_test.rb— one version per digest with redeploy no-op,AgentReleasecut/unchanged/skipped, ingest pinning by digest / latest / unknown, runs and evaluation runs pinned, a real mock generation attributed to the mirror (not a twin) and stamped end to end. 7 runs.telemetry_trace,telemetry_correlation,agent_tool_roster,agent_execution_service, evaluation runner/run suites.bin/lintclean.Deploying it (host side, for reference)
🤖 Generated with Claude Code