@mmainguy/vrek (0.19.0)

Published 2026-09-27 17:46:09 +00:00 by mmainguy

Installation

@mmainguy:registry=
npm install @mmainguy/vrek@0.19.0
"@mmainguy/vrek": "0.19.0"

About this package

Vrek

An API-first system of record for humans and agents doing work together — planning it, deciding it, building it and evidencing it. Software is the first domain, not the only one: requirements gathering and analysis run the same loop.

Jira and Confluence are two systems of record-keeping bolted together. Vrek is one graph: the work, the knowledge, the decisions, the evidence and the principals — human or agent — who touched them are the same objects, reachable through one API, with provenance and freshness as properties of every node.

The reasoning behind it is in RESEARCH_BRIEF.md. The shape of the implementation is in docs/ARCHITECTURE.md.

Status

v0.1. Vrek tracks its own development: .vrek/ in this repository is the live workspace, and every claim below is a verified issue in it.

630 events, 41 issues shipped with evidence, 22 ready to pick up, 7 questions waiting on a human. 161 tests; 64% of statements covered, which understates by about 850 lines because the CLI is tested as a subprocess and V8 cannot see it.

Working: the event log with causal parents and content hashes, the graph, the ready set with footprints, blocking questions, typed verification, principles with recorded violations, documents as views over facts with exact drift, and one core behind five surfaces.

Not built: real identity beyond static bearer tokens, Postgres for a shared server, three-way merge on import, erasure mechanisms, and any UI. The git projection is deferred to 2027.

Known limitations, since a guarantee should say where it stops holding: importLog is not atomic; attrs cannot be written or queried from any surface, which is a mechanism one principle depends on; docDrift is blind to evidence and to path footprints; ready() does a full scan and is the first latency cliff. All are recorded — vrek ready lists them with the rest.

Migrating a workspace named before the rename

The database does not change. Nothing in the schema, the event types or the event payloads carries the product name — only the directory does. So a workspace built under the old name migrates by moving a directory:

cd ~/your-project
mv .flowstate .vrek                 # or: git mv .flowstate .vrek
vrek fsck                           # the chain verifies unchanged

If only the committed log survived and the database is gone, rebuild it:

vrek init
vrek import .vrek/log.ndjson

Then repoint the agent config. .mcp.json needs three things changed: the command becomes vrek-mcp, every FLOWSTATE_* environment variable becomes VREK_*, and any path ending /.flowstate becomes /.vrek. Running vrek register --force rewrites it for you.

Two things change that a shell will not warn you about, because they are protocol-visible. Every MCP tool is now vrek_* rather than flowstate_*, and document resources are vrek://doc/<id>. An agent holding an old connection keeps calling the old names until it reconnects.

The event log is deliberately not rewritten. Every event carries a content hash covering its payload, so editing historical text to say vrek would break the chain on every event and vrek fsck would report the whole log as tampered. The log keeps saying flowstate wherever it did, because that is what happened.

Install

Nothing is required to run it. Node 24 executes the TypeScript directly and the runtime depends on nothing outside Node's standard library, so a fresh clone works with no npm install and no build:

node src/cli/main.ts version   # or: npm run cli -- version

That is always the current source, which is what you want while working on Vrek itself. Anything that reads the graph needs a workspace first — see below.

To get vrek, vrek-mcp and vrek-api onto your PATH instead:

npm install
npm run build        # emits dist/ — the bin entries point INTO it
npm link             # symlinks the three commands at this checkout

npm unlink -g vrek removes them.

Both steps matter and in that order: npm link publishes the bin entries from package.json, which name dist/cli/bin.js, so linking without building leaves you a command that dies on a missing file. dist/ is also a snapshot rather than a live view — after pulling or editing, npm run build again or the linked vrek runs the previous version. Running from source has neither problem.

vrek: command not found after cloning is expected. npm install links the bins of your dependencies, never of the package itself, and a link is machine-local state that does not travel with the repository — so a clone on a second machine has no vrek until you link it there too. Use node src/cli/main.ts or run the two commands above.

Start a workspace

cd ~/some-project
vrek init                            # creates .vrek/
vrek name "My Project"               # records the name in the log
vrek register --model claude-opus-5  # writes .mcp.json so an agent connects
vrek add "the first thing" --touches src/
vrek ready

vrek help prints this. register merges into an existing .mcp.json rather than replacing it, refuses to overwrite an entry without --force, and points at the checkout it was run from — so working on Vrek itself needs no rebuild between edits.

To open this repository's own workspace after a clone:

node src/cli/main.ts init                          # or `vrek init`, if linked
node src/cli/main.ts import .vrek/log.ndjson
node src/cli/main.ts report

