---
name: using-grove
description: Use when orchestrating Grove workspaces from outside, including creating or supervising host or container agents, steering a fleet, using Grove CLI or MCP tools, or having agents write to each other through the mailbox. Also use when splitting work across workspaces, reviewing a diff from several angles, managing ticket links, or checking who needs attention. For a worker reporting from inside a workspace use working-in-grove. For setup use configuring-grove.
---

# Using Grove

[Grove](https://github.com/bearlike/Grove) runs a fleet of coding agents that
live outside your context rather than sub-agents inside it. Each workspace is a
whole separate agent process with its own runtime, its own context window and
its own git worktree, addressable from outside. You create, steer, watch, pause,
resume, respawn and kill them.

Reach for Grove when you want parallel workspaces that outlive a single turn.
For quick in-session parallelism across a few scoped sub-tasks, plain sub-agents
are lighter and fit better.

Grove earns its keep on work too big, too parallel or too cross cutting for one
agent in one context.

- **Decompose and parallelize.** Split by component and spin up one workspace
  per slice. Each gets a clean worktree, so they never touch each other's files.
- **Triage at scale.** Forty Sentry issues or a whole backlog, one workspace per
  issue or per cluster. Each agent investigates independently and you review
  results rather than noise.
- **Work one ticket from several angles at once.** Implementation, tests and
  docs run in parallel in separate environments instead of one after another.
- **Independent review.** Point separate agents, on different models and
  different harnesses, at the same diff for uncorrelated second opinions.

## The model

- **One workspace is one isolated runtime for one agent.** A dedicated git
  worktree, or the repo root for a lightweight case, plus a tmux session running
  that agent's own CLI, on the host or inside a container.
- **Model and harness agnostic by design.** Every workspace picks its own agent
  and model. The point is giving each slice the harness that suits it, not
  stretching one agent across everything.
- **You own the fleet and each member owns its own task.** Decide the split,
  spawn one workspace per slice, then poll and steer and collect. A map step and
  a reduce step, with real isolation so one agent's long tool call never blocks
  another.

## What Grove reports back

Three independent axes describe a workspace and you need all three. Conflating
them is the mistake the design exists to prevent.

| Axis | Answers | Reported by |
|---|---|---|
| Workspace status | Is the runtime there. `active`, `idle`, `paused`, `offline`, `orphaned`, `provisioning`, `error` | Grove observes it |
| Agent activity | Is the agent moving right now. `working`, `waiting`, `blocked`, `error`, `idle` | Grove infers it from transcript, hooks and pane |
| Task phase | How far through the task the agent believes it is | The agent declares it, and nothing derives it |

Phase is the axis you could not get before. An agent reads `working` both while
it is still reading the ticket and while it is pushing the branch, and those two
call for opposite responses from you. Only the agent can tell them apart, which
is why this axis is pushed rather than observed. The six phases run `scoping`,
`planning`, `implementing`, `verifying`, `delivering`, `done`.

Four things about phase are easy to get wrong.

- **A missing phase is not `scoping`.** A workspace that never reported carries
  no phase at all. That is a fact about the agent, where `scoping` is a fact
  about the task, so render them differently.
- **Backwards is a correct report.** An agent that discovers in `verifying` that
  its design was wrong should say `planning` again. Grove never enforces forward
  motion, and an honest reversal is worth more than a phase that only climbs.
- **There is still no `error` phase — that lives on the activity axis.** An
  agent can flag `blocked: true` beside a phase instead, meaning it has no way
  to finish that piece of work; it still names the phase it actually reached.
  This is not the activity axis's own `blocked`, which means waiting on a human
  right now and clears the moment they answer.
- **Grove renders no staleness verdict.** Three hours in `implementing` is a
  long task, not a stale report. Whether the agent is alive is what the other
  two axes answer. The timestamp is the file's own mtime, so hold your own
  policy if you want one.

Phase does not survive `pause`. It lives in the worktree that pause removes, so
a resumed workspace reports afresh and the durable record stays the commit log.

## Watching the fleet

`grove fleet` is the read built for supervising many workspaces at once. It is
host wide rather than repo scoped, takes no arguments and emits JSON. One call
carries every workspace with its state, its reported phase, todo counts, git
ahead and behind, recent commits, and per session activity including which
agents are waiting on you.

```bash
grove fleet | jq '.projects[].workspaces[] | select(.needs_attention)'
```

Prefer it over per workspace phase and peek calls the moment you supervise more
than one agent. `grove_get_fleet_status` is the same payload over MCP. It has no
pagination, so the response grows with the fleet.

For a single workspace, `grove show` gives git, agent, transcript and live pane
state at a glance.

## Tying a workspace to the work item

A workspace carries references to the issues and pull requests it is working.
This is what makes a fleet addressable by work item rather than by workspace id.

```bash
grove tickets attach 42
grove tickets attach https://example.com/acme/widgets/pull/17
grove tickets list
grove tickets detach 42
```

You never name a provider. Grove infers both the provider and whether the ref is
an issue or a pull request from the shape of what you pass. Four input forms
work, a full URL, `#42`, `42`, and `owner/repo#42`. Linear keys such as
`ENG-123` parse for free. Pass `--workspace` to name one, or let Grove infer it
from the directory you are standing in. Attach and detach are both idempotent.

Five things worth knowing before you document or automate this.

- **A pull request is not a separate kind of attachment.** It is one ref list
  with a kind discriminator, so there is no separate verb to go looking for.
  Re-attaching a ref Grove recorded as an issue corrects the kind in place
  rather than duplicating the row.
- **Grove never detects your pull request from the branch.** Branch names yield
  issue refs only. A pull request enters the list solely by explicit attach.
- **Attach stores a link and fetches nothing.** Title, status and assignee are
  fetched on demand and never persisted, which is what keeps attach fast and
  safe offline.
- **A merged pull request reads `merged` and never `closed`.** The issues
  endpoint calls a merged PR closed, which is true and useless, so Grove reads
  the pulls namespace and normalizes. This is how you learn work landed.
- **Attaching a ticket is a private local link.** It does not make an issue
  reference safe to put in pull request text. That ban exists because PR text
  mirrors to public remotes where a bare number resolves against a different
  tracker, and attaching changes nothing about the mirror.
- **Attaching is also what makes the status mirror appear.** When issue-ops is
  enabled, Grove keeps ONE live comment per attached ticket carrying the
  workspace's state, its reported phase and its todo checklist. Nothing is
  published for a workspace with no refs, so an attach is the difference between
  a fleet a human reads from the tracker and one they can only see in Grove.
- **Every one of those comments is authored as whatever account the configured
  token belongs to.** Point it at a bot. Forges set authorship at creation and
  never change it on edit, so a comment first written under a personal token
  stays attributed to that person permanently.
- **Attach succeeding tells you nothing about whether the tracker is reachable.**
  It performs no network call, and publish failures are swallowed so an outage
  cannot break the activity poll — so a misconfigured provider presents as
  silence rather than an error. If comments never appear, suspect the credential
  before the code.

### Keeping the tracker current

The mirror is only as good as what the agent reports, and the agent is the only
one who can report a phase. Two consequences for you as the orchestrator:

- **Tell a workspace to report, and to keep its todo list honest**, in the prompt
  that starts it. An agent that never writes a phase publishes a comment with no
  progress in it, and Grove will not invent one — a missing phase renders as
  nothing rather than as `scoping`. This is the single most common reason a
  status comment looks empty.
- **Attach every ticket the workspace owns, up front.** Attaching is also what
  seeds an entry of its own for that ticket, at `scoping` — so a ticket linked
  late starts with no history and no phase until the agent's next report.

Each attached ticket now carries its own phase, seeded the moment it is
attached and updated independently of the workspace's own — an agent verifying
one issue while another sits untouched reports exactly that, rather than one
shared answer for both. `blocked: true` sits beside a phase, on the workspace's
own claim or on any one ticket's, and means the agent has no way to finish that
piece of work — distinct from the activity axis's own `blocked`, which means
waiting on a human right now and clears the moment they answer.

You can correct a report from outside with `grove phase <ref> <phase> [--ticket
<ticket-ref>] [--blocked]` or `grove_set_workspace_phase` — for when an agent
has stopped reporting or has plainly mis-stated where it is. Use it to fix the
record, not to drive it: a phase you set is your claim about someone else's
work, and the next thing the agent writes overwrites it.

Before you create a second workspace for a ticket, ask whether one already
exists. Over MCP, `grove_list_workspaces` narrows by `ticket_provider` plus
`ticket_id` and answers exactly that, which is the difference between steering
the workspace already on the job and starting a duplicate.

### Handing work over by assignment

The tracker's assignee field can be the fleet's inbound queue. With issue-ops
pickup enabled, the daemon polls each configured tracker for open issues
assigned to Grove's account and starts a workspace on each one, under a ceiling
on how many pickup-started workspaces may be live at once. `grove tickets
handover <ref>` does the same thing on the spot rather than waiting for the
poll, `grove tickets owned` lists what the fleet is holding host wide, and
`grove tickets handback <ref>` unassigns without touching the workspace.

Assigning on the tracker and handing over from the CLI take the same path, so a
ticket handed over is never picked up twice. Handover needs a provider with an
owner and a repo configured, which is Gitea and GitHub; Linear refuses, because
two repos' issue #7 would otherwise share one marker.

Gitea, GitHub and Linear are supported and all three stay off until a repo opts
in. Each wants `enabled`, its scope, a base URL, and the NAME of an env var
holding the token rather than a token literal. With nothing configured, an
attach by bare ref fails loudly and names the providers that are enabled.

## CLI

Run from inside any git repo Grove knows about. `grove config add-project`
registers a repo you are not currently standing in.

| Command | What it does |
|---|---|
| `grove create <title> [--agent <name>] [--model <id>] [--prompt <text>] [--runtime host\|container] [--branch/--base/...]` | Spin up a workspace and kick it off with a first task |
| `grove ls` | This repo's workspaces, as JSON |
| `grove show [<ref>]` | One workspace's git, agent, transcript and live pane state |
| `grove fleet` | Every workspace on the host, as JSON |
| `grove message <ref> <text>` | Steer a running agent |
| `grove phase [<ref>] [<phase>] [--ticket <ticket-ref>] [--blocked] [--note <text>]` | Read a phase, or set one — the workspace's own or one ticket's — from outside |
| `grove tickets attach\|list\|detach <ref> [--workspace <id>]` | Issue and pull request links |
| `grove tickets handover <ref>`, `grove tickets owned`, `grove tickets handback <ref>` | Hand an issue to the fleet by assigning it, list what the fleet holds, give one back |
| `grove pause\|resume\|respawn\|kill <ref>` | Lifecycle |
| `grove attach <ref>` | Drop into the workspace's live tmux session yourself |
| `grove shell <ref>` | An interactive shell inside a container workspace, persistent across visits |
| `grove code <ref>` | Attach VS Code to a container workspace |
| `grove agent list\|add\|kill\|peek\|message\|attach` | Several agents sharing one container workspace |
| `grove sessions list\|show\|dump\|remap` | Recorded agent transcripts across the project |
| `grove doctor` | Check the host dependencies each runtime needs |
| `grove config`, `grove init devcontainer`, `grove skills install`, `grove mcp install` | Setup and onboarding |

`<ref>` is a workspace id or a unique id prefix. `grove <command> --help` carries
every flag. `grove shell`, `grove code` and the whole `grove agent` group require
a containerized workspace and error cleanly on a host one.

A fresh container workspace reads `provisioning` while its image builds, which
can be minutes. That is the one status whose remedy is to wait, and it ends on
its own — `respawn` is the one action that destroys the build in flight.

`--runtime` is fixed at create time and never editable afterwards. If a container
was wanted and the runtime was unavailable, the workspace falls back to host and
names the reason, which `grove show` and `grove ls` report. Fix the runtime, then
`grove respawn` to promote it. A container workspace with no project
`.devcontainer/` runs Grove's default image, and `grove init devcontainer`
graduates it to a committed config.

## MCP

The same fleet, over MCP. What your server actually registers is the census;
each tool's own schema documents its inputs, so read those rather than guessing
from here.

| Group | Tools |
|---|---|
| Discovery | `grove_list_projects`, `grove_list_workspaces`, `grove_get_workspace`, `grove_list_agents`, `grove_list_sessions` |
| Watching | `grove_get_fleet_status`, `grove_peek_workspace`, `grove_get_workspace_phase`, `grove_get_workspace_todo`, `grove_attach_instruction` |
| Lifecycle | `grove_create_workspace`, `grove_pause_workspace`, `grove_resume_workspace`, `grove_respawn_workspace`, `grove_kill_workspace` |
| Workspace metadata | `grove_update_workspace` |
| Steering | `grove_send_workspace_message`, `grove_set_workspace_phase` |
| Links and sessions | `grove_attach_ticket`, `grove_detach_ticket`, `grove_remap_workspace_session` |

`grove_list_projects` takes no arguments and returns the repo roots every other
tool wants, so start there.

- `grove_kill_workspace` requires `delete_branch` explicitly and rejects the call
  without it. Destructive tools never guess.
- `grove_send_workspace_message` that times out means delivery is unknown rather
  than failed. Peek before you resend, or the agent gets the message twice.
- `grove_set_workspace_phase` is for correcting an agent's report from outside,
  with `ticket` and `blocked` parameters mirroring the CLI's `--ticket` and
  `--blocked`. The agent's own channel is a file, described below.
- A server started with `--read-only` withholds every mutating tool by not
  registering it, because a tool an agent can see is a tool it will try to call.
- `grove_peek_workspace` caps its pane snapshot, and a trailing ellipsis is the
  signal that it trimmed.

## Container workers report through the mounted worktree

A container agent should write its phase file because that path works across
runtimes. Ordinary container workspaces may not have the Grove CLI or a general
Grove MCP registration, so handle lifecycle and ticket links from outside.

Mailbox workers are the deliberate exception. Grove supplies their private
coordinator connection when the container meets the native prerequisites. That
grants the session's control channel. It does not expose the
general fleet control surface.

Attach the pull request the agent opens from outside when the agent cannot reach
the relevant CLI or MCP tool.

You do not have to restate the reporting contract in your prompt. By default
Grove hands every new workspace a one-paragraph brief on its first turn
pointing it at the companion `working-in-grove` skill, and `--no-brief` turns
that off per workspace. How it arrives differs, and the difference bites in a
container: a host `claude_code` agent gets it from Grove's status hook whether
or not you gave it a task, while an agent with no such hook gets it prepended to
the initial prompt — so a container workspace created with no `--prompt` is
never briefed at all.

## Native sessions and peer mail

A Claude Code or Codex workspace is a Grove-owned native session by default:
Grove runs the provider's own protocol worker (`claude -p` stream-json or the
Codex app-server) and holds its control channel, so interrupt, a model switch
and peer messaging are real controls. The `claude-terminal` / `codex-terminal`
entries (or `native: false` on your own entry) run the interactive UI instead.
Peer mail is never a way to send text to an arbitrary interactive TUI or to
retrofit a socket into an existing session. A native session is not resumable
and does not attach to an existing conversation.

