Shared builder for the canonical turn event — the substrate-level record of one driver turn, emitted identically by both turn drivers (plan D1):
PtcRunner.Sessionturns (external LLM drives via MCP /mix ptc.repl), and- the
PtcRunner.SubAgentloop (the internal loop drives the LLM).
Both drivers build their turn record through build/1 so the top-level shape
is identical and queryable through the same PtcRunner.TraceLog.Analyzer
calls, regardless of which driver produced it. Driver-specific richness lives
in the data bag; the top-level fields never diverge.
The map is a v2-flat-compatible envelope (event: "turn", schema_version: 2)
so it rides the existing PtcRunner.TraceLog JSONL/in-memory sinks. The sink
stamps trace_id, timestamp, and seq; this builder never sets them.
Top-level fields
schema_version, event ("turn"),
driver ("session" | "sub_agent"),
session_id, agent_id, agent_name,
turn, attempt, committed, status,
duration_ms, input_tokens, output_tokens, total_tokens,
dataturn is the committed-state counter (advances only when an attempt commits);
attempt is the monotonic per-attempt counter (advances on every attempt,
including failed ones); committed flags whether this attempt advanced
committed state. Failed/parse-error/budget-stop attempts are recorded with
committed: false so wasted work is visible without mutating driver state
(plan P2 notes).
data bag
program, raw_response, result_preview, prints, memory_diff,
tool_calls, limits_hit, preludes, fail, turn_typePer-driver fields that don't apply are nil/empty. raw_response carries what
the driver's LLM generated when there is no parsed program (SubAgent
parse/text-mode failures); it is nil for session turns and normal turns. The whole bag is run through
PtcRunner.TraceLog.Event.sanitize/1, which bounds large strings/lists/maps —
that is where memory-diff values and prints get their byte bounds.
Summary
Functions
Builds the canonical turn-event map from normalized attributes.
Computes a memory diff (changed_keys + bounded values) between the
pre-turn and post-turn memory maps. Keys whose value is unchanged are
excluded. PTC-Lisp def cannot remove bindings, so this only surfaces
additions and rebindings.
Slims a prelude trace summary (PtcRunner.Lisp.Prelude.trace_summary/1) to the
turn-event preludes provenance shape — [%{"source_hash" => ..., "namespaces" => ..., "components" => [...]}], or [] when no prelude was
attached. The components key is omitted for single-source prelude artifacts.
Renders a bounded, JSON-safe preview string for a turn result value.
Builds the credential-free turn-log projection for a single tool call.
Types
Functions
Builds the canonical turn-event map from normalized attributes.
Required: :driver (:session | :sub_agent). All other keys are optional
and default to nil / empty. trace_id/timestamp/seq are intentionally
omitted — the sink stamps them.
Computes a memory diff (changed_keys + bounded values) between the
pre-turn and post-turn memory maps. Keys whose value is unchanged are
excluded. PTC-Lisp def cannot remove bindings, so this only surfaces
additions and rebindings.
Slims a prelude trace summary (PtcRunner.Lisp.Prelude.trace_summary/1) to the
turn-event preludes provenance shape — [%{"source_hash" => ..., "namespaces" => ..., "components" => [...]}], or [] when no prelude was
attached. The components key is omitted for single-source prelude artifacts.
Shared by both drivers so the provenance field (the single field that makes A/B benchmarking and derivation provenance trivial, per the plan) reads identically whether a session or a SubAgent turn produced it.
Renders a bounded, JSON-safe preview string for a turn result value.
Shared by both drivers so result_preview reads the same regardless of who
produced the turn.
Builds the credential-free turn-log projection for a single tool call.
The projection intentionally excludes raw arguments and results. It keeps a
stable args_hash derived from the same canonical argument identity used by
tool caching, so PTC-Lisp log analysis can detect duplicate fetches without
ingesting potentially large or sensitive payloads.