Concepts
Five nouns and one process. Everything else in vincent is a consequence of how these fit together.
The daemon
A background process that owns all state and all execution: the SQLite database, the git worktrees, and the agent CLI subprocesses. It listens on loopback only, with a bearer token read from a file in your data directory.
Every client — the TUI, the vincent subcommands, curl — is a thin consumer
of that API. This is not an implementation detail; it is the property the whole
design is built on:
- Killing every client changes nothing about running work. Close the TUI, reboot your terminal, log out of the session: tasks keep running.
- There is one writer. One process opens the database, so writes serialize and no client can corrupt state by racing another.
- Recovery is the daemon’s job. Every transition is persisted before it is acted on, so a crash mid-step is recoverable: on restart, interrupted step runs are finalized, verified orphan processes are killed, and the step re-runs as an attempt that does not consume a retry.
Start it explicitly with vincent daemon start, or let the TUI start one. Make
it survive reboots with vincent service install.
A project
A registered local git repository. Registering one records its path, a display name, the default branch new tasks branch from, an optional default workflow, and an optional per-project concurrency cap.
vincent project add /path/to/repo --name api --default-branch develop
No task ever works in your checkout. Vincent reads the repository to create
worktrees, and changes one thing there: when a task’s base branch is merely
behind its remote, it fast-forwards that branch to the fetched commit — with its
working tree, if it is checked out and clean
(fetch_base_branch, on by
default). A checkout with any change in it, and your stash, are never touched.
A workflow
An ordered list of steps, written as YAML. Workflows live in three scopes, and a name in a narrower scope shadows the same name in a wider one:
| Scope | Location |
|---|---|
| Project | .vincent/workflows/*.yaml inside the repository |
| Global | {config_dir}/workflows/*.yaml |
| Built-in | adhoc, create-workflow, update-workflows, create-trigger and update-triggers — always available |
The daemon watches both directories and reloads on save. A file that fails to parse is reported invalid; the previously loaded version keeps running.
There are nine step types. Three perform work or wait for a person:
agent— runs an agent CLI headlessly in the worktree, with a prompt.command— runs a shell command in the worktree.manual— stops and waits for a person (a gate).
Six compose or control those steps:
parallel— runs sub-steps concurrently in the task’s worktree.fan_out— creates child tasks with isolated worktrees, then merges them.condition— ends a sequence successfully when its expression is true.loop— repeats a body by count or once per item.break— exits the surrounding loop successfully.include— expands another workflow into this one when the task is created.
check is a field on agent and command steps, not another type. It is the
mechanism that stops an agent from grading its own homework: the step succeeds
only if the check command also exits 0.
See Writing workflows.
A task
One run of one workflow against one project. Creating a task snapshots the workflow, so editing the YAML afterwards never changes a task already in flight — the snapshot is that task’s execution truth.
A task moves through a small state machine:
queued → running → done
▲ │ ├──────────→ awaiting_gate (a manual step)
│ │ ├──────────→ awaiting_input (the agent asked a question)
│ │ └──────────→ blocked (a step failed, retries exhausted)
└── paused / retry / approve / skip …
The full table of states, the human actions valid in each, and what each action does is in Task lifecycle. Two properties are worth knowing up front:
- Nothing is silently abandoned. A step that exhausts its retries blocks the task and waits for a human. It does not skip ahead and it does not fail the task.
- Every task representation carries
available_actions— the actions valid right now, computed by the daemon from one state machine. The TUI’s action bar and the CLI both render that list rather than re-deriving it, so “what may happen next” has exactly one definition.
A worktree
Every task gets its own git worktree on its own branch, named
vincent/{id}-{slug} by default, based on the task’s base branch. You can set a
different convention per project or globally, or name one task’s branch outright —
see Configuration. That is where the agent
runs and where commands execute.
What this does buy you: two tasks in the same repository never collide, no task ever works in your own checkout, and the diff of a task is a real git diff you can read before anything is pushed.
What it does not buy you: privilege isolation. A full-auto agent runs as you, with your credentials and your network. See the Security model.
Archiving a task removes its worktree and keeps the record. It keeps the branch
too, unless that branch has no commits past the base it was cut from — a
workflow that never writes to the repository leaves nothing on its branch, and
archiving deletes it rather than leaving an empty ref behind. A branch
carrying any commit is never deleted. Turn the cleanup off with
delete_empty_branch_on_archive: false.
How a step actually runs
Top-level steps advance in order, but a structural step can introduce concurrency, branching, repetition, or composition. An agent, command, or manual step follows this lifecycle:
- Render templates. Prompts,
run,checkandinstructionsare Gotext/template, rendered against the task, project, worktree, and the results of completed steps. Rendering usesmissingkey=error, so a typo fails the step before any process starts rather than writing a silent hole into a prompt. - Run the body. Start an agent CLI subprocess, execute a shell command, or wait for a human. Structural steps coordinate their nested steps or child tasks using the same persisted state model.
- Evaluate success. An agent step succeeds when the process exits 0 and
its event stream produced a terminal result and any declared
checkexits- A command step succeeds when it exits 0 and its check does. A manual step succeeds when someone approves it.
- On failure, retry up to
max_retries(default 1, i.e. two attempts). For agent steps the previous failure is appended to the retried prompt as a structured block, so the agent is told exactly what went wrong. - Advance, persist, repeat. When the last step succeeds the task is
done.
Every agent step is a fresh session. No conversation is resumed between
steps or attempts. State flows forward through the worktree (files and commits)
and through {{.Steps}} in templates. That is what keeps steps individually
re-runnable and keeps context windows small.
The scheduler
Admission from queued to running happens in exactly one place, in a single
goroutine, which is what makes both concurrency caps safe:
max_parallel_tasks— a global cap (default 3).- A per-project cap, set on the project.
Order is by priority, then creation time. Only running and awaiting_input
consume a slot — a task waiting at a gate, blocked, or paused does not, because
a human is the bottleneck there and holding a slot would starve everything else.
awaiting_input does keep its slot: the agent process is alive mid-step,
idle on its stdin, and killing it would lose the very session the answer belongs
to.
Agent adapters
Three adapters ship: claude, codex, cursor. Each runs the CLI you already
installed and authenticated; vincent stores no credentials.
Adapters differ in what they can do, and the differences are documented, never
faked. A capability an adapter lacks is stated and ignored at run time rather
than emulated: codex ships no model catalog and no mid-run input; cursor has no
effort concept at all (it lives in the model id) and cannot honor restricted
on Windows, where the task is refused at creation rather than quietly running
the step full-auto.
See Agent CLIs.
Events and transcripts
Two streams come out of a run, and they have different durability guarantees on purpose:
- State events — durable.
task.created,task.state_changed,task.step_advanced,project.*,workflow.registry_changed, and friends are written to the database with a monotonic id and served over SSE, so a client that reconnects withLast-Event-IDmisses nothing. - Live output — ephemeral. Agent output, tool calls, reasoning and usage chunks stream on the per-task SSE stream and are not stored in the events table. They are durable in the transcript file instead: catch-up means fetching the transcript, then following live.
Every attempt of every step writes a JSONL transcript at
{data_dir}/transcripts/{task_id}/{step_index}-{attempt}.jsonl, containing the
agent’s own stream verbatim plus vincent’s namespaced vincent.* annotations.
Because transcripts are normalized on read, improving a parser improves
transcripts already on disk.
Where to go next
- Quickstart — do it.
- Features — see the complete product surface.
- Writing workflows — the part you will spend time in.
- Using the TUI — operate and inspect active workloads.
- Task lifecycle — states and actions in full.