Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ export class Agent {
this._state.tools = t;
}

getTools(): AgentTool<any>[] {
return this._state.tools;
}

replaceMessages(ms: AgentMessage[]) {
this._state.messages = ms.slice();
}
Expand Down
4 changes: 4 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- Persist and restore system and append prompts overrides from `--system-prompt` or `--append-system-prompt` ([#896](https://github.com/badlogic/pi-mono/pull/896) by [@scutifer](https://github.com/scutifer))

## [0.49.3] - 2026-01-22

### Added
Expand Down
25 changes: 24 additions & 1 deletion packages/coding-agent/src/core/agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,10 @@ export class AgentSession {
private _toolRegistry: Map<string, AgentTool>;

// Function to rebuild system prompt when tools change
private _rebuildSystemPrompt?: (toolNames: string[]) => string;
private _rebuildSystemPrompt?: (
toolNames: string[],
overrides?: { systemPrompt: string | null; appendSystemPrompt: string | null },
) => string;

// Base system prompt (without extension appends) - used to apply fresh appends each turn
private _baseSystemPrompt: string;
Expand Down Expand Up @@ -1917,6 +1920,16 @@ export class AgentSession {
this.setThinkingLevel(sessionContext.thinkingLevel as ThinkingLevel);
}

// Restore system prompt if saved
if ((sessionContext.systemPrompt || sessionContext.appendSystemPrompt) && this._rebuildSystemPrompt) {
const activeToolNames = this.agent.getTools().map((t) => t.name);
this._baseSystemPrompt = this._rebuildSystemPrompt(activeToolNames, {
systemPrompt: sessionContext.systemPrompt,
appendSystemPrompt: sessionContext.appendSystemPrompt,
});
this.agent.setSystemPrompt(this._baseSystemPrompt);
}

this._reconnectToAgent();
return true;
}
Expand Down Expand Up @@ -1982,6 +1995,16 @@ export class AgentSession {
this.agent.replaceMessages(sessionContext.messages);
}

// Restore system prompt if saved
if ((sessionContext.systemPrompt || sessionContext.appendSystemPrompt) && this._rebuildSystemPrompt) {
const activeToolNames = this.agent.getTools().map((t) => t.name);
this._baseSystemPrompt = this._rebuildSystemPrompt(activeToolNames, {
systemPrompt: sessionContext.systemPrompt,
appendSystemPrompt: sessionContext.appendSystemPrompt,
});
this.agent.setSystemPrompt(this._baseSystemPrompt);
}

return { selectedText, cancelled: false };
}

Expand Down
23 changes: 21 additions & 2 deletions packages/coding-agent/src/core/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ export interface CreateAgentSessionOptions {
/** System prompt. String replaces default, function receives default and returns final. */
systemPrompt?: string | ((defaultPrompt: string) => string);

/** Text to append to system prompt (after customPrompt or default). */
appendPrompt?: string;

/** Built-in tools to use. Default: codingTools [read, bash, edit, write] */
tools?: Tool[];
/** Custom tools to register (in addition to built-in tools). */
Expand Down Expand Up @@ -551,7 +554,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}

// Function to rebuild system prompt when tools change
// Captures static options (cwd, agentDir, skills, contextFiles, customPrompt)
const rebuildSystemPrompt = (toolNames: string[]): string => {
const rebuildSystemPrompt = (
toolNames: string[],
overrides?: { systemPrompt: string | null; appendSystemPrompt: string | null },
): string => {
// Filter to valid tool names
const validToolNames = toolNames.filter((n): n is ToolName => n in allBuiltInToolsMap);
const defaultPrompt = buildSystemPromptInternal({
Expand All @@ -560,6 +566,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
skills,
contextFiles,
selectedTools: validToolNames,
customPrompt: overrides?.systemPrompt ?? undefined,
appendSystemPrompt: overrides?.appendSystemPrompt ?? undefined,
});

if (options.systemPrompt === undefined) {
Expand All @@ -572,7 +580,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
}
};

const systemPrompt = rebuildSystemPrompt(initialActiveToolNames);
const systemPrompt = rebuildSystemPrompt(initialActiveToolNames, {
systemPrompt: existingSession.systemPrompt,
appendSystemPrompt: existingSession.appendSystemPrompt,
});
time("buildSystemPrompt");

const promptTemplates = options.promptTemplates ?? discoverPromptTemplates(cwd, agentDir);
Expand Down Expand Up @@ -671,6 +682,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
sessionManager.appendThinkingLevelChange(thinkingLevel);
}

// Save prompt entries if CLI options provided (creates new entries that supersede old ones)
if (typeof options.systemPrompt === "string") {
sessionManager.saveSystemPrompt(options.systemPrompt);
}
if (options.appendPrompt) {
sessionManager.saveAppendSystemPrompt(options.appendPrompt);
}

const session = new AgentSession({
agent,
sessionManager,
Expand Down
54 changes: 50 additions & 4 deletions packages/coding-agent/src/core/session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
display: boolean;
}

/** System prompt entry for user-defined system prompt. */
export interface SystemPromptEntry extends SessionEntryBase {
type: "system_prompt";
systemPrompt: string;
}

/** Append system prompt entry for user-defined appends to the system prompt. */
export interface AppendSystemPromptEntry extends SessionEntryBase {
type: "append_system_prompt";
appendSystemPrompt: string;
}

/** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */
export type SessionEntry =
| SessionMessageEntry
Expand All @@ -142,7 +154,9 @@ export type SessionEntry =
| CustomEntry
| CustomMessageEntry
| LabelEntry
| SessionInfoEntry;
| SessionInfoEntry
| SystemPromptEntry
| AppendSystemPromptEntry;

/** Raw file entry (includes header) */
export type FileEntry = SessionHeader | SessionEntry;
Expand All @@ -159,6 +173,8 @@ export interface SessionContext {
messages: AgentMessage[];
thinkingLevel: string;
model: { provider: string; modelId: string } | null;
systemPrompt: string | null;
appendSystemPrompt: string | null;
}

export interface SessionInfo {
Expand Down Expand Up @@ -319,7 +335,7 @@ export function buildSessionContext(
let leaf: SessionEntry | undefined;
if (leafId === null) {
// Explicitly null - return no messages (navigated to before first entry)
return { messages: [], thinkingLevel: "off", model: null };
return { messages: [], thinkingLevel: "off", model: null, systemPrompt: null, appendSystemPrompt: null };
}
if (leafId) {
leaf = byId.get(leafId);
Expand All @@ -330,7 +346,7 @@ export function buildSessionContext(
}

if (!leaf) {
return { messages: [], thinkingLevel: "off", model: null };
return { messages: [], thinkingLevel: "off", model: null, systemPrompt: null, appendSystemPrompt: null };
}

// Walk from leaf to root, collecting path
Expand All @@ -345,6 +361,8 @@ export function buildSessionContext(
let thinkingLevel = "off";
let model: { provider: string; modelId: string } | null = null;
let compaction: CompactionEntry | null = null;
let systemPrompt: string | null = null;
let appendSystemPrompt: string | null = null;

for (const entry of path) {
if (entry.type === "thinking_level_change") {
Expand All @@ -355,6 +373,10 @@ export function buildSessionContext(
model = { provider: entry.message.provider, modelId: entry.message.model };
} else if (entry.type === "compaction") {
compaction = entry;
} else if (entry.type === "system_prompt") {
systemPrompt = entry.systemPrompt;
} else if (entry.type === "append_system_prompt") {
appendSystemPrompt = entry.appendSystemPrompt;
}
}

Expand Down Expand Up @@ -408,7 +430,7 @@ export function buildSessionContext(
}
}

return { messages, thinkingLevel, model };
return { messages, thinkingLevel, model, systemPrompt, appendSystemPrompt };
}

/**
Expand Down Expand Up @@ -755,6 +777,30 @@ export class SessionManager {
this._persist(entry);
}

/** Save a custom system prompt as child of current leaf, then advance leaf. */
saveSystemPrompt(systemPrompt: string): void {
const entry: SystemPromptEntry = {
type: "system_prompt",
timestamp: new Date().toISOString(),
systemPrompt,
id: generateId(this.byId),
parentId: this.leafId,
};
this._appendEntry(entry);
}

/** Save an append system prompt as child of current leaf, then advance leaf. */
saveAppendSystemPrompt(appendSystemPrompt: string): void {
const entry: AppendSystemPromptEntry = {
type: "append_system_prompt",
timestamp: new Date().toISOString(),
appendSystemPrompt,
id: generateId(this.byId),
parentId: this.leafId,
};
this._appendEntry(entry);
}

/** Append a message as child of current leaf, then advance leaf. Returns entry id.
* Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.
* Reason: we want these to be top-level entries in the session, not message session entries,
Expand Down