## Agents writing to each other

Any live agent in the fleet can write to any other, addressed like email:
`--from`, `--to`, a subject and a body. Native and interactive terminal sessions
are both ordinary recipients, because delivery reuses whatever channel Grove
already steers that workspace through. Nothing is enrolled and no peer holds a
credential of its own.

```bash
grove mailbox contacts
grove mailbox send --from <id> --to <id> --subject "Ready for review" --body "PR #42 is open."
```

A contact reports whether it is `live`; a paused or offline workspace is listed
and refused with `not_live` rather than silently swallowed. The MCP tools are
`grove_list_mailbox_contacts` and `grove_send_mailbox_message`.

A receipt reports Grove's own observation and nothing more: `delivered` means
the text reached that session, never that a model read or obeyed it. Do not
retry an `unknown` send or type a fallback into a pane. A reply is an ordinary
send with the addresses swapped, so a conversation survives a daemon restart.

The agent-facing workflow belongs in `working-in-grove`; configuration belongs
in `configuring-grove`. Run `grove mailbox --help` and read the installed MCP
schema before supplying flags.

## The other Grove skills

- **`working-in-grove`** — the same fleet seen from the inside, including a
  mailbox worker's peer-message workflow. Point a workspace agent at it rather
  than restating the phase or mailbox contract in every prompt.
- **`configuring-grove`** — the config cascade, agents, init scripts, containers,
  native-session switch, and ticket providers. Reach for it when a workspace will
  not start the way you meant, or when the status mirror is silent.
- **`reinstalling-grove`** — when an update did not take: stale daemon, stale
  webapp build, old UI after a pull.

## Progressive help

Use `grove <command> --help` for installed CLI syntax and each MCP tool schema
for its inputs. `grove skills list --details` and `grove_get_skill(details=True)`
list richer skill metadata. Retain the existing skill names. A mailbox message
never grants a default native tool permission.

## Configuration

Setting Grove up is a separate concern from using the fleet. If a
`configuring-grove` skill is available here, use it for that. `grove skills
install` and `grove mcp install`, or `grove config init --with-onboarding`, is
how this skill and Grove's MCP server arrived in the first place. For anything
this skill does not cover, `grove --help` or a repo aware research tool pointed
at `bearlike/Grove` will get you unstuck faster than guessing.
