; lib/agentic/branch.sx — agentic-sx Phase 2: one branch = one agent. ; spawn = branch-from-briefing: write the briefing, then a genesis "spawn" ; agent-commit whose parent is the fork point; the branch ref IS the agent. ; The commit verb snapshots a full worktree VALUE (path -> data) into a typed ; agent-commit and advances the branch by CAS — no shared index, multi-agent ; safe by construction. Branch topology = agent topology: fork points via ; git/merge-base, plus relations typed edges (sub-agent-of / reviews / ; merges) in a Datalog db carried on the space handle. ; Edge direction: (rel src dst kind) reads src=parent/actor, dst=child/object ; (root child sub-agent-of), (reviewer reviewee reviews), (into from merges). ; Session merges are always recorded as a two-parent "session-merge" commit ; (no fast-forward) so the merge itself is an agent action with metadata. ; Requires: lib/agentic/schema.sx, lib/git/*, lib/relations/* (+ datalog). ; ---- space: repo + relations db ---- (define agentic/space (fn (db name) {:repo (git/repo-named db name) :rels (relations-build-db (list))})) (define agentic/space-repo (fn (sp) (get sp :repo))) (define agentic/space-rels (fn (sp) (get sp :rels))) (define agentic/branch-name (fn (agent) (str "agents/" agent))) ; ---- typed edges over relations ---- (define agentic/relate! (fn (sp src dst kind) (begin (dl-assert! (agentic/space-rels sp) (relations-rel src dst kind)) true))) (define agentic/sub-agents (fn (sp agent) (sort (relations-children-of (agentic/space-rels sp) agent (quote sub-agent-of))))) (define agentic/parent-agent (fn (sp agent) (let ((ps (relations-parents-of (agentic/space-rels sp) agent (quote sub-agent-of)))) (if (= (len ps) 0) nil (nth ps 0))))) (define agentic/agent-tree (fn (sp agent) (sort (relations-descendants (agentic/space-rels sp) agent (quote sub-agent-of))))) (define agentic/reviews! (fn (sp reviewer reviewee) (agentic/relate! sp reviewer reviewee (quote reviews)))) (define agentic/reviewers (fn (sp agent) (sort (relations-parents-of (agentic/space-rels sp) agent (quote reviews))))) (define agentic/reviewing (fn (sp agent) (sort (relations-children-of (agentic/space-rels sp) agent (quote reviews))))) (define agentic/merged-sessions (fn (sp agent) (sort (relations-children-of (agentic/space-rels sp) agent (quote merges))))) (define agentic/merged-into (fn (sp agent) (sort (relations-parents-of (agentic/space-rels sp) agent (quote merges))))) ; ---- spawn = branch-from-briefing ---- ; base-cid nil => root agent over an empty tree; parent-agent nil => no edge. ; => {:agent :branch :briefing :genesis} | {:conflict true :actual cid} (define agentic/spawn-at! (fn (sp agent briefing base-cid parent-agent) (let ((repo (agentic/space-repo sp))) (let ((bcid (git/write repo briefing)) (tree (if (nil? base-cid) (git/tree-from-files repo {}) (git/commit-tree (git/read repo base-cid)))) (parents (if (nil? base-cid) (list) (list base-cid)))) (let ((g (agentic/agent-commit tree parents "spawn" {:message (str "spawn: " (agentic/briefing-title briefing)) :agent agent :briefing bcid}))) (let ((gcid (git/write repo g))) (let ((res (git/branch-create! repo (agentic/branch-name agent) gcid))) (if (and (dict? res) (has-key? res :conflict)) res (begin (if (nil? parent-agent) nil (agentic/relate! sp parent-agent agent (quote sub-agent-of))) {:agent agent :briefing bcid :branch (agentic/branch-name agent) :genesis gcid}))))))))) (define agentic/spawn! (fn (sp agent briefing) (agentic/spawn-at! sp agent briefing nil nil))) (define agentic/spawn-from! (fn (sp agent briefing parent-agent) (let ((h (agentic/head sp parent-agent))) (if (nil? h) {:agent parent-agent :error "no-such-agent"} (agentic/spawn-at! sp agent briefing h parent-agent))))) ; ---- heads / listing ---- (define agentic/head (fn (sp agent) (git/branch-get (agentic/space-repo sp) (agentic/branch-name agent)))) (define agentic/agents (fn (sp) (git/refs-under (agentic/space-repo sp) "heads/agents/"))) ; ---- the commit verb: snapshot + typed agent-commit + CAS advance ---- ; files = the agent's FULL worktree value (path -> data). Briefing and agent ; identity propagate from the branch head. => cid | {:error ...} | {:conflict ...} (define agentic/commit! (fn (sp agent kind files meta) (let ((repo (agentic/space-repo sp))) (let ((head (agentic/head sp agent))) (cond ((nil? head) {:agent agent :error "no-such-agent"}) ((not (agentic/commit-kind? kind)) {:error "unknown-kind" :kind kind}) (else (let ((tree (git/tree-from-files repo files)) (b (agentic/commit-briefing (git/read repo head)))) (let ((pm (if (nil? b) {:agent agent} {:agent agent :briefing b}))) (let ((cid (git/write repo (agentic/agent-commit tree (list head) kind (merge meta pm))))) (let ((res (git/branch-cas! repo (agentic/branch-name agent) head cid))) (if (and (dict? res) (has-key? res :conflict)) res cid))))))))))) ; commits authored by this agent, newest first, from its branch head (define agentic/session-log (fn (sp agent) (let ((repo (agentic/space-repo sp))) (let ((head (agentic/head sp agent))) (if (nil? head) (list) (filter (fn (cid) (= (agentic/commit-agent (git/read repo cid)) agent)) (git/log repo head))))))) ; the branch's genesis spawn commit (oldest spawn authored by this agent) (define agentic/genesis (fn (sp agent) (let ((repo (agentic/space-repo sp))) (let ((head (agentic/head sp agent))) (if (nil? head) nil (let ((hits (filter (fn (cid) (let ((c (git/read repo cid))) (and (= (agentic/commit-kind c) "spawn") (= (agentic/commit-agent c) agent)))) (git/log repo head)))) (if (= (len hits) 0) nil (last hits)))))))) (define agentic/briefing-of (fn (sp agent) (let ((repo (agentic/space-repo sp))) (let ((g (agentic/genesis sp agent))) (if (nil? g) nil (let ((bcid (agentic/commit-briefing (git/read repo g)))) (if (nil? bcid) nil (git/read repo bcid)))))))) ; ---- topology: fork points via the DAG ---- (define agentic/fork-point (fn (sp agent-a agent-b) (let ((ha (agentic/head sp agent-a)) (hb (agentic/head sp agent-b))) (if (or (nil? ha) (nil? hb)) nil (git/merge-base (agentic/space-repo sp) ha hb))))) ; ---- session merge: always an explicit two-parent session-merge commit ---- (define agentic/merge-commit-at! (fn (sp into-agent from-agent ours theirs tree meta) (let ((repo (agentic/space-repo sp))) (let ((b (agentic/commit-briefing (git/read repo ours)))) (let ((pm (if (nil? b) {:agent into-agent :merged-agent from-agent} {:agent into-agent :briefing b :merged-agent from-agent}))) (let ((cid (git/write repo (agentic/agent-commit tree (list ours theirs) "session-merge" (merge meta pm))))) (let ((res (git/branch-cas! repo (agentic/branch-name into-agent) ours cid))) (if (and (dict? res) (has-key? res :conflict)) res (begin (agentic/relate! sp into-agent from-agent (quote merges)) {:cid cid :result "merged"}))))))))) ; => {:result "up-to-date"} | {:result "merged" :cid} | ; {:result "conflicts" :tree :conflicts (paths)} | {:error ...} ; Conflicts commit nothing — resolve with agentic/merge-resolve!. (define agentic/merge-session! (fn (sp into-agent from-agent meta) (let ((repo (agentic/space-repo sp))) (let ((ours (agentic/head sp into-agent)) (theirs (agentic/head sp from-agent))) (cond ((nil? ours) {:agent into-agent :error "no-such-agent"}) ((nil? theirs) {:agent from-agent :error "no-such-agent"}) (else (let ((m (git/merge-commits repo ours theirs))) (cond ((= (get m :result) "up-to-date") m) ((= (get m :result) "conflicts") m) (else (let ((tree (if (= (get m :result) "fast-forward") (git/commit-tree (git/read repo theirs)) (get m :tree)))) (agentic/merge-commit-at! sp into-agent from-agent ours theirs tree meta))))))))))) ; conclude a conflicted session merge with resolved worktree files (define agentic/merge-resolve! (fn (sp into-agent from-agent files meta) (let ((repo (agentic/space-repo sp))) (let ((ours (agentic/head sp into-agent)) (theirs (agentic/head sp from-agent))) (if (or (nil? ours) (nil? theirs)) {:error "no-such-agent"} (agentic/merge-commit-at! sp into-agent from-agent ours theirs (git/tree-from-files repo files) meta))))))