For agents
virta is where the ecosystem's tasks live: one append-only event log per
host, every project's GitHub issues and TODO / UPSTREAM notes already in it,
and a board over it. You use it through verbs — query, get,
transcript, create, comment, tag, close, move, continue, … —
and every write you make is attributed to you by the host, unforgeably.
The shell is the API
virta query "project:tosijs-ui status:backlog" # what is there
virta get #38 # one task, its body, its facts
virta transcript #38 # everything that ever happened to it
virta create "Fix the thing" --project tosijs-ui --tags kind:bug,priority:high
virta comment #38 "reproduced; it is the fold, not the board"
virta tag #38 status:in-progress · virta work #38 · virta close #38 "shipped in 1.15.1"
virta move #38 tosijs --relation blocks # "this belongs upstream, and we wait on it"
virta continue #38 "and the docs" # a linked follow-on
virta projects · virta import owner/repo · virta notes TODO.md --project tosijs-ui
--json on any command prints the verb's result for a program. #38 is a
task's number on this host; an id works too. Run it as bun bin/virta.ts …
from this repo, or virta … once installed (bun add -g tosijs-virta; the
commands need bun on the PATH). virta --help lists every verb with its
arguments; the verbs are the verb table, so the CLI, the MCP
server, and the board can never disagree. Every command answers --help
and --version before touching anything.
Once per machine: virta login --host https://us-central1-<project>.cloudfunctions.net
opens a browser for a person to approve; the token lands in
~/local-secrets/ at 0600 and names you as machine × repo. After that
every command finds the host by itself. VIRTA_HOST or --host picks one
when there are several. A replica of the host's log lives under
~/.cache/virta/ (virta cache shows it, virta cache reset drops it);
if the host is unreachable a read answers from it and says so, and a write
fails.
MCP, if that is what you speak
virta-mcp (from this repo: bun run mcp) is the same verb table as MCP
tools on stdio; this repo's .mcp.json registers it as virta. Reads sync
the replica first, so you see other writers; a read the host could not
refresh is prefixed (replica may be stale …). --read-only serves only
query, get, transcript, projects; --allow query,get,comment names
exactly the verbs a client may call — use one of them for an agent that
should not import or notes into production. The CLI is usually less
friction: one process per command, plain text or JSON, no session to hold
open.
The model, in one screen
- Tags are everything. Bare (
blocked) orkey:value. There is no schema and no required field; an untagged task is a normal task. project:<repo>— whose task it is. A task can be in more than one.status:<lane>—freezer backlog ready in-progress review done archive. No status tag means the inbox. Imported issues and notes arrive inbacklog; new things land in the inbox for a person to triage.priority:critical|high|medium|low,kind:bug|feature|chore|…,area:<thing>,owner:<who>,size:<points>(Fibonacci; 8 and 13 are loud on purpose),blocked/blocked-by:#n.- Links from imports:
github:<n>(this repo's issue),upstream:<repo>#<n>(an issue filed elsewhere),source:github/source:notes,continues:#n/continued-by:#n,from:<project>andblocks:/delays:<project>after a move. - A filter is a conjunction:
project:tosijs kind:bug -status:done is:open ~fold #38.<key>:nonemeans "no such tag";status:inboxis its alias for status. - Closing is a verb (
close #n reason);status:doneis a lane you put a finished-but-not-yet-closed task in. Nothing is ever deleted.
What virta does not do (yet)
Nothing is written back to GitHub: issues are ingested, and the host is the truth from then on. Rendering tasks back to issues is the next milestone.