diff --git a/CHANGELOG.md b/CHANGELOG.md
index 050a26c..2916e48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,20 @@ cut and that this clone does not carry.
## [Unreleased]
+## [0.0.73] - 2026-09-19
+
+nddev-builder guidance is refreshed against each harness's current native
+extension model. Software artifacts are refreshed from verified vendor bytes:
+Claude Code 2.1.278, Codex 0.155.1, Grok Build 1.0.38, Cursor
+2026.09.18-9a7762b and Antigravity CLI 1.2.7. OpenCode remains 1.18.31 and
+Pi 0.85.1. Public reusable workflows pin ci-workflows 0.1.20
+(26749820fad5bde1f1726636af5b04d329ed2fe5). Previous artifact pins remain
+available for rollback.
+
+Providers gain the optional patch_instruction_region operation, which replaces
+the managed instruction region in place while preserving unmanaged native
+state. Setup content, postures and ownership are unchanged.
+
## [0.0.72] - 2026-09-16
nddev-builder guidance is refreshed against each harness's current native
diff --git a/Cargo.lock b/Cargo.lock
index 8cc2896..35d837c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,7 +25,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "codex-setup-system"
-version = "0.0.72"
+version = "0.0.73"
dependencies = [
"harness-runtime",
"provider-v3",
@@ -76,7 +76,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "harness-runtime"
-version = "0.0.72"
+version = "0.0.73"
dependencies = [
"provider-v3",
"serde",
@@ -147,7 +147,7 @@ dependencies = [
[[package]]
name = "provider-v3"
-version = "0.0.72"
+version = "0.0.73"
dependencies = [
"serde",
"serde_json",
@@ -209,7 +209,7 @@ dependencies = [
[[package]]
name = "setup-core"
-version = "0.0.72"
+version = "0.0.73"
dependencies = [
"miniz_oxide",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
index 5ddc5ef..9fa5891 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
-version = "0.0.72"
+version = "0.0.73"
edition = "2024"
rust-version = "1.89"
license = "AGPL-3.0-or-later"
@@ -23,9 +23,9 @@ sha2 = "0.11"
# `setup-core::archive`); an inflate loop is not, because its bugs are
# memory-safety bugs and it is not improved by being hand-written here.
miniz_oxide = "0.9"
-setup-core = { path = "crates/setup-core", version = "0.0.72" }
-provider-v3 = { path = "crates/provider-v3", version = "0.0.72" }
-harness-runtime = { path = "crates/harness-runtime", version = "0.0.72" }
+setup-core = { path = "crates/setup-core", version = "0.0.73" }
+provider-v3 = { path = "crates/provider-v3", version = "0.0.73" }
+harness-runtime = { path = "crates/harness-runtime", version = "0.0.73" }
[workspace.lints.rust]
unsafe_code = "forbid"
diff --git a/README.md b/README.md
index 1ea9b86..e49df13 100644
--- a/README.md
+++ b/README.md
@@ -179,7 +179,7 @@ release is a convenience, not the authorised copy.
```bash
docker run --rm -v "$HOME/.config:/config" \
- ghcr.io/nddev-opennetwork/codex-setup-system:0.0.72 \
+ ghcr.io/nddev-opennetwork/codex-setup-system:0.0.73 \
status --target /config/
--json
```
diff --git a/crates/codex-setup-system/src/main.rs b/crates/codex-setup-system/src/main.rs
index 9566023..cc4c408 100644
--- a/crates/codex-setup-system/src/main.rs
+++ b/crates/codex-setup-system/src/main.rs
@@ -305,6 +305,7 @@ pub const CODEX: Harness = Harness {
// one on a disk it was never shipped to.
embedded_setups: include!(concat!(env!("OUT_DIR"), "/embedded_setups.rs")),
software: Some(software::SOFTWARE),
+ instruction_region: Some("AGENTS.md"),
};
fn main() -> ExitCode {
diff --git a/crates/codex-setup-system/src/software.rs b/crates/codex-setup-system/src/software.rs
index 7618617..49a76d9 100644
--- a/crates/codex-setup-system/src/software.rs
+++ b/crates/codex-setup-system/src/software.rs
@@ -20,103 +20,103 @@ use harness_runtime::{Artifact, Delivery, Previous, Shape, Software};
pub(crate) const ARTIFACTS: &[Artifact] = &[
Artifact {
platform: "linux/arm64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-arm64.tgz",
- bytes: 122_610_794,
- sha256: "sha256:a2315b5f64bfeaff79b71e0d35505ba8c22cc1e96cab9dc950b614c804105b24",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-linux-arm64.tgz",
+ bytes: 135_126_766,
+ sha256: "sha256:7da3e7bea6db4751d1b837f046c9cec08918ac7f05427dd0b5bcf64ea6c85964",
shape: Shape::GzipTar,
member: "package/vendor/aarch64-unknown-linux-musl/bin/codex",
},
Artifact {
platform: "linux/x86_64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-x64.tgz",
- bytes: 129_654_638,
- sha256: "sha256:e27c83a49e6031685ee7f956c12aad5f16484d3a80181dd3fea930fb96b3832b",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-linux-x64.tgz",
+ bytes: 142_140_011,
+ sha256: "sha256:f110cccdd50b0be8130b84f45b3144ea775c233f1c8bd8226da6ee719d63d206",
shape: Shape::GzipTar,
member: "package/vendor/x86_64-unknown-linux-musl/bin/codex",
},
Artifact {
platform: "macos/arm64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-arm64.tgz",
- bytes: 116_501_639,
- sha256: "sha256:2a98662d79316a59993c7233e3e25a1aa1d42da4b45904585d33a5a7da1cade1",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-darwin-arm64.tgz",
+ bytes: 127_465_533,
+ sha256: "sha256:93cc218b25b71c8da3edb50a013fbd22acf8f39058fb64083fefff638a084976",
shape: Shape::GzipTar,
member: "package/vendor/aarch64-apple-darwin/bin/codex",
},
Artifact {
platform: "macos/x86_64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-x64.tgz",
- bytes: 124_308_807,
- sha256: "sha256:92c493533c53c433c4d94252251daba4f379ccba06a9964d260abb47a535dce1",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-darwin-x64.tgz",
+ bytes: 135_811_357,
+ sha256: "sha256:819db6dbb57a56cc21383a30331fc4115696e02f4c47ba7f686a285a2173fadd",
shape: Shape::GzipTar,
member: "package/vendor/x86_64-apple-darwin/bin/codex",
},
Artifact {
platform: "windows/arm64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-arm64.tgz",
- bytes: 132_888_437,
- sha256: "sha256:a072b19e67fd65f2827c925a9d3c89b1a55da70b8e24cc21b81c800a77cb3d53",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-win32-arm64.tgz",
+ bytes: 135_509_012,
+ sha256: "sha256:72525256ac769a23381236a374c71679927c988dd10b7a19d342c98193216b7f",
shape: Shape::GzipTar,
member: "package/vendor/aarch64-pc-windows-msvc/bin/codex.exe",
},
Artifact {
platform: "windows/x86_64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-x64.tgz",
- bytes: 142_162_836,
- sha256: "sha256:27eedae55e37ed1da4078b36a6ef9e4ffe8546a60fd6df5ee6408fdd16aff8ea",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-win32-x64.tgz",
+ bytes: 145_165_338,
+ sha256: "sha256:727fd5bfaeed16fe8fdeb57d1c0faa688f48edda4a0541323a103a3138925350",
shape: Shape::GzipTar,
member: "package/vendor/x86_64-pc-windows-msvc/bin/codex.exe",
},
];
-/// The artifacts 0.153.4 was published as, kept so
+/// The artifacts 0.154.0 was published as, kept so
/// `software_update` has a version to move from and `rollback` a tree to
/// return to. Measured from bytes when it was the current pin.
pub(crate) const PREVIOUS_ARTIFACTS: &[Artifact] = &[
Artifact {
platform: "linux/arm64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-linux-arm64.tgz",
- bytes: 121_707_000,
- sha256: "sha256:439c0dd0d6923f607b4e5cd1e3079c12f0b86f6e5007f07e377d6ad25e2d7bb9",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-arm64.tgz",
+ bytes: 122_610_794,
+ sha256: "sha256:a2315b5f64bfeaff79b71e0d35505ba8c22cc1e96cab9dc950b614c804105b24",
shape: Shape::GzipTar,
member: "package/vendor/aarch64-unknown-linux-musl/bin/codex",
},
Artifact {
platform: "linux/x86_64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-linux-x64.tgz",
- bytes: 129_272_137,
- sha256: "sha256:54818cb9fce3360cc6e44cfc5a96952cd5c1243efb43cbe488e11dda84663e08",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-x64.tgz",
+ bytes: 129_654_638,
+ sha256: "sha256:e27c83a49e6031685ee7f956c12aad5f16484d3a80181dd3fea930fb96b3832b",
shape: Shape::GzipTar,
member: "package/vendor/x86_64-unknown-linux-musl/bin/codex",
},
Artifact {
platform: "macos/arm64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-darwin-arm64.tgz",
- bytes: 115_672_312,
- sha256: "sha256:535d301b49131abfda3264f959fb0defa40bbc306976d98ddbc15c424636c55c",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-arm64.tgz",
+ bytes: 116_501_639,
+ sha256: "sha256:2a98662d79316a59993c7233e3e25a1aa1d42da4b45904585d33a5a7da1cade1",
shape: Shape::GzipTar,
member: "package/vendor/aarch64-apple-darwin/bin/codex",
},
Artifact {
platform: "macos/x86_64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-darwin-x64.tgz",
- bytes: 123_544_033,
- sha256: "sha256:5e468958503c60e940b1b1af3fe2064c16fd141f1607111caf99f2c0a0e80725",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-x64.tgz",
+ bytes: 124_308_807,
+ sha256: "sha256:92c493533c53c433c4d94252251daba4f379ccba06a9964d260abb47a535dce1",
shape: Shape::GzipTar,
member: "package/vendor/x86_64-apple-darwin/bin/codex",
},
Artifact {
platform: "windows/arm64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-win32-arm64.tgz",
- bytes: 132_173_674,
- sha256: "sha256:6d0bf07e04810f0ed4ad2984f6c9f0547bb9e0e1bb1f8fe9ecce0de3376bbfef",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-arm64.tgz",
+ bytes: 132_888_437,
+ sha256: "sha256:a072b19e67fd65f2827c925a9d3c89b1a55da70b8e24cc21b81c800a77cb3d53",
shape: Shape::GzipTar,
member: "package/vendor/aarch64-pc-windows-msvc/bin/codex.exe",
},
Artifact {
platform: "windows/x86_64",
- url: "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-win32-x64.tgz",
- bytes: 141_495_386,
- sha256: "sha256:05f473573f38b3f4dc9484e6807a511a1bb0e128dfa50816dafc5f79e553cdca",
+ url: "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-x64.tgz",
+ bytes: 142_162_836,
+ sha256: "sha256:27eedae55e37ed1da4078b36a6ef9e4ffe8546a60fd6df5ee6408fdd16aff8ea",
shape: Shape::GzipTar,
member: "package/vendor/x86_64-pc-windows-msvc/bin/codex.exe",
},
@@ -124,12 +124,12 @@ pub(crate) const PREVIOUS_ARTIFACTS: &[Artifact] = &[
/// Codex's program, and where its bytes come from.
pub(crate) const SOFTWARE: Software = Software {
- version: "0.154.0",
+ version: "0.155.1",
command: "codex",
delivery: Delivery::Artifacts(ARTIFACTS),
unsupported: &[],
previous: Some(Previous {
- version: "0.153.4",
+ version: "0.154.0",
artifacts: PREVIOUS_ARTIFACTS,
}),
};
diff --git a/crates/harness-runtime/src/facts.rs b/crates/harness-runtime/src/facts.rs
index 632cf60..9271d9e 100644
--- a/crates/harness-runtime/src/facts.rs
+++ b/crates/harness-runtime/src/facts.rs
@@ -234,7 +234,20 @@ pub struct Harness {
/// [`Delivery::Manager`], which is a different statement -- the product is
/// installable, but not by fetching bytes whose digest was fixed in advance
/// -- and the refusal says which.
+ /// How the product's own software is installed, when this build can do it.
+ ///
+ /// `None` means the software lifecycle is not offered at all. So does a
+ /// [`Delivery::Manager`], which is a different statement -- the product is
+ /// installable, but not by fetching bytes whose digest was fixed in advance
+ /// -- and the refusal says which.
pub software: Option,
+ /// Target-relative path of the user-global instruction attachment.
+ ///
+ /// `None` when the product has no catalogued global instruction surface
+ /// (Antigravity). Present, this build implements `patch_instruction_region`
+ /// against that path. The bytes travel on `--instruction-section`; they are
+ /// not a setup payload.
+ pub instruction_region: Option<&'static str>,
}
/// A second set of ownings, for a target that is not the product's own home.
@@ -751,21 +764,44 @@ impl Harness {
/// manager this provider does not run.
#[must_use]
pub fn operations(&self) -> &'static [Operation] {
- match (self.can_launch(), self.software) {
+ match (
+ self.can_launch(),
+ self.software,
+ self.instruction_region.is_some(),
+ ) {
(
true,
Some(Software {
delivery: Delivery::Artifacts(_),
..
}),
+ true,
) => Operation::ALL,
+ (
+ true,
+ Some(Software {
+ delivery: Delivery::Artifacts(_),
+ ..
+ }),
+ false,
+ ) => Operation::ALL_WITHOUT_INSTRUCTION,
+ (
+ false,
+ Some(Software {
+ delivery: Delivery::Artifacts(_),
+ ..
+ }),
+ true,
+ ) => Operation::CORE_AND_SOFTWARE_AND_INSTRUCTION,
(
false,
Some(Software {
delivery: Delivery::Artifacts(_),
..
}),
+ false,
) => Operation::CORE_AND_SOFTWARE,
+ (_, _, true) => Operation::CORE_AND_INSTRUCTION,
_ => Operation::CORE,
}
}
@@ -968,6 +1004,7 @@ mod tests {
max_files: 4096,
max_bytes: 1024,
kit_identity: r#"{"aggregate_digest":"sha256:aa","protocol_version":3}"#,
+ instruction_region: None,
};
#[test]
@@ -1009,6 +1046,7 @@ mod tests {
Operation::SoftwareInstall,
Operation::SoftwareUpdate,
Operation::SoftwareRemove,
+ Operation::PatchInstructionRegion,
] {
assert!(
!info.declares(optional),
@@ -1017,6 +1055,18 @@ mod tests {
}
}
+ #[test]
+ fn an_instruction_surface_without_software_still_declares_the_patch() {
+ let named = Harness {
+ instruction_region: Some("AGENTS.md"),
+ ..SAMPLE
+ };
+ let info = named.provider_info().unwrap();
+ assert!(info.declares(Operation::PatchInstructionRegion));
+ assert!(!info.declares(Operation::SoftwareInstall));
+ assert!(!info.declares(Operation::Launch));
+ }
+
#[test]
fn the_build_digest_is_reproducible_and_binds_the_kit() {
let once = SAMPLE.build_digest().unwrap();
diff --git a/crates/harness-runtime/src/human.rs b/crates/harness-runtime/src/human.rs
index a5b9e56..61ab40c 100644
--- a/crates/harness-runtime/src/human.rs
+++ b/crates/harness-runtime/src/human.rs
@@ -1154,6 +1154,8 @@ fn mutate(
// The human surface removes whole and carries no bundle, so no path
// has a second sentence.
end_state: Vec::new(),
+ instruction_path: None,
+ instruction_text: None,
effects: effect_lines(harness, &effect, applied.setup_id.as_deref()),
})?;
let plan_digest = artifact.digest()?;
@@ -1251,6 +1253,9 @@ fn effect_lines(harness: &Harness, effect: &Effect<'_>, setup_id: Option<&str>)
));
lines
}
+ Effect::PatchInstruction { path, .. } => {
+ vec![capture, format!("patch instruction region at {path}")]
+ }
}
}
diff --git a/crates/harness-runtime/src/instruction_region.rs b/crates/harness-runtime/src/instruction_region.rs
new file mode 100644
index 0000000..dde1517
--- /dev/null
+++ b/crates/harness-runtime/src/instruction_region.rs
@@ -0,0 +1,224 @@
+//! Marked user-global instruction attachment.
+//!
+//! The consumer owns the section text. This kernel splices it into one
+//! target-relative file, preserves every byte outside the markers, and is
+//! idempotent on identical bytes. Whole-setup apply/remove must not treat the
+//! region as payload they are free to empty: [`preserve_in_replacement`] and
+//! [`keep_region_on_withdraw`] are the two hooks.
+
+use std::path::Path;
+
+/// Visible begin marker. HTML comments are refused — Claude strips them.
+pub const BEGIN: &str = ":::begin-ai-stp";
+/// Visible end marker. Inclusive of the following newline when present.
+pub const END: &str = ":::end-ai-stp";
+
+/// The marked region, including both markers, or `None` when either is missing
+/// or they are out of order.
+#[must_use]
+pub fn extract(existing: &str) -> Option<&str> {
+ let begin = existing.find(BEGIN)?;
+ let end = existing.find(END)?;
+ if end < begin {
+ return None;
+ }
+ let mut end_at = end + END.len();
+ if existing[end_at..].starts_with('\n') {
+ end_at += 1;
+ }
+ Some(&existing[begin..end_at])
+}
+
+/// Replace the marked region or append it. Preserve every other byte.
+#[must_use]
+pub fn splice(existing: &str, section: &str) -> String {
+ match extract(existing) {
+ None if existing.is_empty() => section.to_owned(),
+ None => {
+ let mut held = existing.to_owned();
+ if !held.ends_with('\n') {
+ held.push('\n');
+ }
+ held.push_str(section);
+ held
+ }
+ Some(held) => match existing.find(BEGIN) {
+ None => existing.to_owned(),
+ Some(begin) => format!(
+ "{}{}{}",
+ &existing[..begin],
+ section,
+ &existing[begin + held.len()..]
+ ),
+ },
+ }
+}
+
+/// Pure region patch. `wrote` is false when the marked bytes already match.
+///
+/// An empty file takes `section` whole so bytes the consumer placed *before*
+/// the markers (Cursor `alwaysApply` YAML) survive the first write. Later
+/// calls splice only the marked region.
+#[must_use]
+pub fn patch(existing: &str, section: &str) -> (String, bool) {
+ let desired = extract(section).unwrap_or(section);
+ if let Some(current) = extract(existing)
+ && current == desired
+ {
+ return (existing.to_owned(), false);
+ }
+ if existing.is_empty() {
+ return (section.to_owned(), true);
+ }
+ (splice(existing, desired), true)
+}
+
+/// Keep an existing attachment when a setup writes the same path.
+#[must_use]
+pub fn preserve_in_replacement(existing: &str, incoming: &str) -> String {
+ let Some(region) = extract(existing) else {
+ return incoming.to_owned();
+ };
+ if extract(incoming).is_some() {
+ let (updated, _) = patch(incoming, region);
+ return updated;
+ }
+ let spliced = splice(incoming, region);
+ let owned = owned_prefix(existing);
+ if owned.is_empty() || spliced.starts_with("---\n") {
+ return spliced;
+ }
+ format!("{owned}{spliced}")
+}
+
+/// After withdrawing a recorded file, keep the attachment remainder when one
+/// existed. Returns `None` when the file had no attachment (caller deletes).
+///
+/// Cursor `alwaysApply` YAML sits before the markers. That prefix is the
+/// initialize attachment, not setup payload, so it survives withdraw.
+#[must_use]
+pub fn keep_region_on_withdraw(existing: &str) -> Option {
+ let region = extract(existing)?;
+ let owned = owned_prefix(existing);
+ if owned.is_empty() {
+ Some(region.to_owned())
+ } else {
+ Some(format!("{owned}{region}"))
+ }
+}
+
+/// YAML frontmatter initialize placed before the markers, or empty.
+///
+/// Not "everything before BEGIN": a setup body can sit between the fence and
+/// the region, and withdraw must still drop that body.
+fn owned_prefix(existing: &str) -> &str {
+ const OPEN: &str = "---\n";
+ const CLOSE: &str = "\n---\n";
+ if !existing.starts_with(OPEN) {
+ return "";
+ }
+ let Some(rel) = existing[OPEN.len()..].find(CLOSE) else {
+ return "";
+ };
+ let mut end = OPEN.len() + rel + CLOSE.len();
+ if existing[end..].starts_with('\n') {
+ end += 1;
+ }
+ if extract(&existing[end..]).is_none() && !existing[end..].contains(BEGIN) {
+ return "";
+ }
+ &existing[..end]
+}
+
+/// True when `relative` is the attachment path this harness named.
+#[must_use]
+pub fn is_attachment(relative: &str, named: Option<&str>) -> bool {
+ named.is_some_and(|path| path == relative)
+}
+
+/// UTF-8 text of a file, or empty when it is missing.
+pub fn read_utf8(path: &Path) -> String {
+ std::fs::read_to_string(path).unwrap_or_default()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const SECTION: &str = ":::begin-ai-stp\nhello\n:::end-ai-stp\n";
+
+ #[test]
+ fn empty_file_receives_the_section() {
+ let (updated, wrote) = patch("", SECTION);
+ assert!(wrote);
+ assert_eq!(updated, SECTION);
+ }
+
+ #[test]
+ fn user_bytes_outside_the_markers_are_kept() {
+ let (updated, wrote) = patch("keep-me\n", SECTION);
+ assert!(wrote);
+ assert_eq!(updated, format!("keep-me\n{SECTION}"));
+ }
+
+ #[test]
+ fn identical_bytes_are_a_no_write() {
+ let first = splice("keep-me\n", SECTION);
+ let (second, wrote) = patch(&first, SECTION);
+ assert!(!wrote);
+ assert_eq!(second, first);
+ }
+
+ #[test]
+ fn first_write_keeps_bytes_before_the_markers() {
+ let section = "---\nalwaysApply: true\n---\n\n:::begin-ai-stp\nhello\n:::end-ai-stp\n";
+ let (updated, wrote) = patch("", section);
+ assert!(wrote);
+ assert_eq!(updated, section);
+ assert!(updated.starts_with("---\n"));
+
+ let next = "---\nalwaysApply: true\n---\n\n:::begin-ai-stp\nchanged\n:::end-ai-stp\n";
+ let (spliced, wrote_again) = patch(&updated, next);
+ assert!(wrote_again);
+ assert!(spliced.starts_with("---\n"));
+ assert_eq!(spliced.matches("alwaysApply: true").count(), 1);
+ assert_eq!(extract(&spliced), extract(next));
+ assert_eq!(
+ extract(&spliced),
+ Some(":::begin-ai-stp\nchanged\n:::end-ai-stp\n")
+ );
+ }
+
+ #[test]
+ fn replacement_of_a_setup_file_keeps_the_region() {
+ let existing = splice("old-setup\n", SECTION);
+ let outgoing = preserve_in_replacement(&existing, "new-setup\n");
+ assert!(outgoing.contains("new-setup"));
+ assert_eq!(extract(&outgoing), Some(SECTION));
+ }
+
+ #[test]
+ fn withdraw_leaves_the_region_and_drops_the_rest() {
+ let existing = splice("setup-bytes\n", SECTION);
+ assert_eq!(keep_region_on_withdraw(&existing).as_deref(), Some(SECTION));
+ assert_eq!(keep_region_on_withdraw("just setup"), None);
+ }
+
+ #[test]
+ fn yaml_frontmatter_survives_setup_replace_and_withdraw() {
+ let section = "---\nalwaysApply: true\n---\n\n:::begin-ai-stp\nhello\n:::end-ai-stp\n";
+ let (first, _) = patch("", section);
+ let replaced = preserve_in_replacement(&first, "new-setup\n");
+ assert!(replaced.starts_with("---\n"));
+ assert_eq!(replaced.matches("alwaysApply: true").count(), 1);
+ assert!(replaced.contains("new-setup"));
+ assert_eq!(extract(&replaced), extract(section));
+
+ let remainder = keep_region_on_withdraw(&replaced).unwrap_or_default();
+ assert!(!remainder.is_empty());
+ assert!(remainder.starts_with("---\n"));
+ assert!(remainder.contains("alwaysApply: true"));
+ assert!(!remainder.contains("new-setup"));
+ assert_eq!(extract(&remainder), extract(section));
+ }
+}
diff --git a/crates/harness-runtime/src/lib.rs b/crates/harness-runtime/src/lib.rs
index 9e27700..a7b945f 100644
--- a/crates/harness-runtime/src/lib.rs
+++ b/crates/harness-runtime/src/lib.rs
@@ -37,6 +37,7 @@ pub mod catalog;
pub mod expiry;
pub mod facts;
pub mod human;
+pub(crate) mod instruction_region;
pub mod probe;
pub(crate) mod software;
pub mod surfaces;
diff --git a/crates/harness-runtime/src/wire.rs b/crates/harness-runtime/src/wire.rs
index 92399d4..7942c82 100644
--- a/crates/harness-runtime/src/wire.rs
+++ b/crates/harness-runtime/src/wire.rs
@@ -654,6 +654,8 @@ fn honourable(harness: &Harness, request: &PlanRequest) -> Result<()> {
}
}
+ honour_instruction_section(harness, request)?;
+
// The same rule for a bundle, and this one was worse than silently
// dropped: only `install` and `replace` read one, but the plan **bound**
// the five names into its artifact for every operation, so a remove plan
@@ -722,6 +724,80 @@ fn honourable(harness: &Harness, request: &PlanRequest) -> Result<()> {
Ok(())
}
+fn honour_instruction_section(harness: &Harness, request: &PlanRequest) -> Result<()> {
+ if request.instruction_section.is_some()
+ && request.operation != Operation::PatchInstructionRegion
+ {
+ return Err(Error::refuse(
+ WireReason::UnsupportedOperation,
+ format!(
+ "{} takes no instruction section; patch_instruction_region is the \
+ operation that reads --instruction-section",
+ request.operation
+ ),
+ ));
+ }
+ if request.operation != Operation::PatchInstructionRegion {
+ return Ok(());
+ }
+ if harness.instruction_region.is_none() {
+ return Err(Error::refuse(
+ WireReason::UnsupportedOperation,
+ format!(
+ "{} does not declare a user-global instruction surface",
+ harness.provider_id
+ ),
+ ));
+ }
+ if request
+ .instruction_section
+ .as_deref()
+ .is_none_or(str::is_empty)
+ {
+ return Err(Error::refuse(
+ WireReason::UnsupportedOperation,
+ "patch_instruction_region needs --instruction-section with marked bytes",
+ ));
+ }
+ Ok(())
+}
+
+fn plan_instruction_patch(
+ harness: &Harness,
+ target: &Target,
+ request: &PlanRequest,
+) -> Result<(Vec, String, String)> {
+ let Some(relative) = harness.instruction_region else {
+ return Err(Error::refuse(
+ WireReason::UnsupportedOperation,
+ format!(
+ "{} does not declare a user-global instruction surface",
+ harness.provider_id
+ ),
+ ));
+ };
+ let Some(section) = request.instruction_section.as_deref() else {
+ return Err(Error::refuse(
+ WireReason::UnsupportedOperation,
+ "patch_instruction_region needs --instruction-section with marked bytes",
+ ));
+ };
+ if crate::instruction_region::extract(section).is_none() {
+ return Err(Error::refuse(
+ WireReason::UnsupportedOperation,
+ "instruction_section must contain :::begin-ai-stp and :::end-ai-stp",
+ ));
+ }
+ let existing = crate::instruction_region::read_utf8(&target.root().join(relative));
+ let (updated, wrote) = crate::instruction_region::patch(&existing, section);
+ let effects = if wrote {
+ vec![format!("patch instruction region at {relative}")]
+ } else {
+ vec![format!("instruction region at {relative} already matches")]
+ };
+ Ok((effects, relative.to_owned(), updated))
+}
+
/// Produce a plan without touching the target.
/// What a mutation **takes away** before it writes, in the words the result
/// line already uses.
@@ -854,6 +930,15 @@ fn plan(harness: &Harness, target: &Path, request: &PlanRequest) -> Result {
+ let mut capture = owned.clone();
+ if let Some(path) = harness.instruction_region
+ && !capture.iter().any(|held| held == path)
+ {
+ capture.push(path.to_owned());
+ }
+ capture
+ }
_ => owned.clone(),
};
refuse_uncapturable(&resolved, &capture)?;
@@ -863,6 +948,8 @@ fn plan(harness: &Harness, target: &Path, request: &PlanRequest) -> Result = None;
let mut software_version_held: Option = None;
let mut end_state = Vec::new();
+ let mut instruction_path = None;
+ let mut instruction_text = None;
let (effects, backup_ref, restore_target_digest) = match request.operation {
Operation::SoftwareInstall | Operation::SoftwareUpdate | Operation::SoftwareRemove => {
let (planned, effects, version) = software::plan(
@@ -929,6 +1016,12 @@ fn plan(harness: &Harness, target: &Path, request: &PlanRequest) -> Result (bundle_effects(harness, request)?, None, None),
+ Operation::PatchInstructionRegion => {
+ let (lines, path, text) = plan_instruction_patch(harness, &resolved, request)?;
+ instruction_path = Some(path);
+ instruction_text = Some(text);
+ (lines, None, None)
+ }
other @ Operation::Launch => {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
@@ -968,6 +1061,8 @@ fn plan(harness: &Harness, target: &Path, request: &PlanRequest) -> Result {
/// Each declared file's bytes and mode, by target-relative path.
files: &'a BTreeMap, u32)>,
},
+ /// Splice a marked instruction region into one owned-or-neighbour file.
+ PatchInstruction {
+ /// Target-relative path.
+ path: String,
+ /// Full file text after splicing.
+ text: String,
+ },
}
/// One authorized mutation, whatever surface asked for it.
@@ -1734,6 +1836,11 @@ fn apply(
files: &ready.files,
}
}
+ Operation::PatchInstructionRegion => {
+ let path = string_field(&artifact, "instruction_path")?;
+ let text = string_field(&artifact, "instruction_text")?;
+ Effect::PatchInstruction { path, text }
+ }
other => {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
@@ -1975,6 +2082,22 @@ pub(crate) fn perform(
Effect::MaterializeBundle { files } => {
write_bundle_files(harness, &resolved, files, mutation.target_scope)
}
+ Effect::PatchInstruction { path, text } => {
+ let destination = resolved.root().join(path);
+ if let Some(parent) = destination.parent() {
+ fs::create_dir_all(parent).map_err(|error| {
+ Error::from(
+ setup_core::Error::new(
+ setup_core::ReasonCode::StateUnavailable,
+ format!("cannot create {}", parent.display()),
+ )
+ .with_source(error),
+ )
+ })?;
+ }
+ lock::atomic_write(&destination, text.as_bytes()).map_err(Error::from)?;
+ Ok(previous_written.clone())
+ }
// Keeping a predecessor's stamp aside writes nothing to the target, so
// the inventory is what it was. Same reason as `Backup` above.
Effect::Adopt { stamp } => {
@@ -2754,6 +2877,21 @@ fn write_host_file(
} else {
bytes.to_vec()
};
+ let outgoing = if crate::instruction_region::is_attachment(relative, harness.instruction_region)
+ {
+ fs::read_to_string(&destination)
+ .ok()
+ .map(|existing| {
+ crate::instruction_region::preserve_in_replacement(
+ &existing,
+ &String::from_utf8_lossy(&outgoing),
+ )
+ .into_bytes()
+ })
+ .unwrap_or(outgoing)
+ } else {
+ outgoing
+ };
if merge_json && let Some(keys) = json_top_keys(bytes) {
remember_written_fields(harness, target, relative, keys)?;
}
@@ -2794,6 +2932,13 @@ fn withdraw_written(
if preserve_json_keys {
forget_written_fields(harness, target, relative);
}
+ if crate::instruction_region::is_attachment(relative, harness.instruction_region)
+ && let Ok(existing) = fs::read_to_string(&destination)
+ && let Some(region) = crate::instruction_region::keep_region_on_withdraw(&existing)
+ {
+ lock::atomic_write(&destination, region.as_bytes()).map_err(Error::from)?;
+ return Ok(());
+ }
remove_keeping(&destination, target.root(), harness.never_touch)
}
@@ -3317,6 +3462,7 @@ pub(crate) mod tests_support {
max_files: 4096,
max_bytes: 64 * 1024 * 1024,
kit_identity: r#"{"aggregate_digest":"sha256:aa","protocol_version":3}"#,
+ instruction_region: Some("AGENTS.md"),
};
}
@@ -8154,6 +8300,7 @@ mod tests {
assert!(info.declares(Operation::SoftwareInstall));
assert!(info.declares(Operation::SoftwareUpdate));
assert!(info.declares(Operation::SoftwareRemove));
+ assert!(info.declares(Operation::PatchInstructionRegion));
}
#[test]
@@ -8166,6 +8313,10 @@ mod tests {
assert!(!info.declares(Operation::SoftwareInstall));
assert!(!info.declares(Operation::SoftwareUpdate));
assert!(!info.declares(Operation::SoftwareRemove));
+ assert!(
+ info.declares(Operation::PatchInstructionRegion),
+ "the instruction attachment is not a software lifecycle"
+ );
let error = software::plan(
&bare,
@@ -8176,6 +8327,103 @@ mod tests {
.unwrap_err();
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
}
+
+ const INSTRUCTION_SECTION: &str = ":::begin-ai-stp\nhello from ai-stp\n:::end-ai-stp\n";
+
+ #[test]
+ fn patch_instruction_region_splices_markers_and_survives_a_recorded_withdraw() {
+ let target = seeded("patch-instruction-region");
+ let applied = plan_then_apply(
+ &target,
+ "patch_instruction_region",
+ &["--instruction-section", INSTRUCTION_SECTION],
+ );
+ assert_eq!(applied["state"], "verified", "{applied}");
+ let after_patch = fs::read_to_string(target.join("AGENTS.md")).unwrap();
+ assert!(
+ after_patch.starts_with("# first\n"),
+ "user bytes outside the markers were dropped: {after_patch:?}"
+ );
+ assert_eq!(
+ crate::instruction_region::extract(&after_patch),
+ Some(INSTRUCTION_SECTION)
+ );
+ assert!(
+ !recorded_written(&target).contains(&"AGENTS.md".to_owned()),
+ "the attachment was recorded as a setup receipt: {:?}",
+ recorded_written(&target)
+ );
+
+ install_global(&target, "with-agents", &[("AGENTS.md", "# setup\n", 0o644)]);
+ let after_install = fs::read_to_string(target.join("AGENTS.md")).unwrap();
+ assert!(
+ after_install.contains("# setup"),
+ "the setup did not land: {after_install:?}"
+ );
+ assert_eq!(
+ crate::instruction_region::extract(&after_install),
+ Some(INSTRUCTION_SECTION),
+ "install emptied the attachment: {after_install:?}"
+ );
+ assert!(
+ recorded_written(&target).contains(&"AGENTS.md".to_owned()),
+ "install did not record the setup file: {:?}",
+ recorded_written(&target)
+ );
+
+ let removed = plan_then_apply(&target, "remove", &[]);
+ assert_eq!(removed["state"], "verified", "{removed}");
+ let after_remove = fs::read_to_string(target.join("AGENTS.md")).unwrap();
+ assert_eq!(
+ crate::instruction_region::extract(&after_remove),
+ Some(INSTRUCTION_SECTION),
+ "withdraw deleted the attachment: {after_remove:?}"
+ );
+ assert!(
+ !after_remove.contains("# setup"),
+ "withdraw left setup bytes beside the region: {after_remove:?}"
+ );
+ }
+
+ #[test]
+ fn a_harness_without_an_instruction_surface_refuses_the_patch() {
+ let mut mute = TEST;
+ mute.instruction_region = None;
+ let target = seeded("no-instruction-surface");
+ let error = refuse_for(
+ &mute,
+ args(
+ "plan-operation",
+ &target,
+ &[
+ "--operation",
+ "patch_instruction_region",
+ "--provider-release-digest",
+ RELEASE,
+ "--operation-id",
+ "operation_01TEST",
+ "--expires-at",
+ far_future(),
+ "--instruction-section",
+ INSTRUCTION_SECTION,
+ ],
+ ),
+ );
+ assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
+ assert!(
+ error
+ .detail()
+ .contains("does not declare a user-global instruction surface"),
+ "{}",
+ error.detail()
+ );
+ assert!(
+ !mute
+ .provider_info()
+ .unwrap()
+ .declares(Operation::PatchInstructionRegion)
+ );
+ }
/// `status` says which slots retention may not take, and whose they are.
///
/// The pool has known this since held slots shipped; `status` did not
diff --git a/crates/provider-v3/src/argv.rs b/crates/provider-v3/src/argv.rs
index aa95412..18d0d68 100644
--- a/crates/provider-v3/src/argv.rs
+++ b/crates/provider-v3/src/argv.rs
@@ -118,6 +118,10 @@ const fn plan_usage(command: Command) -> Usage {
"--target-scope",
"which scope this target is; accepted and not yet acted on",
),
+ (
+ "--instruction-section",
+ "marked user-global instruction bytes for patch_instruction_region",
+ ),
],
note: "Produce a plan. Always pure: reads the target and the local disk, opens no socket.",
}
@@ -360,7 +364,13 @@ pub struct PlanRequest {
/// declarable since `0.0.7` and operable never, because no request field
/// carried a scope; this is the field, arriving one release ahead of the
/// behaviour on purpose.
+ /// Which scope the consumer resolved this target to be.
pub target_scope: Option,
+ /// Marked instruction bytes for `patch_instruction_region`.
+ ///
+ /// Absent on every other operation. The consumer sends this only after a
+ /// provider declares both the operation and `instruction_section`.
+ pub instruction_section: Option,
}
/// Read `--target-scope`, refusing a value this build does not know.
@@ -521,6 +531,7 @@ where
prefix: flags.take_prefix()?,
software_version: flags.take_optional("--software-version"),
target_scope: take_target_scope(&mut flags)?,
+ instruction_section: flags.take_optional("--instruction-section"),
},
}
}
@@ -565,6 +576,15 @@ struct Flags {
/// two different things and only one of them would happen.
const REPEATABLE: &[&str] = &["--software-artifact"];
+/// True when `token` is another flag, not a value that happens to start with
+/// dashes. Cursor's `alwaysApply` payload begins with YAML `---`; treating
+/// every `--` prefix as a missing value refused that first write.
+fn looks_like_flag(token: &str) -> bool {
+ token
+ .strip_prefix("--")
+ .is_some_and(|rest| rest.starts_with(|c: char| c.is_ascii_alphabetic()))
+}
+
impl Flags {
/// Split a bare `--` off the end, keeping what follows verbatim.
///
@@ -601,7 +621,7 @@ impl Flags {
let Some(value) = tokens.get(index + 1) else {
return Err(local(format!("{token} has no value")));
};
- if value.starts_with("--") {
+ if looks_like_flag(value) {
return Err(local(format!("{token} has no value")));
}
let seen = values.entry(token.clone()).or_default();
@@ -1021,6 +1041,57 @@ mod tests {
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
}
+ #[test]
+ fn patch_instruction_region_parses_the_marked_section() {
+ let tokens = with_target(
+ "plan-operation",
+ &[
+ "--operation",
+ "patch_instruction_region",
+ "--provider-release-digest",
+ DIGEST,
+ "--operation-id",
+ "operation_01TEST",
+ "--expires-at",
+ "2026-08-23T15:00:00Z",
+ "--instruction-section",
+ ":::begin-ai-stp\nhello\n:::end-ai-stp\n",
+ ],
+ );
+ let Invocation::PlanOperation { request, .. } = parse(tokens).unwrap() else {
+ panic!("expected a plan invocation");
+ };
+ assert_eq!(request.operation, Operation::PatchInstructionRegion);
+ assert_eq!(
+ request.instruction_section.as_deref(),
+ Some(":::begin-ai-stp\nhello\n:::end-ai-stp\n")
+ );
+ }
+
+ #[test]
+ fn patch_instruction_region_parses_yaml_frontmatter_before_markers() {
+ let section = "---\nalwaysApply: true\n---\n\n:::begin-ai-stp\nhello\n:::end-ai-stp\n";
+ let tokens = with_target(
+ "plan-operation",
+ &[
+ "--operation",
+ "patch_instruction_region",
+ "--provider-release-digest",
+ DIGEST,
+ "--instruction-section",
+ section,
+ "--operation-id",
+ "operation_01TEST",
+ "--expires-at",
+ "2026-08-23T15:00:00Z",
+ ],
+ );
+ let Invocation::PlanOperation { request, .. } = parse(tokens).unwrap() else {
+ panic!("expected a plan invocation");
+ };
+ assert_eq!(request.instruction_section.as_deref(), Some(section));
+ }
+
/// A consumer that sends a scope must not be refused by a build that cannot
/// yet use it.
///
diff --git a/crates/provider-v3/src/info.rs b/crates/provider-v3/src/info.rs
index b94fda9..1efdc2e 100644
--- a/crates/provider-v3/src/info.rs
+++ b/crates/provider-v3/src/info.rs
@@ -409,6 +409,7 @@ impl ProviderInfo {
TargetScope::REQUEST_FIELD.to_owned(),
EndState::REQUEST_FIELD.to_owned(),
"capture_mode".to_owned(),
+ "instruction_section".to_owned(),
],
// Declared 2026-09-02 in the same order as the two above: kit 0.2.9
// names the member (`provider-info.schema.json`), ai-stp-cli
diff --git a/crates/provider-v3/src/plan.rs b/crates/provider-v3/src/plan.rs
index 597abec..4385242 100644
--- a/crates/provider-v3/src/plan.rs
+++ b/crates/provider-v3/src/plan.rs
@@ -256,6 +256,12 @@ pub struct PlanArtifact {
/// verifying. Every other operation has one sentence per path already.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub end_state: Vec,
+ /// Target-relative path of a `patch_instruction_region` write.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub instruction_path: Option,
+ /// Full file text after splicing. Apply writes these bytes.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub instruction_text: Option,
/// What applying it will do, in order. Never empty.
pub effects: Vec,
}
@@ -305,6 +311,10 @@ pub struct PlanInputs<'a> {
/// Per-path end states, for a `remove` that carries a bundle. Empty
/// otherwise, and refused on any other operation.
pub end_state: Vec,
+ /// Target-relative attachment path, when this is a region patch.
+ pub instruction_path: Option,
+ /// Full file text after splicing, when this is a region patch.
+ pub instruction_text: Option,
/// What applying it will do. Never empty.
pub effects: Vec,
}
@@ -418,6 +428,8 @@ impl PlanArtifact {
software_version: inputs.software_version.map(str::to_owned),
software_artifacts: inputs.software_artifacts,
end_state: inputs.end_state,
+ instruction_path: inputs.instruction_path,
+ instruction_text: inputs.instruction_text,
effects: inputs.effects,
})
}
@@ -567,6 +579,8 @@ mod tests {
native_capture: None,
permission_profile: Some("default".to_owned()),
expires_at: "2026-08-23T15:00:00Z",
+ instruction_path: None,
+ instruction_text: None,
effects: vec!["write settings.json".to_owned()],
}
}
diff --git a/crates/provider-v3/src/vocabulary.rs b/crates/provider-v3/src/vocabulary.rs
index f18cafb..3f9c2c1 100644
--- a/crates/provider-v3/src/vocabulary.rs
+++ b/crates/provider-v3/src/vocabulary.rs
@@ -135,6 +135,12 @@ pub enum Operation {
SoftwareRemove,
/// Start the product. Optional.
Launch,
+ /// Patch a marked user-global instruction region. Optional.
+ ///
+ /// The consumer sends the marked bytes on `--instruction-section`. This is
+ /// not a setup install: whole-setup apply must not treat the region as
+ /// payload it is free to empty.
+ PatchInstructionRegion,
}
impl Operation {
@@ -150,6 +156,7 @@ impl Operation {
Self::SoftwareUpdate,
Self::SoftwareRemove,
Self::Launch,
+ Self::PatchInstructionRegion,
];
/// The operations every provider must support.
@@ -174,6 +181,44 @@ impl Operation {
Self::SoftwareRemove,
];
+ /// [`CORE`] plus the instruction-region patch, without software or launch.
+ pub const CORE_AND_INSTRUCTION: &'static [Self] = &[
+ Self::Backup,
+ Self::Install,
+ Self::Remove,
+ Self::Replace,
+ Self::Restore,
+ Self::PatchInstructionRegion,
+ ];
+
+ /// [`CORE_AND_SOFTWARE`] plus the instruction-region patch, without launch.
+ pub const CORE_AND_SOFTWARE_AND_INSTRUCTION: &'static [Self] = &[
+ Self::Backup,
+ Self::Install,
+ Self::Remove,
+ Self::Replace,
+ Self::Restore,
+ Self::Reset,
+ Self::SoftwareInstall,
+ Self::SoftwareUpdate,
+ Self::SoftwareRemove,
+ Self::PatchInstructionRegion,
+ ];
+
+ /// [`ALL`] without the instruction-region patch.
+ pub const ALL_WITHOUT_INSTRUCTION: &'static [Self] = &[
+ Self::Install,
+ Self::Replace,
+ Self::Backup,
+ Self::Restore,
+ Self::Remove,
+ Self::Reset,
+ Self::SoftwareInstall,
+ Self::SoftwareUpdate,
+ Self::SoftwareRemove,
+ Self::Launch,
+ ];
+
/// The optional operations that install the product itself.
///
/// Declared together or not at all. A provider offering to install but not
@@ -198,6 +243,7 @@ impl Operation {
Self::SoftwareUpdate => "software_update",
Self::SoftwareRemove => "software_remove",
Self::Launch => "launch",
+ Self::PatchInstructionRegion => "patch_instruction_region",
}
}
diff --git a/install.ps1 b/install.ps1
index f0bbcd5..1bb44d3 100644
--- a/install.ps1
+++ b/install.ps1
@@ -7,7 +7,7 @@
# powershell -ExecutionPolicy Bypass -File install.ps1 -Version 0.1.0
[CmdletBinding()]
param(
- [string]$Version = "0.0.72",
+ [string]$Version = "0.0.73",
[string]$InstallDir = "$env:LOCALAPPDATA\Programs\codex-setup-system"
)
$ErrorActionPreference = "Stop"
diff --git a/install.sh b/install.sh
index 9da9b72..3ccd8f5 100644
--- a/install.sh
+++ b/install.sh
@@ -14,7 +14,7 @@ set -eu
REPO="NDDev-OpenNetwork/codex-setup-system"
BINARY="codex-setup-system"
-VERSION="${1:-0.0.72}"
+VERSION="${1:-0.0.73}"
PREFIX="${CODEX_INSTALL_DIR:-$HOME/.local/bin}"
case "$(uname -s)" in
diff --git a/provider-kit/v3/KIT-IDENTITY.json b/provider-kit/v3/KIT-IDENTITY.json
index ca45c47..1ed9e58 100644
--- a/provider-kit/v3/KIT-IDENTITY.json
+++ b/provider-kit/v3/KIT-IDENTITY.json
@@ -1,12 +1,12 @@
{
- "aggregate_digest": "sha256:6ab195790be1c7dfacf6e6ab5a892244097cc2002f97090ff9a4b3c3174ed357",
+ "aggregate_digest": "sha256:e2a35eaf2e0f834913962d9a6337948eeb78290b4ad8c3af7674620215610b0e",
"files": [
"conformance-cases.json",
"manifest.json",
"provider-info.schema.json",
"status-response.schema.json"
],
- "kit_version": "0.2.11",
+ "kit_version": "0.2.13",
"protocol_version": 3,
"schema": "ai-stp-provider-kit-identity/1"
}
diff --git a/provider-kit/v3/SHA256SUMS b/provider-kit/v3/SHA256SUMS
index 6f9f240..e4d165d 100644
--- a/provider-kit/v3/SHA256SUMS
+++ b/provider-kit/v3/SHA256SUMS
@@ -1,4 +1,4 @@
fe04d03b15cfe8d5b61835eafd3ea788074684e2be69d419f9c22a37ea0461b7 conformance-cases.json
-ae323ef26648fbaf441aa179008d0899ec79af88b3ae1e738e5bc75b1bbc8f30 manifest.json
-64514da0c61012b5323987d72ee75f5ce8d6451ed557331a634b17aeac8d8966 provider-info.schema.json
+cfc55bb6f301589ded449c12ba0309e12238db7bbce3f5e63fc45e9731d4bee5 manifest.json
+2f28fef790dc2685e9790f4ae3123b1c6d581c94f87d15b75823ea8d575036aa provider-info.schema.json
7e67760f24bbc42a1242dad7ad3b378eef7ae15023c6813a33d582dfd2232293 status-response.schema.json
diff --git a/provider-kit/v3/manifest.json b/provider-kit/v3/manifest.json
index 9a4afcc..7516384 100644
--- a/provider-kit/v3/manifest.json
+++ b/provider-kit/v3/manifest.json
@@ -39,7 +39,7 @@
],
"decision": "docs/adr/ADR-0061-capability-negotiated-provider-protocol-v3.md",
"generated_from": "apps/cli/src/ai_stp_cli/provider/protocol_v3.py",
- "kit_version": "0.2.11",
+ "kit_version": "0.2.13",
"operation_network": {
"backup": [
{
@@ -67,6 +67,16 @@
"phase": "execute"
}
],
+ "patch_instruction_region": [
+ {
+ "network_requirement": "none",
+ "phase": "plan"
+ },
+ {
+ "network_requirement": "none",
+ "phase": "apply"
+ }
+ ],
"remove": [
{
"network_requirement": "none",
@@ -151,6 +161,7 @@
],
"optional_operations": [
"launch",
+ "patch_instruction_region",
"reset",
"software_install",
"software_remove",
diff --git a/provider-kit/v3/provider-info.schema.json b/provider-kit/v3/provider-info.schema.json
index 53ba00d..cc88b58 100644
--- a/provider-kit/v3/provider-info.schema.json
+++ b/provider-kit/v3/provider-info.schema.json
@@ -20,6 +20,7 @@
"enum": [
"capture_mode",
"end_state",
+ "instruction_section",
"target_scope"
],
"type": "string"
@@ -267,7 +268,8 @@
"software_install",
"software_update",
"software_remove",
- "launch"
+ "launch",
+ "patch_instruction_region"
],
"type": "string"
},
diff --git a/references/codex-baseline.json b/references/codex-baseline.json
index 2826f3c..8a37cf0 100644
--- a/references/codex-baseline.json
+++ b/references/codex-baseline.json
@@ -21,12 +21,12 @@
"desktop_documented_product": "ChatGPT",
"desktop_pinned_cli_packaging": "Codex.app/Codex.dmg",
"installer_asset": "install.sh",
- "installer_release": "rust-v0.154.0",
- "installed_version": "0.154.0",
+ "installer_release": "rust-v0.155.1",
+ "installed_version": "0.155.1",
"standalone_layout": "packages/standalone"
},
"verified_version_ref": "build/version.json:codex_cli_tested",
- "verified_at": "2026-09-16T13:21:19+00:00",
+ "verified_at": "2026-09-19T14:51:09+00:00",
"native_surfaces": {
"verified_at": "2026-08-31",
"config_home": "~/.codex",
@@ -212,89 +212,89 @@
"shape": "gzip-tar",
"platforms": {
"linux/arm64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-arm64.tgz",
- "bytes": 122610794,
- "sha256": "sha256:a2315b5f64bfeaff79b71e0d35505ba8c22cc1e96cab9dc950b614c804105b24",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-linux-arm64.tgz",
+ "bytes": 135126766,
+ "sha256": "sha256:7da3e7bea6db4751d1b837f046c9cec08918ac7f05427dd0b5bcf64ea6c85964",
"member": "package/vendor/aarch64-unknown-linux-musl/bin/codex"
},
"linux/x86_64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-x64.tgz",
- "bytes": 129654638,
- "sha256": "sha256:e27c83a49e6031685ee7f956c12aad5f16484d3a80181dd3fea930fb96b3832b",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-linux-x64.tgz",
+ "bytes": 142140011,
+ "sha256": "sha256:f110cccdd50b0be8130b84f45b3144ea775c233f1c8bd8226da6ee719d63d206",
"member": "package/vendor/x86_64-unknown-linux-musl/bin/codex"
},
"macos/arm64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-arm64.tgz",
- "bytes": 116501639,
- "sha256": "sha256:2a98662d79316a59993c7233e3e25a1aa1d42da4b45904585d33a5a7da1cade1",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-darwin-arm64.tgz",
+ "bytes": 127465533,
+ "sha256": "sha256:93cc218b25b71c8da3edb50a013fbd22acf8f39058fb64083fefff638a084976",
"member": "package/vendor/aarch64-apple-darwin/bin/codex"
},
"macos/x86_64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-x64.tgz",
- "bytes": 124308807,
- "sha256": "sha256:92c493533c53c433c4d94252251daba4f379ccba06a9964d260abb47a535dce1",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-darwin-x64.tgz",
+ "bytes": 135811357,
+ "sha256": "sha256:819db6dbb57a56cc21383a30331fc4115696e02f4c47ba7f686a285a2173fadd",
"member": "package/vendor/x86_64-apple-darwin/bin/codex"
},
"windows/arm64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-arm64.tgz",
- "bytes": 132888437,
- "sha256": "sha256:a072b19e67fd65f2827c925a9d3c89b1a55da70b8e24cc21b81c800a77cb3d53",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-win32-arm64.tgz",
+ "bytes": 135509012,
+ "sha256": "sha256:72525256ac769a23381236a374c71679927c988dd10b7a19d342c98193216b7f",
"member": "package/vendor/aarch64-pc-windows-msvc/bin/codex.exe"
},
"windows/x86_64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-x64.tgz",
- "bytes": 142162836,
- "sha256": "sha256:27eedae55e37ed1da4078b36a6ef9e4ffe8546a60fd6df5ee6408fdd16aff8ea",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.155.1-win32-x64.tgz",
+ "bytes": 145165338,
+ "sha256": "sha256:727fd5bfaeed16fe8fdeb57d1c0faa688f48edda4a0541323a103a3138925350",
"member": "package/vendor/x86_64-pc-windows-msvc/bin/codex.exe"
}
},
- "version": "0.154.0",
- "verified_at": "2026-09-16T13:21:19+00:00"
+ "version": "0.155.1",
+ "verified_at": "2026-09-19T14:51:09+00:00"
},
- "setup_catalogue_digest": "sha256:798c777e0ec0accbd155d666d896c1aad341779c8c8a457fdeccaecfb8f5499e",
+ "setup_catalogue_digest": "sha256:bddb409bb05f14d42c9a9891b8579e4a289136d433c76f0b115efc5d1eae4882",
"previous_software_artifacts": {
"command": "codex",
"shape": "gzip-tar",
"platforms": {
"linux/arm64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-linux-arm64.tgz",
- "bytes": 121707000,
- "sha256": "sha256:439c0dd0d6923f607b4e5cd1e3079c12f0b86f6e5007f07e377d6ad25e2d7bb9",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-arm64.tgz",
+ "bytes": 122610794,
+ "sha256": "sha256:a2315b5f64bfeaff79b71e0d35505ba8c22cc1e96cab9dc950b614c804105b24",
"member": "package/vendor/aarch64-unknown-linux-musl/bin/codex"
},
"linux/x86_64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-linux-x64.tgz",
- "bytes": 129272137,
- "sha256": "sha256:54818cb9fce3360cc6e44cfc5a96952cd5c1243efb43cbe488e11dda84663e08",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-linux-x64.tgz",
+ "bytes": 129654638,
+ "sha256": "sha256:e27c83a49e6031685ee7f956c12aad5f16484d3a80181dd3fea930fb96b3832b",
"member": "package/vendor/x86_64-unknown-linux-musl/bin/codex"
},
"macos/arm64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-darwin-arm64.tgz",
- "bytes": 115672312,
- "sha256": "sha256:535d301b49131abfda3264f959fb0defa40bbc306976d98ddbc15c424636c55c",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-arm64.tgz",
+ "bytes": 116501639,
+ "sha256": "sha256:2a98662d79316a59993c7233e3e25a1aa1d42da4b45904585d33a5a7da1cade1",
"member": "package/vendor/aarch64-apple-darwin/bin/codex"
},
"macos/x86_64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-darwin-x64.tgz",
- "bytes": 123544033,
- "sha256": "sha256:5e468958503c60e940b1b1af3fe2064c16fd141f1607111caf99f2c0a0e80725",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-darwin-x64.tgz",
+ "bytes": 124308807,
+ "sha256": "sha256:92c493533c53c433c4d94252251daba4f379ccba06a9964d260abb47a535dce1",
"member": "package/vendor/x86_64-apple-darwin/bin/codex"
},
"windows/arm64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-win32-arm64.tgz",
- "bytes": 132173674,
- "sha256": "sha256:6d0bf07e04810f0ed4ad2984f6c9f0547bb9e0e1bb1f8fe9ecce0de3376bbfef",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-arm64.tgz",
+ "bytes": 132888437,
+ "sha256": "sha256:a072b19e67fd65f2827c925a9d3c89b1a55da70b8e24cc21b81c800a77cb3d53",
"member": "package/vendor/aarch64-pc-windows-msvc/bin/codex.exe"
},
"windows/x86_64": {
- "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.153.4-win32-x64.tgz",
- "bytes": 141495386,
- "sha256": "sha256:05f473573f38b3f4dc9484e6807a511a1bb0e128dfa50816dafc5f79e553cdca",
+ "url": "https://registry.npmjs.org/@openai/codex/-/codex-0.154.0-win32-x64.tgz",
+ "bytes": 142162836,
+ "sha256": "sha256:27eedae55e37ed1da4078b36a6ef9e4ffe8546a60fd6df5ee6408fdd16aff8ea",
"member": "package/vendor/x86_64-pc-windows-msvc/bin/codex.exe"
}
},
- "version": "0.153.4",
- "verified_at": "2026-09-07T03:12:33+00:00"
+ "version": "0.154.0",
+ "verified_at": "2026-09-16T13:21:19+00:00"
},
"capability_features": {
"measured_at": "2026-08-30",
diff --git a/setups/nddev-builder/home/agents/nddev-builder.toml b/setups/nddev-builder/home/agents/nddev-builder.toml
index 0de08af..bf65b6e 100644
--- a/setups/nddev-builder/home/agents/nddev-builder.toml
+++ b/setups/nddev-builder/home/agents/nddev-builder.toml
@@ -55,7 +55,7 @@ what it owns, ask the binary: `codex-setup-system provider-info`.
| `developer_instructions` | **yes** | The role's prompt. Refused when absent, and it is the one a reader leaves out. |
| `nickname_candidates` | no | Names an agent spawned with this role may be given. |
| `model` | no | Model override for sessions spawned with this role. |
-| `model_reasoning_effort` | no | Reasoning effort for the selected model. |
+| `model_reasoning_effort` | no | `low`, `medium`, `high`, `xhigh`, `max` or `ultra` where the selected model supports them; unset means the model's own default, not the parent's effort. |
| `sandbox_mode` | no | Narrow the spawned session's sandbox policy. |
| `mcp_servers` | no | Role-specific MCP server configuration. |
| `skills.config` | no | Skills made available to the spawned session. |