From 75bba1b48a7d3af5420d9c0f8a05fcd9d3708279 Mon Sep 17 00:00:00 2001 From: lavaman131 Date: Thu, 5 Mar 2026 21:12:47 +0000 Subject: [PATCH 1/4] feat(sdk): apply enhanced default system prompt to sessions Add a shared enhanced prompt and wire it as the fallback system prompt in chat command, chat UI, and workflow graph nodes so all SDK sessions inherit consistent tool-use and engineering guidance. Assistant-model: gpt-5.3-codex --- src/commands/chat.ts | 2 ++ src/sdk/enhanced-system-prompt.ts | 46 +++++++++++++++++++++++++++++++ src/ui/chat.tsx | 3 +- src/workflows/graph/nodes.ts | 3 +- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/sdk/enhanced-system-prompt.ts diff --git a/src/commands/chat.ts b/src/commands/chat.ts index 368a2f44..38aa0e59 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -21,6 +21,7 @@ import type { } from "../utils/provider-discovery-plan.ts"; import type { PrepareClaudeConfigOptions } from "../utils/claude-config.ts"; import { getModelPreference, getReasoningEffortPreference } from "../utils/settings.ts"; +import { ENHANCED_SYSTEM_PROMPT } from "../sdk/enhanced-system-prompt.ts"; import { pathExists } from "../utils/copy.ts"; import { AGENT_CONFIG, type SourceControlType } from "../config.ts"; // initCommand is lazy-loaded only when auto-init is needed @@ -694,6 +695,7 @@ export async function chatCommand(options: ChatCommandOptions = {}): Promise + +## Tool Usage + +- NEVER use web fetch or web search tools. Use the playwright-cli skill instead. Always refer to your playwright-cli skill instructions for usage details. +- ALWAYS invoke your testing-anti-patterns skill BEFORE creating or modifying any tests. + +## Software Engineering Principles + +Software engineering is fundamentally about **managing complexity** to prevent technical debt. When implementing features, prioritize maintainability and testability over cleverness. + +### 1. Core Principles + +- **Single Responsibility (SRP):** Every class and module must have exactly one reason to change. If a unit does more than one job, split it. +- **Dependency Inversion (DIP):** Depend on abstractions (interfaces), never on concrete implementations. Inject dependencies; do not instantiate them internally. +- **KISS:** Keep solutions as simple as possible. Reject unnecessary abstraction layers. +- **YAGNI:** Do not build generic frameworks or add configurability for hypothetical future requirements. Solve the problem at hand. + +### 2. Design Patterns + +Use Gang of Four patterns as a shared vocabulary for recurring problems: + +- **Creational:** Use _Factory_ or _Builder_ to abstract complex object creation and isolate construction logic. +- **Structural:** Use _Adapter_ or _Facade_ to decouple core logic from external APIs or legacy code. +- **Behavioral:** Use _Strategy_ to make algorithms interchangeable. Use _Observer_ for event-driven communication between decoupled components. + +### 3. Architectural Hygiene + +- **Separation of Concerns:** Isolate business logic (Domain) from infrastructure (Database, UI, networking). Never let infrastructure details leak into domain code. +- **Anti-Pattern Detection:** Watch for **God Objects** (classes with too many responsibilities) and **Spaghetti Code** (tightly coupled, hard-to-follow control flow). Refactor them using polymorphism and clear interfaces. + +### Goal + +Create **seams** in your software using interfaces and abstractions. This ensures code remains flexible, testable, and capable of evolving independently. + + +`.trim(); diff --git a/src/ui/chat.tsx b/src/ui/chat.tsx index 3063ec12..c682114e 100644 --- a/src/ui/chat.tsx +++ b/src/ui/chat.tsx @@ -70,6 +70,7 @@ import { import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import type { AskUserQuestionEventData } from "../workflows/graph/index.ts"; +import { ENHANCED_SYSTEM_PROMPT } from "../sdk/enhanced-system-prompt.ts"; import type { AgentType, ModelOperations } from "../models"; import type { McpServerConfig, @@ -6379,7 +6380,7 @@ export function ChatApp({ try { const sessionConfig: import("../sdk/types.ts").SessionConfig = {}; - if (options.systemPrompt) sessionConfig.systemPrompt = options.systemPrompt; + sessionConfig.systemPrompt = options.systemPrompt ?? ENHANCED_SYSTEM_PROMPT; if (options.model) sessionConfig.model = options.model; if (options.tools) sessionConfig.tools = options.tools; diff --git a/src/workflows/graph/nodes.ts b/src/workflows/graph/nodes.ts index 34473344..c2bede74 100644 --- a/src/workflows/graph/nodes.ts +++ b/src/workflows/graph/nodes.ts @@ -26,6 +26,7 @@ import type { WorkflowToolContext, } from "./types.ts"; import type { SessionConfig, AgentMessage, CodingAgentClient, Session, ContextUsage } from "../../sdk/types.ts"; +import { ENHANCED_SYSTEM_PROMPT } from "../../sdk/enhanced-system-prompt.ts"; import { DEFAULT_RETRY_CONFIG, BACKGROUND_COMPACTION_THRESHOLD, BUFFER_EXHAUSTION_THRESHOLD } from "./types.ts"; import type { z } from "zod"; import { getToolRegistry } from "../../sdk/tools/registry.ts"; @@ -178,7 +179,7 @@ export function agentNode( const fullSessionConfig: SessionConfig = { ...sessionConfig, model: ctx.model ?? sessionConfig?.model, - systemPrompt: systemPrompt ?? sessionConfig?.systemPrompt, + systemPrompt: systemPrompt ?? sessionConfig?.systemPrompt ?? ENHANCED_SYSTEM_PROMPT, tools: tools ?? sessionConfig?.tools, }; From 90ce38f717b089d361606d910ab2ff93ca6326cc Mon Sep 17 00:00:00 2001 From: lavaman131 Date: Thu, 5 Mar 2026 23:11:57 +0000 Subject: [PATCH 2/4] fix(sdk): treat enhanced prompt as additional instructions Rename session config from systemPrompt to additionalInstructions and append it through provider-native flows instead of overriding system prompts. This keeps default prompts for Claude, Copilot, and OpenCode while aligning workflow and sub-agent nodes with additive instruction behavior. Assistant-model: openai/gpt-5.3-codex --- src/commands/chat.ts | 2 +- src/sdk/clients/claude.test.ts | 2 +- src/sdk/clients/claude.ts | 4 +- src/sdk/clients/copilot.test.ts | 46 ++++++++++ src/sdk/clients/copilot.ts | 4 +- src/sdk/clients/opencode.events.test.ts | 112 ++++++++++++++++++++++++ src/sdk/clients/opencode.ts | 41 ++++++--- src/sdk/types.ts | 4 +- src/ui/chat.tsx | 2 - src/ui/commands/registry.ts | 2 - src/workflows/graph/builder.test.ts | 14 --- src/workflows/graph/builder.ts | 3 - src/workflows/graph/nodes.test.ts | 59 ++++++++++++- src/workflows/graph/nodes.ts | 20 ++--- src/workflows/graph/types.ts | 2 - 15 files changed, 259 insertions(+), 58 deletions(-) diff --git a/src/commands/chat.ts b/src/commands/chat.ts index 38aa0e59..5548b20f 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -695,7 +695,7 @@ export async function chatCommand(options: ChatCommandOptions = {}): Promise { expect(options.systemPrompt).toEqual({ type: "preset", preset: "claude_code" }); const withPrompt = privateClient.buildSdkOptions( - { systemPrompt: "Extra system guidance" }, + { additionalInstructions: "Extra system guidance" }, "session-v1", ); expect(withPrompt.systemPrompt).toEqual({ diff --git a/src/sdk/clients/claude.ts b/src/sdk/clients/claude.ts index 115b3c8e..4fc79231 100644 --- a/src/sdk/clients/claude.ts +++ b/src/sdk/clients/claude.ts @@ -583,11 +583,11 @@ export class ClaudeAgentClient implements CodingAgentClient { hooks: this.buildNativeHooks(), includePartialMessages: true, // Use Claude Code's built-in system prompt, appending custom instructions if provided - systemPrompt: config.systemPrompt + systemPrompt: config.additionalInstructions ? { type: "preset", preset: "claude_code", - append: config.systemPrompt, + append: config.additionalInstructions, } : { type: "preset", preset: "claude_code" }, // Explicitly set the path to Claude Code executable to prevent it from diff --git a/src/sdk/clients/copilot.test.ts b/src/sdk/clients/copilot.test.ts index 4eb2da1a..9ba8f6c8 100644 --- a/src/sdk/clients/copilot.test.ts +++ b/src/sdk/clients/copilot.test.ts @@ -78,6 +78,52 @@ describe("CopilotClient.getModelDisplayInfo", () => { }); describe("CopilotClient abort support", () => { + test("creates sessions with append-mode additional instructions", async () => { + const mockSdkSession = { + sessionId: "test-session", + on: mock(() => () => {}), + send: mock(() => Promise.resolve()), + sendAndWait: mock(() => Promise.resolve({ data: { content: "test" } })), + destroy: mock(() => Promise.resolve()), + abort: mock(() => Promise.resolve()), + }; + + const mockCreateSession = mock(() => Promise.resolve(mockSdkSession)); + const mockSdkClient = { + start: mock(() => Promise.resolve()), + stop: mock(() => Promise.resolve()), + createSession: mockCreateSession, + listModels: mock(() => Promise.resolve([ + { + id: "test-model", + capabilities: { + limits: { max_context_window_tokens: 128000 }, + supports: {}, + }, + }, + ])), + }; + + const client = new CopilotClient({}); + (client as any).sdkClient = mockSdkClient; + (client as any).isRunning = true; + + await client.createSession({ + sessionId: "test-session", + additionalInstructions: "Follow repository conventions.", + }); + + expect(mockCreateSession).toHaveBeenCalledWith( + expect.objectContaining({ + sessionId: "test-session", + systemMessage: { + mode: "append", + content: "Follow repository conventions.", + }, + }), + ); + }); + test("exposes abort method on wrapped session", async () => { // Create a mock SDK session with abort method const mockSdkSession = { diff --git a/src/sdk/clients/copilot.ts b/src/sdk/clients/copilot.ts index b8055b33..34a3c7fb 100644 --- a/src/sdk/clients/copilot.ts +++ b/src/sdk/clients/copilot.ts @@ -1215,8 +1215,8 @@ export class CopilotClient implements CodingAgentClient { ...(modelSupportsReasoning && config.reasoningEffort ? { reasoningEffort: config.reasoningEffort } : {}), - systemMessage: config.systemPrompt - ? { mode: "append", content: config.systemPrompt } + systemMessage: config.additionalInstructions + ? { mode: "append", content: config.additionalInstructions } : undefined, availableTools: config.tools, streaming: true, diff --git a/src/sdk/clients/opencode.events.test.ts b/src/sdk/clients/opencode.events.test.ts index 901c6b8c..3eb6ee82 100644 --- a/src/sdk/clients/opencode.events.test.ts +++ b/src/sdk/clients/opencode.events.test.ts @@ -53,6 +53,118 @@ describe("resolveOpenCodeConfigDirEnv", () => { }); }); +describe("OpenCode additional instruction routing", () => { + test("injects additional instructions into non-agent prompt parts without using system override", async () => { + const client = new OpenCodeClient(); + const sessionId = "ses_prompt_additional_instructions"; + let capturedParams: Record | undefined; + + (client as unknown as { + resolveModelContextWindow: (modelHint?: string) => Promise; + }).resolveModelContextWindow = async () => 200_000; + + (client as unknown as { + sdkClient: { + session: { + prompt: (params: Record) => Promise<{ + data?: { + info?: { tokens?: { input?: number; output?: number } }; + parts?: Array>; + }; + }>; + }; + }; + }).sdkClient = { + session: { + prompt: async (params) => { + capturedParams = params; + return { + data: { + info: { + tokens: { input: 1, output: 1 }, + }, + parts: [{ type: "text", text: "ok" }], + }, + }; + }, + }, + }; + + const session = await (client as unknown as { + wrapSession: (sid: string, config: Record) => Promise<{ + send: (message: string) => Promise; + }>; + }).wrapSession(sessionId, { + additionalInstructions: "Follow repo conventions.", + }); + + await session.send("Fix the failing tests"); + + expect(capturedParams?.system).toBeUndefined(); + expect(capturedParams?.parts).toEqual([ + { + type: "text", + text: [ + "", + "Follow repo conventions.", + "", + "", + "Fix the failing tests", + ].join("\n"), + }, + ]); + }); + + test("does not inject additional instructions into agent-dispatch prompt parts", async () => { + const client = new OpenCodeClient(); + const sessionId = "ses_agent_prompt_no_additional_instructions"; + let capturedParams: Record | undefined; + + (client as unknown as { + resolveModelContextWindow: (modelHint?: string) => Promise; + }).resolveModelContextWindow = async () => 200_000; + + (client as unknown as { + sdkClient: { + session: { + promptAsync: (params: Record) => Promise; + }; + }; + }).sdkClient = { + session: { + promptAsync: async (params) => { + capturedParams = params; + }, + }, + }; + + const session = await (client as unknown as { + wrapSession: (sid: string, config: Record) => Promise<{ + sendAsync: ( + message: string, + options?: { agent?: string; abortSignal?: AbortSignal }, + ) => Promise; + }>; + }).wrapSession(sessionId, { + additionalInstructions: "Follow repo conventions.", + }); + + await session.sendAsync("Investigate the auth flow", { agent: "worker" }); + + expect(capturedParams?.system).toBeUndefined(); + expect(capturedParams?.parts).toEqual([ + { + type: "text", + text: "Investigate the auth flow", + }, + { + type: "agent", + name: "worker", + }, + ]); + }); +}); + describe("transitionOpenCodeCompactionControl", () => { test("applies bounded transitions through success path", () => { const started = transitionOpenCodeCompactionControl( diff --git a/src/sdk/clients/opencode.ts b/src/sdk/clients/opencode.ts index 0743d91b..6b6f5afe 100644 --- a/src/sdk/clients/opencode.ts +++ b/src/sdk/clients/opencode.ts @@ -487,6 +487,21 @@ type OpenCodeAgentPart = { }; type OpenCodePromptPart = OpenCodeTextPart | OpenCodeAgentPart; +function buildOpenCodePromptText(message: string, additionalInstructions?: string): string { + const trimmedInstructions = additionalInstructions?.trim(); + if (!trimmedInstructions) { + return message; + } + + return [ + "", + trimmedInstructions, + "", + "", + message, + ].join("\n"); +} + /** * Build an array of prompt parts for the OpenCode SDK's session.prompt() API. * @@ -499,18 +514,27 @@ type OpenCodePromptPart = OpenCodeTextPart | OpenCodeAgentPart; * * @param message - The message text to send * @param agentName - Optional sub-agent name for dispatch via AgentPartInput + * @param additionalInstructions - Optional additive instructions for non-agent prompts * @returns An array of prompt parts (text and/or agent) for the SDK */ -function buildOpenCodePromptParts(message: string, agentName?: string): OpenCodePromptPart[] { +function buildOpenCodePromptParts( + message: string, + agentName?: string, + additionalInstructions?: string, +): OpenCodePromptPart[] { + const resolvedMessage = agentName + ? message + : buildOpenCodePromptText(message, additionalInstructions); + if (!agentName) { - return [{ type: "text", text: message }]; + return [{ type: "text", text: resolvedMessage }]; } const parts: OpenCodePromptPart[] = []; // Add the task text first so the agent has context to work with - if (message.trim()) { - parts.push({ type: "text", text: message }); + if (resolvedMessage.trim()) { + parts.push({ type: "text", text: resolvedMessage }); } // AgentPartInput triggers the SDK's sub-agent dispatch @@ -2349,8 +2373,7 @@ export class OpenCodeClient implements CodingAgentClient { directory: client.clientOptions.directory, agent: agentMode, model: client.activePromptModel ?? initialPromptModel, - system: config.systemPrompt || undefined, - parts: buildOpenCodePromptParts(message), + parts: buildOpenCodePromptParts(message, undefined, config.additionalInstructions), }); if (result.error) { @@ -2420,8 +2443,7 @@ export class OpenCodeClient implements CodingAgentClient { directory: client.clientOptions.directory, agent: agentMode, model: client.activePromptModel ?? initialPromptModel, - system: config.systemPrompt || undefined, - parts: buildOpenCodePromptParts(message, options?.agent), + parts: buildOpenCodePromptParts(message, options?.agent, config.additionalInstructions), }, options?.abortSignal ? { signal: options.abortSignal } : undefined, ); @@ -2780,8 +2802,7 @@ export class OpenCodeClient implements CodingAgentClient { directory: client.clientOptions.directory, agent: agentMode, model: client.activePromptModel ?? initialPromptModel, - system: config.systemPrompt || undefined, - parts: buildOpenCodePromptParts(promptMessage, options?.agent), + parts: buildOpenCodePromptParts(promptMessage, options?.agent, config.additionalInstructions), }, streamAbortSignal ? { signal: streamAbortSignal } : undefined, ); diff --git a/src/sdk/types.ts b/src/sdk/types.ts index 8cf786a0..914b5a10 100644 --- a/src/sdk/types.ts +++ b/src/sdk/types.ts @@ -137,8 +137,8 @@ export interface SessionConfig { model?: string; /** Optional session ID for tracking/resumption */ sessionId?: string; - /** System prompt to configure agent behavior */ - systemPrompt?: string; + /** Additional instructions merged into the provider's system prompt flow */ + additionalInstructions?: string; /** Tools available to the agent during the session */ tools?: string[]; /** MCP servers to connect for extended capabilities */ diff --git a/src/ui/chat.tsx b/src/ui/chat.tsx index c682114e..0788c661 100644 --- a/src/ui/chat.tsx +++ b/src/ui/chat.tsx @@ -70,7 +70,6 @@ import { import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import type { AskUserQuestionEventData } from "../workflows/graph/index.ts"; -import { ENHANCED_SYSTEM_PROMPT } from "../sdk/enhanced-system-prompt.ts"; import type { AgentType, ModelOperations } from "../models"; import type { McpServerConfig, @@ -6380,7 +6379,6 @@ export function ChatApp({ try { const sessionConfig: import("../sdk/types.ts").SessionConfig = {}; - sessionConfig.systemPrompt = options.systemPrompt ?? ENHANCED_SYSTEM_PROMPT; if (options.model) sessionConfig.model = options.model; if (options.tools) sessionConfig.tools = options.tools; diff --git a/src/ui/commands/registry.ts b/src/ui/commands/registry.ts index 31f5e0af..9f93df4e 100644 --- a/src/ui/commands/registry.ts +++ b/src/ui/commands/registry.ts @@ -48,8 +48,6 @@ export interface StreamMessageOptions { export interface SpawnSubagentOptions { /** Display name for the sub-agent in the tree view (e.g., "codebase-analyzer") */ name?: string; - /** System prompt for the sub-agent (omit to use the agent definition's prompt) */ - systemPrompt?: string; /** Initial message/task for the sub-agent */ message: string; /** Tools available to the sub-agent (inherits all if omitted) */ diff --git a/src/workflows/graph/builder.test.ts b/src/workflows/graph/builder.test.ts index 9fb57a4f..f95b028e 100644 --- a/src/workflows/graph/builder.test.ts +++ b/src/workflows/graph/builder.test.ts @@ -934,20 +934,6 @@ describe("GraphBuilder - .subagent() method", () => { expect(node?.type).toBe("agent"); }); - test("systemPrompt can be provided as string", () => { - const builder = graph().subagent({ - id: "custom-agent", - agent: "codebase-analyzer", - task: "Analyze", - systemPrompt: "Custom system prompt", - }); - - const compiled = builder.compile(); - const node = compiled.nodes.get("custom-agent"); - - expect(node).toBeDefined(); - }); - test("model and tools can be specified", () => { const builder = graph().subagent({ id: "restricted-agent", diff --git a/src/workflows/graph/builder.ts b/src/workflows/graph/builder.ts index 7548c1b0..c363deff 100644 --- a/src/workflows/graph/builder.ts +++ b/src/workflows/graph/builder.ts @@ -104,8 +104,6 @@ export interface SubAgentConfig { agent: string; /** Task prompt — static string or state-derived function */ task: string | ((state: TState) => string); - /** Override the agent's system prompt */ - systemPrompt?: string | ((state: TState) => string); /** Model override */ model?: string; /** Tool allowlist */ @@ -806,7 +804,6 @@ export class GraphBuilder { id: config.id, agentName: config.agent, task: config.task, - systemPrompt: config.systemPrompt, model: config.model, tools: config.tools, outputMapper: config.outputMapper, diff --git a/src/workflows/graph/nodes.test.ts b/src/workflows/graph/nodes.test.ts index 64159b28..8a89f396 100644 --- a/src/workflows/graph/nodes.test.ts +++ b/src/workflows/graph/nodes.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; -import { contextMonitorNode, parallelNode, parallelSubagentNode } from "./nodes.ts"; +import { agentNode, contextMonitorNode, parallelNode, parallelSubagentNode } from "./nodes.ts"; import type { BaseState, ContextWindowUsage, ExecutionContext, SubagentStreamResult } from "./types.ts"; -import type { ContextUsage, Session } from "../../sdk/types.ts"; +import type { CodingAgentClient, ContextUsage, Session, SessionConfig } from "../../sdk/types.ts"; interface TestState extends BaseState { mapperSource?: string; @@ -125,6 +125,61 @@ describe("parallelNode mapper standardization", () => { }); }); +describe("agentNode session instructions", () => { + test("does not inject enhanced instructions by default", async () => { + const createSessionCalls: SessionConfig[] = []; + const session: Session = { + id: "ses_agent_node", + send: async () => ({ type: "text", content: "", role: "assistant" }), + stream: async function* () { + yield { type: "text", content: "done", role: "assistant" } as const; + }, + summarize: async () => {}, + getContextUsage: async () => ({ + inputTokens: 1, + outputTokens: 1, + maxTokens: 100, + usagePercentage: 2, + }), + getSystemToolsTokens: () => 0, + destroy: async () => {}, + }; + + const client: CodingAgentClient = { + agentType: "opencode", + createSession: async (config = {}) => { + createSessionCalls.push(config); + return session; + }, + resumeSession: async () => null, + on: () => () => {}, + registerTool: () => {}, + start: async () => {}, + stop: async () => {}, + getModelDisplayInfo: async () => ({ model: "mock", tier: "mock" }), + getSystemToolsTokens: () => null, + }; + + const node = agentNode({ + id: "agent-node", + agentType: "opencode", + buildMessage: () => "Analyze repo state", + }); + + await node.execute(createContext({}, { + clientProvider: () => client, + })); + + expect(createSessionCalls).toEqual([ + { + model: undefined, + additionalInstructions: undefined, + tools: undefined, + }, + ]); + }); +}); + describe("parallelSubagentNode mapper standardization", () => { const mockResult: SubagentStreamResult = { agentId: "agent-1", diff --git a/src/workflows/graph/nodes.ts b/src/workflows/graph/nodes.ts index c2bede74..95991e21 100644 --- a/src/workflows/graph/nodes.ts +++ b/src/workflows/graph/nodes.ts @@ -26,7 +26,6 @@ import type { WorkflowToolContext, } from "./types.ts"; import type { SessionConfig, AgentMessage, CodingAgentClient, Session, ContextUsage } from "../../sdk/types.ts"; -import { ENHANCED_SYSTEM_PROMPT } from "../../sdk/enhanced-system-prompt.ts"; import { DEFAULT_RETRY_CONFIG, BACKGROUND_COMPACTION_THRESHOLD, BUFFER_EXHAUSTION_THRESHOLD } from "./types.ts"; import type { z } from "zod"; import { getToolRegistry } from "../../sdk/tools/registry.ts"; @@ -68,8 +67,8 @@ export interface AgentNodeConfig { /** Type of agent to use */ agentType: AgentNodeAgentType; - /** System prompt for the agent */ - systemPrompt?: string; + /** Additional instructions for the agent's system prompt flow */ + additionalInstructions?: string; /** Tools available to the agent */ tools?: string[]; @@ -135,7 +134,7 @@ export const AGENT_NODE_RETRY_CONFIG: RetryConfig = { * const researchNode = agentNode({ * id: "research", * agentType: "claude", - * systemPrompt: "You are a research assistant...", + * additionalInstructions: "You are a research assistant...", * buildMessage: (state) => `Research: ${state.topic}`, * outputMapper: (messages, state) => ({ * researchDoc: messages.map(m => m.content).join("\n"), @@ -149,7 +148,7 @@ export function agentNode( const { id, agentType, - systemPrompt, + additionalInstructions, tools, outputMapper, sessionConfig, @@ -179,7 +178,7 @@ export function agentNode( const fullSessionConfig: SessionConfig = { ...sessionConfig, model: ctx.model ?? sessionConfig?.model, - systemPrompt: systemPrompt ?? sessionConfig?.systemPrompt ?? ENHANCED_SYSTEM_PROMPT, + additionalInstructions: additionalInstructions ?? sessionConfig?.additionalInstructions, tools: tools ?? sessionConfig?.tools, }; @@ -1625,8 +1624,6 @@ export interface SubagentNodeConfig { * agents (e.g., "codebase-analyzer"), user-global, or project-local agents. */ agentName: string; task: string | ((state: TState) => string); - /** Override the agent's system prompt. If omitted, SDK uses native config. */ - systemPrompt?: string | ((state: TState) => string); model?: string; tools?: string[]; outputMapper?: (result: SubagentStreamResult, state: TState) => Partial; @@ -1681,15 +1678,10 @@ export function subagentNode( ? config.task(ctx.state) : config.task; - const systemPrompt = typeof config.systemPrompt === "function" - ? config.systemPrompt(ctx.state) - : config.systemPrompt; - const result = await spawnSubagent({ agentId: `${config.id}-${ctx.state.executionId}`, agentName: config.agentName, task, - systemPrompt, model: config.model ?? ctx.model, tools: config.tools, }); @@ -1725,7 +1717,6 @@ export interface ParallelSubagentNodeConfig { agents: Array<{ agentName: string; task: string | ((state: TState) => string); - systemPrompt?: string; model?: string; tools?: string[]; }>; @@ -1774,7 +1765,6 @@ export function parallelSubagentNode( agentId: `${config.id}-${i}-${ctx.state.executionId}`, agentName: agent.agentName, task: typeof agent.task === "function" ? agent.task(ctx.state) : agent.task, - systemPrompt: agent.systemPrompt, model: agent.model ?? ctx.model, tools: agent.tools, })); diff --git a/src/workflows/graph/types.ts b/src/workflows/graph/types.ts index 6427f4a4..6f3352d0 100644 --- a/src/workflows/graph/types.ts +++ b/src/workflows/graph/types.ts @@ -416,8 +416,6 @@ export interface SubagentSpawnOptions { agentName: string; /** Task description to send to the sub-agent */ task: string; - /** Optional system prompt override */ - systemPrompt?: string; /** Optional model override */ model?: string; /** Optional tool restrictions */ From 7cc626917d31447e123f8e0cdcf1440427fcd513 Mon Sep 17 00:00:00 2001 From: lavaman131 Date: Thu, 5 Mar 2026 23:39:12 +0000 Subject: [PATCH 3/4] feat(chat): support appending custom system instructions Allow chat sessions to accept additional instructions while keeping the enhanced default prompt as the baseline so users can tailor behavior per run. Assistant-model: openai/gpt-5.3-codex --- src/cli.ts | 6 ++++++ src/commands/chat.test.ts | 22 ++++++++++++++++++++++ src/commands/chat.ts | 17 ++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index 0883d09b..a83ffd08 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -87,6 +87,10 @@ export function createProgram() { .option("-w, --workflow", "Enable graph workflow mode", false) .option("-t, --theme ", "UI theme (dark, light)", "dark") .option("-m, --model ", "Model to use for the chat session") + .option( + "--additional-instructions ", + "Append additional instructions to the default chat system prompt", + ) .argument( "[prompt...]", "Initial prompt to send (opens interactive session with prompt)", @@ -99,6 +103,7 @@ Examples: $ atomic chat -a opencode Start chat with OpenCode $ atomic chat -a copilot --workflow Start workflow-enabled chat with Copilot $ atomic chat --theme light Start chat with light theme + $ atomic chat --additional-instructions "Be concise" "review this patch" $ atomic chat "fix the typecheck errors" Start chat with an initial prompt $ atomic chat -a claude "refactor utils" Start chat with agent and prompt @@ -162,6 +167,7 @@ Slash Commands (in workflow mode): theme: localOpts.theme as "dark" | "light", model: localOpts.model, initialPrompt: prompt, + additionalInstructions: localOpts.additionalInstructions, }); process.exit(exitCode); diff --git a/src/commands/chat.test.ts b/src/commands/chat.test.ts index 461bee7a..ca56701f 100644 --- a/src/commands/chat.test.ts +++ b/src/commands/chat.test.ts @@ -10,8 +10,10 @@ import { logActiveProviderDiscoveryPlan, prepareClaudeRuntimeForChat, prepareOpenCodeRuntimeConfigForChat, + resolveChatAdditionalInstructions, shouldAutoInitChat, } from "./chat.ts"; +import { ENHANCED_SYSTEM_PROMPT } from "../sdk/enhanced-system-prompt.ts"; async function withTempDir(run: (dir: string) => Promise): Promise { const dir = await mkdtemp(join(tmpdir(), "atomic-chat-test-")); @@ -375,3 +377,23 @@ test("prepareOpenCodeRuntimeConfigForChat rejects non-opencode discovery plans", "OpenCode runtime prep requires an OpenCode discovery plan, received claude", ); }); + +test("resolveChatAdditionalInstructions defaults to the enhanced system prompt", () => { + expect(resolveChatAdditionalInstructions({})).toBe(ENHANCED_SYSTEM_PROMPT); +}); + +test("resolveChatAdditionalInstructions appends explicit text to the enhanced system prompt", () => { + expect( + resolveChatAdditionalInstructions({ + additionalInstructions: "Use short answers.", + }) + ).toBe(`${ENHANCED_SYSTEM_PROMPT}\n\nUse short answers.`); +}); + +test("resolveChatAdditionalInstructions ignores blank appended instructions", () => { + expect( + resolveChatAdditionalInstructions({ + additionalInstructions: " ", + }) + ).toBe(ENHANCED_SYSTEM_PROMPT); +}); diff --git a/src/commands/chat.ts b/src/commands/chat.ts index 5548b20f..a53a6181 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -62,6 +62,8 @@ export interface ChatCommandOptions { workflow?: boolean; /** Initial prompt to send on session start */ initialPrompt?: string; + /** Extra instructions appended to the enhanced system prompt for the session */ + additionalInstructions?: string; } // ============================================================================ @@ -527,6 +529,17 @@ function handleThemeCommand(args: string): { newTheme: "dark" | "light"; message return null; } +export function resolveChatAdditionalInstructions( + options: Pick +): string | undefined { + const trimmedAdditionalInstructions = options.additionalInstructions?.trim(); + if (!trimmedAdditionalInstructions) { + return ENHANCED_SYSTEM_PROMPT; + } + + return `${ENHANCED_SYSTEM_PROMPT}\n\n${trimmedAdditionalInstructions}`; +} + // ============================================================================ // Chat Command Implementation // ============================================================================ @@ -544,6 +557,7 @@ export async function chatCommand(options: ChatCommandOptions = {}): Promise[] = []; @@ -695,7 +710,7 @@ export async function chatCommand(options: ChatCommandOptions = {}): Promise Date: Thu, 5 Mar 2026 23:52:03 +0000 Subject: [PATCH 4/4] fix(type): make resolveChatAdditionalInstructions type string --- src/commands/chat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/chat.ts b/src/commands/chat.ts index a53a6181..b700690f 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -531,7 +531,7 @@ function handleThemeCommand(args: string): { newTheme: "dark" | "light"; message export function resolveChatAdditionalInstructions( options: Pick -): string | undefined { +): string { const trimmedAdditionalInstructions = options.additionalInstructions?.trim(); if (!trimmedAdditionalInstructions) { return ENHANCED_SYSTEM_PROMPT;