grove

Tasks and worktrees

How Grove models units of agent work and the git worktrees that back them. Covers the task model, lazy materialization, setup scripts, archive and revival, and the rebase-based refresh.

The task model

A task is just a folder. Any folder under <workspace>/.grove/workspace/ that holds a meta.json is a task; everything else is a plain note folder. The task folder owns everything about the work:

<workspace>/.grove/workspace/<task-name>/
  meta.json     — what makes the folder a task
  plan.md, …    — your notes, rendered in the sidebar
  chats/        — every agent conversation and its transcript
  worktree/     — the linked git worktree, branched off base
  • The folder name is the task name. Rename the folder — in Grove or in Finder — and you've renamed the task. The id and branch stay stable.
  • Each task gets a branch named task/<slug>-<hash> (e.g. task/fix-cache-ttl-a3f9c1), forked from the workspace base branch (detected mainmaster → current branch).
  • Comments are not per-task files — they live in one shared sqlite database at <workspace>/.grove/grove.db, across all tasks. See Comments.
  • Tasks are created from the task rail, by right-clicking anywhere in the workspace tree, or from a GitHub PR. See Task rail and GitHub PR sync.

Lazy materialization

Creating a task does not populate a working tree.

  • On task create: git worktree add --no-checkout — branch, HEAD, and worktree metadata exist, but no files. Disk cost: negligible.
  • On first agent or terminal spawn: Grove populates the worktree (index and files in one shot) before launching the PTY, so the agent's cwd is fully there by the time it starts reading.

Two consequences worth knowing:

  • Note-only tasks are free. A task that never spawns an agent never materializes. Twenty scratch/planning tasks cost essentially nothing.
  • An un-spawned task shows an empty diff and an empty working tree. Grove doesn't pretend: the diff view reports no changes rather than mis-reporting every base file as deleted.

Setup scripts

Fresh worktrees are missing node_modules, .env, build caches — everything gitignored. Grove solves this at the only correct moment: a repo can commit a grove.json at its root:

{ "scripts": { "setup": "npm install && cp $GROVE_WORKSPACE/.env .env" } }

You can also edit this without knowing the format from Settings → Git / GitHub → "Worktree setup script" — it round-trips the same file.

scripts.setup runs the moment a worktree gets real files: on first agent spawn, when a PR task checks out, or when a review-only PR upgrades to a worktree. Details:

  • Runs with the worktree as cwd, with GROVE_WORKSPACE and GROVE_WORKTREE in the environment.
  • Output streams to setup.log in the task folder, so the log shows up as a task note in the sidebar.
  • A failing script never blocks the agent — the worktree materializes either way, and the exit status lands in the log. Scripts are killed after 10 minutes.
  • Trust model: the script runs because you opened this repo and spawned an agent in it. Don't open repos you don't trust.

One trade-off: setup runs inside the spawn path, so a cold npm install delays the first agent launch.

Archive and revival

  • Archive captures any uncommitted work into a sentinel WIP commit, then removes the worktree. The branch is always preserved — your committed work and the WIP commit ride on it. The task folder (notes, chats) stays, and the comment rows stay in grove.db — they're tiny and they're the audit trail.
  • Unarchive re-adds the worktree from the retained branch. If the WIP commit is at the tip, Grove soft-resets it so your dirty state reappears as unstaged edits — you're back exactly where you left off.

Branches are kept indefinitely; the only thing that deletes a branch is deleting the task itself.

Rename, move, delete, promote, demote

  • Rename / move is a plain folder rename plus git worktree repair. Identity and branch are untouched.
  • Delete removes the worktree, deletes the branch, and removes the folder.
  • Promote turns any workspace folder — or a single file — into a task by adding meta.json and a lazy worktree.
  • Demote reverses it: tears down the worktree and Grove scaffolding, keeps your notes, preserves the branch.

Refresh (rebasing onto a new base)

Triggered explicitly by you, from the task rail. Grove fetches the new base, then rebases each active task's worktree onto it:

  • Clean rebase — the task continues against the new base.
  • Conflicts — the rebase auto-aborts for that task and the conflict is surfaced so you can resolve it in the worktree. Other tasks proceed.

See Design philosophy for why this is single-base, rebase-only.

Out of scope (for now)

  • Stacked tasks (a task built on another task's branch).
  • Cross-task overlapping-hunk warnings.
  • Container-per-worktree isolation — agent isolation is at the filesystem-path level.
  • A conflict-resolution UI — conflicts are flagged, you resolve them with git.

Where this lives in the code

  • Task lifecycle (create, archive, rename, promote/demote, migrations): src-tauri/src/tasks.rs.
  • Worktree porcelain, materialization, diff, rebase: src-tauri/src/git.rs.
  • Setup script runner: src-tauri/src/setup.rs.
  • Cross-window claim so two windows never drive one task: src-tauri/src/task_lock.rs.