Skip to content

Instantly share code, notes, and snippets.

@jakkaj
Created July 22, 2026 00:21
Show Gist options
  • Select an option

  • Save jakkaj/c623c7c37abefcbf3b54a0c77cc34f44 to your computer and use it in GitHub Desktop.

Select an option

Save jakkaj/c623c7c37abefcbf3b54a0c77cc34f44 to your computer and use it in GitHub Desktop.
pij — the deterministic layer (concept tree field guide)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>pij — the deterministic layer</title>
<style>
:root{
--paper:#F3F6F4;
--card:#FFFFFF;
--ink:#1E2724;
--muted:#5D6C66;
--line:#D9E1DD;
--chipbg:#EEF3F0;
--tmux:#A9C25B;
--tmux-ink:#1F2612;
--c-nodes:#15806F;
--c-struct:#3D5CAB;
--c-state:#9A6D14;
--c-platform:#4C7C33;
--c-coord:#7C4BA0;
--c-comms:#B05330;
--c-obs:#45688C;
--c-ops:#94454F;
--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
--serif:Charter,"Iowan Old Style","Palatino Linotype",Georgia,serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
@media (prefers-reduced-motion: reduce){
html{scroll-behavior:auto}
*{transition:none !important}
}
body{
background:var(--paper);
color:var(--ink);
font-family:var(--serif);
font-size:17px;
line-height:1.6;
}
a{color:inherit}
a:focus-visible, .tmuxbar a:focus-visible{
outline:2px solid var(--c-struct);
outline-offset:2px;
border-radius:2px;
}
/* ── tmux status bar as navigation ─────────────────────────── */
.tmuxbar{
position:sticky;top:0;z-index:10;
background:var(--tmux);color:var(--tmux-ink);
font-family:var(--mono);font-size:12.5px;
display:flex;align-items:center;gap:0;
padding:5px 14px;
overflow-x:auto;white-space:nowrap;
border-bottom:1px solid #8FA649;
}
.tmuxbar .sess{font-weight:700;margin-right:10px}
.tmuxbar a{
text-decoration:none;padding:2px 7px;border-radius:3px;
color:var(--tmux-ink);
}
.tmuxbar a:hover{background:rgba(0,0,0,.14)}
.tmuxbar .right{margin-left:auto;opacity:.75;padding-left:16px}
/* ── page frame ────────────────────────────────────────────── */
.wrap{max-width:1000px;margin:0 auto;padding:48px 24px 80px}
header.masthead{margin-bottom:36px}
.masthead h1{
font-family:var(--mono);font-size:clamp(26px,4.5vw,40px);
font-weight:700;letter-spacing:-.02em;line-height:1.15;
}
.masthead h1 .dim{color:var(--muted);font-weight:400}
.masthead .lede{
max-width:64ch;margin-top:14px;font-size:18px;color:var(--ink);
}
.masthead .lede em{font-style:italic}
.principle{
margin-top:22px;
font-family:var(--mono);font-size:13.5px;line-height:1.9;
border:1px solid var(--line);border-left:3px solid var(--ink);
background:var(--card);border-radius:6px;
padding:14px 18px;
overflow-x:auto;
}
.principle b{font-weight:700}
.principle .k{display:inline-block;min-width:5.5ch;color:var(--muted)}
/* ── the tree (hero) ───────────────────────────────────────── */
.treecard{
background:var(--card);border:1px solid var(--line);border-radius:8px;
margin:34px 0 10px;
overflow:hidden;
}
.treecard .cap{
font-family:var(--mono);font-size:12px;color:var(--muted);
padding:9px 16px;border-bottom:1px solid var(--line);
display:flex;gap:8px;align-items:center;
}
.treecard .cap::before{content:"$";color:var(--c-platform);font-weight:700}
.treecard pre{
font-family:var(--mono);font-size:13px;line-height:1.75;
padding:18px 20px 22px;overflow-x:auto;
}
pre .g{color:#9AA8A2} /* box-drawing glyphs */
pre .v{color:var(--muted)} /* verbs, dimmed */
pre a{font-weight:700;text-decoration:none}
pre a:hover{text-decoration:underline}
.fn{ }
.fam-nodes {color:var(--c-nodes)}
.fam-struct {color:var(--c-struct)}
.fam-state {color:var(--c-state)}
.fam-platform {color:var(--c-platform)}
.fam-coord {color:var(--c-coord)}
.fam-comms {color:var(--c-comms)}
.fam-obs {color:var(--c-obs)}
.fam-ops {color:var(--c-ops)}
.treenote{font-size:14.5px;color:var(--muted);margin-bottom:8px;font-style:italic}
/* ── sections ─────────────────────────────────────────────── */
section.family{margin-top:56px;scroll-margin-top:56px}
.famhead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:6px}
.famhead .swatch{
width:13px;height:13px;border-radius:3px;flex:none;align-self:center;
}
.famhead h2{
font-family:var(--mono);font-size:19px;font-weight:700;
text-transform:lowercase;letter-spacing:.01em;
}
.famhead .gloss{font-family:var(--serif);font-style:italic;color:var(--muted);font-size:16px}
.famintro{max-width:70ch;color:var(--ink);margin:6px 0 18px;font-size:16.5px}
.card{
background:var(--card);
border:1px solid var(--line);
border-left-width:3px;
border-radius:6px;
padding:16px 20px 15px;
margin-bottom:14px;
}
.card .row1{
display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:6px;
}
.card h3{font-family:var(--mono);font-size:15.5px;font-weight:700}
.store{
font-family:var(--mono);font-size:11px;color:var(--muted);
background:var(--chipbg);border:1px solid var(--line);border-radius:4px;
padding:2px 7px;white-space:nowrap;
}
.verbs{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 10px}
.verb{
font-family:var(--mono);font-size:11.5px;
border:1px solid var(--line);border-radius:4px;
padding:2px 7px;background:var(--paper);
}
.card p{font-size:15.5px;max-width:74ch}
.card p + p{margin-top:8px}
.invariant{
margin-top:10px;font-size:14px;
display:flex;gap:8px;align-items:baseline;max-width:74ch;
}
.invariant .tag{
font-family:var(--mono);font-size:10.5px;font-weight:700;
text-transform:uppercase;letter-spacing:.06em;
flex:none;padding:1px 6px;border-radius:3px;color:#fff;
}
.invariant em{font-style:italic}
/* per-family accents */
#nodes .card{border-left-color:var(--c-nodes)} #nodes .swatch{background:var(--c-nodes)} #nodes .tag{background:var(--c-nodes)}
#structure .card{border-left-color:var(--c-struct)} #structure .swatch{background:var(--c-struct)} #structure .tag{background:var(--c-struct)}
#state .card{border-left-color:var(--c-state)} #state .swatch{background:var(--c-state)} #state .tag{background:var(--c-state)}
#platform .card{border-left-color:var(--c-platform)} #platform .swatch{background:var(--c-platform)} #platform .tag{background:var(--c-platform)}
#coordination .card{border-left-color:var(--c-coord)} #coordination .swatch{background:var(--c-coord)} #coordination .tag{background:var(--c-coord)}
#comms .card{border-left-color:var(--c-comms)} #comms .swatch{background:var(--c-comms)} #comms .tag{background:var(--c-comms)}
#observation .card{border-left-color:var(--c-obs)} #observation .swatch{background:var(--c-obs)} #observation .tag{background:var(--c-obs)}
#ops .card{border-left-color:var(--c-ops)} #ops .swatch{background:var(--c-ops)} #ops .tag{background:var(--c-ops)}
/* state axes mini-table */
.axes{width:100%;border-collapse:collapse;margin:4px 0 10px;font-size:14px}
.axes td,.axes th{
border:1px solid var(--line);padding:7px 12px;text-align:left;vertical-align:top;
}
.axes th{font-family:var(--mono);font-size:11.5px;background:var(--chipbg);font-weight:700;white-space:nowrap}
.axes .vals{font-family:var(--mono);font-size:12px;white-space:nowrap}
.axes-wrap{overflow-x:auto}
/* ── closing panels ───────────────────────────────────────── */
.nouns{
margin-top:64px;padding-top:28px;border-top:1px solid var(--line);
}
.nouns h2{font-family:var(--mono);font-size:17px;margin-bottom:4px}
.nouns .sub{color:var(--muted);font-size:15.5px;margin-bottom:14px;max-width:70ch}
.nounrow{display:flex;flex-wrap:wrap;gap:8px}
.noun{
font-family:var(--mono);font-size:13px;font-weight:700;
background:var(--card);border:1px solid var(--line);border-radius:5px;
padding:6px 12px;
}
.noun small{display:block;font-weight:400;font-size:10.5px;color:var(--muted);margin-top:1px}
.split{
display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:26px;
}
@media (max-width:640px){.split{grid-template-columns:1fr}}
.split .half{
background:var(--card);border:1px solid var(--line);border-radius:6px;
padding:16px 18px;
}
.split h3{font-family:var(--mono);font-size:13px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px}
.split .advisory h3{color:var(--c-platform)}
.split .strict h3{color:var(--c-comms)}
.split p{font-size:14.5px}
.split ul{margin:8px 0 0 18px;font-size:14.5px}
.split li{margin-bottom:4px}
.split code{font-family:var(--mono);font-size:12.5px}
footer{
margin-top:60px;padding-top:18px;border-top:1px solid var(--line);
font-family:var(--mono);font-size:11.5px;color:var(--muted);
display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;
}
code{font-family:var(--mono);font-size:.88em;background:var(--chipbg);padding:1px 5px;border-radius:3px}
</style>
</head>
<body>
<nav class="tmuxbar" aria-label="Sections">
<span class="sess">[pij]</span>
<a href="#nodes">0:nodes</a>
<a href="#structure">1:structure</a>
<a href="#state">2:state</a>
<a href="#platform">3:platform</a>
<a href="#coordination">4:coordination</a>
<a href="#comms">5:comms</a>
<a href="#observation">6:observation</a>
<a href="#ops">7:ops</a>
<span class="right">"the deterministic layer" 22-Jul-26</span>
</nav>
<div class="wrap">
<header class="masthead">
<h1>pij <span class="dim">—</span> the deterministic layer</h1>
<p class="lede">
pij coordinates agent sessions — pi, claude, copilot, codex — living in tmux panes.
Everything it knows is a plain file under <code>~/.pij</code>: no database, no sockets.
This page maps what pij can do <em>deterministically</em> — the record-keeping the
agents themselves never have to be trusted with.
</p>
<div class="principle">
<div><span class="k">noun</span> = a file store under <b>~/.pij</b></div>
<div><span class="k">verb</span> = a deterministic transition on exactly one store</div>
<div><span class="k">clock</span> = the daemon tick — and <b>reads never need the daemon</b></div>
</div>
</header>
<p class="treenote">The whole map. Every colored name links to its card below.</p>
<div class="treecard">
<div class="cap">pij tree --concepts</div>
<pre>
pij
<span class="g">│</span>
<span class="g">├──</span> <a class="fam-nodes" href="#nodes">NODES &amp; IDENTITY</a> <span class="v">who exists</span>
<span class="g">│ ├──</span> <span class="fam-nodes">session / node</span> <span class="v">spawn · adopt · close · list · node show</span>
<span class="g">│ ├──</span> <span class="fam-nodes">identity ledger</span> <span class="v">phonehome · canary</span>
<span class="g">│ └──</span> <span class="fam-nodes">spawn expectation</span> <span class="v">durable launch intent</span>
<span class="g">│</span>
<span class="g">├──</span> <a class="fam-struct" href="#structure">STRUCTURE</a> <span class="v">how they relate</span>
<span class="g">│ ├──</span> <span class="fam-struct">forest</span> <span class="v">tree · link</span>
<span class="g">│ └──</span> <span class="fam-struct">focus</span> <span class="v">focus save · launch</span>
<span class="g">│</span>
<span class="g">├──</span> <a class="fam-state" href="#state">STATE</a> <span class="v">what condition it's in</span>
<span class="g">│ ├──</span> <span class="fam-state">three measured axes</span> <span class="v">activity · liveness · systemState</span>
<span class="g">│ └──</span> <span class="fam-state">one declared axis</span> <span class="v">state set · clear · verify</span>
<span class="g">│</span>
<span class="g">├──</span> <a class="fam-platform" href="#platform">WORK PLATFORM</a> <span class="v">project-related</span>
<span class="g">│ ├──</span> <span class="fam-platform">project</span> <span class="v">create · list · show · set</span>
<span class="g">│ ├──</span> <span class="fam-platform">stream</span> <span class="v">create · close</span>
<span class="g">│ ├──</span> <span class="fam-platform">fence</span> <span class="v">set · show — notify-only</span>
<span class="g">│ ├──</span> <span class="fam-platform">assignment / task</span> <span class="v">task set</span>
<span class="g">│ └──</span> <span class="fam-platform">anomalies</span> <span class="v">derived safety queries</span>
<span class="g">│</span>
<span class="g">├──</span> <a class="fam-coord" href="#coordination">COORDINATION</a> <span class="v">shared-resource governance</span>
<span class="g">│ ├──</span> <span class="fam-coord">baton</span> <span class="v">define · request · grant · return · reclaim</span>
<span class="g">│ └──</span> <span class="fam-coord">prime</span> <span class="v">set · retire · unset</span>
<span class="g">│</span>
<span class="g">├──</span> <a class="fam-comms" href="#comms">COMMUNICATION</a> <span class="v">messages with receipts</span>
<span class="g">│ ├──</span> <span class="fam-comms">message + inbox</span> <span class="v">send · inbox · broadcast · --command</span>
<span class="g">│ ├──</span> <span class="fam-comms">delivery ownership</span> <span class="v">push (pi) · push (daemon) · pull</span>
<span class="g">│ ├──</span> <span class="fam-comms">dispatch / ack</span> <span class="v">byte-verified packet receipts</span>
<span class="g">│ └──</span> <span class="fam-comms">file watch</span> <span class="v">watch · unwatch</span>
<span class="g">│</span>
<span class="g">├──</span> <a class="fam-obs" href="#observation">OBSERVATION</a> <span class="v">append-only truth</span>
<span class="g">│ ├──</span> <span class="fam-obs">peer events</span> <span class="v">tail · state — per session</span>
<span class="g">│ └──</span> <span class="fam-obs">spine</span> <span class="v">append · events · render — global</span>
<span class="g">│</span>
<span class="g">└──</span> <a class="fam-ops" href="#ops">SUPERVISION &amp; OPS</a> <span class="v">keeping it honest</span>
<span class="g">├──</span> <span class="fam-ops">daemon</span> <span class="v">the only clock</span>
<span class="g">├──</span> <span class="fam-ops">watchdog</span> <span class="v">per-session supervision</span>
<span class="g">├──</span> <span class="fam-ops">agent packs</span> <span class="v">declarative one-shot workers</span>
<span class="g">└──</span> <span class="fam-ops">bridges</span> <span class="v">operator inbox · telegram</span>
</pre>
</div>
<!-- ───────────────────────── 0 · nodes ───────────────────────── -->
<section class="family" id="nodes">
<div class="famhead"><span class="swatch"></span><h2>nodes &amp; identity</h2><span class="gloss">who exists</span></div>
<p class="famintro">
Before anything can be coordinated, pij has to know what's out there. This family answers
one question and never guesses: <em>which terminals hold which agent sessions, and can we
prove it?</em>
</p>
<div class="card">
<div class="row1"><h3>session / node</h3><span class="store">~/.pij/&lt;id&gt;.json · ~/.pij/&lt;id&gt;/</span></div>
<div class="verbs"><span class="verb">spawn</span><span class="verb">adopt</span><span class="verb">close</span><span class="verb">whoami</span><span class="verb">list</span><span class="verb">sessions</span><span class="verb">node show</span><span class="verb">path</span></div>
<p>
Every agent terminal pij knows about gets a descriptor file and a data directory. The
descriptor carries the memorable id (<code>pij-reasonable-dove</code> — never renamed),
the harness it runs on, its pid, and its tmux address (<code>paneId</code> +
<code>windowId</code>). That address is what lets any UI jump straight from a node to
its live terminal.
</p>
<div class="invariant"><span class="tag">invariant</span><span>A session can never <code>close</code> itself, and death is recorded with evidence (<em>pid-missing</em>, pane gone) — never assumed.</span></div>
</div>
<div class="card">
<div class="row1"><h3>identity ledger</h3><span class="store">~/.pij/identities/ · by-native · by-pij</span></div>
<div class="verbs"><span class="verb">phonehome</span><span class="verb">canary</span></div>
<p>
Proof that a pane really is the session — and the model — it claims to be.
<code>phonehome</code> confirms a pending binding from inside the session;
<code>canary</code> dispatches a nonce packet and attaches pass evidence. The ledger is
indexed both ways (native session id ↔ pij id), so "which claude session is this node?"
is a file lookup, not a guess.
</p>
</div>
<div class="card">
<div class="row1"><h3>spawn expectation</h3><span class="store">~/.pij/spawn-expectations/</span></div>
<div class="verbs"><span class="verb">spawn (writes one)</span><span class="verb">daemon reconciles</span></div>
<p>
When you ask for a colleague, pij first writes down what it <em>expects</em> to exist.
The daemon then reconciles that expectation into a bound node when the real session
appears — or reports an honest failure if it never does. Launch intent survives crashes
because it's on disk before anything is running.
</p>
</div>
</section>
<!-- ───────────────────────── 1 · structure ───────────────────────── -->
<section class="family" id="structure">
<div class="famhead"><span class="swatch"></span><h2>structure</h2><span class="gloss">how they relate</span></div>
<p class="famintro">
Sessions form trees: a prime spawns workers, workers spawn helpers. Structure is its own
concept because <em>reporting lines</em> and <em>teardown rights</em> are deliberately not
the same thing.
</p>
<div class="card">
<div class="row1"><h3>forest</h3><span class="store">parentId per descriptor</span></div>
<div class="verbs"><span class="verb">tree</span><span class="verb">tree --global</span><span class="verb">link --parent</span><span class="verb">link --root</span></div>
<p>
<code>tree</code> renders the current repo's forest (or everything with
<code>--global</code>), filterable by liveness, activity, and lifecycle.
<code>link</code> reparents a session with cycle and self-link checks, and every relink
is audited to the spine.
</p>
<div class="invariant"><span class="tag">invariant</span><span>Two distinct edges: <code>effectiveParentId</code> is structure (who reports to whom); <code>spawnedBy</code> is close authorization (who may tear the pane down). Linking never transfers close rights.</span></div>
</div>
<div class="card">
<div class="row1"><h3>focus</h3><span class="store">saved native-session snapshots</span></div>
<div class="verbs"><span class="verb">focus save</span><span class="verb">focus list</span><span class="verb">focus launch</span></div>
<p>
An immutable snapshot of a known-good native session — save it once, fork it on demand.
Instead of re-briefing a fresh agent every time, you launch a copy of one that already
has the context loaded.
</p>
</div>
</section>
<!-- ───────────────────────── 2 · state ───────────────────────── -->
<section class="family" id="state">
<div class="famhead"><span class="swatch"></span><h2>state</h2><span class="gloss">what condition it's in</span></div>
<p class="famintro">
The most opinionated part of pij: three axes are <em>measured</em> and can't be lied
about, and exactly one is <em>declared</em> by agents. Keeping them separate is what makes
"is it actually working?" answerable.
</p>
<div class="card">
<div class="row1"><h3>three measured axes</h3><span class="store">derived from pid · events · daemon ticks</span></div>
<div class="axes-wrap"><table class="axes">
<tr><th>activity</th><td class="vals">working · done</td><td>What the event stream says it's doing right now.</td></tr>
<tr><th>liveness</th><td class="vals">active · stale · dead</td><td>Is the process there, and how old is its last event?</td></tr>
<tr><th>systemState</th><td class="vals">idle · stalled · dead</td><td>The daemon's verdict; every transition is journaled to the spine.</td></tr>
</table></div>
<p>
<code>node show</code> collapses all axes into one display <code>badge</code>, and adds a
live context gauge — current tokens with provenance (<em>claude-transcript</em>), so you
can see a peer nearing its context ceiling from outside.
</p>
</div>
<div class="card">
<div class="row1"><h3>one declared axis — semantic state</h3><span class="store">per-assignment, closed vocabulary</span></div>
<div class="verbs"><span class="verb">state set</span><span class="verb">state clear</span><span class="verb">state verify</span></div>
<p>
The only state an agent gets to <em>say</em> about itself, scoped to an assignment and
drawn from a fixed vocabulary. Clearing a declaration keeps the assignment's history —
nothing is silently forgotten.
</p>
<div class="invariant"><span class="tag">invariant</span><span><em>Done is a claim until verified.</em> <code>state verify</code> stamps <code>verifiedBy</code>; an unverified done shows up in <code>anomalies</code>.</span></div>
</div>
</section>
<!-- ───────────────────────── 3 · platform ───────────────────────── -->
<section class="family" id="platform">
<div class="famhead"><span class="swatch"></span><h2>work platform</h2><span class="gloss">project-related</span></div>
<p class="famintro">
The durable record of <em>what's being worked on</em>: projects, their worktrees, who owns
which paths, and which node holds which task. This is the layer a governance UI would live
on.
</p>
<div class="card">
<div class="row1"><h3>project</h3><span class="store">~/.pij/projects/</span></div>
<div class="verbs"><span class="verb">project create</span><span class="verb">project list</span><span class="verb">project show</span><span class="verb">project set</span></div>
<p>
A named, durable unit of work: kebab slug (collision-resolved), description, an optional
plan path, and a <code>primeId</code> — which session governs it. Created once, updated
by attributed actors.
</p>
</div>
<div class="card">
<div class="row1"><h3>stream</h3><span class="store">worktree allocations under a project</span></div>
<div class="verbs"><span class="verb">stream create</span><span class="verb">stream close</span></div>
<p>
One attributed git-worktree allocation: reserved ordinal, base ref, owner. Closing it
preserves work-in-progress, removes the worktree safely, and leaves a tombstone so the
ordinal's history survives.
</p>
</div>
<div class="card">
<div class="row1"><h3>fence</h3><span class="store">descriptive path ownership</span></div>
<div class="verbs"><span class="verb">fence set --paths</span><span class="verb">fence show --path</span></div>
<p>
Declares which stream owns which paths, with optional shared paths. Ask
<code>fence show --path</code> "who owns this file?" and get a deterministic answer.
</p>
<div class="invariant"><span class="tag">invariant</span><span>Notify-only, by design: overlap is <em>reported, never blocked</em>. Fences inform agents; they don't handcuff them.</span></div>
</div>
<div class="card">
<div class="row1"><h3>assignment / task</h3><span class="store">~/.pij/assignments/asg-*.json</span></div>
<div class="verbs"><span class="verb">task set &lt;node&gt; "…"</span></div>
<p>
<code>task set</code> opens an assignment record and points the node at it. Semantic
state declarations hang off the assignment, not the session — so a node's history of
tasks, each with its own state trail, stays intact across handovers.
</p>
</div>
<div class="card">
<div class="row1"><h3>anomalies</h3><span class="store">derived — no store of its own</span></div>
<div class="verbs"><span class="verb">anomalies</span><span class="verb">--here</span><span class="verb">--project</span></div>
<p>
Read-only safety queries computed from everything above: a node whose declared state
disagrees with its measured axes, a done that was never verified, a hold cleared by
someone who didn't own it. An empty result means the books balance.
</p>
</div>
</section>
<!-- ───────────────────────── 4 · coordination ───────────────────────── -->
<section class="family" id="coordination">
<div class="famhead"><span class="swatch"></span><h2>coordination</h2><span class="gloss">shared-resource governance</span></div>
<p class="famintro">
When many agents share one machine, some things only one of them may touch at a time —
the git index, a deploy, a database file. This family makes that exclusivity a record
instead of a hope.
</p>
<div class="card">
<div class="row1"><h3>baton</h3><span class="store">~/.pij/orchestration/batons/*.json · *.lease</span></div>
<div class="verbs"><span class="verb">define</span><span class="verb">request</span><span class="verb">grant</span><span class="verb">return</span><span class="verb">reclaim</span><span class="verb">list</span><span class="verb">show</span></div>
<p>
An atomic single-holder lease on a named resource, with a purpose queue: requesters say
<em>why</em> they need it, the holder (or an operator) grants discretionarily, and
grants are pushed to the recipient. Blocked time is measured.
</p>
<div class="invariant"><span class="tag">invariant</span><span>A stale holder triggers an <em>alert, never an auto-reclaim</em> — the lease changes hands only by an explicit, attributed act.</span></div>
</div>
<div class="card">
<div class="row1"><h3>prime</h3><span class="store">registry designation on the descriptor</span></div>
<div class="verbs"><span class="verb">prime set</span><span class="verb">prime retire</span><span class="verb">prime unset</span></div>
<p>
Marks which session is the governing seat. One current prime (<code>P</code>), a history
of retired old-primes (<code>O</code>) that keep their record but lose their authority.
Projects point at their prime via <code>primeId</code>.
</p>
</div>
</section>
<!-- ───────────────────────── 5 · comms ───────────────────────── -->
<section class="family" id="comms">
<div class="famhead"><span class="swatch"></span><h2>communication</h2><span class="gloss">messages with receipts</span></div>
<p class="famintro">
Agents talk through durable inboxes, not sockets. The interesting part isn't sending —
it's that <em>delivery ownership is explicit</em> and receipts are part of the message,
so "did they get it?" always has an answer.
</p>
<div class="card">
<div class="row1"><h3>message + inbox</h3><span class="store">~/.pij/&lt;id&gt;/inbox/</span></div>
<div class="verbs"><span class="verb">send</span><span class="verb">send --to … --to …</span><span class="verb">send --command</span><span class="verb">send --wait</span><span class="verb">inbox</span></div>
<p>
One-to-one messages, fan-out broadcasts (one independent result per recipient), or an
allow-listed control command on a peer (<code>compact</code> · <code>new</code> ·
<code>reload</code>). <code>--wait</code> blocks until every send has a terminal receipt.
</p>
</div>
<div class="card">
<div class="row1"><h3>delivery ownership</h3><span class="store">explicit, per harness</span></div>
<div class="verbs"><span class="verb">pi → in-process push</span><span class="verb">tmux claude/copilot/codex → daemon push</span><span class="verb">external → pull</span></div>
<p>
Every inbox has exactly one owner. Pi sessions consume their own inbox in-process
(the extension). Tmux-bound peers get keystrokes pushed by the daemon. Sessions without
tmux pull with <code>pij inbox --wait</code> — and the daemon then never touches that
inbox. No double delivery, no ambiguity about whose job it was.
</p>
</div>
<div class="card">
<div class="row1"><h3>dispatch / ack</h3><span class="store">~/.pij/dispatches/dispatch-*.json</span></div>
<div class="verbs"><span class="verb">dispatch --packet</span><span class="verb">ack --packet-sha</span><span class="verb">canary</span></div>
<p>
For payloads that matter: a packet gets a durable three-state dispatch receipt, and the
recipient acks with the packet's sha-256 — so the record proves the exact bytes arrived.
<code>canary</code> composes this with the identity ledger to verify who and what is on
the other end.
</p>
</div>
<div class="card">
<div class="row1"><h3>file watch</h3><span class="store">per-peer subscriptions</span></div>
<div class="verbs"><span class="verb">watch &lt;glob&gt;</span><span class="verb">unwatch</span><span class="verb">--debounce</span></div>
<p>
A non-pi peer can subscribe to file globs; the daemon injects debounced, coalesced
<code>[file-watch]</code> notices into its pane. Agents that can't watch files still get
told when files change.
</p>
</div>
</section>
<!-- ───────────────────────── 6 · observation ───────────────────────── -->
<section class="family" id="observation">
<div class="famhead"><span class="swatch"></span><h2>observation</h2><span class="gloss">append-only truth</span></div>
<p class="famintro">
Two parallel event planes, both append-only and sequence-numbered — which means any
reader can poll cheaply with <code>--since N</code> and never re-ingest history.
</p>
<div class="card">
<div class="row1"><h3>peer events</h3><span class="store">~/.pij/&lt;id&gt;/events.ndjson</span></div>
<div class="verbs"><span class="verb">tail &lt;id&gt;</span><span class="verb">--since N</span><span class="verb">--type</span><span class="verb">--follow</span><span class="verb">state &lt;id&gt;</span></div>
<p>
What <em>one agent</em> did: its transcript-level activity, seq-numbered. This is how an
expensive reviewer follows a cheap worker incrementally — paying only for new events.
<code>state</code> answers working/idle + liveness without parsing the stream, and even
reports its own freshness (<code>daemonTickStale</code>).
</p>
</div>
<div class="card">
<div class="row1"><h3>spine</h3><span class="store">~/.pij/spine/</span></div>
<div class="verbs"><span class="verb">spine append</span><span class="verb">spine events --since</span><span class="verb">spine render</span></div>
<p>
What <em>the system</em> did: one global governed log of state transitions, dispatches,
links, grants, task openings — each event with seq, timestamp, attributed actor, and
node refs. <code>spine render</code> regenerates a human-readable markdown view,
per-project if asked.
</p>
<div class="invariant"><span class="tag">invariant</span><span>Peer streams say what an agent did; the spine says what the <em>system</em> did. An activity feed wants the spine; a drill-in wants the tail.</span></div>
</div>
</section>
<!-- ───────────────────────── 7 · ops ───────────────────────── -->
<section class="family" id="ops">
<div class="famhead"><span class="swatch"></span><h2>supervision &amp; ops</h2><span class="gloss">keeping it honest</span></div>
<p class="famintro">
The machinery that keeps every store above truthful — and the one process that's allowed
to act on a timer.
</p>
<div class="card">
<div class="row1"><h3>daemon</h3><span class="store">~/.pij/daemon.lock</span></div>
<div class="verbs"><span class="verb">daemon start</span><span class="verb">status</span><span class="verb">stop</span><span class="verb">kill</span></div>
<p>
The only clock in the system. Each tick reconciles: rebuild indexes, detect deaths,
drive pending spawns, push daemon-owned inboxes into tmux panes, run watchdogs.
Single-instance by atomic lock, with stale-lock reclaim.
</p>
<div class="invariant"><span class="tag">invariant</span><span>Reads never need it. If the daemon is down you can still list, tree, and tail — the data is just as fresh as the last tick, and says so.</span></div>
</div>
<div class="card">
<div class="row1"><h3>watchdog</h3><span class="store">~/.pij/&lt;id&gt;/watchdog.json</span></div>
<div class="verbs"><span class="verb">status</span><span class="verb">pause</span><span class="verb">resume</span><span class="verb">exempt</span><span class="verb">interval</span><span class="verb">watch</span><span class="verb">disable-all</span></div>
<p>
Per-session supervision: fires on an interval when a peer looks stuck, with pauses and
time-boxed exemptions recorded — including <em>who</em> paused it. Supervision itself
leaves an audit trail.
</p>
</div>
<div class="card">
<div class="row1"><h3>agent packs</h3><span class="store">project · user · built-in packs</span></div>
<div class="verbs"><span class="verb">agent list</span><span class="verb">agent run</span><span class="verb">agent spawn</span><span class="verb">agent report</span><span class="verb">check</span><span class="verb">eject</span></div>
<p>
Declarative, validated worker definitions run one-shot (<code>run</code>) or as a
resident daemon-bound peer (<code>spawn</code>). A spawned pack pushes its result back to
the spawner with <code>agent report</code> — delegation with a return envelope.
</p>
</div>
<div class="card">
<div class="row1"><h3>bridges</h3><span class="store">~/.pij/operator/inbox · pane-signals/ · telegram.env</span></div>
<div class="verbs"><span class="verb">telegram init</span><span class="verb">start</span><span class="verb">stop</span></div>
<p>
Where the outside world plugs in: an operator inbox for the human seat, raw pane
signals, and a Telegram bridge that runs in-process with the daemon but tears down
independently — a bridge failure never takes the daemon with it.
</p>
</div>
</section>
<!-- ───────────────────────── closing ───────────────────────── -->
<div class="nouns">
<h2>the nine nouns</h2>
<p class="sub">
Strip away the verbs and pij keeps exactly nine kinds of record. Everything else on this
page is either a transition on one of these or plumbing that keeps them truthful.
</p>
<div class="nounrow">
<span class="noun">Node<small>a session</small></span>
<span class="noun">Edge<small>parent / spawnedBy</small></span>
<span class="noun">State<small>3 measured + 1 declared</small></span>
<span class="noun">Project<small>unit of work</small></span>
<span class="noun">Stream<small>a worktree</small></span>
<span class="noun">Fence<small>path ownership</small></span>
<span class="noun">Assignment<small>a task held</small></span>
<span class="noun">Baton<small>a lease</small></span>
<span class="noun">Event<small>spine + peer</small></span>
</div>
<div class="split">
<div class="half advisory">
<h3>Advisory — where it touches agents' judgment</h3>
<p>Reported, never enforced. The system informs; the agent decides.</p>
<ul>
<li><code>fence</code> — overlap is a notice, not a lock</li>
<li><code>anomalies</code> — findings, not failures</li>
<li><code>watchdog</code> — a nudge, pausable and exemptible</li>
</ul>
</div>
<div class="half strict">
<h3>Strict — where machines coordinate</h3>
<p>Atomic, attributed, and verifiable. No trust required.</p>
<ul>
<li><code>baton</code> — one holder, explicit handoff only</li>
<li><code>dispatch/ack</code> — receipts prove the exact bytes</li>
<li><code>state verify</code> — done is a claim until stamped</li>
</ul>
</div>
</div>
</div>
<footer>
<span>source: live ~/.pij registry + pij CLI survey</span>
<span>pi-hacking/pij · 2026-07-22</span>
</footer>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment