AI Agent Versioning and Audit Trail
Which version of the agent did that? A concrete schema for AI agent versioning and audit trails: version rows on apply, a workspace SHA on every tool call.
AI agent versioning is the practice of giving every change to an agent's prompts, skills, and
permissions an identifier, and stamping that identifier onto every action the agent takes, so any
past output can be traced back to the exact configuration that produced it. An audit trail is the
resulting record. Vocion, an open-source agent workforce platform, implements this by authoring
agents as YAML and Markdown in a git repository: each workspace:apply writes a workspace_version
row holding the git SHA, timestamp, file list, per-resource counts, and who applied it, and every
tool_call row is stamped with the workspace_sha in force at the time. A dirty apply is marked
<git sha>-dirty-<hash> rather than being silently recorded as clean.
This is for the engineer who has been asked, by security, legal, or a customer, "which version of the agent produced this output, and can you prove it?" Most guides on this topic list what to log. This one shows the schema and the query.
Why "which version of the agent did this?" is hard to answer
The usual failure mode is a prompt edited through a dashboard text box, with no diff and no link from the output back to the config that generated it. Six months later a customer disputes an email an agent sent, or an auditor asks for the exact instructions an agent was operating under on a given date, and the honest answer is "we don't know, we've edited that prompt a dozen times since." Versioning without a durable record is not versioning, it's a changelog nobody reads.
A second version of the same problem shows up once more than one agent is involved. An agent's skills, sources, and trust rules can each change independently, sometimes edited by different people on different days. Even if any single edit is recorded somewhere, the question an auditor actually asks is a composite one: "what was the entire configuration this agent was running under at the moment it sent that email" — the prompt, the skills it had mounted, the sources it could read, and the trust thresholds that let it act without asking. A log of individual edits cannot answer that composite question; only a single stamp applied to the whole configuration at once can.
What actually needs a version
It is not just the system prompt. A workspace's behavior at any moment is the sum of the prompt,
the skills mounted on the agent, the sources it can reach, the trust thresholds that decide what it
may do without a human, and the base pack it inherits from. Vocion's authored entities live in
docs/entities/: workspace manifest, agent, team, skill, playbook, mission, workflow, automation,
object type, source, trust rules, learning step, eval dataset, workspace page. Change any one of
these and the workspace's effective behavior has changed, whether or not the top-level prompt file
did.
Because a workspace can extend a base pack (extends: core@<version> in workspace.yaml) and then
patch individual defaults, the same composite-configuration problem applies to inheritance too. A
workspace overrides a base agent or object by dropping a same-slug file marked extends: core;
scalar fields like model or systemPromptFile replace the base value outright, array fields like
skills or connectorSources replace by default unless the workspace opts into $append/$remove
semantics, and a workspace SKILL.md with the same slug as a base one replaces it wholesale, no
merge (vocion-core/docs/workspace.md, "Override a base default" section). None of
that resolution logic is visible from the prompt file alone — which is exactly why the version has
to be stamped on the resolved configuration, not on any one source file.
A workspace also pins a base pack version with extends: core@<version> in workspace.yaml
(vocion-core/docs/entities/base-pack.md, line 24: "The version a workspace pins
with extends: core@<version>, and the value folded into workspace_sha"). Publishing a newer
pack never silently reaches a pinned workspace — a workspace moves to a new pack version only by
bumping its own pin.
What does a version row contain
Running npm run workspace:apply -- <path> --project <slug> writes a database row: git SHA,
applied_at, the file list, per-resource counts, and applied_by, which defaults to $USER and
is overridable with --applied-by (vocion-core/docs/workspace.md, the CLI table:
"npm run workspace:apply -- <path> --project <id|slug> | Writes changes to DB. Records a
workspace_version row with the git SHA + diff summary" and the flags list including
--applied-by <name> — who triggered this apply (default: $USER)). In the object model this is
the Workspace version row: produced by applyWorkspace, validated against
workspaceVersionSchema, stored in table workspace_version, visible at /dashboard/workspace
(vocion-core/docs/object-model.md).
How does an output get linked to a version
Every tool_call row carries the workspace_sha that was active when the call ran. The
docs/workspace.md "Audit trail" section gives the query as written:
SELECT tc.agent_slug, tc.tool, tc.input, tc.output, tc.workspace_sha, tc.created_at
FROM tool_call tc
WHERE tc.id = <row_id>;
-- then `git show <workspace_sha>` in the workspace's repo to see the exact
-- prompts + skills active at the moment the call ran.
Run that query against a row you already have, take the workspace_sha it returns, and git show
it in the workspace's own repository. What comes back is the actual prompt files, skill
definitions, and playbooks that were mounted when the call executed — not a description of them, the
files themselves. In the object model, this is the Tool call row: recorded via
withToolCallRecord at the tool registry across all three harness targets, plus skill_read
rows recorded from the stream/relay when a mounted SKILL.md is read, validated against
toolCallSchema, stored in table tool_call, visible at /dashboard/activity?kind=tool, filterable
by agent or tool (vocion-core/docs/object-model.md).
What if someone applied with uncommitted changes
workspace:apply does not require a clean working tree, and it does not pretend one existed when
it didn't. The resulting workspace_sha takes one of four shapes, documented in
docs/workspace.md:
| Shape | Meaning |
|---|---|
<git sha> | Clean apply — no uncommitted changes. |
<git sha>-dirty-<hash> | Applied with uncommitted changes; the hash covers every loaded file. |
local-<hash> | Not in a git repo (or git could not be read) — content hash only. |
…+core@<version> | A base pack was pinned; the suffix is appended to any of the above. |
The -dirty- marker is the point, not a defect. An apply run against uncommitted files still gets
a distinct, reproducible hash, so a later audit can tell "this output came from a clean, committed
state" apart from "this output came from someone's local edits that were never pushed" — instead of
both looking identical.
Do individual entities carry versions too
Yes, separately from the workspace-level SHA. Workflow and mission both carry a version field —
a positive integer, default 1, with the guidance to bump it "when the steps change materially"
(workflow) or "when the charter changes materially" (mission) — per
vocion-core/docs/entities/workflow.md and mission.md. Skill carries the same
shape: version: 1 in vocion-core/docs/entities/skill.md. Base packs are versioned
with full semver (version: 2.0.0 in base-pack.md) and a workspace pins one with
extends: core@<version>. Two different mechanisms answer two different questions: the entity
version int tells you this workflow's authors consider it materially changed; the workspace SHA
tells you exactly which bytes were live at the moment a specific tool call happened. Use the entity
version when you want to signal a meaningful change to teammates reading the workflow, mission, or
skill file itself — it is a human-facing marker, bumped by whoever edits the file, not something
the platform derives automatically. Use the workspace SHA when you need to answer "what was
actually running," because it is recorded by the platform at apply time and cannot be forgotten.
How do you roll back
There is no separate rollback command, because there doesn't need to be one. The workspace is a git
repository; roll back by checking out the earlier commit and re-applying it. Run
npm run workspace:check -- <path> first — it validates every YAML and Markdown file and shows
what would change with no database writes — then npm run workspace:apply -- <path> --project <slug> to write it. Both commands take the workspace path as an argument, or read it from the
WORKSPACE_PATH environment variable, and both honor SEED_ORG_ID
(vocion-core/docs/workspace.md, "Commands" section). The operative discipline,
stated directly in docs/workspace.md's guidance on base packs, is "pin, don't float": a workspace
only moves versions when someone deliberately bumps its pin, never because someone else published
something new. The same discipline applies to a rollback — you are not un-doing an automatic
upgrade, you are re-applying a state that was always explicit.
What this does not give you
It is not per-request model-weights provenance — a workspace_sha tells you which prompts and
skills were active, not which exact model checkpoint answered. It is not an immutable ledger: the
rows in workspace_version and tool_call are ordinary Postgres rows, writable and deletable like
any other row in the database, with no cryptographic signing or write-once guarantee described
anywhere in the code. And it is not a compliance product — it produces evidence a human auditor can
read, not a certification that any particular regulation has been satisfied. If your audit
requirement is "prove to us this system is SOC 2 compliant" or "produce a tamper-proof record,"
this schema is a component of that answer, not the whole of it.
How to set this up on a fresh clone
From a checkout of vocion-core (verified against v2.47.4):
npm run workspace:check -- <path>
npm run workspace:apply -- <path> --project <slug> --applied-by ci
Then pull the workspace_sha off any row you care about and run the SELECT from
docs/workspace.md shown above, followed by git show <workspace_sha> in that workspace's repo.
That is the whole loop: apply writes the version row, every subsequent tool call inherits the SHA,
and git show turns the SHA back into the actual files.
Related
Versioning only matters once you have agents whose outputs are worth tracing — see what an open-source agent workforce platform actually needs to include. And a version is often what a reviewer checks before approving an action; see how deploying AI agents in production with human approval ties trust thresholds to a specific workspace state.
Clone vocion-core, run workspace:apply, and pull the workspace_sha off your first tool_call
row to see the trail for yourself.