// post

· 22 min read

vincent 0.12.0: issues, handoffs, and GitHub Copilot after the three-pillar refactor

// The first vincent release built on the intake, workshop and handoff split: issues vincent owns and syncs from GitHub, one line of work per issue, handoffs with human grants, GitHub Copilot CLI as a fourth agent, and an end-to-end workflow that uses all of it.

// contents
  1. How the pillars landed
  2. Issues vincent owns
  3. The issues board
  4. Starting work from an issue
  5. One line of work per issue
  6. A chat on the issue
  7. Handoffs, deliveries, and grants
  8. GitHub Copilot as the fourth agent
  9. One project at a time
  10. Smaller changes and fixes
  11. An end-to-end workflow
  12. 1. The issue arrives
  13. 2. Talk it through
  14. 3. Run something beside it
  15. 4. Hand the worktree to a workflow
  16. 5. The delivery is recorded
  17. 6. Hand it off
  18. 7. Review, merge, done
  19. Upgrading from 0.11
  20. Next: intake from Jira

vincent 0.12.0 is out, 33 days after 0.11.0. It is the largest release so far, and the first one built on the refactor I described in the three-pillar post: work comes in through intake, gets done in the workshop, and leaves through a handoff. A month ago that was a plan with diagrams. Now it is the shape of the product.

For anyone new here: vincent is a local, vendor-independent control plane for coding agents. It runs Claude Code, Codex, Cursor and now GitHub Copilot CLI as they ship, gives every task its own branch and worktree, drives them through YAML workflows you can read, and keeps the state, the gates and the verification on your own machine. 0.9.0 gave it triggers and pull request actions, 0.10 added skills and @file mentions in chat and tasks on an existing branch, and 0.11 rebuilt the TUI around an Overview tab with failure and outcome cards.

Two-panel meme from The Fairly OddParents: Mr. Turner, in a museum room with spotlights on an empty pedestal behind a velvet rope, gestures at it with a grin, captioned "This is where I'd put my AI SDLC". In the second panel he shouts, captioned "If I had one".
Most teams using coding agents today. 0.12.0 is my attempt at putting something on that pedestal.

Between the two tags there are 765 commits and 73 merged pull requests, and 64 of those pull requests came from vincent/ task branches: vincent opened them while working on itself. The 0.12.0 changelog has the condensed list. This post walks through each piece with real commands and YAML, then strings them together into one engineering workflow from a GitHub issue to a merged pull request.

How the pillars landed

The plan named three pillars: intake (where work comes from), workshop (where it gets done) and handoff (how it leaves). In 0.12.0 each one became something concrete:

  • Intake: an issue tracker inside vincent. Issues can be filed locally, and on a GitHub project they are imported and kept in sync.
  • Workshop: still tasks and workflows, but tasks now belong to an issue, and an issue’s tasks share one branch and one worktree.
  • Handoff: a new object. A finished task records what it delivered, and a handoff claims a batch of finished issues and runs one workflow over them that opens pull requests, merges, comments or closes.

The extraction I argued for happened the way I wanted. My own github-resolve-issue.yaml no longer calls gh issue view at all: its fetch step copies the snapshot the daemon wrote before the step started, and every lane of the DAG variant copies its parent’s snapshot instead of fetching the issue again. Its tail changed too. The run used to go on past verification into a push, gh pr create, a second gate and a rebase-and-merge loop. Now it ends at a step called deliver that records a verified branch, and nothing on its step path reaches GitHub any more. The landing half moved into a separate handoff workflow, github-handoff-train, which I come back to below.

Some things came out differently from the plan. I expected one handoff per work item, and it became a batch: one integration task over several issues, because that is how a merge train wants to work. The workshop also gained something the plan never mentioned, the idea of one line of work per issue. And grants, the rule that a step cannot write to GitHub without a human’s consent, were not in the plan at all. They turned out to be the piece that makes the handoff safe to automate.

