Something went wrong. Try again.
A collaborative coding-agent orchestrator for atproto radl.app
Something went wrong. Try again.
TypeScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474// The approved comp's dataset, as real records.//// `docs/mock/phase6-goals.html` draws a space with synthetic objects shaped "like" the protocol.// This is that same space built out of actual `com.disnetdev.radial.*` records, which makes it two// things at once: a check that the comp is drawable from the protocol as it exists — every row in// it had to survive the lexicon and the fold — and the fixture the UI renders from before there is// any network, auth, or live space to point at (plan 6.1's exit is a side-by-side against the mock,// drawn from these).//// Where the comp got ahead of the protocol, this follows the protocol and the phase-6 plan's// resolutions, not the drawing:// - The comp shows a live lease on an ASSIGNED implementation. Claims arbitrate UNASSIGNED// requests only, and whether a container is running is not on the protocol (§3.1). So the// implementation here is `assigned` — "with builder.radial.bot" — and the one unit that reads// `claimed` is an unassigned request an agent has actually claimed.// - The comp's ADR 0008 "supersedes 0004" is one row with two entries. That is a `prev` chain,// which until the §3.2 fix only implementations got; here 0008 chains onto 0004 and the two// land as one unit, exactly as drawn.// - Living vs append-only is registry data (§3.3), so `architecture` and `conventions` declare// `durability: "living"` and `adr` declares `append-only`, rather than the UI knowing names.// - ADR numbers stay prose in the body (§3.4): the chain carries the real relationship.// - Two of the things agents work on are never units — a `review` somebody asked for and an// `answer` — because neither produces an artifact. The fixture carries one of each with an agent// (an auto-review assigned to the reviewer, a claimed review of a system document, and a message// ask) so "With an agent" has the rows it exists to draw.
import { COLLECTIONS, type RadialRecord, type StrongRef } from './generated/records.js'import { joinRkey } from './records.js'import type { StoredRecord } from './store.js'
export const FIXTURE_DIDS = { tim: 'did:plc:qv7hjr2mzk4x', ana: 'did:plc:a1s2d3f4g5h6', /** Granted membership and has never opened the space: no `join`, and nothing authored anywhere. */ jun: 'did:plc:j5u2n8t4p1qc', planner: 'did:plc:h4n8wsq3vd2p', builder: 'did:plc:z9k1cft6yb8m', reviewer: 'did:plc:r3d5xnp0uq7w',} as const
/** The instant the comp is drawn at: two claims are live, and "6m ago" means 6 minutes. */export const FIXTURE_NOW = '2026-07-25T14:18:00Z'
export interface FixtureSpace { records: StoredRecord[] spaceUri: string /** The instant to materialize at, so claim expiry resolves the way the comp draws it. */ asOf: string projects: { radialNg: string; radialSite: string } goals: { reviewQueue: string capture: string possession: string jetstream: string autoReview: string permutation: string onboarding: string /** The two units the review queue is drawn from: one nobody was asked about, one asked of a human. */ ignored: string /** Ended `dropped` — we decided not to do it. */ budgets: string /** Ended `superseded` — the work moved somewhere else. */ diffViewer: string /** Ended by a legacy `archiveGoal`, which the fold reads as `parked`. */ permissioned: string }}
/** * Build the comp's space. Every record is validated by the fold, so a shape that does not exist in * the lexicon cannot be expressed here — which is the point. */export function fixtureSpace(): FixtureSpace { const records: StoredRecord[] = [] let revision = 0 const { tim, ana, jun, planner, builder, reviewer } = FIXTURE_DIDS
const put = (did: string, rkey: string, value: RadialRecord): StoredRecord => { revision += 1 const record: StoredRecord = { did, collection: value.$type, rkey, uri: `at://${did}/${value.$type}/${rkey}`, cid: `cid-${rkey}`, rev: String(revision).padStart(13, '0'), value, } records.push(record) return record } const ref = (record: StoredRecord): StrongRef => ({ uri: record.uri, cid: record.cid })
// ── the space, its members, and the registry ──────────────────────────────────────────────── const space = put(tim, '3lba0space0001', { $type: COLLECTIONS.space, name: 'Radial', description: 'Building Radial, in Radial.', createdAt: '2026-06-01T09:00:00Z', }) const member = (did: string, rkey: string, kind: 'human' | 'agent', at: string): StoredRecord => put(tim, rkey, { $type: COLLECTIONS.addMember, space: ref(space), did, kind, role: kind === 'agent' ? 'agent' : 'member', createdAt: at, }) member(ana, '3lba1mem0001', 'human', '2026-06-01T09:01:00Z') // Granted and never turned up. It is the state every member passes through and the one a space is // most often actually in just after somebody is added, so the fixture carries it: without it the // members list has no row to draw "not opened" on, and the badge would be code nothing produces. member(jun, '3lba1mem0005', 'human', '2026-07-24T16:40:00Z') member(planner, '3lba1mem0002', 'agent', '2026-06-01T09:02:00Z') member(builder, '3lba1mem0003', 'agent', '2026-06-01T09:03:00Z') member(reviewer, '3lba1mem0004', 'agent', '2026-06-01T09:04:00Z')
// Ana opened the space and her client wrote a `join` into her own repo — a bookmark, so that she can // find a space whose grant lives in tim's. The agents have not: a daemon is handed a space URI by its // operator and has no picker to find one from. Tim needs none either, since the space record is his. // So the fixture carries the asymmetry the members list draws: one human who has turned up, and // several DIDs that were granted and never will. put(ana, joinRkey(tim, '3lba0space0001'), { $type: COLLECTIONS.join, space: ref(space), createdAt: '2026-06-01T09:30:00Z', })
// Agent self-descriptions — what the assignee picker reads to offer only the agents that // produce a given type. put(planner, 'planner', { $type: COLLECTIONS.agent, handleName: 'planner.radial.bot', harness: 'claude-code', models: [{ name: 'claude-opus-5', costHint: 'high' }], artifactTypes: ['plan', 'adr', 'architecture', 'conventions'], createdAt: '2026-06-01T09:05:00Z', }) put(builder, 'builder', { $type: COLLECTIONS.agent, handleName: 'builder.radial.bot', harness: 'claude-code', models: [{ name: 'claude-sonnet-5', costHint: 'medium' }], artifactTypes: ['implementation'], createdAt: '2026-06-01T09:06:00Z', }) put(reviewer, 'reviewer', { $type: COLLECTIONS.agent, handleName: 'reviewer.radial.bot', harness: 'claude-code', models: [{ name: 'claude-opus-5', costHint: 'high' }], // `answer` sits beside `review` for the same reason: neither is registry data, and both are the // same register of work — reading what is there and saying something about it. It is what puts // this agent in the "Ask an agent to reply" picker. artifactTypes: ['review', 'security-review', 'answer'], createdAt: '2026-06-01T09:07:00Z', })
// The ⊕ menu is generated from exactly this list. `review` is deliberately absent: it is a // built-in of the turn layer, not registry data. const type = ( rkey: string, name: string, scope: 'goal' | 'project', brief: string, description: string, at: string, durability?: 'living' | 'append-only', ): StoredRecord => put(tim, rkey, { $type: COLLECTIONS.artifactType, space: ref(space), name, brief, outputSpec: { format: 'markdown', description }, scope, ...(durability ? { durability } : {}), createdAt: at, }) type( '3lba2typ0001', 'plan', 'goal', 'Produce a concrete implementation plan grounded in the goal, repository, and supplied provenance. Call out risks, ordered work, and verification.', 'Approach, tradeoffs, acceptance criteria', '2026-06-01T09:10:00Z', ) type( '3lba2typ0002', 'implementation', 'goal', 'Implement the requested change in the repository, run the project checks, and return exact git and pull-request provenance.', 'Branch, commits, and a pull request', '2026-06-01T09:11:00Z', ) // The product's core claim, as data: an admin adds a capability and a button appears. No deploy. type( '3lba2typ0003', 'security-review', 'goal', 'Audit the change for vulnerabilities: authentication and session boundaries, injection, secret handling, and anything the plan waves at.', 'A security audit naming exact files and lines', '2026-07-20T11:00:00Z', ) type( '3lba2typ0004', 'adr', 'project', 'Distil an architectural decision from the supplied provenance. Write in the register of a decision record — context, decision, consequences — not as a plan with a new title.', 'A decision record', '2026-06-01T09:12:00Z', 'append-only', ) type( '3lba2typ0005', 'architecture', 'project', 'Describe how the system fits together, as it is now. This is the first thing every turn on this project reads.', 'A living architecture document', '2026-06-01T09:13:00Z', 'living', ) type( '3lba2typ0006', 'conventions', 'project', 'Record the repository conventions and what a review should look for.', 'A living conventions document', '2026-06-01T09:14:00Z', 'living', ) // The two doc tiers, and they are nothing but two more registry entries — which is the whole // design decision of the tiered doc viewer, standing where a reader can see it. The briefs are // written for an AGENT, because a brief is the turn prompt when an update is ever asked of one. type( '3lba2typ0007', 'user-doc', 'project', 'Write the user-facing page for one feature, in the register of product documentation: what it does for a reader, what they see, and what it costs them. The records this is based on are the ground truth — distil them, never restate them, and never mention a record type or an implementation detail a user cannot see.', 'A user-facing feature page', '2026-07-26T09:00:00Z', 'living', ) type( '3lba2typ0008', 'roadmap-doc', 'project', 'Write the community-facing page for one epic. Say where the work has got to and where it is going, in a reader’s words. The goals this is based on supply the road ahead and the road travelled; write the part a list of goals cannot say.', 'A community-facing roadmap page', '2026-07-26T09:01:00Z', 'living', )
const radialNg = put(tim, '3lba3prj0001', { $type: COLLECTIONS.project, space: ref(space), name: 'radial-ng', gitUrl: 'https://github.com/disnet/radial-ng.git', defaultBranch: 'main', checks: [ { name: 'pnpm test', command: 'pnpm test' }, { name: 'pnpm lint', command: 'pnpm lint' }, { name: 'pnpm typecheck', command: 'pnpm typecheck' }, ], autoReview: { plan: true, implementation: true }, createdAt: '2026-06-01T09:20:00Z', }) const radialSite = put(tim, '3lba3prj0002', { $type: COLLECTIONS.project, space: ref(space), name: 'radial-site', gitUrl: 'https://github.com/disnet/radial-site.git', defaultBranch: 'main', checks: [{ name: 'pnpm test', command: 'pnpm test' }], autoReview: {}, createdAt: '2026-06-01T09:21:00Z', })
// ── small builders ───────────────────────────────────────────────────────────────────────── const goal = ( author: string, rkey: string, project: StoredRecord, title: string, body: string, at: string, ): StoredRecord => put(author, rkey, { $type: COLLECTIONS.goal, space: ref(space), project: ref(project), title, body, createdAt: at, })
/** * A goal's ending: a standalone `closeGoal` record any active member may write, carrying the word * for what happened to the work. This is how a goal ends now, and the demo shows all four words. */ const end = (author: string, rkey: string, goal: StoredRecord, disposition: string, at: string): void => { put(author, rkey, { $type: COLLECTIONS.closeGoal, goal: ref(goal), closed: true, disposition, createdAt: at, }) }
/** * A goal's labels: a standalone `labelGoal` carrying the WHOLE set, which any active member may * write and the latest of which wins. The demo spreads a small vocabulary over the goals so the * filter bar has chips with real counts behind them rather than one label on one row. */ const label = (author: string, rkey: string, goal: StoredRecord, labels: string[], at: string): void => { put(author, rkey, { $type: COLLECTIONS.labelGoal, goal: ref(goal), labels, createdAt: at, }) }
/** * A goal ended by the LEGACY owner edit: a second version of the goal record differing in `closed` * alone. Nothing writes this any more, and records like it are in people's repos forever, so the * demo carries one — it is how the fold's `completed`-with-no-`endedAt` reading gets on screen. */ const close = (record: StoredRecord): void => { revision += 1 records.push({ ...record, cid: `${record.cid}-closed`, rev: String(revision).padStart(13, '0'), value: { ...record.value, closed: true } as RadialRecord, }) }
/** * A goal ended by the LEGACY `archiveGoal` annotation, which the fold reads as `parked` — what * archiving meant, said in the new vocabulary. Also the demo's only sight of a goal that the old UI * made unreachable: archived goals were on no list at all, and this one is in the Logbook. */ const park = (author: string, rkey: string, goal: StoredRecord, at: string): void => { put(author, rkey, { $type: COLLECTIONS.archiveGoal, goal: ref(goal), archived: true, createdAt: at, }) }
interface RequestInput { author: string rkey: string anchor: { goal: StrongRef } | { project: StrongRef } type: string at: string assignee?: string brief?: string basedOn?: StrongRef[] subject?: StrongRef } const request = (input: RequestInput): StoredRecord => put(input.author, input.rkey, { $type: COLLECTIONS.artifactRequest, ...input.anchor, type: input.type, basedOn: input.basedOn ?? [], ...(input.subject ? { subject: input.subject } : {}), ...(input.assignee ? { assignee: input.assignee } : {}), ...(input.brief ? { brief: input.brief } : {}), createdAt: input.at, })
interface ArtifactInput { author: string rkey: string request: StoredRecord anchor: { goal: StrongRef } | { project: StrongRef } type: string /** * The record's own short title — what every row and chip calls it. Optional here for one reason: * the field is optional in the lexicon because records signed before it existed cannot be given * one, and the demo carries a couple of those on purpose (ADR 0004, and the ignored-records plan) * so the fallback a real space will show for years is on screen rather than only in a test. */ title?: string body: string at: string prev?: StoredRecord criteria?: string[] links?: { branch?: string; commit?: string; pr?: string } model?: string } const artifact = (input: ArtifactInput): StoredRecord => put(input.author, input.rkey, { $type: COLLECTIONS.artifact, request: ref(input.request), ...input.anchor, type: input.type, ...(input.prev ? { prev: ref(input.prev) } : {}), ...(input.title ? { title: input.title } : {}), body: input.body, links: input.links ?? {}, ...(input.criteria ? { criteria: input.criteria } : {}), ...(input.model ? { model: input.model } : {}), createdAt: input.at, })
const review = ( author: string, rkey: string, subject: StoredRecord, verdict: 'approve' | 'request_changes', at: string, findings: Array<{ path?: string; line?: number; severity: 'info' | 'warning' | 'error'; body: string }> = [], reviewRequest?: StoredRecord, model?: string, ): StoredRecord => put(author, rkey, { $type: COLLECTIONS.review, subject: ref(subject), ...(reviewRequest ? { request: ref(reviewRequest) } : {}), verdict, findings, ...(model ? { model } : {}), createdAt: at, })
const checkrun = ( author: string, rkey: string, target: StoredRecord, commit: string, results: Array<{ name: string; pass: boolean; log: string }>, at: string, ): StoredRecord => put(author, rkey, { $type: COLLECTIONS.checkrun, artifact: ref(target), commit, results, createdAt: at, })
const merge = ( author: string, rkey: string, target: StoredRecord, pr: string, mergedAt: string, ): StoredRecord => put(author, rkey, { $type: COLLECTIONS.merge, artifact: ref(target), pr, mergedAt, createdAt: mergedAt, })
const message = ( author: string, rkey: string, anchor: { goal: StrongRef } | { artifact: StrongRef }, body: string, at: string, extra: { re?: StoredRecord; parent?: StoredRecord; mentions?: string[]; model?: string } = {}, ): StoredRecord => { // `root` is derived from the parent exactly as `message post --parent` derives it: the parent's // own root, or the parent itself when the parent starts the thread. Deriving it here rather than // taking it as an argument is what keeps the fixture from demonstrating a shape the command // cannot produce. const parentRoot = extra.parent ? ((extra.parent.value as { root?: StrongRef }).root ?? ref(extra.parent)) : undefined return put(author, rkey, { $type: COLLECTIONS.message, ...anchor, body, mentions: extra.mentions ?? [], ...(extra.re ? { re: ref(extra.re) } : {}), ...(extra.parent ? { parent: ref(extra.parent) } : {}), ...(parentRoot ? { root: parentRoot } : {}), ...(extra.model ? { model: extra.model } : {}), createdAt: at, }) }
// ── g1 · Review queue in the web UI ───────────────────────────────────────────────────────── // A judged plan on its second version, an assigned implementation, and an open custom-type // request — the comp's headline goal. const g1 = goal( tim, '3lbq2xk7wf22h', radialNg, 'Review queue in the web UI', 'Open review requests assigned to me, in one place. Approve or request changes with structured findings, without leaving the page. PR links open on GitHub — we never build a native diff view (design.md §10).', '2026-07-21T09:00:00Z', ) const g1Ref = { goal: ref(g1) }
const u1r1 = request({ author: tim, rkey: '3lbq3a9c2kk2p', anchor: g1Ref, type: 'plan', assignee: planner, at: '2026-07-21T09:14:00Z', brief: 'Cover the queue view, the verdict form, and how findings map onto the review lexicon. Say explicitly what we are NOT building.', }) const u1v1 = artifact({ author: planner, rkey: '3lbq3m1p8dd2s', request: u1r1, anchor: g1Ref, type: 'plan', title: 'Queue route and verdict form', at: '2026-07-21T09:31:00Z', body: '## Approach\n\nAdd a `/queue` route that filters the materialized index for open requests of type `review` whose assignee is the signed-in DID. Render each as a card with the subject artifact inlined, and a two-button verdict form.\n\nThe verdict form posts straight to the PDS from the browser session.', criteria: [ 'A queue route lists open review requests for the signed-in DID', 'Approve and request-changes both write a review record', 'Findings accept a path and a line', ], }) // The findings that produced v2. Severity is a claim about consequence, not confidence. review(reviewer, '3lbq3rev0001', u1v1, 'request_changes', '2026-07-21T09:44:00Z', [ { path: 'queue/+page.server.ts', line: 41, severity: 'error', body: 'The plan resolves the subject artifact by uri, so a reviewer could read v2 and unknowingly sign a verdict pinned to v3. Reviews pin exact versions — resolve the cid from the request’s strongref.', }, { path: 'queue/+page.svelte', severity: 'warning', body: 'No stale-view guard. Two operators can both hold this page open; the second submit silently writes a duplicate review.', }, { severity: 'info', body: 'Say out loud that no diff view is being built, so the next turn does not helpfully add one.', }, ]) const u1r2 = request({ author: tim, rkey: '3lbq4s9d3ll2q', anchor: g1Ref, type: 'plan', assignee: planner, at: '2026-07-21T09:50:00Z', basedOn: [ref(u1v1)], brief: 'Address the findings. The strongref one is the real bug.', }) const u1v2 = artifact({ author: planner, rkey: '3lbq4t0v6nn2r', request: u1r2, anchor: g1Ref, type: 'plan', model: 'anthropic/claude-opus-4-8-20260715', // A v2 addressing findings is usually the same document, so it keeps the same title: the version // rail says which version is being read, and renaming a plan because it was revised would make // the chain look like two different plans. title: 'Queue route and verdict form', prev: u1v1, at: '2026-07-21T09:58:00Z', body: '## Approach\n\nAdd a `/queue` route that filters the materialized index for open requests of type `review` whose assignee is the signed-in DID. The subject artifact is inlined by strongref — we resolve the exact `cid`, never the latest version of that `uri`, so a verdict written here is a verdict on what the reviewer actually read.\n\n## Stale-view guard\n\nBetween render and submit the queue can move: another operator’s daemon may fulfil the same request. The submit handler re-reads the request from the index and, if it is no longer open, returns the landed review instead of writing a second one. The index is order-insensitive so this is a read, not a lock.\n\n## What this is not\n\nNo native diff view. Implementation artifacts link a PR, and the PR is where diffs get read. No inbox-zero mechanics and no unread counts on artifacts — the open-request set is the only queue that exists, and it is already computed.', criteria: [ 'A queue route lists open review requests assigned to the signed-in DID', 'The subject artifact is resolved by strongref cid, not by uri', 'Approve and request-changes both write a com.disnetdev.radial.review', 'Findings accept path, line, severity, and body', 'Submitting an already-fulfilled request returns the landed review instead of writing a duplicate', ], }) review(reviewer, '3lbq4rev0002', u1v2, 'approve', '2026-07-21T10:02:00Z', [], undefined, 'openai/gpt-5.6-sol')
// Assigned, not running: the index knows who it is with, never whether a container is up (§3.1). request({ author: tim, rkey: '3lbq7n4h1cc2v', anchor: g1Ref, type: 'implementation', assignee: builder, at: '2026-07-25T14:12:00Z', basedOn: [ref(u1v2)], brief: 'Follow plan v2. Keep the server route thin — the index work belongs in core.', }) // The custom type an admin added this week, with no deploy. request({ author: tim, rkey: '3lbq8p6j5ee2w', anchor: g1Ref, type: 'security-review', at: '2026-07-25T14:16:00Z', basedOn: [ref(u1v2)], brief: 'The queue writes reviews on the human’s behalf. Check the session boundary and the CSRF story on the submit handler.', })
message( tim, '3lbq2msg0001', g1Ref, 'Starting from the review lexicon rather than the screen — if findings do not round-trip we are drawing pictures.', '2026-07-21T09:02:00Z', ) const u1msg = message( reviewer, '3lbq2msg0002', g1Ref, 'v2 resolves the strongref properly and the stale-view guard is the right shape. Approving.\n\nOne thing for the implementation turn, not a blocker: the guard reads the index twice. That is fine at this size but say so in a comment, or someone will add a lock.', '2026-07-21T10:02:30Z', ) // One conversation, in the three records it takes: a human replies with a question, writes a typed // `answer` request pinning that exact message, and the agent's reply lands in the thread. The // request is what a daemon acts on — nothing parses the message body, and the reply commissions // nothing further (design.md §10). const u1q = message( tim, '3lbq2msg0003', g1Ref, 'Reads the index twice how? If it is the openRequests scan I would rather fix it now than leave a comment.', '2026-07-21T10:11:00Z', { parent: u1msg, mentions: [reviewer] }, ) const u1ans = request({ author: tim, rkey: '3lbq2ans0001', anchor: g1Ref, type: 'answer', subject: ref(u1q), assignee: reviewer, at: '2026-07-21T10:11:10Z', basedOn: [ref(u1v2)], }) message( reviewer, '3lbq2msg0004', g1Ref, 'The scan itself is once. It is `owedBy` that materializes a second time to check the request is still open, a few lines after the queue already had it. Cheap here, and worth a comment rather than a refactor — the second read is what makes a v3 landing mid-form safe.', '2026-07-21T10:19:00Z', // `parent` threads it, `re` closes the request: the two fields a daemon-written reply carries. { parent: u1q, re: u1ans, mentions: [tim], model: 'openai/gpt-5.6-sol' }, ) // A second question, still with the agent. The exchange above is the COMPLETED case — request // fulfilled by the reply threaded under its subject — and this is the same shape mid-flight: an // open `answer` request naming one exact message and one agent. It is not a unit and never will // be, which is exactly why "With an agent" could not see it. const u1q2 = message( tim, '3lbq2msg0005', g1Ref, 'Separate thing: does the stale-view guard hold if the second operator’s daemon fulfils the request between our render and our submit, rather than another tab?', '2026-07-25T14:09:00Z', { mentions: [reviewer] }, ) request({ author: tim, rkey: '3lbq2ans0002', anchor: g1Ref, type: 'answer', subject: ref(u1q2), assignee: reviewer, at: '2026-07-25T14:09:20Z', basedOn: [ref(u1v2)], })
// ── g2 · Capture requests should distil ───────────────────────────────────────────────────── // The blocked-on-a-human case: the assignee asked a question and nobody has answered it. const g2 = goal( ana, '3lbp9wq4tx11c', radialNg, 'Capture requests should distill, not copy', '"Capture as ADR" on a goal artifact writes a project-scoped request with basedOn pointing at the source. The fulfilling turn has to rewrite in the register of a decision record — pinning the plan verbatim would collect stale plans, not documentation (design.md §8).', '2026-07-23T16:00:00Z', ) const g2Ref = { goal: ref(g2) } const u4r = request({ author: ana, rkey: '3lbpa2e8yz11d', anchor: g2Ref, type: 'plan', assignee: planner, at: '2026-07-23T16:40:00Z', brief: 'How does the turn know which parts of the source plan are decisions and which are scaffolding?', }) message( ana, '3lbp9msg0001', g2Ref, 'Filing this off the Phase 5 exit walkthrough — the capture landed but it read like a plan with a new title.', '2026-07-23T16:22:00Z', ) message( planner, '3lbp9msg0002', g2Ref, 'The bundle gives me the source plan and its goal, but nothing marks which paragraphs were decisions versus process. Two options and I do not want to pick alone:\n\n(a) Distill from the plan’s `criteria` only — they are already the load-bearing claims, and everything else is reasoning we can drop.\n\n(b) Distill from the whole plan plus the approving review’s findings, treating a finding that changed the plan as evidence a decision was made there.\n\n(b) is better documentation and roughly triple the tokens. Which do you want as the default in the type’s brief template?', '2026-07-23T16:52:00Z', { re: u4r, mentions: [ana], model: 'anthropic/claude-opus-4-8-20260715' }, )
// ── g3 · Possession-based containment ─────────────────────────────────────────────────────── // An approved plan and the merged implementation that came from it, with checks and a PR. const g3 = goal( tim, '3lbn4kd9rv09a', radialNg, 'Possession-based containment for turn containers', "Drop the egress allowlist and the proxy machinery. Turns get an ordinary bridge network and the operator's forge token; atproto credentials still never enter a container, and the sidecar socket stays the only protocol write path (design.md §13).", '2026-07-18T11:00:00Z', ) const g3Ref = { goal: ref(g3) } const u5r = request({ author: tim, rkey: '3lbn5m2w8h09b', anchor: g3Ref, type: 'plan', assignee: planner, at: '2026-07-18T11:05:00Z', }) const u5v1 = artifact({ author: planner, rkey: '3lbn5s7k3j09c', request: u5r, anchor: g3Ref, type: 'plan', title: 'Drop the egress allowlist for possession', at: '2026-07-18T11:22:00Z', body: '## Approach\n\nDelete `egress.ts` and its wiring. Turn containers join the default bridge network. Containment stops being a network property and becomes a possession property: a container can reach anything, and holds nothing worth stealing.\n\n## What a turn possesses\n\nThe operator’s forge token and a spend-capped model key. What it never possesses is any atproto credential. Validation and signing stay daemon-side behind the sidecar socket, so a turn physically cannot emit a record of a type it was not asked for.', criteria: [ 'egress.ts and its wiring are gone', 'Turn containers run on the default bridge network', 'Host networking is never used and the metadata endpoint stays blocked', 'Check containers have network and an empty env', 'No atproto credential is reachable from inside a container', ], }) review(reviewer, '3lbn5rev0001', u5v1, 'approve', '2026-07-18T11:39:00Z')
const u6r = request({ author: tim, rkey: '3lbn8j1q4m09f', anchor: g3Ref, type: 'implementation', assignee: builder, at: '2026-07-19T08:40:00Z', basedOn: [ref(u5v1)], }) const u6 = artifact({ author: builder, rkey: '3lbn8k2r5n09g', request: u6r, anchor: g3Ref, type: 'implementation', title: 'Removed egress.ts; turns on the bridge network', at: '2026-07-19T10:55:00Z', body: 'Removed `packages/daemon/src/egress.ts` and the internal-network wiring in `container.ts`. Turn containers now attach to the default bridge, and `--network host` is rejected at the call site rather than by convention.\n\nThe env a turn receives is built by one function, `turnEnv()`, which takes the forge token and the model key and nothing else. There is no path by which a session credential reaches it, and the test asserts the exact key set rather than asserting an absence.', links: { branch: 'radial/impl-6a1f0c94d3b7', commit: '8f3c1d94a27b6e05f2a1c8b3d7e04916a2c5b8d1', pr: 'https://github.com/disnet/radial-ng/pull/241', }, }) checkrun( builder, '3lbn8chk0001', u6, '8f3c1d94a27b6e05f2a1c8b3d7e04916a2c5b8d1', [ { name: 'pnpm test', pass: true, log: 'ok' }, { name: 'pnpm lint', pass: true, log: 'ok' }, { name: 'pnpm typecheck', pass: true, log: 'ok' }, ], '2026-07-19T11:05:00Z', ) review(reviewer, '3lbn8rev0002', u6, 'approve', '2026-07-19T11:12:00Z') merge(builder, '3lbn8mrg0001', u6, 'https://github.com/disnet/radial-ng/pull/241', '2026-07-19T15:20:00Z')
// ── g4 · Jetstream ────────────────────────────────────────────────────────────────────────── // Nothing has happened yet: an unassigned, unclaimed request. The `open` state. const g4 = goal( tim, '3lbr1cc8kp30x', radialNg, 'Jetstream subscription with polling fallback', 'Poll-only ingestion is fine at tens of DIDs but merge-observation latency is already noticeable. Subscribe to Jetstream filtered to com.disnetdev.radial.*, keep polling as backfill (design.md §5).', '2026-07-25T11:00:00Z', ) request({ author: tim, rkey: '3lbr1kk2mn30y', anchor: { goal: ref(g4) }, type: 'plan', at: '2026-07-25T11:30:00Z', brief: "Cover reconnect, gap detection against the polling cursor, and what happens when Jetstream and the poller disagree about a record's earliest revision.", })
// ── g5 · Auto-review, closed ──────────────────────────────────────────────────────────────── const g5 = goal( tim, '3lbk7dd1qz88f', radialNg, 'Auto-review trigger — one hop, terminal', 'On a matching artifact landing, the daemon writes a review request under one of its agent identities. Exactly one hop; a verdict never spawns further work (design.md §7).', '2026-07-14T10:00:00Z', ) const g5Ref = { goal: ref(g5) } const u9r = request({ author: tim, rkey: '3lbk7ee2rz88g', anchor: g5Ref, type: 'plan', assignee: planner, at: '2026-07-14T10:11:00Z', }) const u9 = artifact({ author: planner, rkey: '3lbk7ff3sz88h', request: u9r, anchor: g5Ref, type: 'plan', title: 'One-hop auto-review, per type per project', at: '2026-07-14T10:28:00Z', body: '## Approach\n\nPer-type per-project config, overridable per request. The daemon writes the review request the moment a matching artifact lands, under one of its own agent identities.\n\nTerminal by construction rather than by a bound: the trigger matches on `artifact` records only, and the record a review turn emits is a `review`. It cannot re-fire, so there is no iteration counter to get wrong.', criteria: [ 'autoReview is per type per project', 'A request-level override wins over the project default', 'A verdict never triggers further generation', ], }) review(reviewer, '3lbk7rev0001', u9, 'approve', '2026-07-14T10:35:00Z') const u10r = request({ author: tim, rkey: '3lbk9hh5uz88m', anchor: g5Ref, type: 'implementation', assignee: builder, at: '2026-07-15T09:05:00Z', basedOn: [ref(u9)], }) const u10 = artifact({ author: builder, rkey: '3lbk9ii6vz88n', request: u10r, anchor: g5Ref, type: 'implementation', title: 'Auto-review trigger in the index-diff loop', at: '2026-07-15T10:20:00Z', body: "The trigger lives in the daemon's index-diff loop: on each ingestion cycle, any artifact new to the index whose type has `autoReview` enabled gets a review request written under the daemon's agent identity.\n\nTerminality is structural rather than guarded — the diff only inspects `com.disnetdev.radial.artifact` records, and the record a review turn emits is a `com.disnetdev.radial.review`. There is no counter to get wrong.", links: { branch: 'radial/impl-2d9b47e10c85', commit: 'c07a5be3f19d42a8b6015d3e7f24c9081b5a6e3f', pr: 'https://github.com/disnet/radial-ng/pull/228', }, }) checkrun( builder, '3lbk9chk0001', u10, 'c07a5be3f19d42a8b6015d3e7f24c9081b5a6e3f', [ { name: 'pnpm test', pass: true, log: 'ok' }, { name: 'pnpm lint', pass: true, log: 'ok' }, ], '2026-07-15T10:30:00Z', ) review(reviewer, '3lbk9rev0002', u10, 'approve', '2026-07-15T10:40:00Z') merge(builder, '3lbk9mrg0001', u10, 'https://github.com/disnet/radial-ng/pull/228', '2026-07-15T17:44:00Z') // Ended the way a goal ends now: a standalone record, with the word for what happened and the // instant it happened at. Ana ends Tim's goal, which is the write the old author-only rule refused. end(ana, '3lbk9end0001', g5, 'completed', '2026-07-15T18:02:00Z')
// ── g6 · Permutation property tests, closed ───────────────────────────────────────────────── const g6 = goal( ana, '3lbh2aa0mx71b', radialNg, 'Materializer permutation property tests', 'Every arrival permutation of a golden scenario must yield the same index. The index is the thing two independent parties compute identically, so it gets the strongest tests in the repo (plan.md, Phase 1).', '2026-07-09T13:00:00Z', ) const g6Ref = { goal: ref(g6) } const u11r = request({ author: ana, rkey: '3lbh2bb1nx71c', anchor: g6Ref, type: 'implementation', assignee: builder, at: '2026-07-09T13:20:00Z', }) const u11 = artifact({ author: builder, rkey: '3lbh2cc2ox71d', request: u11r, anchor: g6Ref, type: 'implementation', title: 'Six golden scenarios, shuffled 500 ways', at: '2026-07-09T14:40:00Z', body: 'Six golden scenarios — pinned versions, edits, removal ignoring the removed member’s records, re-add restoring them, competing claims, cross-goal system artifacts — each shuffled 500 ways and asserted structurally equal.\n\nThe shuffle is seeded and the seed prints on failure, so a red run reproduces exactly. This is the property the whole design leans on: two materializers that ingested the same records compute the same view.', links: { branch: 'radial/impl-b3e05f7a2c61', commit: '51ab9de0c4f8730b2e6a1c94d5f807b3a6e2c1d9', pr: 'https://github.com/disnet/radial-ng/pull/196', }, }) checkrun( builder, '3lbh2chk0001', u11, '51ab9de0c4f8730b2e6a1c94d5f807b3a6e2c1d9', [ { name: 'pnpm test', pass: true, log: 'ok' }, { name: 'pnpm lint', pass: true, log: 'ok' }, ], '2026-07-09T14:50:00Z', ) review(reviewer, '3lbh2rev0001', u11, 'approve', '2026-07-09T15:02:00Z') merge(builder, '3lbh2mrg0001', u11, 'https://github.com/disnet/radial-ng/pull/196', '2026-07-10T09:15:00Z') // Deliberately the LEGACY ending — the owner edit nothing writes any more. Kept so demo mode shows // what the fold does with the data already in people's repos: ended, read as `completed`, and with // no `endedAt`, because a rewritten goal record carries no instant of its own. close(g6)
// ── g7 · Onboarding, on the other project ─────────────────────────────────────────────────── // The one genuinely-in-flight unit: unassigned, and an agent has claimed it with a live lease. const g7 = goal( ana, '3lbr5ee9pq40z', radialSite, 'Operator onboarding page', 'What an operator has to do to run a daemon, in the order they have to do it. Ends at a working agent turn, not at a feature list.', '2026-07-24T10:00:00Z', ) const u12r = request({ author: ana, rkey: '3lbr5ff0qr41a', anchor: { goal: ref(g7) }, type: 'plan', at: '2026-07-25T13:55:00Z', brief: 'Assume a reader who has an atproto account and a GitHub login and nothing else.', }) // Claimed twenty minutes ago and renewed since: `createdAt` is frozen (it is the tie-break), // `renewedAt` is when the current version of the lease was issued, and the ten minutes between it // and `expiresAt` is the lease itself. put(planner, '3lbr5clm0001', { $type: COLLECTIONS.claim, request: ref(u12r), expiresAt: '2026-07-25T14:27:00Z', renewedAt: '2026-07-25T14:17:00Z', createdAt: '2026-07-25T13:56:00Z', })
// ── g8 · Landed, and waiting on a person ──────────────────────────────────────────────────── // The two flavours of "for me", which nothing else in this space has: an artifact that landed with // no verdict and nobody asked for one, and an artifact somebody explicitly asked a HUMAN to judge. // A review request is not a unit (§3.7) — it is a verdict owed on one exact version — so it hangs // off the implementation's v1 rather than becoming a third row. const g8 = goal( ana, '3lbr7aa1bb50d', radialNg, 'Surface the records the fold ignored', 'The fold already computes `ignored` and `edits` and nothing ever shows them, so a record dropped for a reason nobody can see looks exactly like a record that was never written.', '2026-07-24T09:00:00Z', ) const g8Ref = { goal: ref(g8) }
const u13r = request({ author: ana, rkey: '3lbr7bb2cc50e', anchor: g8Ref, type: 'plan', assignee: planner, at: '2026-07-24T09:10:00Z', brief: 'A quiet disclosure, not a dashboard. Say what each ignored record was dropped for, in the words the fold would use.', }) // Landed, and no verdict pins it. Nobody was asked — this is the flavour of the queue that exists // because landing is not done, not because anyone requested anything. // // Deliberately UNTITLED, and one of only two records here that are: a title is optional in the // lexicon because a signed record cannot be given one after the fact, so a real space keeps rows // like this for as long as its history lasts. Its row shows the line derived from its body, its // drawer draws no heading, and both are what the fallback is supposed to look like. const u13v1 = artifact({ author: planner, rkey: '3lbr7cc3dd50f', request: u13r, anchor: g8Ref, type: 'plan', body: '## Approach\n\nOne disclosure at the foot of the pane, closed by default, listing `index.ignored` grouped by reason and `index.edits` grouped by record. Each row names the record by uri and says the rule that dropped it — non-member author, unresolved ref, unsanctioned edit — in the fold’s own words rather than in a friendlier paraphrase.\n\n## Why it is not a dashboard\n\nAn ignored record is almost always a member who was removed or a ref that has not been ingested yet. Making it loud would train people to ignore it. It is a thing you go and look at when a record you know exists is not on screen.', criteria: [ 'Every entry in index.ignored is reachable from the UI', 'Each says the rule that dropped it, not a paraphrase', 'The disclosure is closed by default and costs nothing when empty', ], at: '2026-07-24T11:48:00Z', })
const u14r = request({ author: ana, rkey: '3lbr7dd4ee50g', anchor: g8Ref, type: 'implementation', assignee: builder, at: '2026-07-25T08:40:00Z', basedOn: [ref(u13v1)], brief: 'Follow the plan. The disclosure element is the whole of the UI surface — resist adding a route.', }) const u14v1 = artifact({ author: builder, rkey: '3lbr7ee5ff50h', request: u14r, anchor: g8Ref, type: 'implementation', title: 'Ignored records in a closed disclosure', body: '## What landed\n\n`Diagnostics.svelte` renders `index.ignored` and `index.edits` inside a `<details>` at the foot of the pane. Grouping and the reason strings come from the fold — the component reads them, it does not compose them.\n\n## Note\n\nBuilt on plan v1, which has no verdict yet. If the verdict changes the grouping this is a small change; if it changes the reason strings it is a change in `core`, not here.', links: { branch: 'radial/impl-91c4de08b7a2', commit: 'c07f3ba91d5e264a8b03f7c1e9d4a2b6f8017c53', pr: 'https://github.com/disnet/radial-ng/pull/263', }, at: '2026-07-25T11:05:00Z', }) checkrun( builder, '3lbr7ff6gg50i', u14v1, 'c07f3ba91d5e264a8b03f7c1e9d4a2b6f8017c53', [ { name: 'pnpm test', pass: true, log: '512 passing' }, { name: 'pnpm lint', pass: true, log: 'ok' }, { name: 'pnpm typecheck', pass: true, log: 'ok' }, ], '2026-07-25T11:12:00Z', ) // The other flavour: a verdict owed by a named human on one exact version. `review` is not in the // registry — it is a built-in of the turn layer — so this request carries a `subject` rather than a // `basedOn`, and it never becomes a row of its own. // Written by the daemon, not by a person: `autoReview` is on for `implementation` on this project, // so the moment the artifact landed the daemon wrote a review request under its own reviewer // identity and assigned it there. This is the most ordinary shape of "an agent is working right // now" in a live space — and, being a review, it is not a unit, so it was the one shape the // in-flight list could not draw. Two asks on one version are two asks: the row count below is // unchanged, and so is the goal's pie. request({ author: reviewer, rkey: '3lbr7ii8jj50k', anchor: g8Ref, type: 'review', subject: ref(u14v1), assignee: reviewer, at: '2026-07-25T11:13:00Z', }) request({ author: ana, rkey: '3lbr7gg7hh50j', anchor: g8Ref, type: 'review', subject: ref(u14v1), assignee: tim, at: '2026-07-25T11:40:00Z', brief: 'You wrote the rule that drops these records. Say whether this reports it honestly before it goes in front of anyone.', })
// ── g9–g11 · the other three endings ──────────────────────────────────────────────────────── // A goal ends in one of four ways and the Logbook says which, so the demo carries one of each. g5 // is `completed` and g6 is the legacy edit read as `completed`; these three are the rest. They are // titles and bodies with no work under them, which is what most abandoned goals actually look like // — and it keeps them out of every list except the Logbook and their project's own logbook section. const g9 = goal( tim, '3lbn1dd0ee60a', radialNg, 'Per-goal spend budgets', 'A ceiling per goal, refused at dispatch. Deferred in v1: every generation already has a human click behind it, so the thing a budget protects against cannot happen yet (design.md §9).', '2026-06-30T09:00:00Z', ) end(ana, '3lbn1dd0ee60b', g9, 'dropped', '2026-07-08T16:20:00Z')
const g10 = goal( ana, '3lbn2ee1ff60c', radialNg, 'A native diff viewer for implementation artifacts', 'Read the branch without leaving the goal page. Overtaken by the decision to delegate diff viewing to the forge permanently (design.md §10) — the artifact links out instead.', '2026-07-02T11:30:00Z', ) end(tim, '3lbn2ee1ff60d', g10, 'superseded', '2026-07-11T10:05:00Z')
const g11 = goal( tim, '3lbn3ff2gg60e', radialNg, 'Permissioned spaces', 'Records are public today, and a space that is not is a different trust model rather than a setting. Worth doing, not now (design.md §13).', '2026-07-05T14:10:00Z', ) // Parked with the LEGACY record: this is the one archived goal in the demo, and before the endings // became one it was on no list at all — project page, rail and Logbook alike — which is exactly the // defect the Logbook now fixes. The fold reads it as `parked`, with the archive's own instant. park(ana, '3lbn3ff2gg60f', g11, '2026-07-06T08:45:00Z')
// ── labels and saved views ────────────────────────────────────────────────────────────────── // // Free-form and unregistered, which is the whole v1 design: the vocabulary below exists because // these two people used these words, and it would stop existing the moment they stopped. Both of // them label, because labelling is any active member's — an annotation about where attention // should go, not governance. // // `web` and `protocol` are the broad cuts, `needs design` is the one with a space in it (a label is // not a tag, and the filter bar has to survive one), and `ingestion` sits on a single goal so the // chip list has a long tail rather than three evenly-weighted rows. label(tim, '3lbq2xk7wf22z', g1, ['web', 'needs design'], '2026-07-21T09:05:00Z') label(ana, '3lbp9wq4tx11z', g2, ['protocol', 'needs design'], '2026-07-23T16:10:00Z') label(tim, '3lbqx3m5vv44z', g3, ['protocol'], '2026-07-24T10:00:00Z') label(tim, '3lbr1cc8kp30z', g4, ['ingestion', 'protocol'], '2026-07-25T11:05:00Z') label(ana, '3lbk7dd1qz88z', g5, ['protocol'], '2026-07-14T10:30:00Z') label(tim, '3lbm4aa9nn50z', g7, ['web'], '2026-07-19T09:00:00Z') label(ana, '3lbn2ee1ff60z', g10, ['web'], '2026-07-02T12:00:00Z')
// Tim's own saved views. Personal records in his repo — Ana's rail shows hers, not these — and // `state` differs between them so the demo shows both halves of what a view can pin. put(tim, '3lbv1aa0zz90a', { $type: COLLECTIONS.savedView, space: ref(space), name: 'Needs design', labels: ['needs design'], state: 'live', createdAt: '2026-07-24T08:00:00Z', }) put(tim, '3lbv1bb1zz90b', { $type: COLLECTIONS.savedView, space: ref(space), name: 'Protocol work', project: ref(radialNg), labels: ['protocol'], state: 'all', createdAt: '2026-07-24T08:01:00Z', })
// ── system artifacts (§8) ─────────────────────────────────────────────────────────────────── // Project-scoped types. They outlive any goal, and the current version of each rides in every // turn's bundle — which is the real reason this section exists. const ngRef = { project: ref(radialNg) }
/** * One version of a system document, with the request that asked for it and the verdict on it. * * `title` is the field the System page leads with — a project has one `architecture` chain but * several `adr` rows, and "adr" plus the first sentence of a decision record is not something a * human scans. It sits next to the body because the two belong together; `undefined` is passed for * exactly one record here, ADR 0004 below, which predates the field. */ const sysVersion = ( rkeyRequest: string, rkeyArtifact: string, typeName: string, title: string | undefined, body: string, requestedBy: string, at: string, verdictAt: string, prev?: StoredRecord, basedOn?: StrongRef[], ): StoredRecord => { const req = request({ author: requestedBy, rkey: rkeyRequest, anchor: ngRef, type: typeName, assignee: planner, at, ...(basedOn ? { basedOn } : {}), }) const art = artifact({ author: planner, rkey: rkeyArtifact, request: req, anchor: ngRef, type: typeName, ...(title ? { title } : {}), body, at, ...(prev ? { prev } : {}), }) review(reviewer, `rev-${rkeyArtifact}`, art, 'approve', verdictAt) return art }
// architecture — a living document on its third version, one chain. const arch1 = sysVersion( '3lbf7bb0jj58z', '3lbf7cc1kk58a', 'architecture', 'Three processes over one shared fold', '## Shape\n\nThree processes and one shared library. The daemon watches, the sidecar writes, `core` computes the view both of them read.', tim, '2026-06-24T10:02:00Z', '2026-06-24T10:30:00Z', ) const arch2 = sysVersion( '3lbf9cc3mm58b', '3lbf9dd4nn58c', 'architecture', 'Three processes and the possession boundary', '## Shape\n\nThree processes and one shared library, plus the possession boundary between them.', tim, '2026-07-01T09:40:00Z', '2026-07-01T10:15:00Z', arch1, [{ uri: arch1.uri, cid: arch1.cid }], ) const arch3 = sysVersion( '3lbg1aa2mm60p', '3lbg2ee5pp60q', 'architecture', // Renamed at v3, and the demo needs it to be: a living document's title belongs to the version // that carries it, so browsing back to v1 in the drawer has to show v1's name, not this one. 'Four moving parts and the write boundary', '## Shape\n\nRadial is four moving parts over one shared library. `ingest` pulls member repos into a record store. `core` folds those records into an index — the same fold the daemon, the CLI, and this UI all run, which is why two materializers that saw the same records agree. `daemon` watches its own index for open requests naming its agents and launches a container per turn. `sidecar` is the only thing inside that container that can reach the protocol.\n\n## The boundary that matters\n\nValidation and signing live daemon-side, behind a local socket. A turn can emit exactly one record type — the one it was asked for — plus messages. No atproto credential ever enters a container.\n\n## Why the index is not a state machine\n\nThere is no derived status anywhere. A request is open because no artifact or review references it, not because something wrote `status: open`. Reviews pin exact CIDs, so an approval of v2 says nothing about v3 and nothing has to be invalidated when a v3 lands.\n\n## What this document is for\n\nThis is the first thing every turn on this project reads. If it is wrong, every plan starts wrong.', tim, '2026-07-08T14:44:00Z', '2026-07-08T15:10:00Z', arch2, [{ uri: arch2.uri, cid: arch2.cid }], ) // Three implementations have merged since v3 landed, so the document has drifted and tim asked for // a fresh read — open to any member rather than named to anybody, which is what makes it claimable. // An agent took it under a live lease, so this is the one ask the index genuinely KNOWS a turn is // running on. Project-scoped too: an ask hangs off a system document as readily as off a goal's. const archReview = request({ author: tim, rkey: '3lbg3rq6ss60r', anchor: ngRef, type: 'review', subject: ref(arch3), at: '2026-07-25T13:48:00Z', brief: 'Three implementations have merged since this landed. Say what is now wrong in it rather than rewriting it.', }) // Dated off FIXTURE_NOW like the g7 claim: claimed half an hour ago, renewed two minutes ago, and // the ten minutes to `expiresAt` is the lease itself. put(reviewer, '3lbg3clm0002', { $type: COLLECTIONS.claim, request: ref(archReview), expiresAt: '2026-07-25T14:26:00Z', renewedAt: '2026-07-25T14:16:00Z', createdAt: '2026-07-25T13:49:00Z', })
// conventions — a second living document, two versions. const conv1 = sysVersion( '3lbj8ff1qq66c', '3lbj8gg2rr66d', 'conventions', 'Permutation coverage before anything consumes the index', '## Tests\n\nAnything touching the index gets permutation coverage before the daemon or UI consumes it.', ana, '2026-07-05T16:20:00Z', '2026-07-05T16:50:00Z', ) sysVersion( '3lbm4ff7qq70r', '3lbm5gg8ss70s', 'conventions', 'Tests, reviews and commit attribution', '## Tests\n\nAnything touching the index lands in `core` with permutation-property coverage before the daemon or the UI consumes it. A shuffled-arrival test that does not print its seed on failure is not a test, it is a rumour.\n\n## Reviews\n\nA review names the exact file and line when it can. Severity is a claim about consequence, not about confidence — if you are unsure whether something is real, say so in the body rather than downgrading it to `low`.\n\n## Commits\n\nThe agent and its DID go in the `Co-Authored-By:` trailer. On-protocol records stay the authoritative attribution; the forge only mirrors it.', ana, '2026-07-16T11:32:00Z', '2026-07-16T12:04:00Z', conv1, [{ uri: conv1.uri, cid: conv1.cid }], )
// ADR 0007 — captured from g3's plan: a project-scoped request whose basedOn is a goal artifact. sysVersion( '3lbnb5r7x309k', '3lbnc6s8y309m', 'adr', '0007 · Containment by possession, not by egress', 'Status: accepted\n\n## Context\n\nTurn containers originally ran behind an egress allowlist naming the git remote, the model API, and the sidecar. Every new capability an agent needed arrived as a proxy rule. The allowlist drifted into being a second, worse specification of what agents are for.\n\n## Decision\n\nContainment is enforced by what a container possesses, not by what it can reach. Turns run on an ordinary bridge network. They hold the operator’s forge token and a spend-capped model key. They never hold an atproto credential.\n\n## Consequences\n\nA compromised turn can read the public internet and can push to the forge as the operator. It cannot forge a signed record, cannot emit a record type it was not asked for, and cannot spend past the model key’s cap.', tim, '2026-07-19T16:31:00Z', '2026-07-19T17:02:00Z', undefined, [{ uri: u5v1.uri, cid: u5v1.cid }], )
// ADR 0004 → 0008. The comp draws "supersedes 0004" as one row with two entries; that is a // `prev` chain on a project-scoped type, which only works because §3.2 generalized prev. // 0004 is the second deliberately UNTITLED record (see u13v1): the oldest thing in the demo, from // before the field existed, and an append-only chain whose successor has a title while it does not. // That is what a real ADR list will look like for years, and it is what the drawer's version rail has // to handle — v1 draws no heading, v2 draws its own. const adr0004 = sysVersion( '3lbc2ii9tt30d', '3lbc2jj0uu30e', 'adr', undefined, 'Status: superseded by 0008\n\n## Decision\n\nPoll every member repo on a short interval. Spaces are tens of DIDs; a subscription is machinery we have not earned yet.', tim, '2026-06-12T09:15:00Z', '2026-06-12T09:40:00Z', ) sysVersion( '3lbr7hh1tt42b', '3lbr8ii2vv42c', 'adr', '0008 · Jetstream first, polling as backfill', 'Status: accepted · supersedes 0004\n\n## Context\n\n0004 chose polling on the grounds that a subscription was machinery we had not earned. That was right at the time and is no longer: merge observation is the latency a human actually feels, and a poll interval short enough to hide it is a poll interval that wastes most of its requests.\n\n## Decision\n\nSubscribe to Jetstream filtered to `com.disnetdev.radial.*`. Polling stays, demoted to backfill.\n\n## Consequences\n\nTwo ingestion paths can now disagree about a record’s earliest observed revision. The fold already keeps the earliest revision seen and flags later CID changes as edit annotations, so this is a reconciliation the index handles rather than a new rule.', tim, '2026-07-25T09:38:00Z', '2026-07-25T10:04:00Z', adr0004, [{ uri: adr0004.uri, cid: adr0004.cid }], )
const adr0005 = sysVersion( '3lbd3kk4ww31f', '3lbd4ll5xx31g', 'adr', '0005 · Artifact types are registry data, not lexicon', 'Status: accepted\n\n## Decision\n\n`plan` and `implementation` ship as built-in registry records, not as lexicon. A space admin adds `security-review` by writing a registry record with a brief template and an output spec.\n\n## Consequences\n\nNew capabilities are configuration. The UI grows a button; nothing grows a schema. The cost is that a bad registry entry is now a deploy-free way to waste tokens, so registry writes stay admin-only.', tim, '2026-07-02T14:02:00Z', '2026-07-02T14:20:00Z', ) const adr0003 = sysVersion( '3lbc9mm6yy28h', '3lbca0n7zz28j', 'adr', '0003 · Reviews annotate, they do not gate', 'Status: accepted\n\n## Decision\n\nA `request_changes` verdict blocks nothing mechanically. It is information on the card. A team wanting "no implementation without an approved plan" gets it as a soft UI warning, not a protocol guarantee.\n\n## Why this is safe\n\nThe thing a gate would protect against is an agent autonomously spending tokens past an unapproved plan. That cannot happen when every generation has a human click behind it.', ana, '2026-06-28T10:44:00Z', '2026-06-28T11:10:00Z', )
// ── the doc tiers (design spike) ──────────────────────────────────────────────────────────── // Tier 1 is everything above, verbatim. These are tiers 2 and 3, and they are the same records as // tier 1 — a project-scoped request and the artifact answering it — differing only in the registry // name they carry and in what their request's `basedOn` pins. // // The linkage is the point. A user doc names the system artifacts it documents; a roadmap doc // names goals. Both are strongrefs on a signed request, so "documents architecture at v1" is a // fact rather than a sentence somebody wrote in prose, which is what lets `sourceDrift()` say the // doc is two versions behind its source without anything having to parse a body. // // Authored by a HUMAN, request and artifact alike, and the request is assigned to its own author: // that is exactly what editing a doc in the UI writes, and an unassigned open request is one any // operator's daemon may claim (`dispatch.ts`), so a half-finished edit must never look open. // // Read by a second member, not by an agent. Auto-review is deliberately off for both doc types — // a member fixing a typo must not dispatch a review turn — but a wrong page is expensive to leave // standing, so the demo shows the same verdict machinery the rest of the space uses, written by a // person. Nothing about the record is different; only who signed it. const doc = ( author: string, rkeyRequest: string, rkeyArtifact: string, typeName: string, title: string, body: string, at: string, verdictAt: string, basedOn: StrongRef[], prev?: StoredRecord, ): StoredRecord => { const req = request({ author, rkey: rkeyRequest, anchor: ngRef, type: typeName, assignee: author, at, basedOn, }) const art = artifact({ author, rkey: rkeyArtifact, request: req, anchor: ngRef, type: typeName, title, body, at, ...(prev ? { prev } : {}), }) review(author === ana ? tim : ana, `rev-${rkeyArtifact}`, art, 'approve', verdictAt) return art }
// Drifted on purpose: written against architecture v1, and the architecture is on v3. The doc // page says "documents Architecture v1 · now at v3" and the obvious click is to update it. doc( tim, '3lbs1dq0aa80a', '3lbs1dc1bb80b', 'user-doc', 'Review queue', 'Everything waiting on your judgement, on one page.\n\n## What it is\n\nWhen an agent finishes a piece of work, somebody has to say whether it is right. The review queue is the list of those — every version pinned to you for a verdict, across every goal and every project in the space.\n\n## Approving, and asking for changes\n\nA verdict is either an approval or a request for changes, and it can carry findings: a file, a line, a severity, and what you want done. Findings travel with the verdict, so the agent writing the next version reads them in its brief.\n\n## What a verdict does not do\n\nNothing is blocked by one. Asking for changes on a plan does not stop anybody building on that plan, and approving one does not authorise anything to run. A verdict is information for the next person, and every generation has a human click behind it either way.', '2026-07-26T10:30:00Z', '2026-07-26T11:02:00Z', [{ uri: arch1.uri, cid: arch1.cid }, { uri: adr0003.uri, cid: adr0003.cid }], ) // Current with both its sources: no drift badge, which is what the badge means. doc( tim, '3lbs2eq2cc80c', '3lbs2ec3dd80d', 'user-doc', 'Artifact types', 'What your space can ask for, and how to add to it.\n\n## Types are settings, not code\n\nA space starts out able to ask for a plan and an implementation. Adding "security review" is a setting an admin changes: name it, write the brief an agent will be given, say what it produces, and it appears on every goal from that moment. Nothing is deployed and nothing is upgraded.\n\n## Where a type can be asked for\n\nA type belongs to a goal or to a project. A goal type answers a piece of work — a plan, an implementation. A project type outlives every goal under it — an architecture document, a decision record, this page.\n\n## Living and append-only\n\nA living type is revised in place: the architecture document has one page and many versions. An append-only type is never edited, only superseded — decision record 0008 replaces 0004 and both stay readable, because the reason a decision changed is part of the record.', '2026-07-26T11:15:00Z', '2026-07-26T11:48:00Z', [{ uri: adr0005.uri, cid: adr0005.cid }, { uri: arch3.uri, cid: arch3.cid }], ) // The roadmap tier: its sources are GOALS, so the page's road-ahead and road-travelled sections // are derived from live goal state rather than authored — a goal closing moves itself between them. doc( tim, '3lbs3fq4ee80e', '3lbs3fc5ff80f', 'roadmap-doc', 'Ingestion', 'How records get from somebody’s repo into your view of the space, and what we are doing about it next.\n\nRadial has no server in the middle. Every participant reads the same records from each member’s own repository and folds them into the same view, which means "has it arrived yet" is a real question with a real answer — and that answer is what this epic is about.', '2026-07-26T12:00:00Z', '2026-07-26T12:35:00Z', [ { uri: g4.uri, cid: g4.cid }, { uri: g5.uri, cid: g5.cid }, { uri: g9.uri, cid: g9.cid }, { uri: arch3.uri, cid: arch3.cid }, ], )
return { records, spaceUri: space.uri, asOf: FIXTURE_NOW, projects: { radialNg: radialNg.uri, radialSite: radialSite.uri }, goals: { reviewQueue: g1.uri, capture: g2.uri, possession: g3.uri, jetstream: g4.uri, autoReview: g5.uri, permutation: g6.uri, onboarding: g7.uri, ignored: g8.uri, budgets: g9.uri, diffViewer: g10.uri, permissioned: g11.uri, }, }}