summaryrefslogtreecommitdiff
path: root/libpanto
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto')
-rw-r--r--libpanto/src/agent.zig23
-rw-r--r--libpanto/src/config.zig8
-rw-r--r--libpanto/src/public.zig93
3 files changed, 84 insertions, 40 deletions
diff --git a/libpanto/src/agent.zig b/libpanto/src/agent.zig
index 5f9bee5..6d62b8a 100644
--- a/libpanto/src/agent.zig
+++ b/libpanto/src/agent.zig
@@ -206,11 +206,6 @@ pub const Agent = struct {
/// Injectable streaming seam. Defaults to the real provider dispatch
/// (`provider_mod.openStream`); tests override it with a stub.
open_stream_fn: provider_mod.OpenStreamFn = provider_mod.openStream,
- /// Compaction system prompt used for automatic compaction on context
- /// overflow. Borrowed; set by the embedder (resolved from its
- /// `COMPACTION.md` layers). When null, auto-compaction is disabled and
- /// a context-overflow error propagates unchanged.
- compaction_system_prompt: ?[]const u8 = null,
/// Set by the embedder after `runStep` returns to learn whether an
/// automatic compaction occurred this turn (so it can persist the
/// rewritten conversation). Reset at the top of each `runStep`.
@@ -454,7 +449,7 @@ pub const Agent = struct {
err: anyerror,
) !provider_mod.ProviderStream {
if (self.auto_compacted) return err; // already retried once this turn
- const sys = self.compaction_system_prompt orelse return err;
+ const sys = self.config.compaction.compaction_prompt orelse return err;
const res = try self.compact(sys, null);
if (!res.compacted) return err; // nothing to shed; give up
self.auto_compacted = true;
@@ -595,11 +590,19 @@ pub const Agent = struct {
/// if anything was compacted, appends the new compaction window
/// (summary + restated kept suffix) to the store. Returns the same
/// `CompactionResult` for the embedder to report.
+ ///
+ /// `override_system_prompt`, when non-null, is the compaction system
+ /// prompt for this run; when null it falls back to
+ /// `config.compaction.compaction_prompt`. With neither set, this errors
+ /// (`error.NoCompactionPrompt`).
pub fn compactAndPersist(
self: *Agent,
- system_prompt: []const u8,
+ override_system_prompt: ?[]const u8,
extra_instructions: ?[]const u8,
) !CompactionResult {
+ const system_prompt = override_system_prompt orelse
+ self.config.compaction.compaction_prompt orelse
+ return error.NoCompactionPrompt;
const res = try self.compact(system_prompt, extra_instructions);
if (res.compacted) {
try turn_persist.persistCompaction(
@@ -2852,13 +2855,12 @@ test "runStep: auto-compacts on context overflow and retries once" {
var h = TestHarness.init(allocator);
defer h.deinit();
h.activate();
- h.config.compaction = .{ .keep_verbatim = 10 };
+ h.config.compaction = .{ .keep_verbatim = 10, .compaction_prompt = "Summarize the conversation." };
var ns = null_store_mod.NullStore.init(allocator);
var agent = Agent.init(allocator, io, &h.config, ns.store().create(), null);
defer agent.deinit();
h.seedInto(&agent);
agent.open_stream_fn = stub.install();
- agent.compaction_system_prompt = "Summarize the conversation.";
const conv = &agent.conversation;
try conv.addSystemMessage("you are helpful");
@@ -3233,13 +3235,12 @@ test "runStep: context-overflow compaction fires a compaction retry notification
var h = TestHarness.init(allocator);
defer h.deinit();
h.activate();
- h.config.compaction = .{ .keep_verbatim = 10 };
+ h.config.compaction = .{ .keep_verbatim = 10, .compaction_prompt = "Summarize the conversation." };
var ns = null_store_mod.NullStore.init(allocator);
var agent = Agent.init(allocator, io, &h.config, ns.store().create(), null);
defer agent.deinit();
h.seedInto(&agent);
agent.open_stream_fn = stub.install();
- agent.compaction_system_prompt = "Summarize the conversation.";
const conv = &agent.conversation;
try conv.addSystemMessage("you are helpful");
diff --git a/libpanto/src/config.zig b/libpanto/src/config.zig
index 203a091..5e01bb1 100644
--- a/libpanto/src/config.zig
+++ b/libpanto/src/config.zig
@@ -116,6 +116,14 @@ pub const WireIdentity = struct {
pub const CompactionConfig = struct {
keep_verbatim: u32 = 20_000,
model: ?ProviderConfig = null,
+ /// The compaction system prompt. Used both for automatic compaction on
+ /// context overflow and as the default for an explicit `Agent.compact`
+ /// call (which may override it per-call). Borrowed; set by the embedder
+ /// (e.g. resolved from its `COMPACTION.md` layers, or a built-in
+ /// default). When null, auto-compaction is disabled and a
+ /// context-overflow error propagates unchanged, and an explicit
+ /// `compact` with no override is a no-op error.
+ compaction_prompt: ?[]const u8 = null,
};
/// Policy for retrying transient provider/API failures. Conservative
diff --git a/libpanto/src/public.zig b/libpanto/src/public.zig
index 951bd3a..56a0676 100644
--- a/libpanto/src/public.zig
+++ b/libpanto/src/public.zig
@@ -64,7 +64,7 @@ pub const RetryConfig = config_mod.RetryConfig;
pub const WireIdentity = config_mod.WireIdentity;
// ===========================================================================
-// Conversation construction (data, aliased — Job 2)
+// Conversation construction (data, aliased)
// ===========================================================================
pub const ContentBlock = conversation_mod.ContentBlock;
@@ -82,9 +82,17 @@ pub const CompactionSummaryBlock = conversation_mod.CompactionSummaryBlock;
pub const Usage = conversation_mod.Usage;
pub const effectiveSystemBlocks = conversation_mod.effectiveSystemBlocks;
-/// A borrowed handle onto a conversation owned by an `Agent`, or a
-/// standalone-owned conversation built by hand. Pointer-wrapping makes the
-/// "don't move the conversation" invariant structural.
+/// The owned conversation value type. This is what `Session.load` returns
+/// and what `Agent.init` adopts. Build one standalone with `init` for
+/// by-hand construction; once handed to an `Agent` it is owned by the agent.
+/// For *operating on* an agent's live conversation, use the `Conversation`
+/// handle (below) via `Agent.conversation()`.
+pub const ConversationData = conversation_mod.Conversation;
+
+/// A borrowed handle onto a conversation owned by an `Agent`. Pointer-
+/// wrapping makes the "don't move the conversation" invariant structural:
+/// the handle is a cheap copyable view, minted per-call by
+/// `Agent.conversation()`, borrowing state owned by its `Agent`.
pub const Conversation = struct {
inner: *conversation_mod.Conversation,
@@ -113,7 +121,7 @@ pub const Conversation = struct {
};
// ===========================================================================
-// Tools (data + small façade — Job 1)
+// Tools (data + small façade)
// ===========================================================================
pub const Tool = tool_mod.Tool;
@@ -179,6 +187,31 @@ pub const CompactionResult = agent_mod.Agent.CompactionResult;
pub const Agent = struct {
inner: *agent_mod.Agent,
+ /// Construct an agent. The inner is heap-pinned at `init`, so the
+ /// returned `Agent` is a cheap copyable handle ("don't move the Agent"
+ /// stops being a rule anyone can violate). `session` is minted via
+ /// `store.create()` (fresh) or `resolve`/`latest` (resume). When
+ /// `maybe_conversation` is non-null it is adopted (ownership
+ /// transferred) — the resume path hands the value returned by
+ /// `Session.load`.
+ pub fn init(
+ allocator: std.mem.Allocator,
+ io: std.Io,
+ config: *const Config,
+ session: Session,
+ maybe_conversation: ?ConversationData,
+ ) !Agent {
+ const inner = try allocator.create(agent_mod.Agent);
+ inner.* = agent_mod.Agent.init(allocator, io, config, session, maybe_conversation);
+ return .{ .inner = inner };
+ }
+
+ pub fn deinit(self: Agent) void {
+ const allocator = self.inner.allocator;
+ self.inner.deinit();
+ allocator.destroy(self.inner);
+ }
+
pub fn registerTool(self: Agent, t: Tool) !void {
return self.inner.registerTool(t);
}
@@ -191,18 +224,41 @@ pub const Agent = struct {
pub fn run(self: Agent, message: UserMessage) !Stream {
return .{ .inner = try self.inner.run(.{ .text = message.text }) };
}
+
+ /// Append a system message to the conversation and persist it (the
+ /// `.append` `SystemMode`: it adds to the effective prompt).
+ pub fn addSystemMessage(self: Agent, text: []const u8) !void {
+ return self.inner.addSystemMessage(text, .append);
+ }
+ /// Replace the effective system prompt and persist it (the `.replace`
+ /// `SystemMode`: it discards all prior system text). On replay the log
+ /// reconstructs the same effective prompt.
+ pub fn setSystemPrompt(self: Agent, text: []const u8) !void {
+ return self.inner.addSystemMessage(text, .replace);
+ }
+
/// Compact and persist (the explicit `/compact` entry point).
- pub fn compact(self: Agent, system_prompt: []const u8, extra: ?[]const u8) !CompactionResult {
- return self.inner.compactAndPersist(system_prompt, extra);
+ /// `override_system_prompt` falls back to
+ /// `config.compaction.compaction_prompt` when null. `extra` is appended
+ /// to the compaction prompt for this run (the `/compact $ARGUMENTS`
+ /// path).
+ pub fn compact(self: Agent, override_system_prompt: ?[]const u8, extra: ?[]const u8) !CompactionResult {
+ return self.inner.compactAndPersist(override_system_prompt, extra);
}
+
/// A borrowed handle onto the agent's live conversation.
pub fn conversation(self: Agent) Conversation {
return .{ .inner = &self.inner.conversation };
}
+
+ /// The id of the session this agent appends to.
+ pub fn sessionId(self: Agent) []const u8 {
+ return self.inner.session.info.id;
+ }
};
// ===========================================================================
-// Pricing (data, aliased — Job 1)
+// Pricing (data, aliased)
// ===========================================================================
pub const Pricing = pricing_mod.Pricing;
@@ -220,27 +276,6 @@ pub const NullStore = null_store_mod.NullStore;
pub const FileSystemJSONLStore = fs_store_mod.FileSystemJSONLStore;
// ===========================================================================
-// Deep-embedder escape hatches.
-//
-// The `panto` CLI is a *deep* embedder: it drives the agent loop at a level
-// the curated `Agent`/`Conversation` façades deliberately do not expose
-// (system-prompt seeding through the agent, `compaction_system_prompt`, the
-// injectable `open_stream_fn` test seam, direct `conversation` field access,
-// raw standalone `Conversation` values, `compactAndPersist`, etc.). Rather
-// than inflate the curated surface to cover one in-tree embedder, these two
-// internal namespaces stay reachable for embedders who opt into the
-// unstable internal API. A C-ABI or language binding should use the curated
-// surface above, not these.
-//
-// FLAGGED (see docs/libpanto-cleanup.md): exposing `agent`/`conversation`
-// internals is a known gap between the curated façade and what a deep
-// embedder needs. Revisit if/when the façade grows the missing operations
-// (e.g. `Conversation` standalone construction, agent system-prompt and
-// compaction-prompt plumbing).
-pub const agent = agent_mod;
-pub const conversation = conversation_mod;
-
-// ===========================================================================
// Tests
// ===========================================================================