One gap from the plan is closed only halfway. The pillars post complained that a rejected or under-specified issue just failed the task with nothing written back. 0.12.0 has the mechanism for that, a reply delivery and a handoff that posts it as a comment, but my own github-resolve-issue.yaml still ends a reject or needs-info verdict as a failed task. Wiring that up is still on my list.

Issues vincent owns

Issues are now vincent’s own records, with a title, a Markdown body, labels, a kind, a priority, a comment thread and a state. You can file, edit, label, comment on, close, reopen and delete them from the TUI, from vincent issue, from the API or from the MCP server. A project with no GitHub remote gets a working tracker too.

Filing one from the shell, or piping the body in from wherever it lives:

vincent issue add --project 1 --title "Crash on cold start" --label bug --kind bug --priority 2
gh issue view 200 --json body --jq .body | vincent issue add --project 1 --title "Crash on cold start" --body-file -

On a project whose origin is a github.com repository, the daemon’s reconciler imports the issues and refreshes them on every github.poll_interval tick, open ones first and then incrementally. Comment threads are mirrored, and a person closing or reopening an imported issue in vincent is written back to GitHub. Title, body and labels of an imported issue stay GitHub’s, so the TUI shows them as mirrored and refuses to edit them, while kind and priority are vincent’s own and save as usual. Deleting an issue in vincent never deletes it on GitHub, and a tombstone stops the next sync from importing it again. In the other direction, an issue GitHub reports as gone is deleted from vincent unless a live task still holds it.

vincent issue sync asks for an import now and prints how the import stands:

vincent issue sync --project 1
vincent issue sync --project 1 --status

The issue detail shows everything vincent knows: lane, labels, kind, priority, the source and its last sync, the description rendered as Markdown, the mirrored thread, and which task currently holds the issue’s worktree:

The detail of an issue imported from acme/web#142: its lane, labels and sync state, the description rendered as Markdown, two comments mirrored from GitHub, the Main worktree section naming the occupying task, and the Source section
Captured from a seeded demo install on a build from before the tag, so the header still reads 0.11.0. Task #15 holds the worktree, waiting at its gate.

The issues board

The issues screen lays out the selected project’s issues in four lanes: open, in progress, hand-off and done. No key moves a card between lanes. Vincent derives the lane from the issue and its root tasks: starting a task moves an issue to in progress, finishing it with a ready delivery moves it to hand-off, and closing it moves it to done.

The issues board on the platform-infra project: two open issues with no tasks, two in progress with one carrying a ! for a blocked task, four in hand-off with change deliveries, one claimed by handoff H1 and one waiting, and two done
The done lane is hidden by default. Pressing s shows it, as here, and the header counts the tasks waiting on you in other projects.

A ! beside a card means one of its root tasks is waiting on you: awaiting input, awaiting a gate, or blocked. It shows on any lane, so a closed issue with a stuck task still gets your attention. s shows or hides the done lane, ← and → fold the section you are in, and / filters by id, title, label or kind. R re-reads what vincent already holds, while S asks the daemon to sync the project from GitHub right now, which also sweeps closed and deleted issues that the reconciler otherwise checks once a day.

The same lanes are available from the shell:

vincent issue ls --project 1 --lane hand_off
vincent issue ls --project 1 --handoff pending

Lanes are also events. The issues trigger source fires on opened, closed, reopened, labeled, unlabeled, and, if a trigger opts in, lane_changed and handoff_changed. Because it reads vincent’s own record of issue changes, it works on a local issue and an imported one the same way.

Starting work from an issue

a on an issue opens the new-task form already filled from it: title, description, and any of the workflow’s declared fields named issue, github_issue, labels, assignee, milestone or kind. Everything stays editable before you create the task. From the shell it is --issue, which carries the vincent issue id and nothing else:

vincent task add --project 1 --issue 7

