Fourteen days and 428 commits after 0.8.0, vincent 0.9.0 is out. I’m excited to share this one. It is the biggest step so far toward a first stable release, and a good part of that step is fixes and reinforcement under features that already existed, which is exactly the kind of work a feature list undersells.
For anyone new here: vincent is a local, vendor-independent control plane for coding agents. It runs Claude Code, Codex and Cursor as they ship, one task per branch and worktree, through workflows you can read, and it keeps the state, the gates and the verification on your machine. 0.7.0 was the release where it started carrying its own development, and 0.8.0 taught it to split one issue into parallel lanes.
The 0.9.0 changelog lists 23 additions, 6 changes and 11 fixes across 57 pull requests. The documentation was refreshed for the release too, including all 34 TUI screenshots, 25 of them new, so the images in this post come straight from it:

Work that starts itself
Until 0.9.0 a task started because something called vincent: a keypress in the TUI, vincent task add, or a script hitting the API. Now vincent can watch for the event itself. An event trigger is one YAML file under {config_dir}/triggers/ that watches a source (a command the daemon polls, a project’s GitHub issues or pull requests, or a signed HTTP push) and turns each new event into a task, or into a follow-up, retry or cancel of an existing one.
Here is the trigger the triggers guide uses to show the shape of one. Labeling an issue agent-please creates a task for it:
# {config_dir}/triggers/label-to-task.yaml
id: label-to-task
enabled: true
source:
type: github_issues
project: 1
match:
action: labeled
labels: agent-please
action:
type: create_task
title: '{{ .Event.Issue.Title }}'
github_issue: '{{ .Event.Issue.Number }}'
dedupe_key: 'gh:issue:{{ .Event.Issue.Number }}:label:agent-please'
limits:
max_per_hour: 5
A trigger can also be the clock. cron: or every:, with an optional time zone, covers a nightly dependency sweep or a weekday-morning report, and a schedule that came due while the machine slept fires once when the daemon is back, however many occurrences it missed. overrun: decides what happens when an event arrives while the previous run is still going: skip it, cancel the running task and fire, keep only the newest event, or queue them in turn, per ticket, pull request or branch.
The defaults are deliberately conservative, because a trigger has no keypress to stand in for your consent:
- Off twice: a file does nothing until both its own
enabled: trueandtriggers.enabledinconfig.yamlare on. - Arming seeds: the first poll after a trigger is armed records what the source already shows and fires nothing, so a backlog that existed before you switched it on never starts work.
- Propose by default: with
on_fire: proposethe task landspausedand nothing runs until you resume it, andpermission: restrictedruns every agent step of a created task in restricted mode. - Agents write, you arm: the
create-triggerandupdate-triggersbuilt-ins and thevincent-triggersskill let an agent draft triggers, butvincent trigger applyrefuses the whole proposal if any file would arm one. There is no override flag.
A new triggers view in the TUI creates, edits and enables them, shows each one’s poll health and delivery ledger, and dry-runs a trigger against a sample event or a live poll:

Work you start by hand gets the same controls. A new task, a retry or a follow-up can be created paused, can run every agent step restricted, and can carry a spend cap tighter than the global one, from the API, vincent task add or the TUI’s forms.
From the task to the merge
0.8.0 let vincent open a task’s pull request and stopped there, because opening was the only thing it wrote to GitHub. 0.9.0 lets you finish the job from the same place: merge, close, reopen, comment on and re-run the checks of a task’s pull request from the Pull Request tab, vincent github pr or the API. These actions run only when a human asks, and none of them is exposed as an MCP tool, so vincent never hands an agent a merge button. A merge targets the exact head whose checks you are looking at, and it is refused up front when the branch is behind, a check is still running, or the head moved.

