Research, 2026-10-17. Question from the owner: a system where a loose
inspiration gets researched into a public domain-expertise document, then turned (with a
human-supplied goal) into a private implementation plan, then built — each step an
independent job, with unbounded waits between steps. This report answers the owner's ten
questions and proposes a concrete design. It is a research document, not an
implementation plan: it contains no folder hierarchies, hooks, or build steps that
are system-unique beyond pointing at the existing tools it would ride. Companion
documents: ~/manifest.md, ~/research/fresh-deno-cockpit-research.html,
~/implementation-plans/checklist-impl-plan.html (the house patterns this borrows),
~/projects/orbit/README.md, the relay skill.
pipeline CLI, a publish script,
and one cockpit card.Each idea moves through five named states, and each state is crossed by exactly one kind of job — an independent, bounded agent session:
| State | Job that entered it | Input | Produces | Human moment |
|---|---|---|---|---|
new | none — the owner speaks it | a loose idea | the pipeline's ledger entry | this is the seed; the only required input |
researched | research job | the inspiration + the web | public expertise doc | owner reads/approves the doc |
planned | plan job | expertise doc + owner's goal + the system (manifest, AGENTS.md, live stack) | private impl plan, incl. modifications to existing parts | owner reads/approves the plan |
built | build job (orbit) | the plan | commit(s) in the project repo, passing suite | glance at the diff before verify |
shipped | verify job | the built code + the plan's acceptance criteria | off/on drill, zero-context agent test (AGENTS rule 10), manifest row, mem note, checklist item closed | owner uses the thing |
Three properties make this work across sessions that may be days apart:
relay, the relay-runner profile)
already does with its baton. The pipeline ledger plays the relay roles: ledger = stable
agreement, per-job output = baton, log.md = history.Three distinct homes, one rule: the filesystem is the source of truth (same ruling orbit's checklist item 2.11 carries — per-task dirs, filesystem is SoT).
~/research/ the PUBLIC tree — domain expertise ├── index.html generated catalog (never hand-edited) └── searxng-self-host/ ├── doc.html the readable document (self-contained) ├── sources.md source list with access dates └── assets/ images, diagrams — only if doc.html needs them (sibling) searxng-self-host-2026-10-17.tar.gz portable bundle ~/implementation-plans/ the PRIVATE tree — exactly what's there today └── searxng-self-host-impl-plan.html (+ its reference tarball, existing pattern) ~/pipelines/ the LEDGER — one small file per idea (the new part) └── searxng-self-host/meta.json ~/pipeline-jobs/ job WORKSPACES — ephemeral, disposable once done └── p002-searxng-self-host-research/ prompt.md output.md progress.md log.md lock meta.json
The key separations:
~/research/. Nothing in ~/pipelines/,
~/pipeline-jobs/, or ~/implementation-plans/ ever leaves the box.
Keeping private and public in separate trees makes the leak a non-event: you cannot
publish the plan by accident, because the plan is not in the tree you publish.searxng-self-host names the research folder, the plan file, the ledger, and the
job workspaces. A human can follow an idea from idea to artifact to code by name alone.
Date suffixes sit on *versions* (-2026-10-17) so a re-research is a new
artifact, not a mutation of the old one — the old one stays readable, same way mem keeps
its raw rows under the rollup.Format: self-contained single-file HTML. One decision, all ten questions get easier. It is already the house standard (AGENTS rule 8): renders identically by an agent reading it, by the owner on a phone, by a browser on the tailnet, or on the public web — and it requires zero tooling to view, which is what matters for a public audience who will not clone a repo.
| Candidate | Viewable in browser | Diffs in git | Public-publish cost | Agent-readable | House precedent |
|---|---|---|---|---|---|
| HTML (chosen) | yes, zero tooling | yes | copy the file | yes (plain text) | all existing research + plan docs |
| Markdown + local renderer | no | best | build step | yes | orbit docs (interior, fine there) |
| SQLite + query UI | no | no | generator needed | awkward | mem — right for ledger rows, wrong for prose |
The one thing Markdown does better is diffing. The trade is acceptable: expertise docs
change rarely (re-research = new file, per §2), and where diff matters — code — git already
does it natively. For documents that genuinely have many multi-kilobyte edits across a
long life, the doc becomes a folder: doc.html is the assembled reader
view; large tables (source lists, benchmarks) live in sibling files it links. That covers
"what a doc needs more than one file" (§7) without breaking the one-file rule for the
95% case — most house documents will always be one file, because the boilerplate keeps
them small.
What goes in an expertise doc (per the owner's spec, made concrete):
sources.md in the folder: URL, publisher, access
date, one-line claim-each. Dates are the currency here: sources rot, and the reader (agent
or human) is trained to check dates, not trust the text (same no-supersedence rule as mem).The no system-specific content rule gets a concrete enforcement: the research job's prompt ends with a checklist the agent verifies before finishing — no paths, no daemon names, no hostnames, no machine specs that aren't generic. (orbit checklist item 2.16, "every lane prompt ends with verification instructions", is the existing pattern.) A doc that fails its own audit gets flagged, not shipped.
Because the document is a self-contained HTML file, publishing is a copy:
pipeline publish # regenerates index.html, copies ~/research/* → ~/research-public/
# (tar.gz alongside; a single rsync/ftp of one folder to the host)
Three viable hosts, in order of friction:
publish
rsyncs into a git worktree, commits, pushes. Pages serves the folder; the generated
index.html is the catalog. The box gets a git remote for the first time in
the system — a new hook that needs a manifest row the moment it lands (AGENTS rule 6).
Note this deliberately differs from the no-remotes-standing policy in manifest §5, which
covers code repos as backup; this one publishes prose, not code, and the owner rules on it./research/… served from
the box. Instant, no remote, but the tailnet-identity gate means it is visible to the
owner's devices, not the open web — fine if "public" really means "me, on any device".The generated index.html lists each doc with its title, date, and a
one-paragraph abstract (read from the doc's <title> + first
<p>), newest first — same newest-first convention as ~/tapes/INDEX.md.
Regenerating it is derivable work like mem sum: you can rm and
rebuild it any time, because the docs are the source.
Files. Specifically: one JSON file per idea. SQLite would be the wrong shape.
cat while debugging.~/todo.checklist, ~/flags.txt,
~/tags.txt, and mem is the exception (SQL for high-churn ledger rows). The
pipeline ledger is a small, slowly-changing set of records — the checklist shape, not the
mem shape. cat ~/pipelines/*/meta.json is the honest ls command.~/pipelines/<slug>/meta.json (the ledger) and its artifact folders. No
third place where state can hide — the standing anti-pattern that burned the manifest before.The ledger, concretely:
{
"slug": "searxng-self-host",
"inspiration": "replace the Exa free tier with a local search backend",
"goal": "orbit research jobs get search that costs nothing and can't be rate-limited",
"state": "planned",
"created": 1763323333, // unix epoch seconds (owner 2026-10-17: decisive sort);
// legacy day-strings normalize at read, never rejected
"jobs": [
{"id": 1, "stage": "research", "status": "shipped",
"workspace": "~/pipeline-jobs/p001-searxng-self-host-research/",
"artifact": "~/research/searxng-self-host/doc.html",
"started": "2026-10-17T21:00:00Z", "finished": "2026-10-17T21:14:00Z",
"attempts": 1},
{"id": 2, "stage": "plan", "status": "shipped",
"artifact": "~/implementation-plans/searxng-self-host-impl-plan.html",
"started": "2026-10-17T21:30:00Z", "finished": "2026-10-17T21:44:00Z",
"attempts": 1},
{"id": 3, "stage": "build", "status": "queued", "artifact": null, "attempts": 0}
]
}
That file is the answer to "how do I track an idea end to end": a state field for the chain and a status field per job, with timestamps that a fresh session can read without asking anyone. It is also the cockpit card's data source (§8) and the publish script's input. One file, three consumers — the inverse of the lesson in manifest §5, where state spread over tailscaled, the app, and a note, and one of them was wrong.
They don't get a new queue. orbit is the queue. The research and plan steps are the research lane with a lane-level prompt orbit's checklist already specifies (2.3: 27B-class model + web search tools); the build step is the code lane with the plan as its prompt; the verify step is the test model (checklist 2.8: "build ↔ test model handoff"). What orbit v2 gives the pipeline for free, item by item:
| Pipeline need | orbit checklist item that covers it |
|---|---|
| a queue that survives reboots | 2.1 — per-task dirs, filesystem is SoT |
| one agent at a time, no pileups | 2.2 — worker pulls serially; no KeepAlive |
| don't dispatch into a dead model | 2.3 — health gate, probe before pull |
| don't retry forever when a job is broken | 2.4 — attempt cap N>3 → poisoned, needs human |
| never two agents writing the same artifact | 2.5 — single-writer assertion, fail fast |
| jobs that end with self-check | 2.6 — verification instructions in every prompt |
| clean on/off | 2.7 — orbit start/stop scripts (rule 5) |
| right model per step | 2.8 / 2.9 — lanes as adapters |
| owner can see the queue | 1.2 — the cockpit task-view item |
| cheap signal on friction | 2.13 — friction log; also fed to the punchclock ledger |
So: pipeline new/research/plan/build <slug> resolves to
dispatch_task with a composed prompt (template + inputs), writes the job row
into the ledger, and orbit runs it. When the job finishes, the workspace's output files
land in the artifact home, the ledger row flips to shipped, the chain's state
advances, and — per the owner ruling in checklist 1.2's kin — that is the end of the
automated part. The next step waits for the owner.
focus tool exists). Concretely: pipeline run reads ~/.run/focus.json before any launch; gate on → the job is held (a message, exit 3, the ledger untouched) and relaunches the moment the gate opens; gate off → the detached session is dispatched. The queue persists through the gate, never cancels. In the first pass the launch trigger stays a manual tap in the terminal — the gate decides whether that tap fires. (Whether the focus watch itself should relaunch on flip — auto-drain, the image-studio behavior — is left open for phase 3 with orbit.)pipeline research/plan/build/verify <slug> is a one-shot verb that launches one bounded agent session (no worker, no queue dir, no scheduling); orbit v2 (checklist §2) is the same verbs pointed at a scheduler when it lands. The ledger, artifacts, and handoffs are identical in both — the daemon only replaces timing. This is exactly the right ordering: the parts the owner can inspect (one folder, one HTML doc, one ledger row) come before the parts that run themselves.The orbit pattern already answers this: a job is a directory, and a directory is atomic to the filesystem — there is no "bundle" step, no zip step, no archive to re-attach. The workspace's contents are the bundle:
~/pipeline-jobs/p002-searxng-self-host-research/ prompt.md ← exactly what the agent was told (re-runnable: this is the job, verbatim) output.md ← working draft (markdown is fine here; rendered at the end) sources.md progress.md ← the baton: what's done, what's next, for a successor after a death log.md ← append-only: what the agent tried, what failed, dates lock ← the single-writer token (orbit 2.5) meta.json ← attempt count, state machine for workspace, model, lane
Two moves keep it clean. First, the finished bundle is the artifact folder
(§2), which carries the stable name in the stable home; the workspace gets tarred to a
sibling .tar.gz (the existing pattern — every house implementation plan ships
with its reference tarball) and the workspace itself is deleted. The tarball is the
process record; the folder is the product record. Second, the ledger row
points at the folder, never at workspace fragments, so a re-run of a job can never be
ambiguous about which work belonged to it. "One job, many files" then has exactly one
meaning: a path, plus a machine-readable manifest inside it.
Two surfaces, same source. Both read the ledgers; neither can write them (the dashboard observes, the terminal mutates — the standing ruling from the cockpit's task card, checklist 1.2).
pipeline ls — one line per idea, and the stepper is five
cells: new ▸ researched ▸ planned ▸ built ▸ shipped, the current state
highlighted, per-job attempts in the margin, newest chain on top. Identical output
philosophy to checklist ls (open by default, --all for the dead):
$ pipeline ls ▸ searxng-self-host researched(1) planned(1) built[queued] 2026-10-17 voice-baragein researched(1) planned[queued] 2026-10-16 focus-adaptive new 2026-10-15
GET /api/pipelines that does one readdir of
~/pipelines/ (same one-file-read shape as the tasks card's
GET /api/tasks). It shows the pipeline's shape, live: what is moving
what is stuck, at a glance from any device. It can also serve the generated research
catalog (§4) behind the tailnet gate — public and private views, one server.Both surfaces are derivations of the same five ledgers, so they cannot disagree in any way a human would notice — and if a ledger is corrupt, both fail loudly together, which is the failure mode you want.
The box is one 64 GB M4 Mac where the owner lives, so the doctrine is the one already in the house: bounded concurrency, gated admissions, hard caps, clean stops. Each clause maps to something that exists:
| Doctrine | Mechanism |
|---|---|
| Serial execution — at most one agent job at a time, per lane | orbit 2.2 — the worker pulls serially. No "three big sessions at once" pileup is possible by construction, because there is no pool to overdraw. |
| Health gate before pull | orbit 2.3 — probe the model (8-token generation) before dispatching. The 2026-10-11 Ollama death (manifest §5: 13 tasks crash-queued into a re-queue spiral) is the exact case this defuses. |
| Poison cap | orbit 2.4 — attempts > 3 → poisoned, human-decidable. No job can burn the box forever while looking busy. |
| One writer | orbit 2.5 — O_EXCL token at workspace start; a second writer fails fast instead of corrupting an artifact. |
| Model budget | lane = adapter (2.8/2.9): research runs the 27B-class, build runs the lighter model, never both loaded. Two 27B-class models simultaneously is the one thing that would actually squeeze 64 GB into trouble; the serial lane + per-lane-model assignment makes "both" unrepresentable. |
| Respect for the owner | the focus gate already owns "is the box in deep use?": job admission checks it (punchclock/focus, already live under launchd). No job launches that the owner's active session would feel; pipeline stop / orbit's off-script (2.7) drains. |
| Everything pausable, cleanly | rule 5: off/on tested at build time. A pipeline job can be killed mid-flight and re-queued with its progress.md intact — the death is boring by design. |
| Signal, not silence | orbit 2.13 friction log + punchclock ledger rows per stage transition. If the box ever does get loaded, the ledger shows where, and mem gets told (its own off/on and cadence discipline is already the model: no KeepAlive). |
The unsaid clause, stated now: the research and plan jobs are the cheap ones — a few minutes of 27B inference and some web fetches. The build job is the expensive one, and its budget is exactly the project's test suite + the off/on drill, both bounded and existing. Nothing in this pipeline can take the box down, because nothing in it runs unbounded in the background: the only daemons involved (Ollama, orbit worker, focus watch) all have manifest rows with named stops.
Piece by piece, the new system is a ledger, a lane prompt, a script, and a card:
| Part | What it is | What exists it rides on |
|---|---|---|
pipeline CLI (the only real new tool) | new / research / plan / build / verify / ls / publish / stop — writes the ledger, composes the prompt, launches one bounded session per step (orbit becomes the executor when it lands), renders the stepper | checklist/flags/mem as the pattern (one state file, atomic writes, exit-code doctrine 0/1/2, env overrides, self-resolving ~/bin symlink); the house HTML boilerplate as the doc template |
| prompt templates | three files: research.md, plan.md, build.md, each ending in its verification checklist | orbit 2.6 / 2.8 / 2.9 lane shapes; the relay skill's baton/compact-state discipline for the plan and build prompts |
| publish script | regenerate index.html + rsync/tar the research tree | the generated-INDEX convention of ~/tapes; the tarball-as-bundle convention of ~/implementation-plans/ |
| cockpit card | read-only stepper per chain + the research catalog behind the tailnet gate | the tasks card's exact shape (checklist 1.2): GET /api/…, one file read, derive in define.page |
| orbit v2 | the scheduler — last, when the owner points at it | …is the 13-item checklist; the pipeline's already-trusted verbs are what it executes |
Sequencing, as ruled (2026-10-17): (1) the pipeline CLI with
one-shot execution — writes the ledger, launches the session, lands the artifact;
(2) publish + cockpit card, each an afternoon and independent;
(3) orbit v2 as written in its checklist — the same verbs start reading
from the queue, and running chains through it becomes the daemon's proof of concept.
Nothing here needs a new service, a new port, or a new launchd job in the first two
phases — the pipeline CLI is on-call, like mem/tags/checklist/flags. The
manifest gets one new row when orbit v2 lands, not a fleet.
| Decision | Ruling |
|---|---|
| The queue daemon. | No daemon in the first pass. Every step initiated manually; the daemon is the last step, adopted once confidence in the system is built. (§6 carries the operative form.) |
| Public host. | Deferred. Undecided for this pass. The research tree and publish script are host-agnostic, so the ruling can land later without design rework; the manifest row writes itself when it does. |
| First chain. | SearXNG self-host (checklist 5.4) — a genuine web research exercise, a real and testable build step, and it lands a named project. |
| Slug convention. | As specified. The slug names the chain; the date suffix rides on re-research versions; pNNN counters the jobs. |
Consequence for scope of the first build: the pipeline CLI (one-shot
verbs), the three prompt templates, the ledger writer, and the SearXNG research job.
Everything else waits its phase.
One file, zero JS, dark mode, phone-readable per house rule 8.
Companion: ~/implementation-plans/ — the plan job for whichever chain
the owner releases first lands there as <slug>-impl-plan.html + tarball.
Written 2026-10-17.