The task gets the issue as a snapshot taken at creation, in two forms. Templates read .Issue, including the full comment thread. Command steps read $VINCENT_ISSUE_FILE, a JSON file in the same shape gh issue view --json prints, so jq written for gh reads it unchanged. The snapshot is written from the task and never fetched, so a step cannot fail because GitHub is down, and a fan-out lane inherits its parent’s.

One detail matters when you migrate templates: .Issue.Number is now the vincent issue id. The GitHub number of an imported issue lives in .Issue.Source.Number. Here is a small workflow that uses both forms. I validated it with vincent workflow validate on the 0.12.0 binary, and it is the one the walkthrough below runs:

name: fix-issue
description: Fix the issue this task was started from, prove it with a test, and record the delivery.
platforms: [posix]

defaults:
  agent: claude
  max_retries: 1

steps:
  - id: guard
    type: command
    run: |
      [ -n "${VINCENT_ISSUE_FILE:-}" ] || { echo "start this task from an issue" >&2; exit 1; }
      jq -e '.state == "OPEN"' "$VINCENT_ISSUE_FILE"

  - id: fix
    type: agent
    prompt: |
      Fix this issue: {{ .Issue.Title }}

      {{ .Issue.Body }}

      The discussion so far:
      {{ range .Issue.Comments }}{{ .Author }} wrote: {{ .Body }}
      {{ end }}
      Add a test that fails without the fix, then make it pass.
    check: go test ./...

  - id: commit
    type: command
    run: 'git add -A && { git diff --cached --quiet || git commit -m "fix: issue {{ .Issue.Number }}"; }'

  - id: deliver
    type: command
    run: |
      set -e
      body=$(git rev-parse --git-path vincent-delivery.md)
      printf 'Fixed with a regression test.\n\nCloses #%s\n' {{ .Issue.Source.Number }} > "$body"
      vincent task deliver --title "Fix issue {{ .Issue.Number }}" --body-file "$body"

The free text of the issue (title, body, comments) only ever reaches the agent’s prompt. The run: bodies template nothing but numbers, which the docs call out as the safe case: an issue title rendered into a shell line is attacker-controlled shell.

One line of work per issue

This is the workshop change I use most. The first task created from an issue is its main task, and its branch becomes the issue’s main branch. Every later main task runs on that same branch, in the same directory, one at a time. When the next one is admitted, the previous task hands over the worktree as it stands, uncommitted changes included, and the new task’s base_sha is the branch tip at that moment, so each task’s diff shows only its own work.

A main task created while another one holds the worktree is queued, never refused, and the CLI says so:

queued behind #61 (main worktree busy); pass --separate-worktree to run now
task 62 created: Crash on cold start (adhoc, branch vincent/61-crash-on-cold-start)
  from issue 7: Crash on cold start

When something needs doing beside the main line, a side task runs now in its own worktree, cut from the issue’s main branch, and merges itself back when it finishes:

vincent task add --project 1 --issue 7 --separate-worktree --merge agent

The merge-back is a task of its own, titled Merge task {side} into issue #{issue}, that queues for the main worktree like any main task and merges the side branch with --no-ff. --merge block, the default, stops and blocks on a conflict with the main worktree left mid-merge, so you can resolve it there and retry. --merge agent first runs a built-in resolver with the side task’s agent, model and effort. If that fails, or leaves conflict markers behind, the merge-back blocks exactly as block would. Related to that: 0.12.0 also fixes an agent’s leftover conflict markers being committed, and two tasks on one existing branch no longer start together.

On the board an in-progress card reads ● #61 running for the occupant, with · +k side while side tasks run and · n merging while merge-backs wait. The issue detail has a Main worktree section naming the branch and the task holding it.

A chat on the issue

T on an issue opens a chat that works the issue’s main line itself, in the main worktree. No form opens: the issue decides the project, worktree and branch, and the chat’s first turn carries the issue’s title, description and comments. The shell equivalent is:

vincent chat start "Reproduce the cold start crash" --project 1 --issue 7

