Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ dispatch/
├── e2e/ # Playwright E2E tests
├── bin/ # dispatch-dev, dispatch-server, install-dispatch, etc.
├── scripts/ # e2e-isolated.sh, generate-icon-colors.ts
├── plugins/
│ └── dispatch/ # official Dispatch plugin (skills for Claude Code + Codex)
├── release-notes/ # release notes + assisted-update metadata
├── .dispatch/ # repo-level Dispatch config
│ ├── config.json # repo-level settings (e.g. Linear integration)
│ ├── job-state/ # persistent state files for recurring jobs
Expand Down
43 changes: 42 additions & 1 deletion apps/web/src/components/app/docs-sections/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ export function ToolsContent() {
The tools above would be available to agents as <Code>repo_lint</Code>
, <Code>repo_test</Code>, and <Code>repo_db_reset</Code>.
</P>
<P>
Every entry needs a <Code>name</Code>, a <Code>description</Code>, and
a non-empty <Code>command</Code> array. The command is executed
directly rather than through a shell, so pipes, globs, and{" "}
<Code>&amp;&amp;</Code> don't work — put anything shell-shaped in a
script and point <Code>command</Code> at that. A malformed entry
aborts the whole manifest, so a typo in one tool makes every{" "}
<Code>repo_</Code> tool disappear.
</P>
</Section>

<Section>
Expand Down Expand Up @@ -78,7 +87,39 @@ export function ToolsContent() {
When an agent calls <Code>repo_dev_up</Code> with{" "}
<Code>{'{ cwd: "/path", live: true }'}</Code>, Dispatch runs{" "}
<Code>./bin/dev up --cwd /path --live</Code>. Parameters that are
omitted or false are skipped.
omitted, false, or an empty string are skipped. Every parameter is
optional to the agent, and <Code>name</Code>, <Code>type</Code>, and{" "}
<Code>flag</Code> are required in the definition — the{" "}
<Code>description</Code> is what the agent reads to decide what to
pass, so it's worth writing.
</P>
</Section>

<Section>
<H3>What agents get back</H3>
<P>
A repo tool call doesn't fail on a non-zero exit. The command's stdout
comes back as the tool's text result, with the exit code, stdout, and
stderr in the structured payload, so the agent can read a failure and
react to it instead of just seeing an error. Write scripts that fail
loudly on stderr.
</P>
<P>
Dispatch puts no time limit on a repo tool command (lifecycle hooks
below do get one), though the agent's own MCP client may give up on a
very long call.
</P>
</Section>

<Section>
<H3>Picking up changes</H3>
<P>
<Code>.dispatch/tools.json</Code> is re-read from disk on every tool
listing — no server restart needed. The limit is on the agent's side:
a CLI fetches its tool list once when the session starts and holds it.
So an edited command runs the new version on the next call, but a
newly added tool usually isn't callable until the agent reconnects or
a new session starts.
</P>
</Section>

Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/lib/tips/tips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ export const tips: Tip[] = [
since: "0.33.6",
surfaces: ["ambient"],
},
{
id: "repo-tools",
title: "Give Agents Your Repo's Commands",
body: "Add a .dispatch/tools.json to a repo and every entry becomes a real tool in each agent's tool list — so nobody has to paste the same build, reset, or dev-stack command into a prompt again.",
docsSection: "tools",
since: "0.11.12",
surfaces: ["ambient"],
},
{
id: "shortcut-pins",
title: "One-Click Agent Shortcuts",
Expand Down
16 changes: 10 additions & 6 deletions plugins/dispatch/skills/repo-tools/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ it at the moment of use.
| ------------- | -------- | --------------------------------------------------------------------------- |
| `name` | yes | Exposed as `repo_<name>`. Dots are stripped — MCP names cannot contain them |
| `description` | yes | This is what makes the tool get used. See below |
| `command` | yes | Argv array, run from the repo root |
| `command` | yes | Argv array, run from the agent's checkout root |
| `params` | no | Turned into CLI flags appended to `command` |
| `scope` | no | Any of `agent`, `reviewer`, `job`. Omit to expose everywhere |

`repo_` prefixing is automatic, and a name that would collide with a built-in
Dispatch tool (`create_pr`, `get_pr_status`, `dispatch_event`, `dispatch_share`)
is rejected at load.
`repo_` prefixing is automatic and keeps repo tools in their own namespace, so a
repo tool can never shadow a built-in Dispatch tool like `create_pr` or
`dispatch_event`.

**Write the description for an agent that has never seen this repo.** It is the
only thing standing between the tool existing and the tool being used. Say what
Expand All @@ -79,7 +79,10 @@ load. Give every param a `description` too — the agent picks values from it.

## Execution model

Commands run from the repo root with `DISPATCH_AGENT_ID` set in the environment.
Commands run from the root of the agent's checkout — the worktree root for
worktree agents, otherwise the repo root — with `DISPATCH_AGENT_ID` set in the
environment. There is no shell: the argv array is spawned directly, so pipes,
globs, and `&&` need a wrapper script.
**Every exit code is returned to the agent** rather than throwing — stdout,
stderr, and the exit code all come back, so the agent can read a failure instead
of just seeing an error. Write scripts that fail loudly on stderr.
Expand All @@ -89,7 +92,8 @@ of just seeing an error. Write scripts that fail loudly on stderr.
`hooks.stop.command` runs when the agent stops. Use it for teardown that would
otherwise leak: stopping a dev stack, removing a container, releasing a port.
Keep it fast and idempotent — it may run when the thing it cleans up was never
started.
started, and it is killed after 15 seconds. A failing or timed-out hook is
logged and never blocks the stop.

## Working on it

Expand Down
Loading