summaryrefslogtreecommitdiff
path: root/libpanto/src/turn_persist.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/turn_persist.zig')
-rw-r--r--libpanto/src/turn_persist.zig133
1 files changed, 0 insertions, 133 deletions
diff --git a/libpanto/src/turn_persist.zig b/libpanto/src/turn_persist.zig
deleted file mode 100644
index 6d260c8..0000000
--- a/libpanto/src/turn_persist.zig
+++ /dev/null
@@ -1,133 +0,0 @@
-//! Turn → session-log persistence: map in-memory `Conversation` messages
-//! to rich `PersistentMessage` write records and append them through a
-//! `Session` handle.
-//!
-//! This logic lives in `libpanto` and is called by the `Agent` itself, so
-//! every embedder gets persistence for free. The functions here are
-//! stateless helpers over a `Session`; the agent owns the store and the
-//! conversation.
-//!
-//! The write record is **maximalist** (full wire identity + usage + the
-//! entire current conversation + the offered tool set). The library offers
-//! it all on every append; each store keeps what it wants. `PersistentMessage`
-//! borrows the in-memory `Message` directly — no disk conversion happens
-//! here; the store does its own serialization.
-//!
-//! Per-message usage is read directly off `Message.usage`.
-
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-
-const conversation = @import("conversation.zig");
-const session_store = @import("session_store.zig");
-
-const PersistentMessage = session_store.PersistentMessage;
-const WireIdentity = session_store.WireIdentity;
-const ToolDecl = session_store.ToolDecl;
-const Session = session_store.Session;
-
-/// Persist every conversation message at index `>= start_index` through
-/// `session` as a single atomic batch.
-///
-/// Each `PersistentMessage` borrows the in-memory `Message`, the full
-/// current conversation, and `tools` (the offered tool set) — all owned by
-/// the caller and valid for the duration of the call. The wire `identity`
-/// is stamped on every message; the store decides per-role what to keep
-/// (the FS store drops the stamp on system entries).
-///
-/// An assistant message carrying a ToolUse with no following matching
-/// ToolResult is skipped (a dangling tool call from an interrupted turn);
-/// persisting it would make the log un-replayable.
-pub fn persistTurn(
- alloc: Allocator,
- session: *Session,
- conv: *conversation.Conversation,
- start_index: usize,
- identity: WireIdentity,
- tools: []const ToolDecl,
-) !void {
- return persistRange(alloc, session, conv, start_index, conv.messages.items.len, identity, tools);
-}
-
-/// Persist conversation messages in `[start_index, end_index)`. Like
-/// `persistTurn` but with an explicit upper bound, so an incremental flush can
-/// exclude a trailing not-yet-coherent message (a dangling tool call awaiting
-/// its results) while still committing everything before it.
-pub fn persistRange(
- alloc: Allocator,
- session: *Session,
- conv: *conversation.Conversation,
- start_index: usize,
- end_index: usize,
- identity: WireIdentity,
- tools: []const ToolDecl,
-) !void {
- var batch: std.ArrayList(PersistentMessage) = .empty;
- defer batch.deinit(alloc);
-
- const all_messages = conv.messages.items;
- var i = start_index;
- while (i < end_index) : (i += 1) {
- const msg = &all_messages[i];
- if (msg.role == .assistant and hasToolUseWithoutFollowingResults(conv, i)) {
- continue;
- }
- // Stamp the producing identity once, on first persist. A message that
- // already carries one (e.g. a kept-verbatim turn carried through
- // compaction, or one rebuilt from disk) keeps it — only the messages
- // freshly produced under `identity` are stamped now. This is what
- // makes the stamp survive a later compaction onto a different model.
- if (msg.identity == null) {
- msg.identity = try conversation.dupeWireIdentity(alloc, identity);
- }
- try batch.append(alloc, .{
- .message = msg.*,
- .usage = msg.usage,
- .identity = msg.identity.?,
- .conversation = all_messages,
- .tools_available = tools,
- });
- }
-
- if (batch.items.len == 0) return;
- try session.append(batch.items);
-}
-
-/// Persist a compaction result. The agent rewrote the conversation to
-/// `[system..., summary, kept-suffix...]`; persist everything from the
-/// latest compaction summary onward as fresh entries.
-pub fn persistCompaction(
- alloc: Allocator,
- session: *Session,
- conv: *conversation.Conversation,
- identity: WireIdentity,
- tools: []const ToolDecl,
-) !void {
- const start = conversation.latestCompactionIndex(conv.messages.items) orelse return;
- try persistTurn(alloc, session, conv, start, identity, tools);
-}
-
-/// True when the assistant message at `index` contains a ToolUse block
-/// with no matching ToolResult in the immediately following user message
-/// — i.e. a dangling tool call (e.g. a turn cut short by an error).
-pub fn hasToolUseWithoutFollowingResults(
- conv: *const conversation.Conversation,
- index: usize,
-) bool {
- const msg = conv.messages.items[index];
- for (msg.content.items) |block| {
- if (block != .ToolUse) continue;
- if (index + 1 >= conv.messages.items.len) return true;
- const next = conv.messages.items[index + 1];
- if (next.role != .user) return true;
- var found = false;
- for (next.content.items) |next_block| {
- if (next_block == .ToolResult and std.mem.eql(u8, next_block.ToolResult.tool_use_id, block.ToolUse.id)) {
- found = true;
- break;
- }
- }
- if (!found) return true;
- }
- return false;
-}