The chat is an occupant like any main task. It cannot open while a main task is working in the worktree, and a main task created while the chat holds it waits queued. It ends one of two ways from its workspace: ctrl+q closes it and leaves the directory as it stands for the next main task, and ctrl+t hands it off into the issue’s next main task on the same worktree. Either way the branch stays the issue’s main branch. This is where I do the exploratory part now: talk the problem through with the full thread in context, then let a workflow take over from exactly where the conversation left the files.

Handoffs, deliveries, and grants

This is the closure pillar, and the handoff guide is the long version of this section.

It starts with a delivery: what a task’s run produced. Its kind is change (commits to integrate), reply (an answer, a refusal or questions to post back where the work came from) or none. A step records one with vincent task deliver, and a task that finishes done without recording one gets an implicit delivery, change when its branch holds commits its base does not and none otherwise.

Whether a finished delivery is ready to hand off is the workflow’s hand_off: key. auto, the default, marks it ready when the task ends done, so the issue moves to hand-off as soon as nothing of it is running. explicit leaves it in progress until a step runs vincent task deliver --ready or a person runs vincent issue ready, which suits spikes and research whose result a person should judge first:

hand_off: explicit           # or: auto, the default

A handoff runs a workflow that declares role: handoff. Nothing else may run such a workflow, and a handoff runs nothing else. You can name the issues or let the handoff select them:

vincent handoff start --project 1 --workflow merge-train 12 14 15
vincent handoff start --project 1 --workflow merge-train --delivery change --label ready-to-ship --max 8

The handoff claims every issue it took, and the claim is exclusive: while it holds an issue, a new task, a chat hand-off or a follow-up on that issue is refused. Its task reads the items from .Handoff or $VINCENT_HANDOFF_FILE, each with the issue, its delivery, and the delivering task’s id, branch and verified head. Each item then ends with one of four verbs:

VerbWhat happens to the issue
vincent handoff deliverClosed with the delivery’s suggested reason (else completed), or left open with --close none
vincent handoff returnClaim dropped, readiness withdrawn, optionally with a --follow-up prompt
vincent handoff waitParked on something outside the handoff; a later close records it delivered
vincent handoff releaseClaim dropped with no verdict, eligible for a later handoff

From the board, space selects a pending hand-off card, V selects all of them, and I opens the handoff form. The form asks for the role: handoff workflow, the grant, an optional title, and whether to start paused:

The handoff form for platform-infra issue #16: the handoff-train workflow selected, seven grant capabilities all unticked, an optional title and a start-paused switch
All seven capabilities start unticked. Pressing ctrl+s starts the handoff, and that keypress is the consent to whatever you ticked.

The grant is the safety rule that came out of this work. A workflow step that writes to GitHub through vincent (vincent github pr create, merge, close, reopen, comment or rerun, or a close, reopen or comment on an imported issue) is now refused with 409 forge_write_needs_human unless a grant covers it. A grant names capabilities from a closed list: issue.comment, issue.close, pull.create, pull.comment, pull.merge, pull.close and checks.rerun. It applies only to the handoff’s own task, only on its own items and their pull requests, and only a person can give one: on the command line with --grant, by ticking boxes in the TUI form, or by arming a trigger that carries a grant:. An agent may start a handoff, but one that asks for a grant is refused. The docs are honest about the limit: this keeps an agent’s honest call off GitHub, but it is not a security boundary against an agent running as you, and a step’s own gh pr call in its worktree is unaffected.

Once the handoff’s task is running, its Overview lists the items and where each one stands:

Handoff task #20 waiting at its review gate, with an Items block for handoff H1 listing three issues: #13 delivered, #14 waiting and #15 still unresolved
Items left unresolved when the handoff's task settles are released, so a failed run loses no work.

