fix(runner): preserve stock Codex base instructions (#14920)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The native Runner connects Paperclip tasks to Codex app-server.
> - Paperclip passed its runtime context as `baseInstructions`.
> - That field replaces the stock Codex base prompt.
> - This pull request sends the same Paperclip context as additive
developer instructions.
> - Codex keeps its stock prompt and still receives Paperclip task
instructions and tools.

## Linked Issues or Issue Description

**What happened?**

The native Codex driver and Rust provider sent Paperclip context through
`baseInstructions` on thread start and resume. Codex used this text in
place of its stock base instructions. Direct-chat resume also sent an
empty replacement base. The Runner Lab session path used the same
replacement field.

**Expected behavior**

Codex should retain its stock base prompt. Paperclip should add its
runtime context through `developerInstructions`. Other provider facades
should retain their current instruction handling.

**Steps to reproduce**

1. Create a native Codex session through Paperclip Runner.
2. Inspect the `thread/start` request in the native provider trace.
3. Resume the session and inspect `thread/resume`.
4. Before this fix, these paths set `baseInstructions`. After this fix,
the Codex paths set `developerInstructions` and omit `baseInstructions`.

**Paperclip version or commit**

Reproduced against master at `cad26c6bfb736039c8ed5743da650a44792a083c`.

**Deployment mode**

Built from source. Native Codex app-server and runnerd paths. A local
protocol probe used codex-cli 0.153.4 and a localhost Responses stub.

No duplicate fix or matching public issue was found in the GitHub
search.

## What Changed

- Send additive developer instructions on Codex start and resume in the
TypeScript driver, Rust provider, and Runner Lab session path.
- Carry the additive fragment through runnerd, including runtime asset
path mapping.
- Preserve existing instruction fields for other provider facades,
including OpenCode.
- Add start/resume/direct-chat regression coverage and check the actual
Rust provider request.
- Document the historical option and trace field names. Record progress
and follow-ups in the working checklist.

## Verification

- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- Targeted Codex driver lifecycle, driver, and live-session Vitest
suites — 139 tests passed.
- `cargo test --manifest-path
packages/paperclip-runner/runner/Cargo.toml --locked -p
paperclip-runner-core --test codex_provider` — 91 passed, 2 ignored
subprocess helpers.
- Real app-server probe: a localhost Responses stub captured identical
14,732-character stock base instructions on fresh start and cold resume.
Both requests retained the Paperclip marker in developer input. Both
stub turns completed. No paid inference was used.
- Runnerd transport Vitest suite — 182 tests passed.
- The initial `pnpm test:run` attempt reported local dependency-loading,
embedded PostgreSQL startup, and macOS `/var` versus `/private/var` path
failures. It was stopped after those failures. Loading-suite reruns
passed 1,428 tests after the build; native interaction/finalization
reruns passed 38 tests. A seven-suite diagnostic rerun passed 463 tests
and isolated the remaining path and PostgreSQL setup failures.
- With `TMPDIR=/private/tmp`, workspace, gateway, interaction, and
attachment suites passed all 356 tests. The remaining environment-image
and native-session-resumption suites passed all 44 tests with the same
canonical temp path. All affected suites passed on rerun. The original
full local command was stopped after failures and is not claimed as
passing.
- All 55 PR checks passed at `83281439456181396f3707eecda5d2ebc90bd14d`.
Greptile scored 5/5 with no open review threads.
- No paid live campaign or Product E2E browser suite was run. This
change has protocol and regression coverage; it does not claim improved
task quality.

## Risks

- Stock Codex behavior may differ from behavior under the previous
Paperclip replacement prompt. Restoring that behavior is the intended
change.
- Existing Codex threads retain their saved replacement base prompt.
They need a provider session reset to receive the stock base. This PR
does not reset active sessions or alter recovery rules.
- The legacy `baseInstructions` option and trace field names remain for
compatibility. They now describe the additive Paperclip fragment for
Codex.
- The separate Codex-through-ACP dependency patch remains a follow-up in
the harness coverage checklist. This PR covers native app-server
execution.

## Model Used

OpenAI Codex, GPT-6. The exact runtime model variant and context window
are not exposed in this session. Used reasoning, repository inspection,
code editing, shell execution, and test tools.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-10-02 08:53:47 -05:00
1 parent c46e41e81c
commit 408f70e69f
13 files changed
+350 -16

No files matched your search

@@ -0,0 +1,219 @@
# Stock harness, with Paperclip: working checklist
Created: 2026-10-02. Status: item 1 implemented for native Codex app-server;
PR validation in progress. Other implementation items remain open.
Goal: keep the agent's stock harness behavior and add only what it needs to work
with Paperclip. Apply this across legacy adapters, the new Runner, and their
configuration variants.
We will work through the numbered items one at a time with Dotta. For each item,
record the proposed behavior and Dotta's direction, make a bounded change, and
verify the affected paths before moving on. New findings get stable IDs in the
ledger below so they do not disappear into conversation history.
**Current item: 1 — validate and review the native Codex instruction fix.**
## Agreed direction and boundaries
- Preserve vendor base instructions. Paperclip context should be additive where
the harness supports it.
- Reduce the default hire operating manual and review every role/team template
for reduction or removal. Prefer a tiny default, potentially one paragraph.
- Keep a minimal coordination boundary; load uncommon procedures on demand.
- Move bookkeeping into the runtime where it can own the operation reliably.
- Check capability and configuration behavior across all harness paths.
- Paperclip owns MCP configuration. Missing personal integrations are acceptable
and intentional; restoring them is not a goal of this work.
- Configuration changes and configuration-driven session resets are acceptable.
- Preserve Paperclip authentication, assigned skills, workspace access, tool
authorization, company boundaries, checkout, approvals, budget hard stops,
pause/cancel behavior, audit records, and usable task/artifact delivery.
- Restore appropriate repository instruction context without accidentally
importing unrelated host configuration or displacing Paperclip-owned MCPs.
- Decide explicitly how changed defaults affect existing agents and sessions;
do not assume existing custom instructions should be rewritten.
## 1. Preserve the native Codex base instructions
- [x] Trace every instruction path: backend composition, TypeScript driver,
runnerd bridge, and Rust provider; include fresh threads, resume, and recovery.
- [x] Record the chosen additive mechanism and the minimum Paperclip context it
needs to carry. Include fallback/direct execution paths.
- [x] Remove default replacement of stock base instructions across those paths.
- [x] Verify the actual app-server request and retained session instructions,
including resume; checking only a prompt builder is insufficient.
- [ ] Verify Paperclip task context, tools, auth, assigned skills, and completion
still work. Record applicable regressions and eval results.
Starting points: [Codex backend](../../packages/paperclip-runner/src/backends/codex-native-backend.ts),
[app-server driver](../../packages/paperclip-runner/src/drivers/codex/codex-app-server-driver-impl.ts),
[runnerd transport](../../packages/paperclip-runner/src/live/runnerd-codex-transport.ts),
[Rust provider](../../packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs).
Decision: use additive `developerInstructions` on Codex start and resume in the
TypeScript driver, Runner Lab/eval sessions, and Rust provider. Retain existing
instruction fields for other provider facades. Keep the Paperclip fragment and
its historical option/trace names unchanged in this bounded fix.
Transition: pre-change Codex threads retain their saved replacement base prompt
and need a provider session reset. Do not reset active sessions automatically.
The separate Codex-through-ACP dependency patch remains a coverage follow-up
under item 6; this change covers the native app-server path.
## 2. Reduce the default operating manual and shared prompt layers
- [ ] Inventory what an agent actually receives: hire instructions, shared
prompt template, wake context, runtime prompt, bootstrap, and loaded skills.
Separate always-present text from content loaded on demand.
- [ ] Agree on the tiny common contract and the coordination details that each
runtime still requires. Legacy API coordination and native semantic tools
need appropriate instructions for their respective interfaces.
- [ ] Remove repeated workflow rules and stock coding/style/autonomy guidance.
- [ ] Move detailed planning, hiring, artifacts, and exceptional procedures to
discoverable references or tools where feasible.
- [ ] Check fresh and resumed task/chat flows for instruction duplication and
contradictory completion or waiting rules.
Starting points: [default hire instructions](../../server/src/onboarding-assets/default/AGENTS.md),
[shared adapter utilities](../../packages/adapter-utils/src/server-utils.ts),
[native runtime contract](../../packages/paperclip-runner/src/contracts/runtime-context.ts),
[Paperclip operational skill](../../skills/paperclip/SKILL.md).
Decision: exact retained paragraph and on-demand boundaries pending.
## 3. Review every hiring and role template
- [ ] Cover default hires, CEO, chief of staff, coder, QA, UX, security, CTO, and
every other shipped team/role instruction set.
- [ ] Trace onboarding, API/UI/CLI hiring, team imports, and agent-creation skill
rules so a removed manual is not regenerated through another entry point.
- [ ] For each template, decide: remove it, retain a tiny role description, or
keep specific domain guidance with a stated reason.
- [ ] Review forced delegation, mandatory memory workflows, procedural review
routing, comment requirements, and old governance instructions.
- [ ] Update hiring references and draft-review requirements alongside templates.
Native Runner agents must not be required to follow legacy skill/API procedures
that their runtime intentionally does not expose.
- [ ] Decide rollout for new hires, existing managed bundles, imported teams,
and custom agent instructions.
- [ ] If substantial behavioral instructions remain, identify the behavior they
should improve and add appropriate eval coverage. A tiny role paragraph may
need only creation/configuration coverage.
Starting points: [onboarding assets](../../server/src/onboarding-assets/),
[default bundle service](../../server/src/services/default-agent-instructions.ts),
[agent routes](../../server/src/routes/agents.ts),
[hiring skill and references](../../skills/paperclip-create-agent/),
[teams catalog](../../packages/teams-catalog/catalog/).
Decision: per-template disposition and migration policy pending.
## 4. Fix repository context while retaining Paperclip configuration
- [ ] Map instruction discovery and precedence for each harness: repository
AGENTS.md/CLAUDE.md or equivalent, project/local settings, isolated homes, and
explicit Paperclip instruction injection.
- [ ] Distinguish repository instructions from settings that also load MCPs,
plugins, credentials, or skills; choose selective loading/injection as needed.
- [ ] Verify repository instructions are available in the correct workspace,
including worktrees, remote execution, and resumed sessions.
- [ ] Verify assigned skill discovery/pinning, authentication, Paperclip MCP
ownership, tool authorization, and configuration-change invalidation.
- [ ] Record intentional isolation separately from accidental lost context.
Claude starting points: [local adapter](../../packages/adapters/claude-local/src/server/execute.ts),
[ACP patch](../../patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch),
[runtime sandbox](../../packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.ts).
Decision: repository-context loading mechanism per harness pending. Personal MCP
isolation is approved and should remain.
## 5. Let the runtime own bookkeeping
- [ ] Map which runtime owns checkout, status transitions, completion comments,
artifacts, waiting/review states, and recovery; identify manual duplicates.
- [ ] Keep one valid completion path per runtime and preserve meaningful user
questions, approval requests, dependency waits, and final deliverables.
- [ ] Remove agent instructions for operations the runtime already performs;
retain necessary coordination for legacy/external adapters.
- [ ] Verify durable task state, visible final answer, artifact access, audit,
and restart/recovery behavior through the real product paths.
Decision: exact runtime responsibilities and legacy compatibility pending.
## 6. Complete the harness and configuration coverage audit
This matrix is an inventory seed, not a claim that every path has been audited
or qualified. Expand it from the registry and provider/profile definitions.
For every numbered change, record applicability here or an explicit reason it
does not apply.
| Execution family | Paths to cover | Audit status |
| --- | --- | --- |
| Legacy Codex / Claude | Local adapters, managed auth and isolated configuration | Initial inspection only |
| Other legacy local adapters | ACPX, OpenCode, Pi, Cursor, Gemini, Grok, Kimi, Hermes | Pending |
| Other adapter transports | Cursor Cloud, Hermes/OpenClaw gateways, process, HTTP, external adapter plugins | Pending |
| Runner Codex | App-server driver, runnerd bridge, Rust provider, direct/fallback paths | Additive instruction fix implemented; PR validation pending |
| Runner ACPX | Enabled profiles, especially Claude/Grok; declared or pending profiles tracked separately | Claude initial inspection; remaining audit pending |
| Runner OpenCode | Native provider and configuration paths | Pending |
| Hosted/remote providers | Claude Managed and AWS AgentCore; identify their own baseline rather than assuming CLI semantics | Pending |
- [ ] Inventory supported profiles and qualification status from the
[adapter registry](../../server/src/adapters/registry.ts) and
[provider resolver](../../server/src/services/native-runtime/provider-profile.ts).
- [ ] Cover local/remote environments, fresh/resumed sessions, managed/custom
hires, task/chat/planning flows, and auth/configuration variants as applicable.
- [ ] Audit restrictions on tools, skills, subagents, memory, and other harness
capabilities. Trace effective provider configuration, not just intermediate
configuration objects; document intentional limits and decide accidental ones.
- [ ] Check shared fixes reach every relevant adapter; document provider-specific
exceptions instead of silently extending a Codex/Claude assumption.
## Verification and evals — apply to each item
- [ ] Map existing coverage before adding cases; use [doc/evals.md](../evals.md).
Keep Runner protocol evals and Product E2E evals distinct.
- [ ] Start with narrow deterministic checks for instruction layering, effective
configuration, skill/auth delivery, and session behavior where appropriate.
- [ ] Use Runner evals for provider/session/tool protocol changes; use Product
E2E for real hiring, repository context, task lifecycle, and artifact delivery.
- [ ] Review existing context-integrity, hiring, completion-updates, and blocker
suites for reusable coverage; record gaps rather than claiming coverage from
a similarly named case.
- [ ] Compare task quality as well as Paperclip protocol compliance when claiming
that fewer instructions improve agent performance. Keep model, effort,
permissions, tools, and fixture comparable.
- [ ] Select narrow live cells when implementation is ready; retain revisions,
profile/environment, grader, retries, usage/cost, and failure classification.
- [ ] Run the relevant checks for each change and the repository's required full
verification before a PR-ready handoff. Record unrun checks and their reasons.
## Findings ledger
Confirmed mechanics below do not by themselves establish an effect on task quality.
| ID | Finding | Work item / disposition |
| --- | --- | --- |
| F1 | Native Codex sent Paperclip text as `baseInstructions`. Probes on codex-cli 0.153.4 showed replacement; additive `developerInstructions` retained the stock base on start and cold resume. | 1; app-server fix implemented, PR validation pending |
| F2 | Default hires and role templates prescribe substantial operating procedures; common prompt and wake layers add further coordination text. | 2–3; mechanics confirmed, performance effect unmeasured |
| F3 | Hiring references require legacy Paperclip skill/comment procedures, while native Runner intentionally omits that operational skill and uses semantic tools. | 2–3, 5; reconcile runtime contracts |
| F4 | Local Claude appends instructions; Runner Claude preserves the Claude Code preset. Runner isolation excludes project/local settings, which can also exclude repository instruction discovery. | 4; selective context fix to design |
| F5 | Some Codex capability settings differ between the direct driver and daemon path; an intermediate configuration does not prove the final provider behavior. | 6; effective-path audit pending |
| F6 | Omitting or nulling `baseInstructions` on an old Codex thread's resume preserves its saved replacement; an empty string produces an empty base. | 1; document the required provider session reset; no automatic migration in this PR |
| F7 | The isolated Codex-through-ACP dependency patch also sets `baseInstructions` on start/resume. It is a separate path from the native app-server backend. | 6; follow-up patch/profile audit pending |
Append new findings with evidence, affected paths, and the numbered item that
will address them. Record intentional behavior explicitly rather than as a bug.
## Decision and completion log
| Date | Decision / outcome | Evidence / follow-up |
| --- | --- | --- |
| 2026-10-02 | Dotta approved preserving stock instructions, smaller defaults/templates, minimal coordination, runtime bookkeeping, and coverage across harnesses. | Implementation details to work through one item at a time. |
| 2026-10-02 | Paperclip-owned MCP isolation and configuration changes/session resets are acceptable. | Preserve Paperclip auth and assigned skills while fixing repository context. |
| 2026-10-02 | Dotta requested implementation and a PR for item 1. Native app-server paths now use additive developer instructions. | 139 targeted TypeScript tests and 91 Rust provider tests passed; repository typecheck/build passed. Remaining test/review results to record. |
| 2026-10-02 | Verified actual Codex instruction layering using a localhost Responses stub, without paid inference. | codex-cli 0.153.4 sent identical 14,732-character stock base instructions on start and cold resume, with the Paperclip marker retained in developer input. This is protocol evidence, not a task-quality eval. |
For each completed item, add the chosen behavior, changed paths, verification
results, remaining exceptions, and follow-ups here before checking it off.
@@ -73,6 +73,17 @@ test "$(cat "$codex_workspace/hello.txt")" = "hello from Codex runner"
## Step 3: Inspect the exact model boundary
Paperclip sends its runtime instructions as additive `developerInstructions`
on Codex thread start and resume. Codex retains its stock base instructions.
The historical `context.baseInstructions` trace field contains the Paperclip
fragment, not the full Codex base prompt. The driver option with the same name
also supplies this additive fragment.
Threads created before this change retain their saved replacement base prompt
when resumed. Reset those provider sessions to apply the stock base instructions;
adding developer instructions does not repair an already saved base prompt.
This change does not alter session recovery or reset active sessions automatically.
```sh
jq '.context | {
protocolVersion,
@@ -1038,12 +1038,19 @@ impl CodexProvider {
"model": config.model,
"approvalPolicy": config.approval_policy,
"runtimeWorkspaceRoots": [config.cwd],
"baseInstructions": config.instructions,
"dynamicTools": dynamic_tools,
});
let params_object = params
.as_object_mut()
.expect("Codex thread parameters are an object");
// Codex's baseInstructions replaces its stock prompt. OpenCode
// uses the same protocol facade but keeps its existing contract.
let instruction_field = if config.provider == "codex" {
"developerInstructions"
} else {
"baseInstructions"
};
params_object.insert(instruction_field.to_owned(), json!(config.instructions));
if provider.permission_profile == "paperclip-runner-external-sandbox" {
// The execution target (for example Daytona) is the OS sandbox.
// Codex must not try to create nested user/network namespaces,
@@ -6290,6 +6290,34 @@ fn skill_wire_requests(directory: &Path) -> Vec<Value> {
.collect()
}
#[test]
fn runtime_instructions_are_additive_for_codex_on_start_and_resume() {
let directory = temporary_directory("instruction-channel-wire");
let log = directory.join("requests.ndjson");
let config = provider_config(&directory, &["--request-log", log.to_str().unwrap()]);
let mut provider = CodexProvider::start(&config, None).unwrap();
let thread_id = provider.thread_id().to_owned();
provider.shutdown().unwrap();
let mut resumed = CodexProvider::start(&config, Some(&thread_id)).unwrap();
resumed.shutdown().unwrap();
let frames = skill_wire_requests(&directory);
for method in ["thread/start", "thread/resume"] {
let frame = frames
.iter()
.find(|frame| frame["method"] == method)
.unwrap();
assert_eq!(
frame["params"]["developerInstructions"], config.instructions,
"{method}"
);
assert!(
frame["params"].get("baseInstructions").is_none(),
"{method}"
);
}
fs::remove_dir_all(directory).unwrap();
}
#[test]
fn skill_instructions_flag_reaches_start_and_resume_and_preserves_absent_config() {
for flag in [Some(true), Some(false), None] {
@@ -50,6 +50,8 @@ export interface CodexModelContextSnapshot {
collaborationMode: "default" | "plan";
sandbox: unknown;
approvalPolicy: unknown;
/** Historical trace field for the Paperclip instruction fragment, supplied
* to Codex as developerInstructions rather than replacing its stock base. */
baseInstructions: string;
instructionSources: string[];
instructionPolicy: {
@@ -199,6 +199,14 @@ export class CodexAppServerDriver implements HarnessDriver {
return this.#options.baseInstructions ?? CODEX_SKILLLESS_BASE_INSTRUCTIONS;
}
#instructionParams(instructions = this.#baseInstructions()): Record<string, string> {
// baseInstructions replaces Codex's stock prompt. Other providers use this
// driver as a protocol facade and retain their existing instruction field.
return (this.#options.driverIdentity?.kind ?? DRIVER_KIND) === DRIVER_KIND
? { developerInstructions: instructions }
: { baseInstructions: instructions };
}
async descriptor(): Promise<HarnessDriverDescriptor> {
const unsupported = Object.entries(this.#caps)
.filter(([, supported]) => !supported)
@@ -280,7 +288,7 @@ export class CodexAppServerDriver implements HarnessDriver {
...(this.#direct()
? {}
: {
baseInstructions: this.#baseInstructions(),
...this.#instructionParams(),
completionContract: {
revision:
this.#options.taskEnvelope.completionContract.revision,
@@ -408,7 +416,7 @@ export class CodexAppServerDriver implements HarnessDriver {
this.#options.includeSkillInstructions ?? false,
this.#options.environment,
),
baseInstructions: this.#direct() ? "" : this.#baseInstructions(),
...this.#instructionParams(this.#direct() ? "" : this.#baseInstructions()),
approvalPolicy: this.#options.approvalPolicy ?? "never",
...(this.#options.model ? { model: this.#options.model } : {}),
dynamicTools: this.#providerDynamicTools(),
@@ -477,7 +477,7 @@ describe("Codex app-server Codex driver", () => {
});
});
it("places Paperclip runtime instructions in Codex's system channel and enables only selected skill instructions", async () => {
it("adds Paperclip developer instructions without replacing the Codex base and enables selected skills", async () => {
const transport = new FakeCodexTransport();
const baseInstructions = [
"You are running as a Paperclip agent.",
@@ -499,17 +499,68 @@ describe("Codex app-server Codex driver", () => {
(call) => call.method === "thread/start",
);
expect(threadStart?.params).toMatchObject({
baseInstructions,
developerInstructions: baseInstructions,
config: {
"skills.include_instructions": true,
include_apps_instructions: false,
},
});
expect(threadStart?.params).not.toHaveProperty("baseInstructions");
expect(JSON.stringify(threadStart?.params.input ?? null)).not.toContain(
baseInstructions,
);
});
it.each(["task", "prepared", "direct"] as const)("preserves stock Codex instructions on %s recovery", async (conversationMode) => {
const originalTransport = new FakeCodexTransport();
const recoveryTransport = new FakeCodexTransport();
const baseInstructions = "Paperclip coordination and assigned instruction paths.";
const driver = makeDriver([originalTransport, recoveryTransport], {
conversationMode,
baseInstructions,
includeSkillInstructions: true,
});
const original = await driver.openSession({
runId: "run-additive-recovery",
normalizedSessionId: "normalized-additive-recovery",
workingDirectory: WORKSPACE,
});
const snapshot = await original.snapshot();
recoveryTransport.readResponse = {
thread: { id: snapshot.driverSessionId, sessionId: snapshot.providerSessionId, cwd: WORKSPACE, turns: [] },
};
await original.close({ reason: "verify additive recovery" });
const recovered = await driver.recoverSession(snapshot);
expect(recovered.recovered).toBe(true);
const started = originalTransport.calls.find((call) => call.method === "thread/start")!.params;
const resumed = recoveryTransport.calls.find((call) => call.method === "thread/resume")!.params;
expect(started).not.toHaveProperty("baseInstructions");
expect(resumed).not.toHaveProperty("baseInstructions");
expect(started.developerInstructions).toBe(conversationMode === "direct" ? undefined : baseInstructions);
expect(resumed.developerInstructions).toBe(conversationMode === "direct" ? "" : baseInstructions);
expect(resumed.dynamicTools).toEqual(started.dynamicTools);
expect(resumed.config).toEqual(started.config);
await recovered.session?.close({ reason: "verified additive recovery" });
});
it("retains the instruction field for non-Codex provider facades", async () => {
const transport = new FakeCodexTransport();
const driver = makeDriver([transport], {
baseInstructions: "Provider-specific runtime context.",
driverIdentity: { kind: "opencode_server", displayName: "OpenCode", version: "1" },
});
const session = await driver.openSession({
runId: "run-facade-instructions",
normalizedSessionId: "normalized-facade-instructions",
workingDirectory: WORKSPACE,
});
const started = transport.calls.find((call) => call.method === "thread/start")!.params;
expect(started.baseInstructions).toBe("Provider-specific runtime context.");
expect(started).not.toHaveProperty("developerInstructions");
await session.close({ reason: "verified facade instructions" });
});
it("passes the common typed-event contract and reports one provider turn terminal", async () => {
const transport = new FakeCodexTransport();
const driver = makeDriver([transport]);
@@ -572,7 +572,7 @@ describe("Codex app-server Codex driver", () => {
});
});
it("places Paperclip runtime instructions in Codex's system channel and enables only selected skill instructions", async () => {
it("adds Paperclip developer instructions without replacing the Codex base and enables selected skills", async () => {
const transport = new FakeCodexTransport();
const baseInstructions = [
"You are running as a Paperclip agent.",
@@ -592,12 +592,13 @@ describe("Codex app-server Codex driver", () => {
const threadStart = transport.calls.find((call) => call.method === "thread/start");
expect(threadStart?.params).toMatchObject({
baseInstructions,
developerInstructions: baseInstructions,
config: {
"skills.include_instructions": true,
include_apps_instructions: false,
},
});
expect(threadStart?.params).not.toHaveProperty("baseInstructions");
expect(JSON.stringify(threadStart?.params.input ?? null)).not.toContain(baseInstructions);
});
@@ -20,6 +20,8 @@ export interface CodexAppServerDriverOptions {
/** Per-run reasoning effort sent with each Codex turn, including resumed turns. */
reasoningEffort?: string;
approvalPolicy?: "never" | "on-request" | "untrusted";
/** Paperclip runtime instructions. Codex receives these as additive developer
* instructions; the historical option name remains compatible with callers. */
baseInstructions?: string;
includeSkillInstructions?: boolean;
/** Private instruction directory registered by the control plane for this run. */
@@ -918,8 +918,9 @@ describe("Capability live runnerd and Codex session", () => {
expect(names.filter((name) => name === "paperclip_finish")).toHaveLength(1);
expect(names.filter((name) => name === "paperclip_block")).toHaveLength(1);
expect(names).not.toContain("create_task");
expect(opened.params.baseInstructions).toContain('"revision":"paperclip-capability-live-v1"');
expect(opened.params.baseInstructions).toContain('"criterionIds":["objective"]');
expect(opened.params).not.toHaveProperty("baseInstructions");
expect(opened.params.developerInstructions).toContain('"revision":"paperclip-capability-live-v1"');
expect(opened.params.developerInstructions).toContain('"criterionIds":["objective"]');
await first.suspend();
const restoredService = new CapabilityLiveSessionService({ store, transportFactory: factory });
const restored = await restoredService.restore(first.id);
@@ -928,7 +929,8 @@ describe("Capability live runnerd and Codex session", () => {
names.filter((name) => name !== "paperclip_finish" && name !== "paperclip_block"),
);
const resumed = state.transports[1]!.requests.find((request) => request.method === "thread/resume")!;
expect(resumed.params.baseInstructions).toBe(opened.params.baseInstructions);
expect(resumed.params).not.toHaveProperty("baseInstructions");
expect(resumed.params.developerInstructions).toBe(opened.params.developerInstructions);
await restoredService.shutdown(restored.id);
await firstService.shutdown(first.id);
});
@@ -989,11 +991,11 @@ describe("Capability live runnerd and Codex session", () => {
expect(
state.transports[0]?.requests.find(
(request) => request.method === "thread/start",
)?.params.baseInstructions,
)?.params.developerInstructions,
).toContain(
"Native instructions\n\nRead-only instruction sibling root: /runtime/instructions",
);
expect(state.transports[0]?.requests.find((request) => request.method === "thread/start")?.params.baseInstructions)
expect(state.transports[0]?.requests.find((request) => request.method === "thread/start")?.params.developerInstructions)
.toContain('Native completion report contract: {"revision":"paperclip-capability-live-v1","criterionIds":["objective"]}');
await service.shutdown(session.id);
});
@@ -2479,7 +2479,7 @@ export class CapabilityLiveSession {
config: createSkilllessCodexThreadConfig(this.#config.workingDirectory),
permissions: CODEX_PERMISSION_PROFILE,
runtimeWorkspaceRoots: [this.#config.workingDirectory],
baseInstructions,
...(provider === "codex" ? { developerInstructions: baseInstructions } : { baseInstructions }),
persistExtendedHistory: true,
});
const resumedThread = record(resumed.thread);
@@ -2504,7 +2504,7 @@ export class CapabilityLiveSession {
permissions: CODEX_PERMISSION_PROFILE,
runtimeWorkspaceRoots: [this.#config.workingDirectory],
approvalPolicy: "never",
baseInstructions,
...(provider === "codex" ? { developerInstructions: baseInstructions } : { baseInstructions }),
completionContract: LIVE_COMPLETION_CONTRACT,
dynamicTools: [...semanticTools, ...codexSemanticToolSpecs()],
experimentalRawEvents: true,
@@ -4247,11 +4247,14 @@ it("captures exact provider frames and correlates Rust and TypeScript interpreta
decodedFrames.find((frame) => frame.method === "thread/start"),
).toMatchObject({
params: {
baseInstructions: withCodexCollaborationRuntimeInstructions(
developerInstructions: withCodexCollaborationRuntimeInstructions(
CODEX_SKILLLESS_BASE_INSTRUCTIONS,
),
},
});
expect(
(decodedFrames.find((frame) => frame.method === "thread/start")?.params as Record<string, unknown>),
).not.toHaveProperty("baseInstructions");
const stages = new Set(
[...nativeEntries, ...rehydratedEntries]
.filter((entry) => entry.kind === "interpretation")
@@ -4544,7 +4544,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
provider === "codex" &&
record(params.config).include_collaboration_mode_instructions !== false;
const unboundBaseInstructions = String(
params.baseInstructions ?? "You are a Paperclip agent.",
params.developerInstructions ?? params.baseInstructions ?? "You are a Paperclip agent.",
);
const baseInstructions =
sourceRuntimeContext && runtimeContext