; Phase 3 — trace: console output as attached content-addressed objects. ; Fixture story: tracer-1 logs console/tool entries and commits with traces ; (drain-at-commit granularity); quiet-1 stays silent and gets a manual ; genesis trace attached + rebound; a failed commit keeps the buffer; a ; plain commit! deliberately leaves the buffer alone (agent-chosen binding). (define agt-db (persist/mem-backend)) (define agt-sp (agentic/space agt-db "agentic-trace-test")) (define agt-repo (agentic/space-repo agt-sp)) (define agt-a (agentic/spawn! agt-sp "tracer-1" (agentic/briefing "trace things" "exercise the trace layer" {}))) (define agt-b (agentic/spawn! agt-sp "quiet-1" (agentic/briefing "stay quiet" "no console output" {}))) (agentic-test "fresh agent has an empty buffer" (= (agentic/trace-pending agt-sp "tracer-1") (list)) true) (agentic-test "trace! appends to the buffer" (agentic/trace! agt-sp "tracer-1" "console" "$ compiling") true) (agentic/trace! agt-sp "tracer-1" "tool" "sx_eval (+ 1 2)") (agentic-test "pending sees logged entries" (len (agentic/trace-pending agt-sp "tracer-1")) 2) (agentic-test "pending preserves log order" (get (nth (agentic/trace-pending agt-sp "tracer-1") 0) :text) "$ compiling") (agentic-test "buffers are per agent" (= (agentic/trace-pending agt-sp "quiet-1") (list)) true) ; ---- commit drains the buffer into an attached trace ---- (define agt-c1 (agentic/commit-with-trace! agt-sp "tracer-1" "finding" (assoc {} "notes.md" "found it\n") {:message "first finding"})) (agentic-test "commit-with-trace! commits" (starts-with? (get agt-c1 :cid) "sx1:") true) (agentic-test "commit-with-trace! attaches a trace" (starts-with? (get agt-c1 :trace) "sx1:") true) (agentic-test "commit advances the head" (= (agentic/head agt-sp "tracer-1") (get agt-c1 :cid)) true) (agentic-test "trace-for finds the bound trace" (agentic/console-trace? (agentic/trace-for agt-sp (get agt-c1 :cid))) true) (agentic-test "bound trace carries the entries" (len (agentic/trace-entries (agentic/trace-for agt-sp (get agt-c1 :cid)))) 2) (agentic-test "bound trace keeps entry order" (get (nth (agentic/trace-entries (agentic/trace-for agt-sp (get agt-c1 :cid))) 0) :text) "$ compiling") (agentic-test "trace names its commit by cid" (get (agentic/trace-for agt-sp (get agt-c1 :cid)) :commit) (get agt-c1 :cid)) (agentic-test "trace names its agent" (get (agentic/trace-for agt-sp (get agt-c1 :cid)) :agent) "tracer-1") (agentic-test "trace is NOT in the commit tree" (= (git/tree-names (git/read agt-repo (git/commit-tree (git/read agt-repo (get agt-c1 :cid))))) (list "notes.md")) true) (agentic-test "buffer drained after commit" (= (agentic/trace-pending agt-sp "tracer-1") (list)) true) ; ---- granularity = the commit: only entries since the last drain travel ---- (agentic/trace! agt-sp "tracer-1" "console" "$ second round") (define agt-c2 (agentic/commit-with-trace! agt-sp "tracer-1" "refactor" (assoc {} "notes.md" "refined\n") {:message "second"})) (agentic-test "next trace carries only new entries" (len (agentic/trace-entries (agentic/trace-for agt-sp (get agt-c2 :cid)))) 1) (agentic-test "next trace text" (get (nth (agentic/trace-entries (agentic/trace-for agt-sp (get agt-c2 :cid))) 0) :text) "$ second round") (agentic-test "earlier trace is unchanged" (len (agentic/trace-entries (agentic/trace-for agt-sp (get agt-c1 :cid)))) 2) ; ---- a silent commit binds nothing ---- (define agt-c3 (agentic/commit-with-trace! agt-sp "tracer-1" "decision" (assoc {} "notes.md" "done\n") {:message "silent"})) (agentic-test "silent commit has no trace key" (has-key? agt-c3 :trace) false) (agentic-test "silent commit still commits" (= (agentic/head agt-sp "tracer-1") (get agt-c3 :cid)) true) (agentic-test "trace-for nil on a traceless commit" (agentic/trace-for agt-sp (get agt-c3 :cid)) nil) ; ---- attachment is external to the object layer ---- (agentic-test "attached commit round-trips to the same cid" (= (git/cid (git/read agt-repo (get agt-c1 :cid))) (get agt-c1 :cid)) true) (agentic-test "trace object is content-addressed" (= (get agt-c1 :trace) (git/cid (agentic/console-trace (list (agentic/trace-entry "console" "$ compiling") (agentic/trace-entry "tool" "sx_eval (+ 1 2)")) {:agent "tracer-1" :commit (get agt-c1 :cid)}))) true) (define agt-manual (agentic/attach-trace! agt-sp (get agt-b :genesis) (agentic/console-trace (list (agentic/trace-entry "console" "spawn log")) {:commit (get agt-b :genesis)}))) (agentic-test "manual attach to any commit" (starts-with? agt-manual "sx1:") true) (agentic-test "manual attachment is found" (= (agentic/trace-cid-for agt-sp (get agt-b :genesis)) agt-manual) true) (agentic-test "attach validates the object type" (get (agentic/attach-trace! agt-sp (get agt-b :genesis) (agentic/briefing "x" "y" {})) :error) "not-a-console-trace") (define agt-manual2 (agentic/attach-trace! agt-sp (get agt-b :genesis) (agentic/console-trace (list (agentic/trace-entry "console" "amended log")) {:commit (get agt-b :genesis)}))) (agentic-test "re-attach rebinds the note ref" (= (agentic/trace-cid-for agt-sp (get agt-b :genesis)) agt-manual2) true) (agentic-test "rebinding keeps the old object in the store" (agentic/console-trace? (git/read agt-repo agt-manual)) true) ; ---- session-wide view ---- (agentic-test "session-traces pairs commits with traces, newest first" (= (agentic/session-traces agt-sp "tracer-1") (list (list (get agt-c2 :cid) (get agt-c2 :trace)) (list (get agt-c1 :cid) (get agt-c1 :trace)))) true) (agentic-test "session-traces sees manual genesis attachments" (= (agentic/session-traces agt-sp "quiet-1") (list (list (get agt-b :genesis) agt-manual2))) true) ; ---- failed commits keep the buffer ---- (agentic/trace! agt-sp "tracer-1" "console" "$ doomed") (define agt-bad (agentic/commit-with-trace! agt-sp "tracer-1" "frobnicate" {} {})) (agentic-test "failed commit passes the error through" (get agt-bad :error) "unknown-kind") (agentic-test "failed commit keeps the buffer" (len (agentic/trace-pending agt-sp "tracer-1")) 1) (define agt-c4 (agentic/commit-with-trace! agt-sp "tracer-1" "test" (assoc {} "notes.md" "recovered\n") {:message "recover"})) (agentic-test "kept entries travel with the next commit" (get (nth (agentic/trace-entries (agentic/trace-for agt-sp (get agt-c4 :cid))) 0) :text) "$ doomed") ; ---- binding is agent-chosen: plain commit! leaves the buffer alone ---- (agentic/trace! agt-sp "tracer-1" "console" "$ held back") (define agt-c5 (agentic/commit! agt-sp "tracer-1" "decision" (assoc {} "notes.md" "plain\n") {:message "plain"})) (agentic-test "plain commit! binds nothing" (agentic/trace-for agt-sp agt-c5) nil) (agentic-test "plain commit! leaves the buffer" (len (agentic/trace-pending agt-sp "tracer-1")) 1)