vivac 0.3.0

Provenance tree for work: every node knows which node it was born from
vivac-0.3.0 is not a library.

vivac

A tree where every node knows which node it was born from. It exists to answer "why are we here?" months later, when nobody remembers any more.

$ vivac why 11

  Why we are here  ->  t11
  ------------------------------------------------------------------

  g1    vivac 0.1 publishable
        A provenance system for work that can answer "why are we
        here" months later.
        (7 open / 4 closed below)
        |
        v
  t8    Port to Rust in the public repo
        When the format stops moving, not before.
        (3 open below)
        |
        v
  t11   Redaction guard on write
        Security pillar. Goes BEFORE any cloud mode.

        ^^^ you are here

  In parallel, still open (2):
      t9     TUI for the maintainer
      t10    Migrate from JSON to SQLite

  t8 does not close until these close (1):
      t11    Redaction guard on write

The problem

When you develop with an agentic AI, work spawns more work. Three hops in, you have lost the thread of what you originally set out to do.

It is not a memory problem: usually everything is written down. It is a provenance problem. What is written does not say what it was born from, and without that edge there is no way to reconstruct why you are where you are.

Measured on a real compiler: the path between the goal and the day's work was six levels deep, spread across a chronologically ordered 8,853-line tracker, 52 planning documents and 21 issues. The structure was temporal, which is exactly the opposite of provenance.

Logbooks, ADRs and issues all store the node. None of them stores the edge. That is how you can have everything written down and still not be able to say where something came from.

How it is used

There are two audiences, and the tool splits in two because of them.

The agent writes. Capture hangs off the seams of the work: you open a node when you start, you close it when you finish. The provenance edge is created on its own, with nobody having to remember to declare it.

vivac push "Fix the cache adapter" --why "the session bug needs it"
vivac push "No test for expiry" --why "no way to reproduce the bug" --blocks
vivac pop "reproduced: expires at 300s, not 3600"
vivac pop "adapter fixed"

The maintainer reads.

vivac brief         where you are, what governs this point, what NOT to touch
vivac why 11        the path from the root, narrated
vivac tree          the tree, with false closes marked
vivac open          the open fronts, each with its lineage
vivac stack         the focus stack
vivac parked        DO NOT TOUCH NOW
vivac triage        what can be pruned, and with which command
vivac reconcile     files that changed with nothing in the tree claiming them

And there are safe stops. A vivac is the bivouac partway up a climb: a coherent state, with the stack frozen and the identity of the code at that moment. push, pop and park leave one without anybody asking.

vivac save "before touching the adapter" --next "extract the validator"
vivac restore v14   rebuilds the stack and says what changed since

restore never touches the working tree. Mixing context navigation with tree manipulation gives you a branch manager worse than git.

Everything the agent needs to do can be done with no interactive interface, and every read command accepts --json.

The two edges

It is the distinction that holds the model up, and it came out of seeding two real trees and putting them side by side:

Question it answers When it is created
born from where did this come from? on its own, at every push
--blocks does this stop its parent from closing? explicitly

A closed batch of issues with an open finding underneath is correct: the batch finished and the finding is another thing. An audit marked DONE with its findings open is a false marker — one of those took 26 days to be spotted. Same shape, opposite verdict.

That is why vivac done refuses to close with open conditions and lists what is missing. It is the only rule in the model that rejects an operation, and it earns that privilege because the case it prevents is measured.

$ vivac done 8

  t8 CANNOT close: 1 open closure condition(s)

      t11    Redaction guard on write

  A run closes with its findings, not with its report.
  Closing it anyway leaves a trace:  vivac done 8 --force

What it never stores

A provenance tree is a map of where a system is weak and not yet fixed. That forces a few things, and they are not negotiable:

  • No keys and no secrets. There is a redaction guard at write time. In doubt it refuses and says why; it never stores in silence.
  • No personal data. No email, no name, no home path. The actor on every event is an opaque identifier.
  • No file contents. Only paths, references and prose about what was decided. It bounds the blast radius of a leak to what was being worked on, never to what the code is.
  • No telemetry. The binary does not phone home.

These rules come from the pillars, which govern by definition: security vetoes, performance budgets, DX judges.

Status

Tier 0 complete. The tree, the two edges, the closure rule, the redaction guard, the brief with its token budget, the session hooks, the vivacs and the Anchor with its Git and Null implementations. 73 tests, of which 11 are the brief specification's contract executed against the real binary.

reconcile is the first of Tier 1. It answers the one question that keeps the tree honest -- what changed since the tree last looked, and which of it does no node claim? -- by diffing the anchor's history against the governs globs the nodes declare. It reports and never writes: it can say nobody claims a file, and it cannot say which thread that file belongs to.

The brief is deterministic by contract: same log, same --now, same bytes. The spine — the path from the root to the focus — is never truncated: if it does not fit the budget it comes out anyway, and the warning says that what is left over is tree, not render.

Measured on this machine, excluding process startup:

nodes push brief tree
100 ~5 ms ~5 ms ~5 ms
1,000 ~11 ms ~10 ms ~13 ms
10,000 ~54 ms ~63 ms ~95 ms

The write budget is p99 < 5 ms and the read budget < 50 ms over 10,000 nodes. In the low hundreds of nodes it holds, and from there up it degrades linearly with the size of the log, which is read whole on every call. That is where SQLite comes in, and now it has a number instead of a hunch.

Not there yet: TUI, search, cascading invalidation, team mode.

0.3.0 does not read a log written by 0.1.x or 0.2.x. The tool was written in Spanish and those releases stored the event fields under Spanish names, which 0.2.x read through aliases. 0.3.0 speaks one language, so it reports those lines as unreadable rather than guessing. If you have such a log, 0.2.1 still reads it.

Hooks

vivac hooks     prints what to paste into .claude/settings.json

SessionStart injects the brief into the agent's context; Stop leaves an automatic stop. Stop runs on every turn, not at session close — there is no end-of-session event — so the stop is only saved if the tree changed since the previous one: a stop that repeats identically is not a stop, it is a log. Both stay quiet and exit 0 where there is no .vivac/, so they can be left in the global configuration without getting in the way of other projects.

Install

cargo install vivac
vivac init

From source, cargo install --path . inside the repo.

No daemon, no server and no network. The store is .vivac/, two files.

Licence

MIT OR Apache-2.0, at the option of whoever uses it. The text of each is in LICENSE-MIT and LICENSE-APACHE.