| name | align |
| description | Turn a task prompt into a prompt-align brief page — reading, scope, done-means, and autonomy as editable sentences with ghost alternatives and AI steering — wait for the human's saved brief, then implement to completion from the persisted result. Trigger: /align <task>. |
Align (prompt-align edition)
/align <task> is a three-phase contract between an agent and a human, mediated by
prompt-align:
- Align — the agent inspects the code, finds the genuine forks, and publishes ONE
brief page where every decision is an editable sentence.
- Edit — the human picks ghosts, types over anything, and steers the AI with
// comments. Sending the brief saves result.json.
- Implement — the saved result is standing authorization. Implement, verify, and
report to completion. Never re-ask "should I proceed?" after the result exists.
The invocation itself is the human's opt-in to all three phases.
Requirements
- Bun and a checkout of prompt-align. Resolve its location in this order:
$PROMPT_ALIGN_DIR if set
~/dev/prompt-align or ~/prompt-align if present
- otherwise clone it:
git clone https://github.com/johnlindquist/prompt-align ~/prompt-align
- Call the CLI as
bun <dir>/src/server.ts … (referred to as prompt-align … below).
Phase 1 — Inspect, then compose the job
- Inspect before asking. Read the relevant code, config, and logs first. Never put a
question on the page that the repository can answer. Every alternative must be grounded
in what actually exists.
- Find the genuine forks. A fork is real only when two readings of the prompt lead to
materially different work. If there is no real fork in a section, still fill the slot —
one honest recommended sentence plus one clearly-worse alternative keeps the human's
veto cheap.
- Compose the job — four slots, each a complete sentence the human can keep, replace,
edit, or steer. Put the recommended sentence first in every slot. Put your
inspection findings in
context so the page is decidable without another window.
{
"jobId": "align-<short-task-slug>-<4 random hex>",
"title": "Align: <the task in a few words>",
"seed": "<the user's task prompt, one line>",
"context": "<what you found during inspection: key files, current behavior, constraints>",
"slots": [
{ "key": "reading", "name": "READING", "detail": "what to build",
"alternatives": [
"<recommended reading, as a build-this sentence>",
"<plausible bigger/smaller reading, stated fairly>" ] },
{ "key": "scope", "name": "HOW FAR", "detail": "quality bar"
Slot rules (enforced by the server): slug keys, names ≤ 24 chars, 1-9 alternatives of
≤ 400 chars each. Add extra slots (max 12) only for forks that genuinely exist —
constraints, migrations, naming — never filler.
Phase 2 — Serve, push, deliver, wait
curl -sf "http://127.0.0.1:$(bun -e 'console.log(JSON.parse(await Bun.file(process.env.HOME + "/.prompt-align/server.json").text()).port)' 2>/dev/null)/api/health" >/dev/null 2>&1 \
|| (nohup prompt-align serve --port auto >/tmp/prompt-align.log 2>&1 & sleep 1)
prompt-align push /path/to/job.json
open "<url>"
prompt-align wait <jobId> --timeout 4h
Never end a turn with a live page and no armed wait: a brief nobody is waiting for is a
dead end. When the wait exits 0, its stdout is the result JSON and Phase 3 starts
immediately — no confirmation prompt, no summary-and-stop.
The server binds 127.0.0.1 only and every URL carries a token; do not expose the page
beyond the machine without the human's say-so.
Phase 2½ — reach the human on their phone (optional)
If the operator has set up a Tailscale serve route to this server and a tfy push
server (https://github.com/johnlindquist — tfy: a self-hosted Web Push PWA), deliver
the brief to the human wherever they are:
- Build the phone URL by swapping the loopback origin for the serve route, keeping
path and token (the page derives its API base from the request Host, so
generation and submit work from any device on the tailnet):
PHONE_URL="https://<machine>.<tailnet>.ts.net:<port>/j/<jobId>?t=<token>"
- Notify, if
~/.config/tfy/env exists (defines TFY_URL, TFY_TOKEN, TFY_TOPIC):
source ~/.config/tfy/env 2>/dev/null && curl -s -X POST "$TFY_URL/$TFY_TOPIC" \
-H "Authorization: Bearer $TFY_TOKEN" \
-H "Title: Align: <task in a few words>" -H "Click: $PHONE_URL" \
-d "Your brief is ready — tap to review, steer, and send." >/dev/null
- The armed
wait is unchanged — a submission from any device resumes the agent.
Phase 3 — Implement from the result
Continue from the persisted file — <data-dir>/jobs/<jobId>/result.json (default data
dir ~/.prompt-align) — never from conversation memory.
result.brief is the contract. Its sentences ARE the selected reading, scope,
done-means, and cadence. Implement the smallest coherent version that satisfies them.
sentences[].source tells you how much the human engaged: edited and generated
sentences are strong signals — quote them back in the report. steeringUsed holds the
human's own words; treat them as constraints.
- Honor the cadence sentence literally: if it says check in early, check in early.
- The result authorizes local edits and verification for the selected work only. It does
not authorize pushing, deploying, or publishing unless a sentence explicitly says so.
- Verify at the layer of each claim in the PROOF sentence, then report: map every
sentence of the brief to what was done, and cite the result.json path.
Failure handling
wait timed out (exit 1): implement nothing. Report the page URL, the jobId, and the
exact resume command (prompt-align wait <jobId> --timeout 4h).
- Server died:
prompt-align serve again — jobs and results are on disk and survive.
- A result that exists is final; to re-run alignment, push a NEW jobId rather than
overwriting (submit refuses duplicates without
?replace=1 for exactly this reason).
Job template
A ready-to-edit starter lives next to this file: template.job.json.