Skip to content

Commit 26ec331

Browse files
fix(codex): preserve native agent model settings (#3150)
Preserve authored string model and model_reasoning_effort settings as top-level Codex TOML keys, leaving omitted settings absent. Warn safely about unsupported or invalid metadata without exposing values. Add regression coverage and document the bounded behavior and proposed specification amendment. Fixes #3126. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 0c51257 commit 26ec331

17 files changed

Lines changed: 1337 additions & 48 deletions

File tree

‎.agents/skills/apm-spec-guardian/assets/synthesizer-return-schema.json‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,21 +93,7 @@
9393
"defer_v0_2": {
9494
"type": "array",
9595
"description": "Architectural work bound to a reserved slot in a future major. Each entry MUST cite the existing reserved-slot section anchor in the artifact (e.g. 'sec.4.8 workspaces reserved', 'sec.10.12 publisher attestations reserved').",
96-
"items": {
97-
"allOf": [
98-
{ "$ref": "#/definitions/fold_item" },
99-
{
100-
"type": "object",
101-
"required": ["reserved_slot_anchor"],
102-
"properties": {
103-
"reserved_slot_anchor": {
104-
"type": "string",
105-
"description": "Section / anchor in the current artifact that already reserves space for this work."
106-
}
107-
}
108-
}
109-
]
110-
}
96+
"items": { "$ref": "#/definitions/fold_item_with_reserved_slot" }
11197
},
11298
"reject": {
11399
"type": "array",
@@ -148,6 +134,23 @@
148134
"patch_instruction": { "type": "string", "description": "Concrete mechanical instruction the drafter applies. ASCII only. MUST NOT add any forbidden vendor / foundation token to the artifact." },
149135
"success_criterion": { "type": "string", "description": "Verifiable post-fold check (grep, schema validation, or anchor count)." }
150136
}
137+
},
138+
"fold_item_with_reserved_slot": {
139+
"type": "object",
140+
"description": "Same shape as fold_item plus a required reserved_slot_anchor. Inlined (not composed via allOf over fold_item) because Draft 7 has no unevaluatedProperties: an allOf branch's own additionalProperties:false would otherwise reject the sibling branch's reserved_slot_anchor key. The five base properties are intentionally duplicated here, not $ref-shared, to keep this a single closed object; test_shared_fold_item_properties_structurally_identical guards against the two copies drifting apart.",
141+
"required": ["id", "theme", "spec_section", "patch_instruction", "success_criterion", "reserved_slot_anchor"],
142+
"additionalProperties": false,
143+
"properties": {
144+
"id": { "type": "string", "pattern": "^F[1-9][0-9]*$" },
145+
"theme": { "type": "string", "description": "Convergent-theme id this fold addresses (e.g. T1, T2), or 'standalone' if not part of a clustered theme." },
146+
"spec_section": { "type": "string", "description": "Section / appendix / req-XXX anchor where the patch lands." },
147+
"patch_instruction": { "type": "string", "description": "Concrete mechanical instruction the drafter applies. ASCII only. MUST NOT add any forbidden vendor / foundation token to the artifact." },
148+
"success_criterion": { "type": "string", "description": "Verifiable post-fold check (grep, schema validation, or anchor count)." },
149+
"reserved_slot_anchor": {
150+
"type": "string",
151+
"description": "Section / anchor in the current artifact that already reserves space for this work."
152+
}
153+
}
151154
}
152155
}
153156
}