Four example handoff workflows ship with 0.12.0, each installed with vincent workflow init <name> --from <name> and paired with a trigger under docs/examples/triggers/ that ships with enabled: false:

  • merge-train: stacks every delivered change onto the handoff task’s branch with git rebase --onto, verifies them together, opens one pull request, merges it pinned to the verified commit, then delivers every item. Its trigger runs at 02:00 on weekdays and grants pull.create, pull.merge and issue.close.
  • comment-and-close: takes the reply deliveries, posts each body on its issue, and closes it, without a single agent session. Its trigger grants issue.comment and issue.close.
  • pr-per-issue: opens one pull request per item from the delivering task’s branch and marks each item wait. Review and merge stay yours, and GitHub’s Closes #N plus the sync record the item delivered.
  • notify-handoff: posts the handoff file to a webhook and delivers every item with --close none. It writes nothing to GitHub, so its trigger grants nothing.

This is the pr-per-issue trigger as it ships. It fires each time a task moves an issue into hand-off:

id: pr-per-issue
enabled: false
source:
  type: issues
  project: 1
match:
  action: lane_changed
  lane: hand_off
  by: task
action:
  type: start_handoff
  workflow: pr-per-issue
  select: {lane: hand_off, delivery: change}
  grant: [pull.create]
overrun: queue_coalesce
concurrency_key: pr-per-issue

overrun: queue_coalesce folds work that becomes ready while one handoff runs into the next one. Arming the file is the consent: switching enabled to true, or widening its grant:, counts as arming, and vincent trigger apply refuses an agent’s staged proposal that would do either.

Meme of a soldier kneeling with arms spread, on fire and hit by bullets, grenades and knives, shielding a man asleep in bed. The incoming fire is labeled "Agents", the soldier "vincent's robust, well defined workflows", and the sleeper "Code base".
Checks, gates, grants and one worktree per line of work, between the agent and main.

The vincent repository lands its own work this way. github-handoff-train is the merge train plus an agent review of the whole train, a repair loop for its findings and the required checks, and a close of every issue through vincent. Its trigger runs on weekday nights with select: {lane: hand_off, delivery: change, max: 8} and grants pull.create, pull.merge, issue.comment and issue.close.

GitHub Copilot as the fourth agent

GitHub Copilot CLI joins Claude Code, Codex and Cursor as agent: copilot. That is the standalone copilot binary, not the retired gh copilot extension. Workflow steps and chats can run on it, with vincent’s MCP tools, resumed chat sessions, and @ file mentions, which copilot reads itself rather than vincent expanding them. The tested build is 1.0.105.

Restricted mode holds on macOS, Linux and Windows alike. Vincent passes --available-tools=view,grep,glob,edit,create,apply_patch, which leaves the shell tool out entirely, so a restricted copilot step has no shell and no git. The shipped copilot-review example is built around that:

name: copilot-review
description: Review the working tree with GitHub Copilot in restricted mode, write the findings to a file, and commit them.

defaults:
  agent: copilot
  max_retries: 1

Its review step runs permission_mode: restricted and writes REVIEW.md, and a separate command step commits it. A few copilot properties are worth knowing before you switch a workflow over. Copilot exposes no model catalog, so models are free text and the CLI picks auto when none is given. auto refuses any effort, so a step that sets effort: needs an explicit model:. And copilot reports tokens but no cost: each step spends premium requests, which vincent never shows as dollars, so max_task_cost_usd never counts a copilot run. The agents guide covers login, tokens and the MCP config in detail.

One project at a time

The TUI now works on one selected project. The header names it (◆ web), and @, a click on that segment, or “switch project” in the palette changes it. Boards, lists, new tasks, chats and issues all follow the selection, while tasks that need you in other projects are still counted in the header. vincent --project <name|id> picks the project at launch, and without it the TUI picks the project whose checkout or worktree you launched from, then tui.default_project, then the one you used last.

The Projects screen became a project overview, with the work in every project and a “Needs you, across projects” list:

The project overview: seven projects with their attention, running, queued, blocked, done, issue and chat counts, GitHub sync state, and a Needs you across projects list of seven tasks
Only web is bound to a GitHub repository, acme/web, which is why it is the one project with imported issues in the gh column.

