summaryrefslogtreecommitdiff
path: root/src/system_prompt.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/system_prompt.zig')
-rw-r--r--src/system_prompt.zig111
1 files changed, 61 insertions, 50 deletions
diff --git a/src/system_prompt.zig b/src/system_prompt.zig
index 6104034..d8c06bb 100644
--- a/src/system_prompt.zig
+++ b/src/system_prompt.zig
@@ -462,29 +462,28 @@ const TmpLayers = struct {
}
};
-fn openTmpSession(arena: Allocator, root: []const u8) !panto.session_manager.SessionManager {
+fn openTmpStore(arena: Allocator, root: []const u8) !panto.session_manager.FileSystemJSONLStore {
const sessions = try std.fs.path.join(arena, &.{ root, "sessions" });
- return panto.session_manager.SessionManager.init(testing.allocator, testing.io, sessions, "/cwd");
+ return panto.session_manager.FileSystemJSONLStore.init(testing.allocator, testing.io, sessions, "/cwd");
}
/// Minimal agent harness for system-prompt tests: a throwaway provider
-/// config wrapping a session store and (optionally) an adopted
-/// conversation. Post-R1 the agent owns its own (empty) tool registry, so
-/// the harness only holds the config to keep the agent's borrowed pointer
-/// valid for its lifetime.
+/// config plus a session (minted from / resolved against the catalog
+/// store). Post-R1 the agent owns its own (empty) tool registry, so the
+/// harness only holds the config to keep the agent's borrowed pointer valid.
const SPAgentHarness = struct {
config: panto.config.Config,
agent: panto.agent.Agent,
fn init(
self: *SPAgentHarness,
- store: panto.session_store.SessionStore,
+ session: panto.session_store.Session,
adopted: ?panto.conversation.Conversation,
) void {
self.config = .{
.provider = .{ .openai_chat = .{ .api_key = "k", .base_url = "u", .model = "m" } },
};
- self.agent = panto.agent.Agent.init(testing.allocator, testing.io, &self.config, store, adopted);
+ self.agent = panto.agent.Agent.init(testing.allocator, testing.io, &self.config, session, adopted);
}
fn deinit(self: *SPAgentHarness) void {
@@ -492,17 +491,19 @@ const SPAgentHarness = struct {
}
};
-/// Force the session to flush to disk by appending an assistant entry.
-/// (System/user entries buffer in memory until the first assistant entry;
-/// a realistic resume only sees what a completed turn persisted.)
-fn forceFlush(mgr: *panto.session_manager.SessionManager) !void {
- const blocks = try mgr.allocator.alloc(panto.session_manager.DiskContentBlock, 1);
- blocks[0] = .{ .text = .{ .text = try mgr.allocator.dupe(u8, "ok") } };
- _ = try mgr.appendMessage(
- .{ .role = .assistant, .content = blocks },
- "prov",
- "model",
- );
+/// Force the session to flush to disk by appending an assistant message
+/// through the agent's session. (System/user messages buffer in memory
+/// until the first assistant message; a realistic resume only sees what a
+/// completed turn persisted.)
+fn forceFlush(agent: *panto.agent.Agent) !void {
+ var conv = panto.conversation.Conversation.init(testing.allocator);
+ defer conv.deinit();
+ try conv.addAssistantMessage(&.{});
+ const id: panto.session_store.WireIdentity = .{ .api_style = .openai_chat, .base_url = "u", .model = "m" };
+ var batch = [_]panto.session_store.PersistentMessage{
+ .{ .message = conv.messages.items[0], .identity = id },
+ };
+ try agent.session.append(&batch);
}
test "seedFresh then no-config-change resume is a no-op" {
@@ -518,29 +519,39 @@ test "seedFresh then no-config-change resume is a no-op" {
const project_dir = try layers.projectDir(arena);
// Fresh session: seed + persist (through the agent).
- var mgr = try openTmpSession(arena, layers.root);
+ var store_impl = try openTmpStore(arena, layers.root);
+ defer store_impl.deinit();
+ const store = store_impl.store();
+ var session_id_buf: [64]u8 = undefined;
+ var session_id_len: usize = 0;
{
var h: SPAgentHarness = undefined;
- h.init(mgr.store(), null);
+ h.init(store.create(), null);
defer h.deinit();
try seedFreshLayers(arena, testing.io, null, null, project_dir, &h.agent);
+ try forceFlush(&h.agent); // persist seed (+ assistant) to disk
+ @memcpy(session_id_buf[0..h.agent.session.info.id.len], h.agent.session.info.id);
+ session_id_len = h.agent.session.info.id.len;
}
- try forceFlush(&mgr); // persist seed + append (+ assistant) to disk
- const entries_on_disk = mgr.getEntries().len;
- try testing.expectEqual(@as(usize, 3), entries_on_disk); // seed + append + assistant
- const session_file = try arena.dupe(u8, mgr.getSessionFile());
- mgr.deinit();
+ const session_id = session_id_buf[0..session_id_len];
- // Resume: reopen the file, adopt the rebuilt conversation + reconcile
- // against unchanged config → no new entries.
- var mgr2 = try panto.session_manager.SessionManager.open(alloc, testing.io, session_file);
- defer mgr2.deinit();
- const conv2 = try mgr2.rebuildConversation();
+ // The persisted conversation reflects the seeded system prompt.
+ var conv_disk = (try store.load(session_id)).?;
+ const eff_disk = try panto.conversation.effectiveSystemBlocks(arena, conv_disk.messages.items);
+ const seeded_count = eff_disk.items.len;
+ conv_disk.deinit();
+ try testing.expect(seeded_count > 0);
+
+ // Resume: adopt the rebuilt conversation + reconcile against unchanged
+ // config → the effective system prompt is unchanged.
+ var sess2 = (try store.resolve(session_id)).?;
+ const conv2 = try sess2.load();
var h2: SPAgentHarness = undefined;
- h2.init(mgr2.store(), conv2);
+ h2.init(sess2, conv2);
defer h2.deinit();
try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &h2.agent);
- try testing.expectEqual(entries_on_disk, mgr2.getEntries().len);
+ const eff_after = try panto.conversation.effectiveSystemBlocks(arena, h2.agent.conversation.messages.items);
+ try testing.expectEqual(seeded_count, eff_after.items.len);
}
test "resume after config change appends replace + append sequence" {
@@ -554,44 +565,44 @@ test "resume after config change appends replace + append sequence" {
try layers.writeProject("SYSTEM.md", "old seed");
const project_dir = try layers.projectDir(arena);
- var mgr = try openTmpSession(arena, layers.root);
+ var store_impl = try openTmpStore(arena, layers.root);
+ defer store_impl.deinit();
+ const store = store_impl.store();
+ var session_id_buf: [64]u8 = undefined;
+ var session_id_len: usize = 0;
{
var h: SPAgentHarness = undefined;
- h.init(mgr.store(), null);
+ h.init(store.create(), null);
defer h.deinit();
try seedFreshLayers(arena, testing.io, null, null, project_dir, &h.agent);
+ try forceFlush(&h.agent); // persist old seed (+ assistant) to disk
+ @memcpy(session_id_buf[0..h.agent.session.info.id.len], h.agent.session.info.id);
+ session_id_len = h.agent.session.info.id.len;
}
- try forceFlush(&mgr); // persist old seed (+ assistant) to disk
- try testing.expectEqual(@as(usize, 2), mgr.getEntries().len); // seed + assistant
- const session_file = try arena.dupe(u8, mgr.getSessionFile());
- mgr.deinit();
+ const session_id = session_id_buf[0..session_id_len];
// Change the config: new seed + a new append.
try layers.writeProject("SYSTEM.md", "new seed");
try layers.writeProject("APPEND_SYSTEM.md", "new append");
- var mgr2 = try panto.session_manager.SessionManager.open(alloc, testing.io, session_file);
- defer mgr2.deinit();
- const conv2 = try mgr2.rebuildConversation();
+ var sess2 = (try store.resolve(session_id)).?;
+ const conv2 = try sess2.load();
var h2: SPAgentHarness = undefined;
- h2.init(mgr2.store(), conv2);
+ h2.init(sess2, conv2);
defer h2.deinit();
try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &h2.agent);
- // old seed + assistant + replace(new seed) + append(new append) = 4.
- try testing.expectEqual(@as(usize, 4), mgr2.getEntries().len);
-
// The effective prompt now reflects only the new config blocks.
const eff = try panto.conversation.effectiveSystemBlocks(arena, h2.agent.conversation.messages.items);
try testing.expectEqual(@as(usize, 2), eff.items.len);
try testing.expectEqualStrings("new seed", eff.items[0]);
try testing.expectEqualStrings("new append", eff.items[1]);
- // A second no-op resume must not append again (anchors to the new
- // `replace` window, not the stale original seed).
- const after_first = mgr2.getEntries().len;
+ // A second no-op resume must not change the effective prompt (anchors
+ // to the new `replace` window, not the stale original seed).
try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &h2.agent);
- try testing.expectEqual(after_first, mgr2.getEntries().len);
+ const eff2 = try panto.conversation.effectiveSystemBlocks(arena, h2.agent.conversation.messages.items);
+ try testing.expectEqual(@as(usize, 2), eff2.items.len);
}
test "Resolved.blocks - seed leads appends" {