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

16 KiB
Raw Blame History

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 eff216efc66ee350, 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 :typed 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

cd /root/rose-ash-loops/agentic
bash lib/agentic/conformance.sh   # 196/196; durable suite loads scheme+flow (~2 min)