diff --git a/docs/plugins/codex-harness.md b/docs/plugins/codex-harness.md index f66707dd3b6f..df11cd8e9dff 100644 --- a/docs/plugins/codex-harness.md +++ b/docs/plugins/codex-harness.md @@ -254,6 +254,18 @@ Changing auth order does not make a custom, Completions, HTTP, or request-overridden route Codex-compatible. Valid model-scoped Fast-mode and cutoff controls are runtime controls, not request overrides. +### Project instructions + +Codex loads `AGENTS.md` files through native project-document discovery. For +normal app-server threads, OpenClaw raises Codex's aggregate root-to-working- +directory budget from the upstream 32 KiB default to a bounded 128 KiB so later +scoped instructions are not silently clipped. Lightweight and restricted turns +set the native project-document budget to zero instead. + +This byte budget is separate from the character-based workspace bootstrap +limits configured through `agents.defaults.bootstrapMaxChars` and +`agents.defaults.bootstrapTotalMaxChars`. + ### Compaction Do not set `compaction.model` or `compaction.provider` on Codex-backed diff --git a/extensions/codex/media-understanding-provider.test.ts b/extensions/codex/media-understanding-provider.test.ts index 19da02b49088..87c56a9f47b5 100644 --- a/extensions/codex/media-understanding-provider.test.ts +++ b/extensions/codex/media-understanding-provider.test.ts @@ -330,6 +330,7 @@ describe("codex media understanding provider", () => { "features.multi_agent_v2": false, "features.plugins": false, "features.standalone_web_search": false, + project_doc_max_bytes: 131_072, "tools.update_plan.enabled": false, web_search: "disabled", }, @@ -676,6 +677,7 @@ describe("codex media understanding provider", () => { "features.multi_agent_v2": false, "features.plugins": false, "features.standalone_web_search": false, + project_doc_max_bytes: 131_072, "tools.update_plan.enabled": false, web_search: "disabled", }, diff --git a/extensions/codex/src/app-server/bounded-turn.test.ts b/extensions/codex/src/app-server/bounded-turn.test.ts index fe136b62e6a2..41ab68b8d4d1 100644 --- a/extensions/codex/src/app-server/bounded-turn.test.ts +++ b/extensions/codex/src/app-server/bounded-turn.test.ts @@ -367,6 +367,9 @@ describe("runBoundedCodexAppServerTurn settled finalization isolation", () => { isolation: "private-stdio", }), ).rejects.toThrow("hosted search turn returned no text"); + + const startParams = fake.request.mock.calls.find(([method]) => method === "thread/start")?.[1]; + expect(startParams).toMatchObject({ config: { project_doc_max_bytes: 131_072 } }); }); it("still fails on a terminal error notification", async () => { diff --git a/extensions/codex/src/app-server/project-doc-thread-config.ts b/extensions/codex/src/app-server/project-doc-thread-config.ts new file mode 100644 index 000000000000..7ad5a7b95888 --- /dev/null +++ b/extensions/codex/src/app-server/project-doc-thread-config.ts @@ -0,0 +1,9 @@ +import { mergeCodexThreadConfigs } from "./plugin-thread-config.js"; +import type { JsonObject } from "./protocol.js"; + +const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES = 128 * 1024; + +export function buildCodexProjectDocThreadConfig(config?: JsonObject): JsonObject { + const defaults: JsonObject = { project_doc_max_bytes: CODEX_NATIVE_PROJECT_DOC_MAX_BYTES }; + return mergeCodexThreadConfigs(defaults, config) ?? defaults; +} diff --git a/extensions/codex/src/app-server/side-question.test.ts b/extensions/codex/src/app-server/side-question.test.ts index bd55c24d262a..ba2b20a865df 100644 --- a/extensions/codex/src/app-server/side-question.test.ts +++ b/extensions/codex/src/app-server/side-question.test.ts @@ -663,6 +663,7 @@ describe("runCodexAppServerSideQuestion", () => { expect(forkParams?.approvalsReviewer).toBe("user"); expect(forkParams?.cwd).toBe("/tmp/workspace"); expect(forkParams?.config).toEqual({ + project_doc_max_bytes: 131_072, "features.goals": false, "tools.update_plan.enabled": false, "features.code_mode": true, diff --git a/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts b/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts index 2236bc18fa5d..307ac864de25 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts @@ -114,6 +114,7 @@ function createParams(sessionFile: string, workspaceDir: string) { } const DEFAULT_CODEX_RUNTIME_THREAD_CONFIG = { + project_doc_max_bytes: 131_072, "features.goals": false, "tools.update_plan.enabled": false, "features.code_mode": true, diff --git a/extensions/codex/src/app-server/thread-lifecycle.test.ts b/extensions/codex/src/app-server/thread-lifecycle.test.ts index 7d6a87fb7f60..6f074e88a921 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.test.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.test.ts @@ -360,6 +360,7 @@ describe("Codex ring-zero thread config", () => { dynamicTools: [], hostSystemAgentActive: true, nativeCodeModeEnabled: false, + config: { project_doc_max_bytes: 64_000 }, }); const resume = buildThreadResumeParams(params, { appServer, @@ -367,6 +368,7 @@ describe("Codex ring-zero thread config", () => { hostSystemAgentActive: true, nativeCodeModeEnabled: false, threadId: "thread-1", + config: { project_doc_max_bytes: 64_000 }, }); expect(start.environments).toEqual([]); @@ -1434,6 +1436,7 @@ describe("Codex app-server native code mode config", () => { }); expect(request.config).toEqual({ + project_doc_max_bytes: 131_072, "features.hooks": true, apps: { _default: { enabled: false } }, mcp_servers: { @@ -1588,6 +1591,7 @@ describe("Codex app-server native code mode config", () => { ); expect(request.config).toEqual({ + project_doc_max_bytes: 131_072, "features.code_mode": true, "features.code_mode_only": false, "features.goals": false, @@ -1717,6 +1721,7 @@ describe("Codex app-server native code mode config", () => { }); expect(request.config).toEqual({ + project_doc_max_bytes: 131_072, "features.code_mode": true, "features.code_mode_only": true, "features.goals": false, @@ -1740,6 +1745,7 @@ describe("Codex app-server native code mode config", () => { }); expect(request.config).toEqual({ + project_doc_max_bytes: 131_072, "features.code_mode": true, "features.code_mode_only": true, "features.goals": false, @@ -1799,6 +1805,7 @@ describe("Codex app-server native code mode config", () => { }); expect(request.config).toEqual({ + project_doc_max_bytes: 131_072, "features.code_mode": true, "features.code_mode_only": false, "features.goals": false, @@ -1825,6 +1832,7 @@ describe("Codex app-server native code mode config", () => { }); expect(request.config).toEqual({ + project_doc_max_bytes: 131_072, "features.code_mode": false, "features.code_mode_only": false, "features.goals": false, @@ -1846,6 +1854,7 @@ describe("Codex app-server native code mode config", () => { }); expect(request.config).toEqual({ + project_doc_max_bytes: 131_072, "features.code_mode": false, "features.code_mode_only": false, "features.goals": false, @@ -1887,8 +1896,17 @@ describe("Codex app-server native code mode config", () => { }); }); - it("keeps native Codex project docs enabled when context is not lightweight", () => { - const request = buildThreadResumeParams( + it("defaults native Codex project docs to 128 KiB while honoring explicit overrides", () => { + const defaultRequest = buildThreadStartParams( + createAttemptParams({ provider: "openai", bootstrapContextRunKind: "cron" }), + { + cwd: "/repo", + dynamicTools: [], + appServer: createAppServerOptions() as never, + developerInstructions: "test instructions", + }, + ); + const overrideRequest = buildThreadResumeParams( createAttemptParams({ provider: "openai", bootstrapContextRunKind: "cron" }), { threadId: "thread-1", @@ -1900,7 +1918,8 @@ describe("Codex app-server native code mode config", () => { }, ); - expect(request.config).toEqual({ + expect(defaultRequest.config?.project_doc_max_bytes).toBe(131_072); + expect(overrideRequest.config).toEqual({ project_doc_max_bytes: 64_000, "features.code_mode": true, "features.code_mode_only": false, @@ -2105,6 +2124,7 @@ describe("Codex app-server turn params", () => { approvalPolicy: "on-request", approvalsReviewer: "guardian_subagent", config: { + project_doc_max_bytes: 131_072, "features.code_mode": true, "features.code_mode_only": false, "features.goals": false, @@ -3293,6 +3313,7 @@ describe("Codex app-server supervised branch lifecycle", () => { excludeTurns: true, developerInstructions: agentWorkspaceDeveloperInstructions, config: { + project_doc_max_bytes: 131_072, allow_login_shell: false, shell_environment_policy: { experimental_use_profile: false, @@ -3312,6 +3333,7 @@ describe("Codex app-server supervised branch lifecycle", () => { dynamicTools, environments: [{ environmentId: "local", cwd: workspaceDir }], config: { + project_doc_max_bytes: 131_072, allow_login_shell: false, shell_environment_policy: { experimental_use_profile: false, diff --git a/extensions/codex/src/app-server/thread-requests.ts b/extensions/codex/src/app-server/thread-requests.ts index fc514acd63ef..5dacae817616 100644 --- a/extensions/codex/src/app-server/thread-requests.ts +++ b/extensions/codex/src/app-server/thread-requests.ts @@ -11,6 +11,7 @@ import { shouldDisableCodexToolSearchForModel, } from "./dynamic-tool-profile.js"; import { mergeCodexThreadConfigs } from "./plugin-thread-config.js"; +import { buildCodexProjectDocThreadConfig } from "./project-doc-thread-config.js"; import { CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, isJsonObject, @@ -325,6 +326,7 @@ export function buildCodexRuntimeThreadConfig( directOnlyToolNamespaces?: readonly string[]; } = {}, ): JsonObject { + const configured = buildCodexProjectDocThreadConfig(config); // Native goal RPCs remain available through app-server, but the Codex goals // feature also starts autonomous turns. Keep it disabled until a run owner exists. const codeModeConfig: JsonObject = { @@ -333,7 +335,7 @@ export function buildCodexRuntimeThreadConfig( }; if (options.nativeCodeModeEnabled === false) { const disabledConfig = mergeCodexThreadConfigs( - config, + configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, @@ -350,7 +352,7 @@ export function buildCodexRuntimeThreadConfig( if (options.nativeCodeModeOnlyEnabled === true) { const merged = mergeCodexThreadConfigs( codeModeConfig, - config, + configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, { @@ -366,7 +368,7 @@ export function buildCodexRuntimeThreadConfig( } const merged = mergeCodexThreadConfigs( codeModeConfig, - config, + configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, ) ?? { diff --git a/extensions/codex/src/conversation-binding.test.ts b/extensions/codex/src/conversation-binding.test.ts index bed496f64e9f..9db3489b2d5b 100644 --- a/extensions/codex/src/conversation-binding.test.ts +++ b/extensions/codex/src/conversation-binding.test.ts @@ -741,6 +741,7 @@ describe("codex conversation binding", () => { expect(requests[0]?.params.model).toBe("gpt-5.4-mini"); expect(requests[0]?.params.personality).toBe("none"); expect(requests[0]?.params.ephemeral).toBe(true); + expect(requests[0]?.params.config).toMatchObject({ project_doc_max_bytes: 131_072 }); expect(requests[0]?.params).not.toHaveProperty("modelProvider"); await expect(readCodexAppServerBinding(sessionFile)).resolves.toMatchObject({ authProfileId: "openai:default", @@ -1020,7 +1021,11 @@ describe("codex conversation binding", () => { expect(requests[1]?.params).toMatchObject({ threadId: "thread-native-child", sandbox: "read-only", - config: { apps: { _default: { enabled: false } }, "features.apps": false }, + config: { + project_doc_max_bytes: 131_072, + apps: { _default: { enabled: false } }, + "features.apps": false, + }, }); await expect(consumeCodexAppServerLiveThread(client, "thread-native-child")).resolves.toEqual( expect.objectContaining({ release: expect.any(Function) }), diff --git a/extensions/codex/src/conversation-binding.ts b/extensions/codex/src/conversation-binding.ts index 756462743ef0..fe4237b12892 100644 --- a/extensions/codex/src/conversation-binding.ts +++ b/extensions/codex/src/conversation-binding.ts @@ -59,6 +59,7 @@ import { buildDisabledAppsConfigPatch, mergeCodexThreadConfigs, } from "./app-server/plugin-thread-config.js"; +import { buildCodexProjectDocThreadConfig } from "./app-server/project-doc-thread-config.js"; import { assertCodexThreadStartResponse } from "./app-server/protocol-validators.js"; import type { CodexServiceTier, @@ -591,12 +592,10 @@ function codexConversationSandboxOrPermissions( const disabledApps = mergeCodexThreadConfigs(buildDisabledAppsConfigPatch(), { "features.apps": false, })!; - if (networkProxy) { - return { - config: mergeCodexThreadConfigs(networkProxy.configPatch, disabledApps), - }; - } - return { sandbox, config: disabledApps }; + const config = buildCodexProjectDocThreadConfig( + mergeCodexThreadConfigs(networkProxy?.configPatch, disabledApps), + ); + return networkProxy ? { config } : { sandbox, config }; } async function requestNewConversationBindingThread( diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md index 2a97234ea634..b41a1ab396b7 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md @@ -70,6 +70,7 @@ "features.code_mode_only": false, "features.goals": false, "features.standalone_web_search": false, + "project_doc_max_bytes": 131072, "tools.update_plan.enabled": false, "web_search": "cached" }, @@ -113,6 +114,7 @@ "features.code_mode_only": false, "features.goals": false, "features.standalone_web_search": false, + "project_doc_max_bytes": 131072, "tools.update_plan.enabled": false, "web_search": "cached" }, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md index 323de0d49fed..9fcb75a170ac 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md @@ -70,6 +70,7 @@ "features.code_mode_only": false, "features.goals": false, "features.standalone_web_search": false, + "project_doc_max_bytes": 131072, "tools.update_plan.enabled": false, "web_search": "cached" }, @@ -113,6 +114,7 @@ "features.code_mode_only": false, "features.goals": false, "features.standalone_web_search": false, + "project_doc_max_bytes": 131072, "tools.update_plan.enabled": false, "web_search": "cached" }, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md index 8bf1cd409874..04f678e6e561 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md @@ -70,6 +70,7 @@ "features.code_mode_only": false, "features.goals": false, "features.standalone_web_search": false, + "project_doc_max_bytes": 131072, "tools.update_plan.enabled": false, "web_search": "cached" }, @@ -114,6 +115,7 @@ "features.code_mode_only": false, "features.goals": false, "features.standalone_web_search": false, + "project_doc_max_bytes": 131072, "tools.update_plan.enabled": false, "web_search": "cached" },