This is the step a clone always needs and the one that is easy to miss: .vrek/workspace.db is gitignored, so a fresh checkout has the log and nothing to read it into, and every command answers no Vrek workspace found until you import. init takes no name: the name travels in the log and the import supplies it. .vrek/log.ndjson is the committed event log; .vrek/workspace.db is a projection of it and is not committed. Export after any session that changed the workspace: vrek export --out .vrek/log.ndjson. Export and import also reach MCP (vrek_export, vrek_import) and HTTP (GET /export, POST /import), so an agent can get the workspace out of the machine without a shell — commit it beside the code and it travels.

The loop

vrek ready                  # what can be picked up, with each item's footprint
vrek status <id> in_progress
vrek ask "Postgres or SQLite for the shared server?" \
    --option "postgres|a service to run on day one" \
    --option "sqlite|single binary, one writer" \
    --blocks <id> --owner mike --revisit 2026-09-19
vrek status <id> done       # a claim
vrek verify <id> "integration suite passes" \
    --result pass --evidence "npm test"   # the evidence behind it
vrek report                 # shipped / blocked on you / claimed / ready

vrek status <id> done records that somebody said it was done. Until a verification passes, vrek report and vrek unverified both show the claim as a claim. That distinction is the point of the product.

A check can be registered before it is performed — --result pending is the honest state for that — and resolved afterwards with vrek resolve <ver-id> --result pass --evidence "…". Correcting the result appends a new event naming who corrected it; the original stays in the log.

Work put off on purpose is vrek defer <id> --until DATE --because "why", not blocked — blocked asserts something is in the way, and a deferred item returns to the inbox on its wake date.

Documents are views over facts

A document is not a page of prose that rots. It is an ordered list of blocks:

vrek doc "Phase 1 scope"
vrek block doc-abc --text "Phase 1 covers EMEA only."
vrek block doc-abc --node dec-5kg0g6n            # renders current state
vrek block doc-abc --query kind=question,status=open
vrek render doc-abc
vrek ack doc-abc          # one action: you vouch for it as of now
vrek drift                # documents something has moved under

node and query blocks resolve when the document is read, so they cannot go out of date. Only what you write as text can later be wrong, which is the point: the part that rots is reduced to the part a human wrote.

Acknowledging stamps the document with the current event seq. Drift is then any event on a covered node with a higher seq — exact rather than heuristic, and it names which node moved and when. A render reports the seq it resolved at, because a query block changes a document without any event on the document.

Every block records the principal who wrote it, and every renderer attributes it. A doc block is text one principal wrote that another principal's agent reads, so authored text reaches a model as a quoted claim by a named person rather than as an instruction from the system.

The log is checkable

Every event carries the events it was appended after and a hash over its canonical form, so the hash covers the history reaching it and altering any ancestor changes every descendant.

vrek fsck      # recompute every hash, resolve every parent and actor
vrek fsck --fields  # also replay, to name payload keys this build does not read

It reports three different things and does not confuse them: a hash mismatch is tampering, a missing parent or orphaned actor is a broken reference, and unchained means an event predates hashing and cannot be checked at all. Unverifiable is not a failure; corruption is.

Surfaces

Surface Entry point For
CLI vrek humans and shell-driven agents; --json on any command
MCP over stdio vrek-mcp one trusted writer; see Choosing a transport
HTTP vrek-api services and future UIs; SSE at /events/stream
MCP over HTTP POST /mcp on that server agents with no checkout and no terminal
MCP resources vrek://doc/<id> documents as agent-readable context

All of them call the same Workspace class. A capability reachable from one and not the others is a bug, and gets filed as one.

Choosing a transport

Over stdio the actor comes from VREK_ACTOR, so it is asserted rather than authenticated: anyone who can start the process can claim any identity. That is fine for one person who is the sole writer of their own workspace, and it fails silently the moment that stops being true — a shared machine, a CI runner, or a workspace several people write to.

So stdio is for a single trusted writer, and MCP over HTTP with bearer tokens is the transport for anything else:

VREK_PORT=7373 \
VREK_TOKENS=tok-mike:mike:human,tok-agent:claude:agent \
  vrek-api

vrek register --http https://your-host:7373/mcp
export VREK_HTTP_TOKEN=tok-agent     # in the agent's environment, never in the file

MCP is then at /mcp, and a call carries Authorization: Bearer tok-agent. The .mcp.json entry holds ${VREK_HTTP_TOKEN} rather than the token, so the file can be committed; the client expands it. An unauthenticated call is refused with 401 rather than silently attributed to somebody.

The console on the same server signs in with any of the same tokens: paste one into the form and it becomes an HttpOnly session cookie, gone when the browser closes. Writes from the console are attributed to that token's handle.

