Skip to content

Assign and dispatch tasks to agents - #120

Open
smarzban wants to merge 272 commits into
mainfrom
dispatch
Open

smarzban wants to merge 272 commits into
mainfrom
dispatch

Conversation

@smarzban

@smarzban smarzban commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

Ships the agents feature as v0.12.0. You assign a task to an agent, dispatch it into its own Git worktree and Herdr workspace, then clean up when you complete the task.

What ships

  • Agent profiles: [agent.<name>] tables in <state dir>/config.toml. Each has an argv command template, an optional prompt, and optional env. Templates use the {number} {title} {notes} {steps} {worktree} {branch} {base} placeholders. Profiles without their own prompt get a default prompt: work only on the branch, open a PR into the base without merging, then set the task to review or blocked. The first board open seeds a commented starter file.
  • Assign: !a name, @ (board, peek, task page), the task page's Assignee field and footer, palette set assignee (also on marked sets), and tsk add/edit --assignee. Filter by assignee with the project board's t Filter (threads · @assignees) or a Projects v view per assignee. / search and tsk list --assignee match assignees too.
  • Dispatch: ctrl+g, palette, or tsk dispatch T<n>. It creates a branch, worktree, and Herdr workspace named after the task, launches the agent (named t<n>-<assignee>), and records the dispatch and sets started in one save. An unassigned task asks for an agent first. A marked set gets one confirm card and launches in the background. Relaunch with a second ctrl+g, dispatch again, or --again. A live dispatch shows ◉. Requires Herdr on macOS or Linux; Windows refuses with unsupported-platform.
  • Base branch: !b, the task page's ⎇ footer or Base field, palette set base, or --base. Without one, dispatch uses the repository's origin/HEAD, never the current checkout. The branch picker opens on cached refs and refreshes after a background fetch. Dispatch, the picker, and cleanup share a 60-second fetch window.
  • Complete and clean up: ctrl+d on a live dispatch opens a card showing merged status. y completes at once and, in the background, removes the worktree and workspace, and deletes the branch only if it is merged into the recorded base. A marked set gets one card and one undo step. Uncommitted work is never touched. Any branch whose merge cannot be confirmed is kept, with a reason. Scripts use tsk clean T<n> [--json] and tsk status T<n> done --clean.

Breaking: store format v6 adds task assignees, base branches, and dispatch records. A v5 store migrates on first save and keeps tsk.json.v5; older binaries refuse a v6 store, so restoring tsk.json.v5 is the rollback.

Version bump to 0.12.0, the v0.12.0 changelog section, the board announcement, and the release docs pass land via #152 into dispatch.

Verification

  • CI on the current dispatch head (e78cb60) is green: Verify on macOS, Ubuntu, Windows x86-64 and ARM64; parity; site build; installer Windows smokes.
  • Each feature PR into dispatch got the local green bar (fmt, clippy -D warnings, cargo test), Windows-gnu clippy, site tests (with parity for UI/site changes), and an independent review before merge.
  • Dispatch and cleanup go through the DispatchHost seam, which has fake-host unit and integration coverage. Dispatch has been dogfooded through Herdr against real repositories to build this feature.

A leftover prunable worktree whose directory is already gone no longer
fails the whole listing, so board completion still offers cleanup.
Herdr failures report the error message instead of the raw JSON envelope.
Resolve the store v6 docs and tests against the platform-aware v5 helpers,
keep main's v0.11.6 changelog section, bump the unreleased skill to 1.5.0
because main ships 1.4.0, and build dispatch test exit statuses per platform
so the Windows targets compile.
@vercel

vercel Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
herdr-tsk Ready Ready Preview Oct 16, 2026 1:18pm UTC

@smarzban smarzban changed the title Assign and dispatch tasks to agents (store v6) Assign and dispatch tasks to agents Sep 22, 2026
Dispatch renders a POSIX $SHELL -lc launch line, which a Windows Herdr
pane cannot run. ctrl+g, the palette, and tsk dispatch now refuse on
Windows with unsupported-platform before any worktree is created, and the
POSIX shell-quoting test runs on Unix only.

