Files
rose-ash/plans/agentic-sx-status.md
giles c2a4144784 plans: agentic-sx Phases 1-4 handback + proposed rulings R1-R11
Durable copy of /tmp/sx-build/agentic-status.md: what was built (196/196),
boundary conventions, the 11 open design questions for held Phases 5/7/8/9,
and the proposed rulings awaiting sign-off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:47:13 +00:00

274 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# agentic-sx — Phases 14 DONE, HELD at Phase 5 (handback)
Status: **196/196 green** (schema 65, branch 53, trace 35, durable 43).
Worktree `/root/rose-ash-loops/agentic`, branch `loops/agentic` (off `loops/git`).
Source `lib/agentic/{schema,branch,trace,durable}.sx`, suites
`lib/agentic/tests/*.sx`, runner `lib/agentic/conformance.sh`, scores
`lib/agentic/scoreboard.{md,json}`. Commits eff216ef → c66ee350, one per
phase. NOT pushed. Phases 5+ deliberately NOT started (per brief).
## What was built
The agentic structure IS the open-branch set of a repo: **one branch = one
agent**, seeded by a briefing. Pure assembly over `lib/git` + `persist` +
`relations` + `flow` — zero new substrates, zero third-party deps.
### Phase 1 — schema (`schema.sx`)
Objects are plain SX dicts, content-addressed via sx-git's native CID
(`git/cid` / `git/write` accept any `:type`d dict — the extensibility sx-git
promised downstream).
- **Type registry** `agentic/types`: object types `briefing`,
`console-trace`, `behaviour` (TAG only — library HELD Phase 8) + commit
kinds `agent-commit` (base) and subtypes `spawn`, `finding`, `refactor`,
`test`, `session-merge`, `decision`. `agentic/is-a?` (reflexive,
transitive, cycle-bounded), `agentic/register-type!` (create-only,
parent-checked — how sx-gitea adds e.g. `review` kinds at runtime).
- **Agent-commits ARE git commits**: the kind rides in `:agent-type`, open
fields (`:briefing :agent :message :behaviour-cid …`) round-trip and
participate in the CID; all sx-git DAG/branch/merge machinery applies
unchanged. `spawn` subtype added beyond the briefed five for branch
genesis commits.
- Constructors/predicates/accessors for briefing, console-trace, behaviour;
`agentic/agent-commit` validates its kind against the registry.
### Phase 2 — branch (`branch.sx`)
- `agentic/space db name``{:repo (git/repo-named …) :rels (relations db)}`.
- **spawn = branch-from-briefing**: `spawn!` (root, empty tree) /
`spawn-from!` (forks at parent agent's head + asserts `sub-agent-of`
edge) / `spawn-at!` (arbitrary fork cid). Writes briefing → genesis
`spawn` commit (carries `:briefing` cid) → `git/branch-create!`
`agents/<name>` (create-only; conflict dict on reuse).
- **The commit verb** `agentic/commit!`: snapshots a FULL worktree VALUE
(path→data) via `git/tree-from-files`, builds a typed agent-commit
(briefing + agent identity propagate from the head), advances the branch
by `git/branch-cas!`. No shared index → multi-agent safe by construction.
- **Topology**: `agents`, `head`, `genesis`, `briefing-of`, `session-log`
(own commits newest-first), `fork-point` = `git/merge-base` of heads.
Typed edges over relations: `sub-agent-of` / `reviews` / `merges` with
query wrappers (`sub-agents`, `agent-tree`, `reviewers`, `merged-sessions`,
`merged-into`, generic `relate!`).
- **Session merge** `merge-session!`: always records an explicit two-parent
`session-merge` commit (no-ff — the merge is itself an agent action with
metadata `:merged-agent`), via `git/merge-commits`; `up-to-date` passes
through; conflicts commit NOTHING and conclude via `merge-resolve!`
(resolved worktree → same two-parent shape). Asserts the `merges` edge.
### Phase 3 — trace (`trace.sx`)
- Per-agent buffer = **persist append-only log stream**
(`<prefix>/trace/<agent>`) + kv drain cursor. `agentic/trace! sp agent
kind text` appends; `trace-pending` reads since last drain.
- `commit-with-trace!` = `commit!` + drain everything-since-last-commit into
a `console-trace` object `{:commit cid :agent :entries}` bound **git-note
style**: ref `notes/trace/<commit-cid>` → trace cid. NOT in the commit
tree — attaching never changes the commit cid; the note is a re-bindable
ref layer over immutable objects. Failed commits keep the buffer; plain
`commit!` deliberately leaves binding to the agent (granularity =
commit, agent-chosen). `trace-for` / `trace-cid-for` / `session-traces`,
manual `attach-trace!` for any commit.
### Phase 4 — durable (`durable.sx`)
- **Deterministic replay IS the durability mechanism.** Every transition
re-runs a self-contained flow program: the session's `defflow` source
(durable in kv via `defsession!`) + `flow/start` + replay of every
recorded resume value (fresh `flow-run` resets the flow store, so the
flow id is always 1 — a feature). The ONLY durable state is
`{:flow :input :resumes}` + derived `{:status :tag/:result}` in kv.
- Restart-safe by construction: a fresh `agentic/space` over the same
backend sees suspended sessions and resumes them mid-flight (tested).
- **fork-an-agent-run** `session-fork!` = copy the record to another
spawned agent; replay rebuilds the run to the same suspended state; the
two then diverge independently (tested: 45 vs 1500 from a shared prefix).
- **Effects are data**: suspend tags and typed `(request kind payload)`
envelopes surface as plain SX (`session-pending`, `effect-request?`,
`effect-kind`, `effect-payload`) — the exact seam Phase 5
hold-for-human-input plugs into.
- Session transitions append `"session"`-kind entries to the Phase-3 trace
buffer → session history travels with the agent's next commit (tested).
## Boundary conventions discovered (bake into Phase 5+ designs)
- Guest Scheme numbers box differently at the SX boundary: compare with
`=`, never `equal?` (7 tests initially failed on `45` vs `45`).
- Scheme strings surface as `{:scm-string …}` (unbox via `agentic/scm-out`);
symbols surface as plain strings. Session flows should use quoted-symbol
suspend tags and NUMERIC decision values (`(eq? d 1)`) — a resumed SX
string does not `eq?` a Scheme symbol.
- `agentic/scm-lit` serializes numbers/strings/booleans/lists into program
source (no string escaping — keep payloads quote-free).
## Open design questions for the HELD phases
**Phase 5 — hold-for-human-input** (blocked on sx-gitea web API, in flight):
1. The seam is ready: a suspended session's `(request "human" payload)` is
already exposed as data. Open: does sx-gitea poll `session-pending` per
agent, or should agentic maintain a space-wide pending-requests index
(flow's `flow-host-requests` shape) for the forge UI to list?
2. Should the human decision be recorded as a `decision` agent-commit on
the agent's branch (decision-commit = resume value + rationale, trace
attached) before/atomically-with `session-resume!`? I'd argue yes —
"hold-for-human-input (flow suspend → decision-commit)" per the trilogy
memory — but the commit's files snapshot semantics during a suspension
need a ruling (empty diff? head tree re-snapshot?).
3. Resume-value typing: currently any `scm-lit`-serializable value; does
the sx-gitea API constrain it (approve/reject enum vs free payload)?
**Phase 7 — fed + type×trigger/execute trust gate**:
4. fed-sx (DefineType/SubtypeOf runtime) is NOT in this worktree — only
design plans (`plans/fed-sx-*.md`) and `lib/relations/federation.sx`
(peer_rel + trust facts). Phase 1's registry declares tags locally as
agreed; migrating `agentic/types` to fed-sx types needs the fed-sx-types
loop's substrate (loops/fed-sx-types) merged or vendored.
5. relations already gives a per-peer trust gate over federated edges
(`erel :- peer_rel + trust`) — is the type×trigger gate a Datalog rule
over (peer, agentic-type, trigger-verb) facts in the same db, or a
separate capability object in the git store? The former composes with
what's built.
6. Briefings/commits federate as content-addressed objects for free
(same-CID-everywhere tested); what does NOT federate yet: refs
(branch heads are local mutable state) and note bindings. Ref
replication policy is the real Phase 7 design question.
**Phase 8/9 — behaviour library + self-improvement**:
7. `behaviour` is a registered TAG with constructor/predicate only; an
`agent-commit` carries `:behaviour-cid` (tested). Open: behaviour BODY
format (SX source string? content-addressed flow def like
`defsession!` sources? both?) — deliberately unspecified.
8. Suggestion from the build: `defsession!` sources are already durable,
content-addressable session *behaviours* — Phase 8 could unify
behaviour bodies with session-flow defs (behaviour = the flow an agent
runs). Needs human ruling.
**Smaller notes**:
9. Relations edges (`sub-agent-of`/`reviews`/`merges`) live in the
in-memory Datalog db on the space handle — NOT persisted. Spawn/merge
verbs re-assert them, but a restarted space loses edge history not
re-derivable from commits (reviews edges especially). Option: mirror
edges into a persist event stream and re-saturate on space open
(cheap; ruleset stays minimal). Left for the Phase 5+ pass.
10. `git/repo` index/porcelain (`git/commit!`, `git/merge!`) is repo-global
and deliberately UNUSED here; agentic-sx only uses the value-level API.
If sx-gitea drives both, keep it that way.
11. Trace note refs (`notes/trace/<cid>`) are reflogged like any ref —
free audit trail of re-bindings; surfaced in case the forge wants it.
## Proposed rulings (build loop, 2026-07-03 — for human sign-off)
Numbers match the questions above. Nothing below is implemented; these are
the designs I would build if approved.
**R1 — Derive the pending index, don't maintain one.** Session records
already live in kv (`<prefix>/session/<agent>`). Phase 5 adds one function,
`(agentic/pending-requests sp)``((agent tag) ...)`: enumerate
`(agentic/agents sp)` (refs are the agent registry), kv-get each record,
filter `:status "suspended"`. O(agents) kv reads, no duplicated state, no
invalidation bugs — mirrors `flow-host-requests` shape. The forge polls
this one call per space. If a space ever holds thousands of agents, add an
event-sourced index then, not now.
**R2 — Yes: decision-commit, tree = parent's tree, commit-then-resume.**
New Phase 5 verb `(agentic/decide! sp agent value rationale-meta)`:
(a) trace! a `"session"` entry recording the human decision;
(b) create a `decision` agent-commit whose `:tree` is the PARENT's tree cid
verbatim (empty diff is correct — the decision changes the session, not
the worktree; the payload rides in open commit fields `:resume-tag`,
`:resume-value`, message = rationale) and CAS-advance the branch;
(c) then `session-resume!`, and append the decision-commit cid to a new
`:decisions` list on the session record (parallel to `:resumes`, which
stays bare replay values — serialization unchanged).
Ordering rationale: true atomicity across kv+refs doesn't exist; commit
first so the human's intent is durably recorded even if resume errors, and
resume is deterministic replay so re-applying from the decision-commit is
an idempotent repair. Hold-for-human-input then IS: suspend → forge shows
pending request → `decide!` → decision-commit + resume.
**R3 — Agentic stays permissive; the forge constrains per request kind.**
The `(request kind payload)` envelope already names the kind; sx-gitea maps
kind → input schema (e.g. "human"-review → numeric enum 1/0; free-text
comment goes in the decision-commit message/meta, NOT the resume value —
numeric decisions dodge the symbol/string `eq?` trap by construction).
One small hardening lands with Phase 5: `agentic/scm-lit` gains
backslash/quote escaping for strings (two-line change) so the quote-free
convention becomes a nicety, not a requirement.
**R4 — Local registry = authoritative runtime cache; PROJECT it into
fed-sx when federation lands.** Do not vendor fed-sx here. Each registry
entry maps mechanically to DefineType (+ SubtypeOf parent) activities; on
Phase 7 the emitted type objects' CIDs get pinned back into the registry
(`:fed-cid` per entry), and `register-type!` becomes the local-cache
updater for received DefineType activities — exactly the peer_types cache
pattern from loops/fed-sx-types. Sequencing: merge fed-sx-types substrate
(or both to architecture) BEFORE starting Phase 7; zero rework in
lib/agentic either way because tags are plain strings.
**R5 — Both, layered: capability OBJECTS in the git store as source of
truth, compiled to Datalog FACTS for enforcement.** A grant is a typed
content-addressed object `{:type "capability" :peer :agentic-type :trigger
:execute ...}` — auditable, versionable, federable like everything else.
The current capability SET is a ref (`caps/current` → a tree/list of cap
cids), so revocation = a ref move with reflog, same shape as branches.
At space-open/refresh the set compiles to flat `allowed(Peer, Type,
Trigger)` facts + at most one non-recursive rule composing with the
existing `trust(P)` gate — ruleset stays minimal per the relations
re-saturation constraint. Enforcement = `(agentic/allowed? sp peer type
trigger)` fact lookup at activity-accept time.
**R6 — Owner-writes / others-track; announcements, not replication.**
Branch heads never replicate as writable state. A peer's head move
federates as a signed announcement activity ("agent X: head → cid C");
receivers materialize it as a read-only remote-tracking ref
`remotes/<peer>/agents/<name>` — sx-git's free-form ref names support this
today with zero changes, and CAS stays a purely local concern (only the
owner moves `heads/agents/<name>`). Object transfer = CID closure of the
announced head (`git/reachable-all` gives the exact fetch set; everything
is idempotent content-addressed writes). Cross-peer session-merge = merge
FROM a remote-tracking ref, locally, producing a local session-merge
commit. The R5 gate applies at announcement-accept time (peer × type ×
trigger "head-move" / "spawn" / "merge"). Note bindings federate the same
way: announce `notes/trace/<cid>` → trace cid, land under
`remotes/<peer>/notes/trace/<cid>`.
**R7+R8 — Unify: a behaviour IS the content-addressed unit of agent skill;
sessions are behaviours instantiated.** Behaviour body = SX data with a
`:kind` discriminator; two kinds at launch: `"session-flow"` (body = a
defflow Scheme source string, directly installable) and `"prompt"` (body =
briefing-shaped instructions for LLM-driven agents). Phase 8 adds
`(agentic/defsession-from! sp behaviour-cid)`: read the behaviour object,
check `:kind "session-flow"`, install its body under the behaviour's name
— and the commit verb stamps that cid into `:behaviour-cid`. Then
`:behaviour-cid` means literally "the flow this agent was running when it
made this commit", which closes the Phase 9 loop: self-improvement =
statistics over commits grouped by behaviour-cid → new behaviour objects →
adoption via new spawns referencing them. Evolution stays in immutable
objects + ref-shaped adoption, same invariant as everything else.
**R9 — Yes, event-source the edges (do it in the Phase 5 pass, ~20
lines).** `agentic/relate!` additionally appends `{:src :dst :kind}` to
stream `<prefix>/edges`; `agentic/space` reads the stream on open and
passes the accumulated facts to `relations-build-db` (one build, no
per-fact assert). A future unrelate appends a retract event; replay
applies both in order. Space handles become fully rehydratable — the
in-memory rels db is then just a cache of the stream.
**R10 — Confirmed: porcelain stays unused by agentic and by sx-gitea's
multi-agent paths.** The repo-global index/HEAD is a single-user
convenience; anything concurrent uses the value-level API (tree-from-files
/ agent-commit / branch-cas!). If sx-gitea wants per-user checkouts, each
gets its own repo handle or stays value-level. Document, don't change.
**R11 — Surface trace-note reflogs read-only in the forge.** "Trace
history" tab on a commit = `git/reflog repo "notes/trace/<cid>"` — free
audit of re-bindings (amended traces are visible, nothing is lost since
old trace objects stay in the store). No new API.
## How to run
```bash
cd /root/rose-ash-loops/agentic
bash lib/agentic/conformance.sh # 196/196; durable suite loads scheme+flow (~2 min)
```