docs(website): rewrite worktrees page around scripts, services, and proxy

This commit is contained in:
Mohamed Boudra
2026-04-18 21:15:06 +07:00
parent 93cda8e60f
commit f8815bf1c2
+172 -221
View File
@@ -5,7 +5,7 @@ export const Route = createFileRoute("/docs/worktrees")({
head: () => ({ head: () => ({
meta: pageMeta( meta: pageMeta(
"Git Worktrees - Paseo Docs", "Git Worktrees - Paseo Docs",
"Run agents in isolated git worktrees for parallel feature development.", "Run agents in isolated git worktrees with setup hooks, scripts, and long-running services.",
), ),
}), }),
component: Worktrees, component: Worktrees,
@@ -25,285 +25,236 @@ function Worktrees() {
<div> <div>
<h1 className="text-3xl font-medium font-title mb-4">Git Worktrees</h1> <h1 className="text-3xl font-medium font-title mb-4">Git Worktrees</h1>
<p className="text-white/60 leading-relaxed"> <p className="text-white/60 leading-relaxed">
Git worktrees let you have multiple working directories from the same repository. Paseo Each agent runs in its own git worktree — a separate directory on a separate branch — so
uses them to run agents in isolated branches without switching contexts. parallel agents never step on each other. You configure setup, scripts, and long-running
services through a <code className="font-mono">paseo.json</code> file at your repo root.
</p> </p>
</div> </div>
{/* Why worktrees */} {/* Layout & workflow */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-xl font-medium">Why worktrees?</h2> <h2 className="text-xl font-medium">Layout and workflow</h2>
<p className="text-white/60 leading-relaxed"> <p className="text-white/60 leading-relaxed">
Without worktrees, running multiple agents on the same repo means they share the working Worktrees live under <code className="font-mono">$PASEO_HOME/worktrees/</code>, grouped by
directory. One agent's changes interfere with another's. You can't safely run parallel a hash of the source checkout path. Each worktree gets a random slug; the branch name is
tasks. chosen when you first launch an agent.
</p>
<p className="text-white/60 leading-relaxed">
With worktrees, each agent gets its own directory and branch. They can work simultaneously
without conflict. When an agent finishes, you review the diff, merge the branch, and
archive the worktree.
</p>
</section>
{/* Directory structure */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Directory structure</h2>
<p className="text-white/60 leading-relaxed">
Paseo creates worktrees under <code className="font-mono">$PASEO_HOME/worktrees/</code>,
organized by a short hash of the source checkout path:
</p> </p>
<Code> <Code>
<pre className="text-white/80">{`~/.paseo/worktrees/ <pre className="text-white/80">{`~/.paseo/worktrees/
├── 1vnnm9k3/ └── 1vnnm9k3/ # hash of source checkout path
│ ├── tidy-fox/ # random slug ├── tidy-fox/ # worktree slug (branch set on first agent)
│ └── bold-owl/ # random slug └── bold-owl/`}</pre>
└── 4k8q2d1p/
└── swift-hare/ # random slug`}</pre>
</Code> </Code>
<p className="text-white/60 leading-relaxed"> <ol className="text-white/60 space-y-2 list-decimal list-inside">
The hash avoids collisions between repositories that share the same directory or remote <li>Create a worktree — Paseo runs your setup hooks</li>
name. Worktree directory names are random slugs — the branch name is separate and chosen <li>Launch an agent — a branch is created or assigned</li>
when you first launch an agent in the worktree. <li>Review the diff against the base branch</li>
</p> <li>Merge or archive — archive runs teardown and removes the directory</li>
</section> </ol>
{/* Branches */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Branches</h2>
<p className="text-white/60 leading-relaxed">
When you create a worktree, Paseo generates a random directory name. The branch name is
set when you first launch an agent — Paseo generates one automatically.
</p>
<p className="text-white/60 leading-relaxed">
This means the worktree directory and branch are independent. You can rename the branch
later without affecting the worktree path.
</p>
</section>
{/* Multiple agents */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Multiple agents per worktree</h2>
<p className="text-white/60 leading-relaxed">
You can launch multiple agents into the same worktree. They share the working directory
and branch, which is useful when you want agents to collaborate on the same feature or
when one agent hands off to another.
</p>
<p className="text-white/60 leading-relaxed">
Be mindful of conflicts — agents working on the same files simultaneously can step on each
other. This works best when agents have distinct responsibilities or run sequentially.
</p>
</section> </section>
{/* paseo.json */} {/* paseo.json */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-xl font-medium">Lifecycle hooks with paseo.json</h2> <h2 className="text-xl font-medium">paseo.json</h2>
<p className="text-white/60 leading-relaxed"> <p className="text-white/60 leading-relaxed">
When Paseo creates a worktree, it's a fresh checkout. Dependencies aren't installed, Drop a <code className="font-mono">paseo.json</code> in your repo root. Paseo reads it
config files aren't copied. You can automate setup by creating a{" "} from the committed version of the base branch you picked, so uncommitted changes in other
<code className="font-mono">paseo.json</code> file in your repository root: branches don't apply.
</p>
<Code>
<pre className="text-white/80">{`{
"worktree": {
"setup": ["npm ci"],
"teardown": ["rm -rf .cache"]
},
"scripts": {
"test": { "command": "npm test" },
"web": { "command": "npm run dev", "type": "service", "port": 3000 }
}
}`}</pre>
</Code>
</section>
{/* Setup & teardown */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Setup and teardown</h2>
<p className="text-white/60 leading-relaxed">
<code className="font-mono">setup</code> runs once after the worktree is created. A fresh
worktree has no installed dependencies and no ignored files (like{" "}
<code className="font-mono">.env</code>), so use setup to install and copy what you need.
<code className="font-mono">teardown</code> runs during archive, before the directory is
removed.
</p> </p>
<Code> <Code>
<pre className="text-white/80">{`{ <pre className="text-white/80">{`{
"worktree": { "worktree": {
"setup": [ "setup": [
"npm ci", "npm ci",
"cp \\"$PASEO_SOURCE_CHECKOUT_PATH/.env\\" \\"$PASEO_WORKTREE_PATH/.env\\"" "cp \\"$PASEO_SOURCE_CHECKOUT_PATH/.env\\" .env",
"npm run db:migrate"
],
"teardown": [
"npm run db:drop || true"
] ]
} }
}`}</pre> }`}</pre>
</Code> </Code>
<p className="text-white/60 leading-relaxed"> <p className="text-white/60 leading-relaxed">
The <code className="font-mono">setup</code> array contains shell commands that run after Commands run with the worktree as <code className="font-mono">cwd</code>. Use{" "}
the worktree is created. Use it to install dependencies, copy local config files, or run <code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code> to reach files in the
any other initialization. original checkout (untracked config, local caches, etc).
</p> </p>
</section>
{/* Scripts & services */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Scripts and services</h2>
<p className="text-white/60 leading-relaxed"> <p className="text-white/60 leading-relaxed">
You can also add a <code className="font-mono">teardown</code> array for cleanup commands <code className="font-mono">scripts</code> are named commands you can run inside a
that run before Paseo removes the worktree directory during archive: worktree on demand. Mark one as a <em>service</em> and Paseo supervises it as a
long-running process, assigns it a port, and routes HTTP traffic to it through the
daemon's reverse proxy.
</p>
<h3 className="text-lg font-medium mt-4">Plain scripts</h3>
<Code>
<pre className="text-white/80">{`{
"scripts": {
"test": { "command": "npm test" },
"lint": { "command": "npm run lint" },
"generate": { "command": "npm run codegen" }
}
}`}</pre>
</Code>
<h3 className="text-lg font-medium mt-6">Services</h3>
<Code>
<pre className="text-white/80">{`{
"scripts": {
"web": {
"type": "service",
"command": "npm run dev -- --port $PASEO_PORT",
"port": 3000
},
"api": {
"type": "service",
"command": "npm run api -- --port $PASEO_PORT"
}
}
}`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
Omit <code className="font-mono">port</code> to let Paseo auto-assign one. Bind your
process to <code className="font-mono">$PASEO_PORT</code> rather than hard-coding — each
worktree gets a distinct port so multiple copies of the same service coexist.
</p>
<h3 className="text-lg font-medium mt-6">Reverse proxy</h3>
<p className="text-white/60 leading-relaxed">
Every service is reachable through the daemon at a deterministic hostname:
</p>
<Code>
<pre className="text-white/80">{`http://<script>.<branch>.<project>.localhost:<daemon-port>
# on the default branch, the branch label is dropped:
http://<script>.<project>.localhost:<daemon-port>`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
<code className="font-mono">*.localhost</code> resolves to{" "}
<code className="font-mono">127.0.0.1</code> on modern systems, so these URLs work out of
the box. The proxy supports WebSocket upgrades.
</p>
<h3 className="text-lg font-medium mt-6">Service-to-service</h3>
<p className="text-white/60 leading-relaxed">
Services launched from the same workspace see each other's ports and proxy URLs. Given{" "}
<code className="font-mono">web</code> and <code className="font-mono">api</code> above,
each process gets:
</p>
<Code>
<pre className="text-white/80">{`PASEO_PORT=3000 # this service's port
PASEO_URL=http://web.my-app.localhost:6767 # this service's proxy URL
PASEO_SERVICE_API_PORT=51732
PASEO_SERVICE_API_URL=http://api.my-app.localhost:6767
PASEO_SERVICE_WEB_PORT=3000
PASEO_SERVICE_WEB_URL=http://web.my-app.localhost:6767`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
Script names are upper-cased and non-alphanumerics become{" "}
<code className="font-mono">_</code>. Point your frontend at{" "}
<code className="font-mono">$PASEO_SERVICE_API_URL</code> instead of hard-coding a port.
</p>
</section>
{/* Terminals */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Terminals</h2>
<p className="text-white/60 leading-relaxed">
Open terminals automatically when a worktree is created. Useful for tailing logs or
leaving a REPL ready to go.
</p> </p>
<Code> <Code>
<pre className="text-white/80">{`{ <pre className="text-white/80">{`{
"worktree": { "worktree": {
"teardown": [ "terminals": [
"pkill -f \\"vite --port $PASEO_WORKTREE_PORT\\" || true", { "name": "logs", "command": "tail -f dev.log" },
"rm -rf \\"$PASEO_WORKTREE_PATH/.cache\\"" { "name": "shell", "command": "bash" }
] ]
} }
}`}</pre> }`}</pre>
</Code> </Code>
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 text-white/80">
<strong>Important:</strong> Setup commands come from{" "}
<code className="font-mono">paseo.json</code> in the selected base branch. If you pick{" "}
<code className="font-mono">main</code>, Paseo reads the committed file on{" "}
<code className="font-mono">main</code>. Local or uncommitted changes in another branch
are not used for that worktree.
</div>
</section> </section>
{/* Environment variables */} {/* Env vars */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-xl font-medium">Environment variables</h2> <h2 className="text-xl font-medium">Environment variables</h2>
<p className="text-white/60 leading-relaxed"> <p className="text-white/60 leading-relaxed">
Setup and teardown commands have access to these environment variables: Setup, teardown, scripts, and services all see:
</p> </p>
<ul className="text-white/60 space-y-2 list-disc list-inside"> <ul className="text-white/60 space-y-2 list-disc list-inside">
<li> <li>
<code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code> — your source checkout <code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code> — the original repo root
path (original repository root)
</li> </li>
<li> <li>
<code className="font-mono">$PASEO_ROOT_PATH</code> — legacy alias of{" "} <code className="font-mono">$PASEO_WORKTREE_PATH</code> — the worktree directory
<code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code>
</li> </li>
<li> <li>
<code className="font-mono">$PASEO_WORKTREE_PATH</code> — the new worktree directory <code className="font-mono">$PASEO_BRANCH_NAME</code> — the worktree's branch
</li> </li>
<li> <li>
<code className="font-mono">$PASEO_BRANCH_NAME</code> — the branch name created <code className="font-mono">$PASEO_WORKTREE_PORT</code> — legacy per-worktree port
(prefer <code className="font-mono">$PASEO_PORT</code> inside services)
</li> </li>
</ul>
<p className="text-white/60 leading-relaxed">Services additionally get:</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li> <li>
<code className="font-mono">$PASEO_WORKTREE_PORT</code> — the worktree port, when <code className="font-mono">$PASEO_PORT</code> — this service's assigned port
runtime metadata exists </li>
<li>
<code className="font-mono">$PASEO_URL</code> — this service's proxy URL
</li>
<li>
<code className="font-mono">$PASEO_SERVICE_&lt;NAME&gt;_PORT</code> /{" "}
<code className="font-mono">_URL</code> — peer service ports and URLs
</li>
<li>
<code className="font-mono">$HOST</code> — <code className="font-mono">127.0.0.1</code>{" "}
for local-only daemons, <code className="font-mono">0.0.0.0</code> when the daemon binds
all interfaces
</li> </li>
</ul> </ul>
<p className="text-white/60 leading-relaxed">
Use <code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code> to copy files that
shouldn't be in git (like <code className="font-mono">.env</code>) from your source
checkout to the worktree.
</p>
<p className="text-white/60 leading-relaxed">
<code className="font-mono">$PASEO_WORKTREE_PORT</code> is available when the worktree was
bootstrapped with a port. That makes it useful for both starting services in setup and
stopping them again in teardown.
</p>
</section> </section>
{/* Teardown */} {/* CLI */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-xl font-medium">Teardown</h2> <h2 className="text-xl font-medium">CLI</h2>
<p className="text-white/60 leading-relaxed">
Teardown runs during archive, before Paseo removes the worktree directory. Use it for
cleanup that needs access to the worktree path or its assigned port.
</p>
<p className="text-white/60 leading-relaxed">
Common uses include stopping dev servers on{" "}
<code className="font-mono">$PASEO_WORKTREE_PORT</code>, deleting generated files, or
deregistering services tied to that worktree.
</p>
<Code> <Code>
<pre className="text-white/80">{`{ <pre className="text-white/80">{`paseo run --worktree feature-auth --base main "implement auth"
"worktree": { paseo worktree ls
"setup": [ paseo worktree archive feature-auth`}</pre>
"npm ci",
"nohup npm run dev -- --port $PASEO_WORKTREE_PORT > \\"$PASEO_WORKTREE_PATH/dev.log\\" 2>&1 &"
],
"teardown": [
"pkill -f \\"npm run dev -- --port $PASEO_WORKTREE_PORT\\" || true",
"rm -f \\"$PASEO_WORKTREE_PATH/dev.log\\""
]
}
}`}</pre>
</Code> </Code>
</section> </section>
{/* Common patterns */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Common patterns</h2>
<h3 className="text-lg font-medium mt-6">Node.js / npm</h3>
<Code>
<pre className="text-white/80">{`{
"worktree": {
"setup": ["npm ci"]
}
}`}</pre>
</Code>
<h3 className="text-lg font-medium mt-6">Python / Poetry</h3>
<Code>
<pre className="text-white/80">{`{
"worktree": {
"setup": ["poetry install"]
}
}`}</pre>
</Code>
<h3 className="text-lg font-medium mt-6">Copy environment files</h3>
<Code>
<pre className="text-white/80">{`{
"worktree": {
"setup": [
"npm ci",
"cp \\"$PASEO_SOURCE_CHECKOUT_PATH/.env\\" \\"$PASEO_WORKTREE_PATH/.env\\"",
"cp \\"$PASEO_SOURCE_CHECKOUT_PATH/.env.local\\" \\"$PASEO_WORKTREE_PATH/.env.local\\""
]
}
}`}</pre>
</Code>
<h3 className="text-lg font-medium mt-6">Run database migrations</h3>
<Code>
<pre className="text-white/80">{`{
"worktree": {
"setup": [
"npm ci",
"cp \\"$PASEO_SOURCE_CHECKOUT_PATH/.env\\" \\"$PASEO_WORKTREE_PATH/.env\\"",
"npm run db:migrate"
]
}
}`}</pre>
</Code>
</section>
{/* Workflow */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Workflow</h2>
<p className="text-white/60 leading-relaxed">The typical workflow is:</p>
<ol className="text-white/60 space-y-2 list-decimal list-inside">
<li>Create a worktree — Paseo creates the directory and runs setup</li>
<li>Launch an agent — Paseo creates or assigns a branch</li>
<li>Agent works in isolation — changes stay in its worktree</li>
<li>Review the diff — compare against the base branch</li>
<li>Merge or discard — if approved, merge the branch; otherwise archive</li>
<li>Archive the worktree — cleans up the directory and optionally the branch</li>
</ol>
<p className="text-white/60 leading-relaxed">
You can run multiple agents in different worktrees simultaneously. Each worktree has its
own branch and working directory.
</p>
</section>
{/* CLI reference */}
<section className="space-y-4">
<h2 className="text-xl font-medium">CLI reference</h2>
<p className="text-white/60 leading-relaxed">Create an agent in a new worktree:</p>
<Code>
<pre className="text-white/80">{`paseo run --worktree feature-auth --base main "implement auth"`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">List all worktrees:</p>
<Code>
<pre className="text-white/80">{`paseo worktree ls`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
Archive a worktree (stops agents, removes directory):
</p>
<Code>
<pre className="text-white/80">{`paseo worktree archive feature-auth`}</pre>
</Code>
</section>
{/* Metadata */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Metadata</h2>
<p className="text-white/60 leading-relaxed">
Paseo stores metadata in each worktree's git directory to track the base branch. This is
used for diff operations and to know what branch to merge into.
</p>
<p className="text-white/60 leading-relaxed">
You don't need to manage this manually — Paseo handles it when creating and archiving
worktrees.
</p>
</section>
</div> </div>
); );
} }