The landing tsk add --json sample gains the assignee key the CLI now
emits, and the PTY smoke expects the dispatch peek footer order
(#thread · project).
T148: refuse dispatch on Windows, fix parity, merge main
@mruderman

Copy link
Copy Markdown

Very much looking forward to this PR, let me know if you need any testers for Linux! Thank you!

Bare @ on the board, peek, or task page opens an assignee picker (profiles,
then none) over the cursor or marked set; Enter is one save and one undo
entry. ctrl+g on an unassigned task opens the same picker and dispatches
after the assignment is saved. The task view footer paints a clickable
+ assign when unassigned and profiles exist; the palette's set assignee
routes to the picker. NO_ASSIGNEE now mentions @.
…rdr-agent-e-g-t105

T153: name the dispatched Herdr agent t<number>-<assignee>
…sign-picker-ctrl-g-on-unassigned

# Conflicts:
#	CHANGELOG.md
#	site/src/content/docs/docs/board.md
#	src/app.rs
…signee wording

- A quick-picker assignment updates an open task form only once its save
  lands (incl. Retry); Cancel leaves the form untouched.
- Esc in a list picker closes it even with marks active, instead of
  clearing the marks behind a still-open picker.
- The CLI no-assignee refusal names tsk edit; the board keeps the @ hint.
- Agent naming after a board dispatch goes through an injectable seam so
  tests never call herdr.
- Tests: failed launch after picking, mouse pick on the ctrl+g picker,
  palette set assignee, wide-split + assign hit target.
…rl-g-on-unassigned

T157: quick assign (@ picker, ctrl+g on unassigned, + assign on task view)
….txt

Windows refuses dispatch before the assignee picker opens, so the ctrl+g
picker tests run on unix only; the @-only parts are split out and run
everywhere, and a Windows test pins the platform refusal with no picker.
llms.txt names the dispatch shortcut instead of its chord.
…rl-g-on-unassigned

T157 follow-up: Windows tests and llms.txt
…last box (T228) (#196)

* Send back and unblock deliver every entry of the closing round, not just the box's (T228)

* fmt, clippy

* Closing deliveries survive save recovery and reach bulk and handled starts; counted summary; proof tests (T228 review)

* clippy: if let for the no-launch batch

* Mark replies tsk reply --send delivered (store v12) so closing never resends them (T228)

* Bind the --send mark to the record the reply was stored on; literal v11 ladder test (T228)

* tsk list --json: sent: true on replies and feedback --send delivered (T228)
Conflict in tests/queue_board_verbs.rs: both sides appended a test at the
same spot; kept both.
…ktree (T229) (#197)

* Cleanup card: each option says what it does to the PR, branch and worktree; selector; bulk m tidies merged rows; truthful remote branch (T229)

* Cleanup card: guard Enter from when the card shows; quit bound covers the remote-branch waits (T229)

* Cleanup card review fixes: open and resize scrolled to the selected option; disarm the preview card's double click; branch checked out in another worktree; bulk m that merges nothing keeps marks and says why (T229)

* Bulk m never merges a branch checked out in another worktree (T229)

* Cleanup card: Enter on an option scrolled out of sight scrolls it back instead of running it (T229)

* Cleanup card: no GitHub remote is its own case, not 'gh is not logged in'; 'discard it' for one file (T229)
…unch cards; strip gh's failure glyph (T230) (#198)

* Start, relaunch and launch cards share the cleanup card's option selector (T230)

* Strip gh's failure glyph from merge errors; docs and tests for the shared card selector (T230)

* Review fixes: gh's whole message without its glyph; launch-bearing bulk card test; guard test from opened_at; changelog (T230)
…very ctrl+u names its tasks (T221 + T219 + T212, #173) (#201)

* Undo restores the status a done left, and undoes status changes and step edits (T221, T219; store v13)

* Docs: undo restores a done's status and covers status and step changes (T221, T219)

* Undoing a status change on a dispatched task says its agent kept running; doc page-save undo and the reopen undo's no-release (T219)

* Archive is undoable; every ctrl+u names the tasks it undid or refused, and says when there is nothing to undo; an emptied step is refused on its row (T221, T219, T212)

* Docs and demo: archive undo, undo messages, emptied-step refusal (#173)

* Docs: v13 also carries archive undo

* Review K1/K2: a refused undo names the action's tasks; 'kept running' only for tasks the undo takes out of started
# Conflicts:
#	CHANGELOG.md
#	site/public/board-demo.js
#	site/public/llms.txt
#	site/src/content/docs/docs/board.md
#	site/src/content/docs/docs/cli.md
#	site/src/content/docs/docs/keys.md
#	site/src/content/docs/docs/steps.md
#	skills/tsk-cli/SKILL.md
#	src/cli/list.rs
#	src/cli/parser.rs
#	src/cli/presenter.rs
#	src/domain/task.rs
#	src/setup.rs
#	src/setup/tests.rs
#	src/setup_agent.rs
#	src/ui/board/apply.rs
#	src/ui/board/mod.rs
#	src/ui/input.rs
#	src/ui/mouse.rs
#	tests/cli_list.rs
#	tests/docs_parity.rs
#	tests/queue_board_mouse.rs
# Conflicts:
#	CHANGELOG.md
#	scripts/parity-pty-smoke.py
#	site/public/board-demo.js
#	site/src/content/docs/docs/board.md
#	site/src/content/docs/docs/keys.md
#	src/app.rs
#	src/ui/board/draw.rs
#	src/ui/board/mod.rs
#	src/ui/board/model.rs
#	src/ui/input.rs
#	src/ui/mouse.rs
#	src/ui/queue.rs
#	src/ui/render.rs
#	tests/demo_parity.rs
#	tests/fixtures/queue_board/accordion.txt
#	tests/fixtures/queue_board/board.txt
#	tests/fixtures/queue_board/board_default_split_78.txt
#	tests/fixtures/queue_board/board_marked.txt
#	tests/fixtures/queue_board/done_drawer.txt
#	tests/fixtures/queue_board/done_drawer_archived.txt
#	tests/fixtures/queue_board/help.txt
#	tests/queue_board_render.rs
#	tests/queue_board_verbs.rs
#	tests/v1_keymap_guard.rs
* Marked-set m acts on every task in the mix: merge open PRs, tidy the rest as y (T239)

* Test bulk m whose only merge fails still tidies the rest; no '(not merged)' for unconfirmed rows (T239 review F-1, F-2)
…T238) (#209)

* Group, sort and filter by assignee; [board] defaults in config.toml (T238)

* Review fixes: seed the cursor on a configured layout; test the [board] load handoff and the peek's assignee branches; keys.md rows
…t/no-thread)

# Conflicts:
#	CHANGELOG.md
#	site/parity/board.spec.mjs
#	site/public/llms.txt
#	site/src/content/docs/docs/board.md
#	site/src/content/docs/docs/cli.md
#	src/app.rs
#	src/cli/list.rs
#	src/cli/presenter.rs
#	src/setup_agent.rs
#	src/ui/board/apply.rs
#	src/ui/board/model.rs
* Help, footers and palette: short, verb-first, whole-action wording (T235)

* Address Pi review of #211; lowercase field-edit hints (T235)
… keys)

# Conflicts:
#	CHANGELOG.md
#	site/public/board-demo.js
#	site/src/content/docs/docs/keys.md
#	site/test/site.test.mjs
#	src/ui/input.rs
#	src/ui/render.rs
#	tests/quick_add_capture.rs
* Back up and restore the task store (T220, #128 part 1)

tsk backup add|list|remove|now writes [[backup]] targets (folder or git) to
config.toml. Saves and board opens start a detached backup run when a target
is due, so a backup never blocks or fails a save; failures land in
backup-status.json and show once on the board. tsk restore lists versions on
every target and swaps one in under the store lock, keeping the replaced store
as tsk.json.pre-restore-<time>; it refuses while a board or quick capture holds
the new board.lock, since an open board would write back removed tasks.

* Validate a restored document before the swap; sweep backup status temps

* Review pass 1: no-follow git writes, trigger hand-off, durable pre-restore copy

- Git targets check out with core.symlinks=false and refuse to write through a
  link anywhere under the clone; the store is written beside its name and renamed.
- A background run records its trigger before taking the waiter slot; the waiter
  backs up for every recorded trigger, so a save is not lost behind an open.
- The pre-restore copy is synced (file and directory) before the swap.
- Restore's git listing fetches under the backup run lock.
- Same-second folder copies get -1, -2 instead of replacing each other.
- PTY tests prove an open board and quick capture block restore and that a
  board open backs up every = "open" targets.

* Review pass 2: count same-second copies, order them, flush on Windows

- Retention and listing key folder copies on (second, suffix), so a burst of
  saves keeps keep_last files and lists tsk-<s>-2 above tsk-<s>-1 above tsk-<s>.
- The pre-restore copy is synced through a write handle (Windows needs one).
- Frequencies read as (every save), (on open), (daily), (twice daily).

* Review round 4: plain Windows paths, whole git errors, suffix after prune

- Targets store canonical paths without the Windows verbatim prefix
  (\\?\C:\… to C:\…, \\?\UNC\… to \\…), so git can clone a local repository.
- A git failure carries git's last few stderr lines, not only the last.
- A same-second copy takes the suffix above every copy of that second, so one
  made after its unsuffixed sibling was pruned still sorts newest.

* Review round 5: test targets on add, back up config.toml, safer retention

- tsk backup add runs a first backup to the new target in the foreground (a git
  target is still cloned when there is no store yet); a failure keeps the entry,
  exits 1, and says what to fix.
- A store that does not parse is never copied; the target records why.
- Each backup carries config.toml (folder: config-<UTC>.toml twin pruned with its
  copy; git: machines/<machine>/config.toml). Restore never writes config.toml and
  prints where the backed-up one is.
- keep_days defaults to 30 and keeps the first copy of each day.
- One-time board nudge once the store holds 10 tasks and no target exists; the
  starter CLI guide and landing page mention backups.
- Restore keeps the newest five pre-restore copies and suggests tsk trash restore
  for a T<n> argument.
- Docs: folder honesty, OneDrive and Windows paths, error shown after each failed
  attempt, unpruned machine dirs and clones.

* Review round 6: git-only backups, tsk backup setup, secrets, hardened git, detached probe

- Drop folder targets; every is save or daily
- tsk backup setup creates or reuses a private GitHub repository via gh,
  with host/owner/name (GH_HOST), internal repositories, gh's own errors,
  and a winget hint on Windows
- Push config.toml with [agent.<name>.env] values replaced; machines/<id>/
  layout with machine.toml and commit summaries
- Git runs without hooks, signing, prompts, links, or automatic gc; pushes
  retry three times on races
- add and setup check that a detached run reaches the target too
- A save that finds a waiting run leaves its trigger to it instead of spawning

* Push backups to the remote's default branch, even when it is empty

An empty bare repository made by Git for Windows names master; the first
push went to main, leaving the remote's HEAD unborn. Follow the unborn
branch git clone records. Tests pin a non-main default branch and feed
hash-object through stdin (2000 paths overflow a Windows command line).

* Review round 7: relative state dirs, new-machine setup, locked targets, steady failure notice

- Clone paths are absolute, so a relative state dir backs up
- The state dir exists before any backup lock (setup on a new machine)
- A target unreachable before any store is recorded for backup list
- Runs read targets under the run lock, and remove writes config.toml under it
- A failing target shows on every full board open until it succeeds
- Restore lists only machine-id directories; backup URLs lose credentials
- restore T12 points at trash first; multi-line refusals print as lines

This branch was successfully deployed

1 active deployment
Preview — 98c1088e Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants