Pipelines and transactions
Knowledge changes in batches: a single piece of work usually creates and updates several related entities at once. Kvendra groups those changes into a transaction (TXN) so they land together or not at all, and records every change in a typed, server-side changelog.
The transaction (TXN) flow
Changes are grouped into a transaction (TXN). The flow: create the TXN; for each phase, create entities carrying the txn id so the server forces them to ‘draft’ status; on success, activate the TXN so the drafts move to their terminal status; on failure, cancel the TXN so the drafts become ‘cancelled’. Before starting, you check for an interrupted TXN to avoid duplicates.
Naming and identifiers
Ids are stable and human-readable. PRJ-<PROJECT>, CMP-<PROJECT>-<COMP> and REL-<PROJECT>[-<COMP>]-<X.Y.Z> accept an explicit id. Every other type gets an auto id of the form <TYPE>-<PROJECT>[-<COMP>]-<6HEX>. Transaction ids look like TXN-<PROJECT>-<YYYYMMDD>-<NNN>. STD entities follow STD-<PROJECT>-<COMP?>-<TOPIC>.
Validation
The engine validates entity ids against the naming rules, validates relation targets, and enforces transaction state. Typical conflicts: a duplicate TXN (resolved by checking for an interrupted TXN first), or operating on an already-cancelled or already-completed TXN (re-read the state).
The lifecycle, step by step
1. txn_create -> open a transaction, scoped to a project (and optionally a component)
2. entity_create -> create entities carrying the txn id; the server forces them to "draft"
entity_update -> further edits stay inside the same transaction
3a. txn_activate -> success: every draft moves to its terminal status
3b. txn_cancel -> failure: every draft becomes "cancelled"
Drafts are invisible to normal reads until the transaction activates, so a
half-finished pipeline never pollutes the knowledge base. The terminal
status depends on the type: most land on active, an issue on
open, a decision on accepted, a run or an
environment on recorded.
Why a transaction at all
A single piece of work — planning a feature, fixing a bug, cutting a
release — rarely touches one entity. It creates a requirement, drafts the
tests, updates the component, links them together. The transaction makes
that an atomic, multi-entity change-set: every draft in
the transaction commits on txn_activate, or none of them do
on txn_cancel. There is no intermediate state where half the
entities are real and half are not.
That is also where the resilience comes from. If a pipeline fails partway
through — an agent stops, a step errors — the work so far is sitting in
draft, quarantined from every normal read. Nothing leaks into
the knowledge base until the transaction is deliberately activated, so a
crashed pipeline leaves the KB exactly as it was, not subtly corrupted.
A walkthrough: from idea to release
The lifecycle is easiest to see on a real arc of work. Suppose a developer asks their agent to plan a new export feature for the knowledge base.
- Plan. The developer says "let's plan a clean JSON
export". The agent reaches for a planning skill, which opens a
transaction and drafts a ROADMAP
(a
ROADentity) describing the goal and the slices it breaks into. Activating that transaction promotes the roadmap fromdrafttoactive: now it is real, and the rest of the work has something to hang off. - Specify. As the first slice starts, another transaction
drafts a requirement — a REQ entity — capturing exactly
what "clean export" means, linked back to the roadmap with a typed
part_ofrelation. It landsactiveon activation. - Build, and hit a snag. Mid-implementation the agent finds
that nested entities double-encode. It opens a transaction and drafts an
ISSUE (which lands in status
open), related to the REQ itaffects. The fix and the issue travel in the same change-set, so the record of the problem and its resolution stay together. - Prove it. Before calling the slice done, a tester skill
records one or more TEST entities — preconditions,
steps, expected output, evidence — linked to the REQ they
verify. Now the requirement is not just written down, it is demonstrably met. - Release. Finally a release skill cuts a REL entity that groups the slice's changes. Because every REQ, ISSUE and TEST is connected in the graph and recorded in history, the release ships with an automatic changelog — assembled from what actually changed, not hand-written after the fact.
Two properties make this trustworthy. Every multi-entity step is atomic: the whole step activates together or, if anything goes wrong, the transaction is cancelled and none of its drafts ever existed for a normal reader. And the combination of the typed relation graph and the server-side history leaves a complete trace of how the roadmap became a release — which requirement drove which change, which issue it fixed, which test proved it — without anyone having to remember to write it down.
Concurrent writers: optimistic locking
When two writers touch the same entity at once, Kvendra does not silently
take the last write. Each update carries the expected_version
it was based on — a compare-and-set check. If the entity has moved on
since, the write is rejected as a conflict, and the response hands back the
intervening changes so the caller can reconcile against what actually
happened rather than clobber it. Optimistic, because writes are not
blocked up front; they are validated at commit. The effect is that
concurrent work — say, the owner editing the same project in another
session — cannot be lost to a blind overwrite.
The typed changelog
Activation is also what populates the audit trail. Every create and update
is recorded by the engine itself — not by the caller — as a versioned
entity_history entry carrying the author, the trigger (often
the transaction id), and a change summary of who changed what, when, and
why. Because history is written server-side, it cannot be skipped,
back-dated or rewritten by a client, which is exactly what makes the audit
trail trustworthy: the changelog is a consequence of committing, not a
courtesy the caller has to remember.
This is the other half of the entity model. The typed relations tell you how things connect; the history tells you how they got that way. Together they let you trace any entity both across the graph and back through time.
Avoiding duplicates
Before starting, a pipeline checks for an interrupted transaction so it does not open a duplicate. Operating on a transaction that is already cancelled or already completed is reported as a conflict, prompting the caller to re-read the current state rather than guess.