Main tabs are new too. tui.tabs lists the screens the TUI opens on, by default the issues list and the task board, drawn in the header as Issues │ Tasks. tab and shift+tab or a click switch between them, and closing any other screen returns to the tab you came from:

tui:
  tabs: [tasks, issues, chats]

The first entry is where the TUI opens. A single entry turns the strip off. With one project on screen the board’s project grouping level stopped making sense, so the board now groups by workflow by default and the project level is deprecated.

Smaller changes and fixes

  • Claude waits for background work: a claude chat turn or step no longer ends while a background command it started (a test suite, a CI watch) is still running. Claude Code wakes the agent when that work finishes, but only while its process is alive, so vincent keeps the run open for up to defaults.background_wait, 30 minutes by default, until the agent answers. 0 restores the old behaviour. Only the claude adapter has background work.

  • Versioned docs: the documentation site is versioned now. It shows the latest release by default, with every release since v0.6.0 and the unreleased docs a selector away. A privacy policy is published too.

  • Small terminals: popups, forms, the help sheet, the issue screens, chats, the daemon view and the task workspace’s panes wrap and scroll instead of cutting text off, and the TUI uses the terminal’s alternate screen. vincent daemon stop now waits for the daemon to exit, containerized steps reach their MCP tools on Docker Desktop, and vincent is built with Go 1.26.10.

An end-to-end workflow

Here is how the pieces fit for a small team or a solo developer on a GitHub repository registered as project 1. The issue number and titles are an example; every command and file is the real 0.12.0 syntax.

The alternative, which I still see a lot, is a prompt in a chat window, a diff that looks plausible, and a reset when it turns out not to be.

Two-panel Hide the Pain Harold meme: an older man at a laptop holding a mug smiles, captioned "My prompt delivered a totally useless solution", then smiles through visible pain, captioned "I'll just delete the changes and try again".
The tokens are gone either way. A workflow at least leaves a failure card saying which check caught it.

1. The issue arrives

Someone files issue #200 on GitHub. The reconciler imports it on its next tick, or you ask for it:

vincent issue sync --project 1
id=$(vincent issue ls --project 1 --github 200 --json | jq '.[0].id')

It appears in the open lane of the issues board with no tasks, its comments mirrored under the description.

2. Talk it through

T on the card opens a chat on the issue. The first turn already carries the title, the body and the thread, and the chat cuts the issue’s main branch. The issue moves to in progress, and the card reads ● chat #C.

3. Run something beside it

While the chat holds the main worktree, a side task can run now instead of queueing, for example a fixture the reproduction needs:

vincent task add --project 1 --issue "$id" --separate-worktree --merge agent --title "Add a cold start fixture"

It runs in its own worktree cut from the main branch. When it finishes, its merge-back queues behind the chat.

4. Hand the worktree to a workflow

Close the chat with ctrl+q. The merge-back takes the worktree and merges the side branch, then the main task runs on the same directory, uncommitted chat changes included. With fix-issue.yaml from above in the repository’s .vincent/workflows/:

vincent task add --project 1 --issue "$id" --workflow fix-issue

If the merge-back still holds the worktree, the task waits queued behind it. To let a label start this step instead, an issues trigger does the same. This one is adapted from the triggers guide and passes vincent trigger validate:

id: bug-ready
enabled: true
source:
  type: issues
  project: 1
match:
  action: labeled
  labels: ready
  by: [human, sync]
action:
  type: create_task
  workflow: fix-issue
  title: '{{ .Event.Issue.Title }}'
  issue: '{{ .Event.issue_id }}'
dedupe_key: 'issue:{{ .Event.issue_id }}:ready'
limits:
  max_per_hour: 5

Leave on_fire at its default and the task it creates lands paused until you resume it, with its agent steps in restricted mode unless the trigger says permission: workflow.

5. The delivery is recorded

