@mrciphersmith/keryx 0.3.41 → 0.3.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -487,7 +487,7 @@ What is in it today:
487
487
  bounded parallel scheduling, and an offline fleet report over a recorded event
488
488
  log (`keryx agents monitor <events-file>`).
489
489
  - **Vendor CLIs as child agents — off by default.** keryx can hand a bounded,
490
- **read-only** task (or, for `claude-cli` only, a reviewed write — see below)
490
+ **read-only** task (or, for `claude-cli` and `codex-cli`, a reviewed write — see below)
491
491
  to a coding CLI you already have installed (`codex exec`,
492
492
  `claude -p`, or Google's Antigravity CLI, `agy -p --output-format
493
493
  stream-json`) and host it as a child of the same harness: a disposable git
@@ -509,16 +509,20 @@ What is in it today:
509
509
  - **A write from an external agent you review before it lands.**
510
510
  `keryx agents external run claude-cli --task "…" --write` runs `claude` in a
511
511
  throwaway worktree with only `Read Grep Glob Edit Write` (no shell, no network,
512
- no MCP). Its diff is captured, secret-redacted, hashed and stored as a pending
512
+ no MCP); `codex-cli --write` runs `codex` in the same kind of worktree under an
513
+ OS sandbox (as measured: writes outside the worktree fail, a DNS lookup fails, `.git` read-only)
514
+ that still leaves it a shell able to read files your account can read, so review
515
+ its output too. Its diff is captured, secret-redacted, hashed and stored as a pending
513
516
  review — nothing reaches your checkout. `keryx agents external review <run-id>`
514
517
  shows it; `apply <run-id>` needs a real terminal, shows the diff and asks you to
515
518
  type the first 12 hex digits of the patch hash, then creates a NEW local branch
516
519
  `external/<run-id>` with one commit. Your current branch and working tree are
517
520
  never touched, nothing is pushed and no pull request is opened;
518
521
  `discard <run-id>` drops it. There is no flag that skips the confirmation. Only
519
- `claude-cli` can write (`codex-cli` and `antigravity-cli` refuse `--write`), the
520
- review is a human reading the diff (a model review is not built), and a full run
521
- through the installed CLI is not recorded yet. In the TUI: `/external-diff` and a
522
+ `claude-cli` and `codex-cli` can write (`antigravity-cli` refuses `--write`), the
523
+ review is a human reading the diff (a model review is not built), and neither
524
+ has a long live history (`codex` was verified in a scratch repository on
525
+ `codex` 0.159.2; versions outside 0.159.2 up to 0.160.0 are refused). In the TUI: `/external-diff` and a
522
526
  sidebar row while a diff waits. See the [external agent write
523
527
  guide](docs/docs/guides/external-agent-write.md).
524
528
  - **Completion you can audit.** The completion gate blocks on missing evidence: a
@@ -948,7 +952,7 @@ untouched.
948
952
  | No bundled embedding runtime | No semantic ranking in memory search | Lexical memory search remains fully available |
949
953
  | ripgrep is external | `keryx ctx rg` needs `rg` on `PATH` | Install ripgrep, or let the agent read files directly |
950
954
  | Model commands need a credential | Four of the five commands above exit non-zero without one; `wiki enrich` exits `0` and marks the affected pages skipped | Everything else runs deterministically offline |
951
- | External agents are read-only, except `claude-cli` write mode, which has no full live run recorded | A delegated CLI can read and search; only `claude-cli` can write, and only as a stored diff a human reviews (`keryx agents external review\|apply\|discard`) that lands as a new local branch — never on your checkout, never pushed. `codex-cli` and `antigravity-cli` refuse `--write` ("write mode is claude-only in this release"). A model review is not built. The parent gets the child's result and nothing before it — supervision of a running external child is not implemented. `claude` and `agy` read-only runs are live-verified; a write run through the installed CLI is not yet | Use keryx's own child agents for work that must mutate the tree, or the [external agent write guide](docs/docs/guides/external-agent-write.md) for a reviewed `claude-cli` diff |
955
+ | External agents are read-only, except `claude-cli` and `codex-cli` write mode, which have no long live history | A delegated CLI can read and search; only `claude-cli` and `codex-cli` can write, and only as a stored diff a human reviews (`keryx agents external review\|apply\|discard`) that lands as a new local branch — never on your checkout, never pushed. `claude` is confined by a tool allow-list (no shell, no network); `codex` by an OS sandbox that still leaves it a shell that can read files your user can read (it cannot send them, but they can show up in the run output), and is refused when older than 0.159.0 — live-verified in a scratch repository on `codex` 0.159.0. `antigravity-cli` refuses `--write`. A model review is not built. The parent gets the child's result and nothing before it — supervision of a running external child is not implemented. `claude` and `agy` read-only runs are live-verified | Use keryx's own child agents for work that must mutate the tree, or the [external agent write guide](docs/docs/guides/external-agent-write.md) for a reviewed `claude-cli` or `codex-cli` diff |
952
956
  | A dispatched `flow-next` task is "done" by checks, not review | `task done` means normal end + a commit on the trigger branch + `keryx health gate` passing in the worktree; nobody has read the diff | Review and merge the `trigger/<flow>-<task>` branch yourself |
953
957
  | `trust` dispatch needs Linux + a working bubblewrap | The hardened unattended sandbox (network off, home hidden, allow-listed env) is bwrap-only; without it — or on macOS — a `trust` dispatch refuses before starting | Use `permissionMode: "ask"` (read-only), or run triggers on a Linux host with bwrap |
954
958
  | `dispatch.network: true` is the host's full network | The agent's commands then reach the internet and every host loopback service; the model call never needs it (it is made outside the sandbox) | Leave `network` off; review the `trigger/*` branch before installing or building it |
package/dist/cli.js CHANGED
@@ -14119,7 +14119,7 @@ var package_default;
14119
14119
  var init_package = __esm(() => {
14120
14120
  package_default = {
14121
14121
  name: "@mrciphersmith/keryx",
14122
- version: "0.3.41",
14122
+ version: "0.3.42",
14123
14123
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
14124
14124
  private: false,
14125
14125
  publishConfig: {
@@ -51368,13 +51368,15 @@ permission bridge.
51368
51368
  A line-stream agent (\`claude-cli\`, \`codex-cli\`) is a DIFFERENT thing:
51369
51369
  \`run\` refuses it outright ("speaks the one-way line stream. Delegate to it
51370
51370
  from \`keryx shell\` with /delegate"). There is no ACP permission bridge and
51371
- no patch capture for it \u2014 this release implements \`worktree-write\` for the
51372
- ACP transport only; a dispatch that asks for \`worktree-write\` on a
51373
- line-stream agent is REFUSED fail-closed with a distinguishable
51374
- \`not-implemented\` reason everywhere keryx dispatches one (the internal
51375
- \`runtime\` block, and \`keryx shell\`'s /delegate) \u2014 never silently accepted
51376
- and its writes discarded. It stays \`read-only\`, contained by the same
51377
- disposable worktree as every external run.
51371
+ no patch capture for it in those paths \u2014 \`worktree-write\` is implemented for
51372
+ the ACP transport, and for \`claude-cli\` and \`codex-cli\` only through the
51373
+ operator's own \`keryx agents external run <id> --write\`, which captures the
51374
+ diff for human review. A dispatch that asks for \`worktree-write\` on a
51375
+ line-stream agent anywhere else (the internal \`runtime\` block, and
51376
+ \`keryx shell\`'s /delegate) is REFUSED fail-closed with a distinguishable
51377
+ \`not-implemented\` reason \u2014 never silently accepted and its writes
51378
+ discarded. There it stays \`read-only\`, contained by the same disposable
51379
+ worktree as every external run.
51378
51380
 
51379
51381
  See \`docs/docs/guides/acp-client.md\` for the full ACP contract, including the
51380
51382
  output size bounds (a stderr-only flood and an output flood both end the run
@@ -85139,14 +85141,14 @@ var init_registry3 = __esm(() => {
85139
85141
  label: "Codex",
85140
85142
  binary: "codex",
85141
85143
  detect: ["--version"],
85142
- versionPattern: "codex-cli (\\d+\\.\\d+\\.\\d+)",
85144
+ versionPattern: "codex-cli (\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.]+)?)",
85143
85145
  knownGoodRange: { min: "0.147.0" },
85144
85146
  sandboxModes: ["read-only", "worktree-write"],
85145
85147
  streamingInput: false,
85146
85148
  resumable: true,
85147
85149
  reportsCost: false,
85148
85150
  budgetFlag: false,
85149
- notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so its sandbox level cannot " + "be re-asserted and it must be spawned with cwd already set to the worktree."
85151
+ notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so a read-only resume inherits " + "its sandbox, a write run re-asserts its confinement as `-c` overrides, and it must be " + "spawned with cwd already set to the worktree. Write mode needs codex 0.159.2 up to, but not including, 0.160.0."
85150
85152
  },
85151
85153
  {
85152
85154
  id: "claude-cli",
@@ -85196,10 +85198,27 @@ var init_registry3 = __esm(() => {
85196
85198
  });
85197
85199
 
85198
85200
  // src/harness/external/dispatch.ts
85201
+ function codecWriteVersionRefusal(entry, version) {
85202
+ const verified = CODEC_WRITE_VERIFIED_VERSIONS[entry.id];
85203
+ if (verified === undefined)
85204
+ return;
85205
+ const range = `\`${entry.binary}\` ${verified.min} up to, but not including, ${verified.before}`;
85206
+ const prefix = `${entry.label} write mode needs ${range}, the versions whose sandbox confinement was measured; `;
85207
+ if (version === undefined)
85208
+ return `${prefix}the installed version could not be read`;
85209
+ if (!/^\d+\.\d+\.\d+$/.test(version))
85210
+ return `${prefix}found ${version}, a pre-release`;
85211
+ if (compareVersions(version, verified.min) < 0)
85212
+ return `${prefix}found ${version}, which is older`;
85213
+ if (compareVersions(version, verified.before) >= 0) {
85214
+ return `${prefix}found ${version}, which is newer and not yet verified: update keryx, or use \`--task\` without \`--write\``;
85215
+ }
85216
+ return;
85217
+ }
85199
85218
  function implementedSandboxModesFor(entry, options = {}) {
85200
85219
  if (transportOf(entry) === "acp")
85201
85220
  return IMPLEMENTED_ACP_SANDBOX_MODES;
85202
- return entry.id === CODEC_WRITE_AGENT_ID && options.ownsWriteCapture === true ? IMPLEMENTED_ACP_SANDBOX_MODES : IMPLEMENTED_SANDBOX_MODES;
85221
+ return CODEC_WRITE_AGENT_IDS.includes(entry.id) && options.ownsWriteCapture === true ? IMPLEMENTED_ACP_SANDBOX_MODES : IMPLEMENTED_SANDBOX_MODES;
85203
85222
  }
85204
85223
  function validateRuntimeBlock(runtime, allowedActions, options = {}) {
85205
85224
  if (runtime === undefined || runtime.kind === "keryx") {
@@ -85241,7 +85260,7 @@ function validateRuntimeBlock(runtime, allowedActions, options = {}) {
85241
85260
  return {
85242
85261
  ok: false,
85243
85262
  code: "not-implemented",
85244
- reason: `sandbox "${sandbox}" is not implemented in this release; only ${implemented.join(", ")} is available` + (sandbox === "worktree-write" && transportOf(entry) !== "acp" ? `; write mode is claude-only in this release (${CODEC_WRITE_AGENT_ID})` : "")
85263
+ reason: `sandbox "${sandbox}" is not implemented in this release; only ${implemented.join(", ")} is available` + (sandbox === "worktree-write" && transportOf(entry) !== "acp" ? `; write mode is available for ${CODEC_WRITE_AGENT_IDS.join(", ")} only` + (entry.id === "antigravity-cli" ? ": antigravity-cli's edit tool writes outside the worktree" : "") : "")
85245
85264
  };
85246
85265
  }
85247
85266
  if (sandbox === "read-only") {
@@ -85267,12 +85286,16 @@ function readRuntimeBlock(dispatch) {
85267
85286
  return;
85268
85287
  return raw;
85269
85288
  }
85270
- var READ_ONLY_FORBIDDEN_ACTIONS, IMPLEMENTED_SANDBOX_MODES, IMPLEMENTED_ACP_SANDBOX_MODES, CODEC_WRITE_AGENT_ID = "claude-cli";
85289
+ var READ_ONLY_FORBIDDEN_ACTIONS, IMPLEMENTED_SANDBOX_MODES, IMPLEMENTED_ACP_SANDBOX_MODES, CODEC_WRITE_AGENT_IDS, CODEC_WRITE_VERIFIED_VERSIONS;
85271
85290
  var init_dispatch = __esm(() => {
85272
85291
  init_registry3();
85273
85292
  READ_ONLY_FORBIDDEN_ACTIONS = ["write", "network", "spawn-subagent"];
85274
85293
  IMPLEMENTED_SANDBOX_MODES = ["read-only"];
85275
85294
  IMPLEMENTED_ACP_SANDBOX_MODES = ["read-only", "worktree-write"];
85295
+ CODEC_WRITE_AGENT_IDS = ["claude-cli", "codex-cli"];
85296
+ CODEC_WRITE_VERIFIED_VERSIONS = {
85297
+ "codex-cli": { min: "0.159.2", before: "0.160.0" }
85298
+ };
85276
85299
  });
85277
85300
 
85278
85301
  // src/harness/external/env-deny.ts
@@ -85806,7 +85829,7 @@ var init_prompt2 = __esm(() => {
85806
85829
  EXTERNAL_WRITE_RUNTIME_DIRECTIVE = [
85807
85830
  "# Runtime directive",
85808
85831
  "",
85809
- "You are running non-interactively as a bounded child agent. Produce the work itself as " + "your final message, in the requested output schema. Do not ask questions, do not offer a " + "choice of modes, do not route to another skill, orchestrator, or flow, do not delegate to " + "another agent. You may create and modify files, only inside your current working " + "directory, using the Read, Grep, Glob, Edit and Write tools; no shell is available. Do not " + "touch .git, .github, .claude or .metaproject. Your changes are captured as a patch that a " + "human reviews before anything lands, so summarise what you changed in the final message."
85832
+ "You are running non-interactively as a bounded child agent. Produce the work itself as " + "your final message, in the requested output schema. Do not ask questions, do not offer a " + "choice of modes, do not route to another skill, orchestrator, or flow, do not delegate to " + "another agent. You may create and modify files, only inside your current working " + "directory, using your file reading, searching and editing tools. Do not use a shell to reach " + "outside it. Do not touch .git, .github, .claude or .metaproject. Your changes are captured as a patch that a " + "human reviews before anything lands, so summarise what you changed in the final message."
85810
85833
  ].join(`
85811
85834
  `);
85812
85835
  DIFF_INTRO_COMPLETE = "The operator's uncommitted changes are below. Your worktree is checked out at HEAD and " + "does not contain them, so this diff is the only place they exist for you.";
@@ -88338,6 +88361,8 @@ function buildCodexArgv(input) {
88338
88361
  "--ignore-user-config",
88339
88362
  "--skip-git-repo-check"
88340
88363
  ];
88364
+ if (input.sandbox === "worktree-write")
88365
+ argv.push(...CODEX_WRITE_CONFINEMENT_FLAGS);
88341
88366
  if (input.resultSchemaPath !== undefined)
88342
88367
  argv.push("--output-schema", input.resultSchemaPath);
88343
88368
  if (input.model !== undefined)
@@ -88345,8 +88370,9 @@ function buildCodexArgv(input) {
88345
88370
  argv.push(input.prompt);
88346
88371
  return argv;
88347
88372
  }
88348
- function buildCodexResumeArgv(sessionRef, message) {
88349
- return ["codex", "exec", "resume", sessionRef, "--json", "--ignore-user-config", "--skip-git-repo-check", message];
88373
+ function buildCodexResumeArgv(sessionRef, message, input) {
88374
+ const confinement = input?.sandbox === "worktree-write" ? ["-c", `sandbox_mode="${CODEX_SANDBOX_MODES["worktree-write"]}"`, ...CODEX_WRITE_CONFINEMENT_FLAGS] : [];
88375
+ return ["codex", "exec", "resume", sessionRef, "--json", "--ignore-user-config", "--skip-git-repo-check", ...confinement, message];
88350
88376
  }
88351
88377
  function parseCodexEvents(line) {
88352
88378
  const record = readJsonObject2(line);
@@ -88501,12 +88527,21 @@ function brief2(text, max = 200) {
88501
88527
  const flat = text.replace(/\s+/g, " ").trim();
88502
88528
  return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
88503
88529
  }
88504
- var CODEX_SANDBOX_MODES, RECOGNISED_TYPES, NARRATED_FAILURE_LINE, CLI_USAGE_PATTERNS, AUTH_PATTERNS2, LIMIT_PATTERNS2, codexCliCodec;
88530
+ var CODEX_SANDBOX_MODES, CODEX_WRITE_CONFINEMENT_FLAGS, RECOGNISED_TYPES, NARRATED_FAILURE_LINE, CLI_USAGE_PATTERNS, AUTH_PATTERNS2, LIMIT_PATTERNS2, codexCliCodec;
88505
88531
  var init_codex_cli = __esm(() => {
88506
88532
  CODEX_SANDBOX_MODES = {
88507
88533
  "read-only": "read-only",
88508
88534
  "worktree-write": "workspace-write"
88509
88535
  };
88536
+ CODEX_WRITE_CONFINEMENT_FLAGS = [
88537
+ "--ignore-rules",
88538
+ "-c",
88539
+ "sandbox_workspace_write.exclude_slash_tmp=true",
88540
+ "-c",
88541
+ "sandbox_workspace_write.exclude_tmpdir_env_var=true",
88542
+ "-c",
88543
+ "sandbox_workspace_write.network_access=false"
88544
+ ];
88510
88545
  RECOGNISED_TYPES = new Set([
88511
88546
  "thread.started",
88512
88547
  "turn.started",
@@ -88536,7 +88571,7 @@ var init_codex_cli = __esm(() => {
88536
88571
  parseLine: parseCodexLine,
88537
88572
  parseEvents: parseCodexEvents,
88538
88573
  classifyFailure: classifyCodexFailure,
88539
- buildResumeArgv: (sessionRef, message) => buildCodexResumeArgv(sessionRef, message),
88574
+ buildResumeArgv: buildCodexResumeArgv,
88540
88575
  isRecognisedLine: isRecognisedCodexLine
88541
88576
  };
88542
88577
  });
@@ -88650,15 +88685,21 @@ async function runExternalChild(input, deps) {
88650
88685
  if (!isAcp && codec === undefined) {
88651
88686
  return refuse2("Error", `no codec is registered for external agent "${entry.id}"`);
88652
88687
  }
88688
+ let detectedVersion;
88653
88689
  if (deps.detect !== undefined) {
88654
88690
  const availability = resolveAvailability(entry, await deps.detect(entry.binary, entry.detect));
88655
88691
  if (availability.state === "binary-missing") {
88656
88692
  return refuse2("Denied", `${entry.label} is not installed (\`${entry.binary}\` not on PATH)`);
88657
88693
  }
88694
+ if (availability.state === "available")
88695
+ detectedVersion = availability.version;
88658
88696
  if (availability.state === "available" && availability.verdict.state !== "in-range") {
88659
88697
  deps.onWarning?.(`${entry.label} version ${availability.version ?? "unknown"} is outside the range this build's fixtures were recorded against`);
88660
88698
  }
88661
88699
  }
88700
+ const versionRefusal = sandbox === "worktree-write" ? codecWriteVersionRefusal(entry, detectedVersion) : undefined;
88701
+ if (versionRefusal !== undefined)
88702
+ return refuse2("Denied", versionRefusal);
88662
88703
  const schemaPrep = await prepareResultSchema();
88663
88704
  if (!schemaPrep.ok)
88664
88705
  return refuse2("Error", schemaPrep.reason);
@@ -179443,7 +179484,7 @@ async function runCommand4(args, deps, log) {
179443
179484
  maxExternalDepth: DEFAULT_MAX_EXTERNAL_DEPTH,
179444
179485
  onWarning: (warning) => console.error(`warning: ${plain(warning)}`)
179445
179486
  };
179446
- if (write && id === CODEC_WRITE_AGENT_ID) {
179487
+ if (write && CODEC_WRITE_AGENT_IDS.includes(id)) {
179447
179488
  let handle;
179448
179489
  let interrupted = false;
179449
179490
  const onSigint = () => {
@@ -179717,7 +179758,7 @@ function printExternalHelp() {
179717
179758
  { flag: "--no-probe", desc: "Skip detection entirely; every entry reports `not-probed`." },
179718
179759
  { flag: "--task", desc: "run: what the agent should do. It runs in a disposable worktree; your tree is never touched." },
179719
179760
  { flag: "--unattended", desc: "run: refuse every permission that would need a human (also implied without a TTY)." },
179720
- { flag: "--write", desc: "run: writes land in the disposable worktree and leave as a never-applied patch. An ACP agent advertises fs.writeTextFile; for a line-stream agent only claude is supported (Edit and Write inside the worktree)." },
179761
+ { flag: "--write", desc: "run: writes land in the disposable worktree and leave as a never-applied patch. An ACP agent advertises fs.writeTextFile; for a line-stream agent only claude-cli and codex-cli are supported. claude gets Edit and Write inside the worktree and no shell; codex runs under its OS sandbox (writes confined to the worktree, network closed as far as measured) and keeps a shell that can read any file you can read. codex needs version 0.159.2 or newer but older than 0.160.0. antigravity-cli is refused: its edit tool writes outside the worktree." },
179721
179762
  { flag: "--timeout", desc: "run: wall-clock ceiling in ms (default: externalAgents.defaultTimeoutMs)." }
179722
179763
  ]);
179723
179764
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.41",
3
+ "version": "0.3.42",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -165,7 +165,7 @@
165
165
  "minLength": 1
166
166
  },
167
167
  "sandbox": {
168
- "description": "Permission level for the external child. `worktree-write` is schema-valid but refused at runtime in the read-only release, with a reason distinguishable from an unsupported-by-this-agent refusal.",
168
+ "description": "Permission level for the external child. `worktree-write` is schema-valid but refused at runtime except for an ACP agent and, through the operator's `--write` command only, `claude-cli` and `codex-cli`, with a reason distinguishable from an unsupported-by-this-agent refusal.",
169
169
  "type": "string",
170
170
  "enum": ["read-only", "worktree-write"]
171
171
  },