‎.apm/skills/apm-spec-guardian/assets/synthesizer-return-schema.json‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,21 +93,7 @@
9393
"defer_v0_2": {
9494
"type": "array",
9595
"description": "Architectural work bound to a reserved slot in a future major. Each entry MUST cite the existing reserved-slot section anchor in the artifact (e.g. 'sec.4.8 workspaces reserved', 'sec.10.12 publisher attestations reserved').",
96-
"items": {
97-
"allOf": [
98-
{ "$ref": "#/definitions/fold_item" },
99-
{
100-
"type": "object",
101-
"required": ["reserved_slot_anchor"],
102-
"properties": {
103-
"reserved_slot_anchor": {
104-
"type": "string",
105-
"description": "Section / anchor in the current artifact that already reserves space for this work."
106-
}
107-
}
108-
}
109-
]
110-
}
96+
"items": { "$ref": "#/definitions/fold_item_with_reserved_slot" }
11197
},
11298
"reject": {
11399
"type": "array",
@@ -148,6 +134,23 @@
148134
"patch_instruction": { "type": "string", "description": "Concrete mechanical instruction the drafter applies. ASCII only. MUST NOT add any forbidden vendor / foundation token to the artifact." },
149135
"success_criterion": { "type": "string", "description": "Verifiable post-fold check (grep, schema validation, or anchor count)." }
150136
}
137+
},
138+
"fold_item_with_reserved_slot": {
139+
"type": "object",
140+
"description": "Same shape as fold_item plus a required reserved_slot_anchor. Inlined (not composed via allOf over fold_item) because Draft 7 has no unevaluatedProperties: an allOf branch's own additionalProperties:false would otherwise reject the sibling branch's reserved_slot_anchor key. The five base properties are intentionally duplicated here, not $ref-shared, to keep this a single closed object; test_shared_fold_item_properties_structurally_identical guards against the two copies drifting apart.",
141+
"required": ["id", "theme", "spec_section", "patch_instruction", "success_criterion", "reserved_slot_anchor"],
142+
"additionalProperties": false,
143+
"properties": {
144+
"id": { "type": "string", "pattern": "^F[1-9][0-9]*$" },
145+
"theme": { "type": "string", "description": "Convergent-theme id this fold addresses (e.g. T1, T2), or 'standalone' if not part of a clustered theme." },
146+
"spec_section": { "type": "string", "description": "Section / appendix / req-XXX anchor where the patch lands." },
147+
"patch_instruction": { "type": "string", "description": "Concrete mechanical instruction the drafter applies. ASCII only. MUST NOT add any forbidden vendor / foundation token to the artifact." },
148+
"success_criterion": { "type": "string", "description": "Verifiable post-fold check (grep, schema validation, or anchor count)." },
149+
"reserved_slot_anchor": {
150+
"type": "string",
151+
"description": "Section / anchor in the current artifact that already reserves space for this work."
152+
}
153+
}
151154
}
152155
}
153156
}

‎CHANGELOG.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- `docs/src/content/docs/specs/openapm-v0.1.md` adds proposed `req-tg-015` for Codex-native `model`/`model_reasoning_effort` preservation and bounded dropped-metadata diagnostics. (#3150)
13+
14+
### Fixed
15+
16+
- Codex agent conversion preserves native `model` and `model_reasoning_effort` settings and warns about dropped metadata instead of silently losing it. (#3150)
17+
1018
## [0.33.0] - 2026-10-02
1119

1220
### Added

‎CONFORMANCE.json‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,12 +1544,23 @@
15441544
"tests": [
15451545
"tests/spec_conformance/test_manifest_reqs.py::test_user_scoped_mcp_target_selection_ignores_project_signals"
15461546
]
1547+
},
1548+
{
1549+
"conformance_class": "consumer",
1550+
"id": "req-tg-015",
1551+
"keyword": "MUST",
1552+
"section": "8.5.1",
1553+
"status": "active",
1554+
"test_count": 1,
1555+
"tests": [
1556+
"tests/spec_conformance/test_manifest_reqs.py::test_codex_native_model_settings_preserved_and_dropped_metadata_bounded"
1557+
]
15471558
}
15481559
],
15491560
"spec_version": "v0.1.1",
15501561
"summary_by_class": {
15511562
"consumer": {
1552-
"active": 91,
1563+
"active": 92,
15531564
"skipped": 1,
15541565
"unbound": 0,
15551566
"xfail": 0
@@ -1573,5 +1584,5 @@
15731584
"xfail": 0
15741585
}
15751586
},
1576-
"total_requirements": 125
1587+
"total_requirements": 126
15771588
}

