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",
"alternatives": [
"<recommended scope sentence, e.g. cover the obvious edge cases — nothing more>",
"<proof-only sentence>",
"<fully-polished sentence>" ] },
{ "key": "proof", "name": "PROOF", "detail": "done means",
"alternatives": [
"<observable success signal + how it will be verified>",
"<weaker alternative, stated honestly>" ] },
{ "key": "cadence", "name": "CADENCE", "detail": "check-ins",
"alternatives": [
"<recommended check-in cadence>",
"<show-first-slice sentence>",
"<run-unattended sentence>" ] }
]
}
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
# 1. ensure a server is running (idempotent — check first)
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)
# 2. push the job — prints { jobId, url } with the token baked into the url
prompt-align push /path/to/job.json
# 3. hand the url to the human (open it locally, or send it however you reach them)
open "<url>"
# 4. ARM THE WAIT BEFORE ENDING THE TURN — as a background task, never foreground
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.
1---2name: align3description: 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>.4---56# Align (prompt-align edition)78`/align <task>` is a three-phase contract between an agent and a human, mediated by9[prompt-align](https://github.com/johnlindquist/prompt-align):10111. **Align** — the agent inspects the code, finds the genuine forks, and publishes ONE12 brief page where every decision is an editable sentence.132. **Edit** — the human picks ghosts, types over anything, and steers the AI with14 `// comments`. Sending the brief saves `result.json`.153. **Implement** — the saved result is standing authorization. Implement, verify, and16 report to completion. Never re-ask "should I proceed?" after the result exists.1718The invocation itself is the human's opt-in to all three phases.1920## Requirements2122- [Bun](https://bun.sh) and a checkout of prompt-align. Resolve its location in this order:23 1. `$PROMPT_ALIGN_DIR` if set24 2. `~/dev/prompt-align` or `~/prompt-align` if present25 3. otherwise clone it: `git clone https://github.com/johnlindquist/prompt-align ~/prompt-align`26- Call the CLI as `bun <dir>/src/server.ts …` (referred to as `prompt-align …` below).2728## Phase 1 — Inspect, then compose the job29301. **Inspect before asking.** Read the relevant code, config, and logs first. Never put a31 question on the page that the repository can answer. Every alternative must be grounded32 in what actually exists.332. **Find the genuine forks.** A fork is real only when two readings of the prompt lead to34 materially different work. If there is no real fork in a section, still fill the slot —35 one honest recommended sentence plus one clearly-worse alternative keeps the human's36 veto cheap.373. **Compose the job** — four slots, each a complete sentence the human can keep, replace,38 edit, or steer. Put the **recommended sentence first** in every slot. Put your39 inspection findings in `context` so the page is decidable without another window.4041```json42{43 "jobId": "align-<short-task-slug>-<4 random hex>",44 "title": "Align: <the task in a few words>",45 "seed": "<the user's task prompt, one line>",46 "context": "<what you found during inspection: key files, current behavior, constraints>",47 "slots": [48 { "key": "reading", "name": "READING", "detail": "what to build",49 "alternatives": [50 "<recommended reading, as a build-this sentence>",51 "<plausible bigger/smaller reading, stated fairly>" ] },52 { "key": "scope", "name": "HOW FAR", "detail": "quality bar",53 "alternatives": [54 "<recommended scope sentence, e.g. cover the obvious edge cases — nothing more>",55 "<proof-only sentence>",56 "<fully-polished sentence>" ] },57 { "key": "proof", "name": "PROOF", "detail": "done means",58 "alternatives": [59 "<observable success signal + how it will be verified>",60 "<weaker alternative, stated honestly>" ] },61 { "key": "cadence", "name": "CADENCE", "detail": "check-ins",62 "alternatives": [63 "<recommended check-in cadence>",64 "<show-first-slice sentence>",65 "<run-unattended sentence>" ] }66 ]67}68```6970Slot rules (enforced by the server): slug keys, names ≤ 24 chars, 1-9 alternatives of71≤ 400 chars each. Add extra slots (max 12) only for forks that genuinely exist —72constraints, migrations, naming — never filler.7374## Phase 2 — Serve, push, deliver, wait7576```bash77# 1. ensure a server is running (idempotent — check first)78curl -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 \79 || (nohup prompt-align serve --port auto >/tmp/prompt-align.log 2>&1 & sleep 1)8081# 2. push the job — prints { jobId, url } with the token baked into the url82prompt-align push /path/to/job.json8384# 3. hand the url to the human (open it locally, or send it however you reach them)85open "<url>"8687# 4. ARM THE WAIT BEFORE ENDING THE TURN — as a background task, never foreground88prompt-align wait <jobId> --timeout 4h89```9091Never end a turn with a live page and no armed wait: a brief nobody is waiting for is a92dead end. When the wait exits 0, its stdout is the result JSON and Phase 3 starts93immediately — no confirmation prompt, no summary-and-stop.9495The server binds `127.0.0.1` only and every URL carries a token; do not expose the page96beyond the machine without the human's say-so.979899## Phase 2½ — reach the human on their phone (optional)100101If the operator has set up a Tailscale serve route to this server and a tfy push102server (https://github.com/johnlindquist — tfy: a self-hosted Web Push PWA), deliver103the brief to the human wherever they are:1041051. Build the phone URL by swapping the loopback origin for the serve route, keeping106 path and token (the page derives its API base from the request Host, so107 generation and submit work from any device on the tailnet):108 `PHONE_URL="https://<machine>.<tailnet>.ts.net:<port>/j/<jobId>?t=<token>"`1092. Notify, if `~/.config/tfy/env` exists (defines TFY_URL, TFY_TOKEN, TFY_TOPIC):110111```bash112source ~/.config/tfy/env 2>/dev/null && curl -s -X POST "$TFY_URL/$TFY_TOPIC" \113 -H "Authorization: Bearer $TFY_TOKEN" \114 -H "Title: Align: <task in a few words>" -H "Click: $PHONE_URL" \115 -d "Your brief is ready — tap to review, steer, and send." >/dev/null116```1171183. The armed `wait` is unchanged — a submission from any device resumes the agent.119120## Phase 3 — Implement from the result121122Continue from the persisted file — `<data-dir>/jobs/<jobId>/result.json` (default data123dir `~/.prompt-align`) — never from conversation memory.124125- `result.brief` is the contract. Its sentences ARE the selected reading, scope,126 done-means, and cadence. Implement the smallest coherent version that satisfies them.127- `sentences[].source` tells you how much the human engaged: `edited` and `generated`128 sentences are strong signals — quote them back in the report. `steeringUsed` holds the129 human's own words; treat them as constraints.130- Honor the cadence sentence literally: if it says check in early, check in early.131- The result authorizes local edits and verification for the selected work only. It does132 not authorize pushing, deploying, or publishing unless a sentence explicitly says so.133- Verify at the layer of each claim in the PROOF sentence, then report: map every134 sentence of the brief to what was done, and cite the result.json path.135136## Failure handling137138- `wait` timed out (exit 1): implement nothing. Report the page URL, the jobId, and the139 exact resume command (`prompt-align wait <jobId> --timeout 4h`).140- Server died: `prompt-align serve` again — jobs and results are on disk and survive.141- A result that exists is final; to re-run alignment, push a NEW jobId rather than142 overwriting (submit refuses duplicates without `?replace=1` for exactly this reason).143144## Job template145146A ready-to-edit starter lives next to this file: `template.job.json`.