The workflow’s deliver step records a change delivery whose body ends in Closes #200. fix-issue keeps the default hand_off: auto, so once nothing of the issue is running, it moves to the hand-off lane with the marker pending:

vincent issue ls --project 1 --handoff pending

6. Hand it off

Install the example and start the handoff by hand, granting only what it needs:

vincent workflow init pr-per-issue --from pr-per-issue
vincent handoff start --project 1 --workflow pr-per-issue --grant pull.create "$id"

I on the card with pull.create ticked does the same, and arming the pr-per-issue trigger shown earlier makes it happen every time an issue reaches hand-off. The handoff’s task pushes the delivering task’s branch, opens the pull request with the delivery’s title and body, and marks the item wait.

7. Review, merge, done

The pull request is yours to review and merge. When it merges, GitHub closes #200 through Closes #200, the sync imports the close, the close records the waiting item delivered, and the issue moves to the done lane.

On a project where I trust the pipeline more, steps 6 and 7 collapse into an armed merge-train or github-handoff-train trigger that merges and closes overnight. That is a decision about the grant, not about the workflow.

SpongeBob meme: Squidward, labeled "Prompters", watches from a dark window through the blinds as SpongeBob and Patrick, labeled "vincent users", jump around happily outside.
One side is still watching the agent's terminal. The other armed a trigger and went outside.

Upgrading from 0.11

0.12.0 has breaking changes. This is what to check:

  • --github-issue: removed, along with the new-task form’s GitHub issue picker. A GitHub issue reaches a task once it is imported: look up its id with vincent issue ls --project P --github N --json, then pass --issue.
  • Templates: .Issue.Number is the vincent issue id on new tasks. Anything that hands a number to gh should read .Issue.Source.Number, or a declared github_issue field. Tasks created before the upgrade keep rendering their old snapshot unchanged.
  • Triggers: type: github_issues is deprecated in favour of type: issues. Old triggers keep firing, and vincent trigger validate and apply warn. A github_issues event carries the imported issue’s id, so issue: '{{ .Event.IssueID }}' links its task until you migrate.
  • GitHub writes: a workflow step or chat agent that ran vincent github pr create or merge used to be accepted as you. It is now refused with forge_write_needs_human. Move that tail into a role: handoff workflow and grant it there, as github-resolve-issue.yaml did.
  • Deleted issues: an issue deleted on GitHub is deleted in vincent on the next sync, unless a live task still holds it.
  • Existing tasks: tasks linked to GitHub issues were backfilled into imported issues on upgrade. The first sync that finds each one adopts it and takes GitHub’s state, writing nothing back.
  • The board: it groups by workflow by default, and the project grouping level is deprecated.
  • New config keys: tui.tabs and defaults.background_wait. A daemon older than 0.12.0 refuses a config.yaml that sets them, since unknown keys are an error. Set background_wait: 0 if you want claude runs to end at the first answer, as before.

Next: intake from Jira

The next major step is extending the set of integrations, and the first one is Jira from Atlassian: syncing issues from Jira into vincent the same way GitHub issues are synced now. That is the point of the intake pillar being pluggable. The issues board, the lanes, --issue, .Issue, the main worktree and the handoff all work on vincent’s own issue record, so a second source should change where issues come from and nothing downstream. Jira is also the real test of that boundary. Until now it has only ever had one implementation behind it, and the pillars post already named the risk that an interface designed around one adapter encodes that adapter’s accidents.

Vincent is still 0.x, and this release spends that label on breaking changes. In return the core no longer cares whether an issue came from GitHub or was filed by hand, and the workflows do the work and stop at a delivered branch. Landing and closing happen in the handoff, where a person has said yes in advance. That is the trade I wanted when I wrote the plan. The docs, including the new issues guide and handoff guide, have the full detail, and the GitHub project takes issues and contributions. The rest of what I work on is at lezli01.is-a.dev.

// thoughts? reply by email

// comments

// comments load as you scroll here. They live in GitHub Discussions; sign in with GitHub to join in.

← all posts