‎CONFORMANCE.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ APM claims the proposed deployed-prompt-audit capability (req-pl-019, req-pl-020
3333
| Class | Active | Skipped | Xfail | Unbound |
3434
|-------|-------:|--------:|------:|--------:|
3535
| Producer | 12 | 0 | 0 | 0 |
36-
| Consumer | 91 | 1 | 0 | 0 |
36+
| Consumer | 92 | 1 | 0 | 0 |
3737
| Registry | 1 | 0 | 0 | 0 |
3838
| Governance | 20 | 0 | 0 | 0 |
3939

@@ -166,6 +166,7 @@ APM claims the proposed deployed-prompt-audit capability (req-pl-019, req-pl-020
166166
| [req-tg-012](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-012) | MUST | 8.5.6 | consumer | active | 1 | - |
167167
| [req-tg-013](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-013) | MUST | 8.5.7 | consumer | active | 7 | - |
168168
| [req-tg-014](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-014) | MUST | 8.5.8 | consumer | active | 1 | - |
169+
| [req-tg-015](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-015) | MUST | 8.5.1 | consumer | active | 1 | - |
169170

170171
## Waivers
171172

‎apm.lock.yaml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,7 +1590,7 @@ deployments:
15901590
owners:
15911591
- .
15921592
active_owner: .
1593-
content_hash: sha256:e6e2771f2fe8578e72d12e51edcc3817717f8a0bbef66d8a8a5c556f0dce06d7
1593+
content_hash: sha256:2115c15be67270f2b969b7f49e88d9686382d4aed10fed21ae47811c36981c98
15941594
- kind: project-relative
15951595
target: legacy
15961596
value: .agents/skills/apm-strategy
@@ -3104,7 +3104,7 @@ local_deployed_file_hashes:
31043104
.agents/skills/apm-spec-guardian/assets/comment-template.md: sha256:8f5703aa98d1036d2418ffa45eac9d0270504dac33f3e9b1892884a9d48da1a6
31053105
.agents/skills/apm-spec-guardian/assets/linter-checklist.md: sha256:b3b60af18f4b37e9f68815aaf8001617b81cc24fb8fe0a1f19074db93efa6b3b
31063106
.agents/skills/apm-spec-guardian/assets/panelist-return-schema.json: sha256:bb66b270394c386665c6b93d83befe05ce9f8206abf8d656079c29d65729fefd
3107-
.agents/skills/apm-spec-guardian/assets/synthesizer-return-schema.json: sha256:e6e2771f2fe8578e72d12e51edcc3817717f8a0bbef66d8a8a5c556f0dce06d7
3107+
.agents/skills/apm-spec-guardian/assets/synthesizer-return-schema.json: sha256:2115c15be67270f2b969b7f49e88d9686382d4aed10fed21ae47811c36981c98
31083108
.agents/skills/apm-strategy/SKILL.md: sha256:6c7fbc821fef930ab76764c7b33d55bd181f31cdd406cff5ce7cf14d1071c2ef
31093109
.agents/skills/auth/SKILL.md: sha256:bf1039cb63dbb968d50dbf4a9e4ceec9a4c30e40e8dd666d404f1c39eda55bc0
31103110
.agents/skills/cli-logging-ux/SKILL.md: sha256:b04d5282a9f21b33c75ac98c41c7eab7e562eafea8c89bbb5732d9956958fe7c

‎docs/public/specs/manifests/openapm-v0.1.requirements.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,11 @@ requirements:
460460
section: "8.5.8"
461461
conformance_class: consumer
462462
notes: "user-scoped MCP target selection uses a disclosed versioned capability contract, ignores project-only detection signals, applies first-source precedence without fallback, filters mixed sets, and refuses zero-supported selections before user manifest, lockfile, or target-config mutation"
463+
- id: req-tg-015
464+
keyword: MUST
465+
section: "8.5.1"
466+
conformance_class: consumer
467+
notes: "a conforming consumer providing the Codex-native agent conversion capability preserves a source-declared model or model_reasoning_effort string value in its native top-level placement, leaves either field absent when absent from the source, diagnoses a non-string source value by field name without including that value, and emits a diagnostic bounded on dropped-field count (at least one shown when any are dropped), per-field name length, and ASCII-sanitized field-name display (never the value) for any other dropped, non-capability-restriction frontmatter field"
463468
- id: req-pr-006
464469
keyword: MUST
465470
section: "8.1"

‎docs/src/content/docs/contributing/development-guide.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,13 @@ The workflow distinguishes three modes:
500500

501501
These checks cannot detect every semantic drift. Choosing the appropriate
502502
mode remains a human decision; the harness exposes the choice, not the answer.
503+
504+
The same limit applies to the `apm-spec-guardian` skill's review panel. Its
505+
synthesizer-return schema validates that a `defer_v0_2` entry's
506+
`reserved_slot_anchor` field is **present and of type string** -- a
507+
structural, type-only check. It does not verify that the cited anchor is one of
508+
the spec's actual reserved-for-v0.2 sections (for example Section 4.8
509+
workspaces, Section 7.9 version withdrawal, Section 10.12 publisher
510+
attestations, or Appendix B). Confirming that a cited anchor genuinely
511+
reserves the space it claims remains a human/panel judgment, not something
512+
schema validation can substitute for.

‎docs/src/content/docs/producer/author-primitives/instructions-and-agents.md‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ for...
168168
| `name` | recommended | Display name; defaults to filename stem |
169169
| `description` | yes | Used by Cascade and Copilot to decide when to surface the agent |
170170
| `model` | optional | Pinned model the harness should switch to when invoked |
171+
| `model_reasoning_effort` | optional | Native Codex reasoning effort, supplied as a string |
171172
| `tools` | optional | Whitelist of tools the persona may call |
172173
| `color` | optional | Display color for harnesses that render it (Copilot, Claude, OpenCode). OpenCode requires a `#rgb`/`#rrggbb` hex literal or one of its theme names; see "Common pitfalls" below |
173174
| `handoffs` | optional | List of agent names (or VS Code structured handoff objects) this agent can hand off to |
@@ -182,9 +183,21 @@ of the supported capability tags (`read`, `write`, `shell`, `web`,
182183
`*`). If any unsupported tag is present, the agent is not deployed at
183184
all -- APM fails closed. Kiro may warn and fall back if the specified
184185
`model` is unavailable; APM passes the value through without
185-
validation. Codex translates only `name`, `description`, and the
186-
Markdown body; APM does not yet generate the complete per-agent MCP
187-
transport definitions needed to preserve `model` or `tools`. When
186+
validation.
187+
188+
Codex receives `name`, `description`, and the Markdown body as
189+
`developer_instructions`. Supplied string values for `model` and
190+
`model_reasoning_effort` become top-level keys in the generated agent TOML,
191+
as documented in the [Codex custom agent format](https://developers.openai.com/codex/subagents/#custom-agents).
192+
Omitted fields remain absent; APM does not translate model names, choose
193+
defaults, or validate which models and effort values your Codex supports.
194+
Non-string values are dropped with a warning naming the field.
195+
196+
Other frontmatter fields are dropped with a warning that APM does not
197+
translate them for Codex. This includes other native Codex settings and
198+
any `codex:` block; there is no namespaced or arbitrary passthrough.
199+
Remove unnecessary fields, or do not rely on their settings in the
200+
generated agent. APM still cannot preserve `tools` restrictions. When
188201
`tools` is present, `apm install` warns that the generated agent may
189202
inherit every project or session MCP server. Remove `tools` if
190203
unrestricted access is intentional; otherwise, do not use the
@@ -222,7 +235,7 @@ offending package and field so you can fix the source.
222235
| grok-build | `.grok/agents/<name>.md` | verbatim |
223236
| cursor | `.cursor/agents/<name>.md` | verbatim |
224237
| opencode | `.opencode/agents/<name>.md` | verbatim |
225-
| codex | `.codex/agents/<name>.toml` | `name` and `description` -> TOML; body becomes `developer_instructions`; unsupported `tools` emits a warning |
238+
| codex | `.codex/agents/<name>.toml` | `name`, `description`, and supplied string `model` / `model_reasoning_effort` -> top-level TOML; body becomes `developer_instructions`; dropped metadata emits warnings |
226239
| kiro | `.kiro/agents/<relative-stem>.md` | `description`, `model`, `tools` kept; `name` and unknown fields stripped; identity from path; fail closed on unsupported tools (ref: [kiro.dev/docs/custom-agents](https://kiro.dev/docs/custom-agents/), accessed 2026-08-03) |
227240
| grok-build | `.grok/agents/<name>.md` | verbatim |
228241
| windsurf | not deployed | Windsurf has no agents primitive -- author personas as skills (Cascade auto-invokes by description) |

0 commit comments

Comments
 (0)