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.zig114
1 files changed, 64 insertions, 50 deletions
diff --git a/src/system_prompt.zig b/src/system_prompt.zig
index 0b449dc..b5423f8 100644
--- a/src/system_prompt.zig
+++ b/src/system_prompt.zig
@@ -137,12 +137,11 @@ pub fn seedFresh(
io: Io,
environ_map: *const std.process.Environ.Map,
base_dir: []const u8,
- conv: *panto.conversation.Conversation,
- mgr: *panto.session_manager.SessionManager,
+ agent: *panto.agent.Agent,
) !void {
const user_dir = try userLayerDir(arena, environ_map);
const project_dir = try projectLayerDir(arena, io);
- return seedFreshLayers(arena, io, base_dir, user_dir, project_dir, conv, mgr);
+ return seedFreshLayers(arena, io, base_dir, user_dir, project_dir, agent);
}
/// Layer-explicit variant of `seedFresh` (see `resolveLayers`).
@@ -152,14 +151,13 @@ pub fn seedFreshLayers(
base_dir: ?[]const u8,
user_dir: ?[]const u8,
project_dir: ?[]const u8,
- conv: *panto.conversation.Conversation,
- mgr: *panto.session_manager.SessionManager,
+ agent: *panto.agent.Agent,
) !void {
const resolved = try resolveLayers(arena, io, base_dir, user_dir, project_dir);
const blocks = try resolved.blocks(arena);
for (blocks) |text| {
- try conv.addSystemMessage(text);
- try appendSystemEntry(mgr.allocator, mgr, text, .append);
+ // The agent adds to its conversation and persists the entry.
+ try agent.addSystemMessage(text, .append);
}
}
@@ -176,12 +174,11 @@ pub fn reconcileResume(
io: Io,
environ_map: *const std.process.Environ.Map,
base_dir: []const u8,
- conv: *panto.conversation.Conversation,
- mgr: *panto.session_manager.SessionManager,
+ agent: *panto.agent.Agent,
) !void {
const user_dir = try userLayerDir(arena, environ_map);
const project_dir = try projectLayerDir(arena, io);
- return reconcileResumeLayers(arena, io, base_dir, user_dir, project_dir, conv, mgr);
+ return reconcileResumeLayers(arena, io, base_dir, user_dir, project_dir, agent);
}
/// Layer-explicit variant of `reconcileResume` (see `resolveLayers`).
@@ -191,22 +188,21 @@ pub fn reconcileResumeLayers(
base_dir: ?[]const u8,
user_dir: ?[]const u8,
project_dir: ?[]const u8,
- conv: *panto.conversation.Conversation,
- mgr: *panto.session_manager.SessionManager,
+ agent: *panto.agent.Agent,
) !void {
const resolved = try resolveLayers(arena, io, base_dir, user_dir, project_dir);
const config_blocks = try resolved.blocks(arena);
- const window = try effectiveConfigWindow(arena, conv.messages.items);
+ const window = try effectiveConfigWindow(arena, agent.conversation.messages.items);
if (blocksEqual(config_blocks, window)) return;
// Re-seed: one `replace` for the seed, then one `append` per append.
- try conv.replaceSystemMessage(config_blocks[0]);
- try appendSystemEntry(mgr.allocator, mgr, config_blocks[0], .replace);
+ // The agent adds to its conversation and persists each entry (with the
+ // correct system mode).
+ try agent.addSystemMessage(config_blocks[0], .replace);
for (config_blocks[1..]) |text| {
- try conv.addSystemMessage(text);
- try appendSystemEntry(mgr.allocator, mgr, text, .append);
+ try agent.addSystemMessage(text, .append);
}
}
@@ -292,22 +288,6 @@ fn blocksEqual(a: []const []const u8, b: []const []const u8) bool {
return true;
}
-/// Append a system-role entry to the session log carrying `mode`.
-fn appendSystemEntry(
- alloc: Allocator,
- mgr: *panto.session_manager.SessionManager,
- text: []const u8,
- mode: panto.session_manager.DiskSystemMode,
-) !void {
- const blocks = try alloc.alloc(panto.session_manager.DiskContentBlock, 1);
- blocks[0] = .{ .text = .{ .text = try alloc.dupe(u8, text) } };
- _ = try mgr.appendMessage(
- .{ .role = .system, .mode = mode, .content = blocks },
- null,
- null,
- );
-}
-
/// Read `dir/name`, returning its contents (owned by `arena`) or null if the
/// file does not exist. Other I/O errors propagate.
fn readLayerFile(
@@ -487,6 +467,34 @@ fn openTmpSession(arena: Allocator, root: []const u8) !panto.session_manager.Ses
return panto.session_manager.SessionManager.init(testing.allocator, testing.io, sessions, "/cwd");
}
+/// Minimal agent harness for system-prompt tests: an empty registry and a
+/// throwaway provider config, wrapping a session store and (optionally) an
+/// adopted conversation. Holds the registry/config so the agent's borrowed
+/// pointers stay valid for the agent's lifetime.
+const SPAgentHarness = struct {
+ registry: panto.ToolRegistry,
+ config: panto.config.Config,
+ agent: panto.agent.Agent,
+
+ fn init(
+ self: *SPAgentHarness,
+ store: panto.session_store.SessionStore,
+ adopted: ?panto.conversation.Conversation,
+ ) void {
+ self.registry = panto.ToolRegistry.init(testing.allocator);
+ self.config = .{
+ .provider = .{ .openai_chat = .{ .api_key = "k", .base_url = "u", .model = "m" } },
+ .registry = &self.registry,
+ };
+ self.agent = panto.agent.Agent.init(testing.allocator, testing.io, &self.config, store, adopted);
+ }
+
+ fn deinit(self: *SPAgentHarness) void {
+ self.agent.deinit();
+ self.registry.deinit();
+ }
+};
+
/// 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.)
@@ -512,12 +520,13 @@ test "seedFresh then no-config-change resume is a no-op" {
try layers.writeProject("APPEND_SYSTEM.md", "project append");
const project_dir = try layers.projectDir(arena);
- // Fresh session: seed + persist.
+ // Fresh session: seed + persist (through the agent).
var mgr = try openTmpSession(arena, layers.root);
{
- var conv = panto.conversation.Conversation.init(alloc);
- defer conv.deinit();
- try seedFreshLayers(arena, testing.io, null, null, project_dir, &conv, &mgr);
+ var h: SPAgentHarness = undefined;
+ h.init(mgr.store(), null);
+ defer h.deinit();
+ try seedFreshLayers(arena, testing.io, null, null, project_dir, &h.agent);
}
try forceFlush(&mgr); // persist seed + append (+ assistant) to disk
const entries_on_disk = mgr.getEntries().len;
@@ -525,13 +534,15 @@ test "seedFresh then no-config-change resume is a no-op" {
const session_file = try arena.dupe(u8, mgr.getSessionFile());
mgr.deinit();
- // Resume: reopen the file, rebuild + reconcile against unchanged config
- // → no new entries.
+ // 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();
- var conv2 = try mgr2.rebuildConversation();
- defer conv2.deinit();
- try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &conv2, &mgr2);
+ const conv2 = try mgr2.rebuildConversation();
+ var h2: SPAgentHarness = undefined;
+ h2.init(mgr2.store(), conv2);
+ defer h2.deinit();
+ try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &h2.agent);
try testing.expectEqual(entries_on_disk, mgr2.getEntries().len);
}
@@ -548,9 +559,10 @@ test "resume after config change appends replace + append sequence" {
var mgr = try openTmpSession(arena, layers.root);
{
- var conv = panto.conversation.Conversation.init(alloc);
- defer conv.deinit();
- try seedFreshLayers(arena, testing.io, null, null, project_dir, &conv, &mgr);
+ var h: SPAgentHarness = undefined;
+ h.init(mgr.store(), null);
+ defer h.deinit();
+ try seedFreshLayers(arena, testing.io, null, null, project_dir, &h.agent);
}
try forceFlush(&mgr); // persist old seed (+ assistant) to disk
try testing.expectEqual(@as(usize, 2), mgr.getEntries().len); // seed + assistant
@@ -563,15 +575,17 @@ test "resume after config change appends replace + append sequence" {
var mgr2 = try panto.session_manager.SessionManager.open(alloc, testing.io, session_file);
defer mgr2.deinit();
- var conv2 = try mgr2.rebuildConversation();
- defer conv2.deinit();
- try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &conv2, &mgr2);
+ const conv2 = try mgr2.rebuildConversation();
+ var h2: SPAgentHarness = undefined;
+ h2.init(mgr2.store(), 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, conv2.messages.items);
+ 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]);
@@ -579,7 +593,7 @@ test "resume after config change appends replace + append sequence" {
// 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;
- try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &conv2, &mgr2);
+ try reconcileResumeLayers(arena, testing.io, null, null, project_dir, &h2.agent);
try testing.expectEqual(after_first, mgr2.getEntries().len);
}