Skip to content
Merged
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
36 changes: 36 additions & 0 deletions packages/agent/src/adapters/codex/spawn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,39 @@ describe("spawnCodexProcess MCP disable args", () => {
expect(args.some((arg) => arg.includes("weird name"))).toBe(false);
});
});

describe("spawnCodexProcess developer instructions", () => {
it("passes guidance via developer_instructions to preserve the base prompt", () => {
spawnMock.mockClear();
spawnMock.mockReturnValue(makeFakeChild());

spawnCodexProcess({
logger: new Logger({ debug: false }),
developerInstructions: "Follow PostHog signed-commit rules.",
});

const args: string[] = spawnMock.mock.calls[0][1];
expect(args).toContain(
'developer_instructions="Follow PostHog signed-commit rules."',
);
// The bare `instructions` and `model_instructions_file` keys replace Codex's
// model-optimized base prompt, so guidance must never go through them.
expect(args.some((arg) => arg.startsWith("instructions="))).toBe(false);
expect(args.some((arg) => arg.startsWith("model_instructions_file="))).toBe(
false,
);
});

it("escapes backslashes, newlines and quotes in developer_instructions", () => {
spawnMock.mockClear();
spawnMock.mockReturnValue(makeFakeChild());

spawnCodexProcess({
logger: new Logger({ debug: false }),
developerInstructions: 'a\\b\n"c',
});

const args: string[] = spawnMock.mock.calls[0][1];
expect(args).toContain('developer_instructions="a\\\\b\\n\\"c"');
});
});
13 changes: 9 additions & 4 deletions packages/agent/src/adapters/codex/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export interface CodexProcessOptions {
apiKey?: string;
model?: string;
reasoningEffort?: string;
instructions?: string;
/**
* Guidance appended on top of Codex's model-optimized base prompt via the
* `developer_instructions` config key. Unlike `instructions` /
* `model_instructions_file`, this does not replace the native base prompt.
*/
developerInstructions?: string;
binaryPath?: string;
logger?: Logger;
processCallbacks?: ProcessSpawnedCallback;
Expand Down Expand Up @@ -73,13 +78,13 @@ function buildConfigArgs(options: CodexProcessOptions): string[] {
args.push("-c", `sandbox_workspace_write.writable_roots=[${escaped}]`);
}

if (options.instructions) {
const escaped = options.instructions
if (options.developerInstructions) {
const escaped = options.developerInstructions
.replace(/\\/g, "\\\\")
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/"/g, '\\"');
args.push("-c", `instructions="${escaped}"`);
args.push("-c", `developer_instructions="${escaped}"`);
}

return args;
Expand Down
2 changes: 1 addition & 1 deletion packages/agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class Agent {
binaryPath: options.codexBinaryPath,
model: sanitizedModel,
reasoningEffort: options.reasoningEffort,
instructions: options.instructions,
developerInstructions: options.developerInstructions,
additionalDirectories: options.additionalDirectories,
}
: undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/agent/src/server/agent-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ export class AgentServer {
apiKey: this.config.apiKey,
model: this.config.model ?? DEFAULT_CODEX_MODEL,
reasoningEffort: this.config.reasoningEffort,
instructions: codexInstructions,
developerInstructions: codexInstructions,
}
: undefined,
onStructuredOutput: async (output) => {
Expand Down
6 changes: 5 additions & 1 deletion packages/agent/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ export interface TaskExecutionOptions {
gatewayUrl?: string;
codexBinaryPath?: string;
reasoningEffort?: EffortLevel;
instructions?: string;
/**
* Codex-only. Appended on top of the model's base prompt via the Codex
* `developer_instructions` config key, preserving Codex's native base prompt.
*/
developerInstructions?: string;
processCallbacks?: ProcessSpawnedCallback;
/** Callback invoked when the agent calls the create_output tool for structured output */
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
Expand Down
3 changes: 2 additions & 1 deletion packages/workspace-server/src/services/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,8 @@ When creating pull requests, add the following footer at the end of the PR descr
adapter === "codex" ? this.getCodexBinaryPath() : undefined,
model,
reasoningEffort: adapter === "codex" ? effort : undefined,
instructions: adapter === "codex" ? systemPrompt.append : undefined,
developerInstructions:
adapter === "codex" ? systemPrompt.append : undefined,
additionalDirectories:
adapter === "codex" ? additionalDirectories : undefined,
onStructuredOutput: jsonSchema
Expand Down
Loading