Every entry needs all three parts, and the kind is exactly human or agent. The server will not start on an entry that is missing a part, misspells a kind or repeats a token, or on one whose kind disagrees with the principal its handle already names. See Identifying an agent for how that disagreement is resolved.

The server binds loopback unauthenticated by default and refuses a non-loopback bind without VREK_TOKENS. VREK_ALLOWED_ORIGINS=https://app.example.com,... lists the origins a browser may call /mcp from; a request carrying any other Origin is refused, and one carrying none (curl, an MCP client, a service) passes. vrek-api --help lists every variable the server reads.

TLS

vrek-api speaks plain HTTP and expects TLS, when there is any, to be terminated in front of it. Caddy does that in one line, including obtaining and renewing a Let's Encrypt certificate:

caddy reverse-proxy --from vrek.example.com --to 127.0.0.1:7373

nginx with certbot works the same way. Whatever the proxy is, it must pass X-Forwarded-Proto: https (Caddy does by default; nginx with proxy_set_header X-Forwarded-Proto $scheme;), which is how the server knows to mark the console's session cookie Secure.

For a private network where a proxy is one more thing to run, vrek-api can serve https itself from a certificate it is handed:

openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
  -keyout key.pem -out cert.pem -subj "/CN=vrek.internal"
VREK_TLS_CERT=cert.pem VREK_TLS_KEY=key.pem VREK_TOKENS=... vrek-api

A self-signed certificate has to be trusted by every browser that opens the console and by every agent that connects (NODE_EXTRA_CA_CERTS=cert.pem for a Node-based MCP client), or the connection is refused. vrek-api never obtains or renews a certificate: replace the files and restart. dec-23gfv9w records why.

Without a repository

Nothing in the codebase touches git. .vrek/ is a directory, not a checkout. The CLI once fell back to git config user.email for an identity, which put real email addresses into the log. It now reads a handle you choose with vrek whoami <handle>, kept in .vrek/.actor and never in the log. A hosted workspace is a data directory and a server:

VREK_DIR=/srv/vrek VREK_HOST=0.0.0.0 \
VREK_TOKENS=tok-priya:priya:human,tok-agent:claude:agent \
  vrek-api

An agent then connects to https://your-host/mcp — no clone, no Node, no .mcp.json. Its calls are attributed to whichever token it presented, so "who decided this" stays a query rather than an inference.

Instructions

What an agent is told on connect belongs to the workspace, not to a file in a repository that a non-git user does not have:

vrek instructions          # what agents are told today
vrek instructions "This workspace is the Q4 requirements phase. \
    Requirements are issues; a sign-off is a verification with the meeting \
    that produced it as evidence."

The default names no codebase. The loop — ready, ask when you would guess, claim, then evidence the claim — is the same for requirements gathering, analysis and planning as it is for software. Instructions and the workspace name are both event-sourced, so both survive an export into a fresh workspace.

Identifying an agent

VREK_ACTOR=claude VREK_ACTOR_KIND=agent \
VREK_MODEL=claude-opus-5 VREK_SESSION=$SESSION_ID \
  vrek ready

Every event records the principal, whether it was a human or an agent, and for agents the model and session. "Who did this and why" is a query, and vrek_principals answers "who else works here" from the agent side.

A principal is recorded by its first write, in the same transaction as that write. A caller that only reads (vrek ready, vrek_ready, GET /ready) is never recorded, and takes no write lock doing it. Until the first write, vrek whoami and GET /session report the caller as not recorded yet.

A principal's kind is recorded along with it, and how it connects later does not change it. A caller that states a different kind (VREK_ACTOR_KIND, a VREK_TOKENS entry, or an X-Vrek-Actor-Kind header) can still read, but its writes are refused with a message saying which side disagrees. A kind that was never stated is only a default, and the record wins. When the record is the side that is wrong, correct it:

vrek principal-kind mike human --because "a person; the console token named no kind"

This works the same through vrek_set_principal_kind and PUT /principals/:id/kind. The correction is an event, so it records who made it and why. Events written before it keep the kind they were written under.

Dependencies

Development Dependencies

ID Version
@awesome.me/webawesome ^3.12.0
@sveltejs/vite-plugin-svelte ^7.3.0
@types/node ^26.2.0
@vitest/coverage-v8 ^4.1.11
mermaid ^11.17.2
svelte ^5.57.0
svelte-check ^4.7.6
typescript ^5.9.2
vite ^8.2.2
vitest ^4.1.10
Details
npm
2026-09-27 17:46:09 +00:00
0
UNLICENSED
latest
1.3 MiB
Assets (1)
vrek-0.19.0.tgz 1.3 MiB
Versions (24) View all
0.19.0 2026-09-27
0.16.0 2026-09-24
0.13.3 2026-09-21
0.12.0 2026-09-15
0.8.0 2026-09-14