Agent steps also moved into the task’s container. With container.image set, a containerized task no longer starts its agents on your machine. The image’s own agent CLI runs next to the command steps and checks, with the same transcripts, costs and controls as a host run. In 0.8.0 only the command steps and checks ran in the container while the agent process stayed on the host, so a containerized task with agent steps was a mixed run. Credentials are mounted under a vincent home inside the container, and on macOS a containerized claude step needs its token passed through environment.
And a stopped task can now be talked to. When a task is blocked, waiting at a gate, done or aborted, you can open a chat on it: a conversation with an agent working on the task’s branch, starting from the task’s context and the failure or gate it stopped at. Nothing can retry or skip the task underneath while that chat is open.
The reinforcement underneath
The rest of the release is what I mean by foundational work. Read the changelog looking for refusals and a pattern shows up.
Much of a stable release is a promise about what the tool will refuse to do.
vincent trigger apply refuses a proposal that would arm a trigger. A pull request merge is refused on any of the three conditions above. parallel and manual steps now refuse max_retries and retry_backoff instead of silently ignoring them, and update-workflows moves the value to where it applies. tui.keys lets you rebind the TUI, and refuses a key that would collide with anything before writing it. The new permanent delete on the archived boards never deletes a branch carrying commits past its base, and it is not something an agent can run. A new task fast-forwards your local base branch only when that is safe, and shows the base commit it actually started from.
The fan-out from 0.8.0 got its first round of real repairs. A fan-out whose lanes blocked was a dead end: one retry per lane, or a cancel that threw the run away. Now one retry on the parent re-admits every blocked lane beneath it. The board header could read 0/6 running while lanes held every slot, and a parent whose lanes were working looked stalled. The header now counts every slot in use, the running fan-out step shows on the timeline, and max_tree_cost_usd puts one spending ceiling on a task and every lane below it, retries and follow-ups included.

Loops got the same kind of fix: retrying a task blocked inside a loop now evaluates its guards again, instead of keeping a stale answer and running a pass the loop should have ended.
One fix reads like a joke and was not one. A claude run that succeeded could be mistaken for a usage-limit stop when its output merely wrote about usage limits, and the task was re-queued, spending money, again and again. That is gone. Usage limits in general got stricter: once vincent watches an agent’s usage window close, other tasks on that agent wait for the reset instead of each starting a process into the same wall. usage_limit_auto_continue decides whether that wait happens unattended: always, only when the CLI named the reset time, or never, so nothing spends while nobody is watching.
Backups moved from a command you run to a schedule the daemon keeps. backup.interval makes the daemon take its backup archive on a schedule and keep the newest few, and vincent task import brings a single deleted task back out of an archive, from this installation or another one. The daemon view shows the config in effect against the built-in defaults, next to the database and the agent adapters:

Seeing what actually ran got better too. A new Step Details tab shows what each attempt was given: the rendered prompt or script, the agent, model and effort with the level each value came from, and the outcome, all recorded when the step ran. vincent task show --step prints the same thing.

Smaller items fill in the edges. vincent agents shows each agent CLI’s version, login state and quota, and vincent doctor warns when claude is not logged in before a task burns its retries. Vincent is built with Go 1.26.8, and on Windows the install docs lead with Scoop while the WinGet package waits for Microsoft’s review. After upgrading, vincent doctor also tells you whether your installed agent skills are behind this build, and vincent skills install brings them up to date, including the new vincent-triggers skill.
Built with itself, again
Of the 88 pull requests merged between the two tags, 76 came from vincent/ task branches, which is to say vincent opened them while working on itself. The release was prepared the same way: task 328 ran the prepare-release workflow through all 21 of its steps, and its branch became #488, the refresh of the release-facing docs and Go module dependencies that went in ahead of the tag.
0.9.0 also carries the first contribution from someone other than me. Zhengzhuo Wang fixed the MCP tool descriptions so they name the request bodies the daemon actually accepts (#416). Thank you.
Vincent is still 0.x, and this release shows why that label matters. A few TUI keys moved so that every shared operation has the same key on every screen, and two workflow fields that used to be ignored on some steps are now errors. That is the cost of this release. What it buys is a surface that fails loudly where it used to fail quietly, and that is the ground I want to stand on when I call something 1.0.
The documentation and the GitHub project have the full detail. Feedback is welcome at lezli01@gmail.com, and contributions through the contributing guide. Everything else I am building is at lezli01.is-a.dev.