delegate-agent-cli 0.13.1__tar.gz → 0.14.0__tar.gz

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.
Files changed (133) hide show
  1. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/CHANGELOG.md +47 -0
  2. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/PKG-INFO +13 -1
  3. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/README.md +12 -0
  4. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/cli-reference.md +80 -22
  5. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/security-model.md +29 -0
  6. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/worktrees.md +4 -0
  7. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/pyproject.toml +1 -1
  8. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/__init__.py +1 -1
  9. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/argv_builders.py +76 -5
  10. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/argv_utils.py +9 -1
  11. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/cli.py +40 -7
  12. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/cli_parser.py +81 -3
  13. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/command_help.py +124 -44
  14. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/constants.py +25 -0
  15. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/describe_payload.py +100 -6
  16. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/git_utils.py +1 -1
  17. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/harness_events.py +28 -1
  18. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/isolation.py +1 -1
  19. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/json_types.py +2 -2
  20. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/profiles.py +67 -9
  21. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/reasoning.py +3 -3
  22. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/request_build.py +124 -28
  23. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/request_models.py +6 -0
  24. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/runner.py +421 -29
  25. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/safe_workspace.py +85 -19
  26. delegate_agent_cli-0.14.0/src/delegate_agent/seatbelt.py +170 -0
  27. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/snapshot_view.py +0 -2
  28. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/wait_cancel_commands.py +50 -23
  29. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/workflows/commands.py +131 -34
  30. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/workflows/registry.py +77 -1
  31. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/workflows/runtime.py +79 -40
  32. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/workflows/schema.py +34 -4
  33. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/workflows/script.py +3 -2
  34. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/worktree_execution.py +9 -48
  35. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/worktree_summary.py +1 -2
  36. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent_cli.egg-info/PKG-INFO +13 -1
  37. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent_cli.egg-info/SOURCES.txt +3 -0
  38. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_capability_commands.py +17 -0
  39. delegate_agent_cli-0.14.0/tests/test_codex_pure_sandbox.py +511 -0
  40. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_command_help.py +85 -11
  41. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_delegate_help_cli.py +72 -1
  42. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_delegate_isolation.py +3 -3
  43. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_delegate_parser.py +144 -20
  44. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_delegate_validation.py +0 -11
  45. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_end_to_end_tracking.py +6 -0
  46. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_engine_argv.py +3 -1
  47. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_execution_argv_and_prompt.py +10 -4
  48. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_execution_dry_run.py +0 -11
  49. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_execution_worktree_failure_cleanup.py +1 -12
  50. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_execution_worktree_preflight.py +0 -4
  51. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_execution_worktree_run.py +0 -8
  52. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_model_discovery.py +2 -2
  53. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_model_selection_wave1a.py +1 -1
  54. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_model_selection_wave1b.py +3 -3
  55. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_profiles.py +64 -0
  56. delegate_agent_cli-0.14.0/tests/test_pure_call.py +570 -0
  57. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_retention.py +0 -3
  58. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_run_registry.py +0 -2
  59. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_runner_capture.py +0 -8
  60. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_safe_workspace_isolation.py +143 -0
  61. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_wait_cancel_commands.py +130 -59
  62. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_workflow_commands.py +887 -19
  63. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_worktree_list_show.py +0 -1
  64. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_worktree_prune_gc.py +0 -1
  65. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_worktree_remove.py +0 -1
  66. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/CONTRIBUTING.md +0 -0
  67. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/LICENSE +0 -0
  68. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/MANIFEST.in +0 -0
  69. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/SECURITY.md +0 -0
  70. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/bin/delegate-profile-shim +0 -0
  71. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/bin/delegate.py +0 -0
  72. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/config.example.json +0 -0
  73. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/agent-setup.md +0 -0
  74. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/assets/delegate-agent-header.png +0 -0
  75. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/configuration.md +0 -0
  76. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/development.md +0 -0
  77. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/live-runtime.md +0 -0
  78. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/publishing-checklist.md +0 -0
  79. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/docs/troubleshooting.md +0 -0
  80. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/examples/task.claude.json +0 -0
  81. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/examples/task.codex.json +0 -0
  82. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/examples/task.cursor.json +0 -0
  83. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/examples/task.droid.json +0 -0
  84. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/examples/task.grok.json +0 -0
  85. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/examples/task.judge.json +0 -0
  86. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/setup.cfg +0 -0
  87. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/archived_logs.py +0 -0
  88. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/bundled_models.py +0 -0
  89. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/capability_commands.py +0 -0
  90. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/command_errors.py +0 -0
  91. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/config.py +0 -0
  92. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/config_commands.py +0 -0
  93. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/errors.py +0 -0
  94. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/inspection_commands.py +0 -0
  95. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/log_output.py +0 -0
  96. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/model_discovery.py +0 -0
  97. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/private_io.py +0 -0
  98. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/profile_commands.py +0 -0
  99. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/profile_guard.py +0 -0
  100. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/prompt_instructions.py +0 -0
  101. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/prompt_transport.py +0 -0
  102. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/redaction.py +0 -0
  103. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/rendering.py +0 -0
  104. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/retention.py +0 -0
  105. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/run_metadata.py +0 -0
  106. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/run_output_commands.py +0 -0
  107. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/run_registry.py +0 -0
  108. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/run_status.py +0 -0
  109. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/workflows/__init__.py +0 -0
  110. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/worktree_commands.py +0 -0
  111. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/worktree_gc.py +0 -0
  112. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/worktree_mgmt.py +0 -0
  113. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/worktree_records.py +0 -0
  114. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/worktree_remove.py +0 -0
  115. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent/wsl.py +0 -0
  116. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent_cli.egg-info/dependency_links.txt +0 -0
  117. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent_cli.egg-info/entry_points.txt +0 -0
  118. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent_cli.egg-info/requires.txt +0 -0
  119. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/src/delegate_agent_cli.egg-info/top_level.txt +0 -0
  120. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_config_commands.py +0 -0
  121. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_harness_events.py +0 -0
  122. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_inspection_commands.py +0 -0
  123. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_kimi_commands.py +0 -0
  124. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_packaging.py +0 -0
  125. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_reasoning_capabilities.py +0 -0
  126. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_slash_passthrough.py +0 -0
  127. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_snapshot_redaction.py +0 -0
  128. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_snapshot_rendering.py +0 -0
  129. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_snapshot_run_output.py +0 -0
  130. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_snapshot_view.py +0 -0
  131. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_utility_modules.py +0 -0
  132. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_wave4_launch_features.py +0 -0
  133. {delegate_agent_cli-0.13.1 → delegate_agent_cli-0.14.0}/tests/test_wsl_guardrails.py +0 -0
@@ -5,6 +5,52 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.14.0] - 2026-07-15
9
+
10
+ ### Added
11
+
12
+ - `workflow result --field` for extracting a single field from a workflow
13
+ result, reliable latest-workflow selection, immutable creation ordering,
14
+ dry-run visibility, and orphan detection (PR #14).
15
+ - Schema validation for `agent()` results supports `minLength` and
16
+ `minItems`, including in retries and dry-run placeholders (PR #14).
17
+ - Grouped work runs sharing one non-isolated workspace now emit a warning
18
+ with guidance for commit boundaries and persistent worktrees (PR #13).
19
+
20
+ ### Changed
21
+
22
+ - Focused call help (`delegate <engine> call --help`) now reflects
23
+ call-mode option boundaries and documents the grouped-call tracking
24
+ exception (`--cwd` accepted only with `--group`); explicit `agent` input
25
+ is rejected on engines that do not support it (PR #13).
26
+ - Overview, dry-run, agent-help, README, CLI reference, and worktree docs
27
+ corrected from user feedback (PR #13).
28
+
29
+ ### Fixed
30
+
31
+ - Structured Codex workflows consume the child-authored completion report
32
+ (including on resume) and fail closed when it is missing; workflow resume
33
+ transitions atomically to `starting`, hides stale results, rolls back
34
+ failed launches, and releases locks on preparation errors (PR #14).
35
+ - Safe isolation re-roots exact source-workspace paths while preserving
36
+ slash-command payloads verbatim (PR #14).
37
+ - `wait`: a run matched by both `--latest HARNESS` and `--group NAME`
38
+ was emitted twice in the resolved-target list; it now appears once, with
39
+ first-selection precedence and ordering preserved.
40
+ - `git_root_for` degrades any `OSError` from the git probe (not just
41
+ `FileNotFoundError`) to the non-git fallback, so e.g. a `PermissionError`
42
+ spawning git no longer crashes workspace resolution.
43
+
44
+ ### Changed
45
+
46
+ - Internal cleanup pass across `src/` and `tests/`: removed dead code (unused
47
+ protocol types, private helpers, stale fixtures), collapsed redundant
48
+ exception tuples into their `OSError` base, replaced a hand-rolled
49
+ path-containment helper with `Path.is_relative_to`, and stripped stale
50
+ review-artifact comments. Behavior-preserving; the workflow supervisor's
51
+ broad exception boundary around child-model output is now explicitly
52
+ commented and regression-tested.
53
+
8
54
  ## [0.13.1] - 2026-07-09
9
55
 
10
56
  ### Added
@@ -304,6 +350,7 @@ Usage-audit fix wave: 82 sessions and 1,241 delegate invocations from one week o
304
350
 
305
351
  - Releases before 0.1.3 predate this changelog.
306
352
 
353
+ [0.14.0]: https://github.com/treygoff24/delegate-agent/compare/v0.13.1...v0.14.0
307
354
  [0.13.1]: https://github.com/treygoff24/delegate-agent/compare/v0.13.0...v0.13.1
308
355
  [0.13.0]: https://github.com/treygoff24/delegate-agent/compare/v0.12.0...v0.13.0
309
356
  [0.12.0]: https://github.com/treygoff24/delegate-agent/compare/v0.11.0...v0.12.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: delegate-agent-cli
3
- Version: 0.13.1
3
+ Version: 0.14.0
4
4
  Summary: A tiny CLI for delegating bounded agent tasks to Cursor, Droid, OpenAI Codex, Claude Code, Grok Build, or Kimi Code runtimes.
5
5
  Author: Trey Goff
6
6
  License-Expression: MIT
@@ -221,8 +221,17 @@ run, use stateless `call` mode:
221
221
 
222
222
  ```bash
223
223
  delegate --json codex call "Summarize this context in three bullets."
224
+ delegate --json claude call --pure --timeout 60 --output-schema result.schema.json < prompt.txt
224
225
  ```
225
226
 
227
+ `call --pure` is a hostile-input completion boundary available on Claude only.
228
+ It uses an empty temporary cwd, an allowlisted child environment, no Delegate
229
+ prompt framing, and no session persistence or tools according to the engine
230
+ capability contract. Claude receives the prompt only on stdin and is
231
+ schema-capable via `--output-schema`; `delegate --json describe` reports
232
+ `pureCall`, `structuredOutput`, `noSessionPersistence`, `usageEvents`, and
233
+ `promptStdin` per engine. `--timeout SECONDS` applies to every call-mode engine.
234
+
226
235
  For multi-step fan-out or gated review flows, use Delegate Workflows. A workflow
227
236
  is a Python script that launches normal Delegate child runs, journals progress,
228
237
  can pause on approval gates, and can resume from cached child results:
@@ -294,6 +303,9 @@ cancelled, and `124` on timeout. `cancel` signals the run's process group
294
303
  start-identity check against PID reuse; a cancelled run reports `cancelled`
295
304
  rather than a false success. Tag a batch of launches with `--group NAME` and
296
305
  `wait`, `runs`, and the worktree commands can select the whole group at once.
306
+ Commit between grouped feature waves that edit the same workspace. If features
307
+ need separate review or commits, give each a persistent worktree and integrate
308
+ them separately; `wait --group` warns about shared non-isolated workspaces.
297
309
 
298
310
  ## Task design: cluster related work into fewer, richer delegations
299
311
 
@@ -188,8 +188,17 @@ run, use stateless `call` mode:
188
188
 
189
189
  ```bash
190
190
  delegate --json codex call "Summarize this context in three bullets."
191
+ delegate --json claude call --pure --timeout 60 --output-schema result.schema.json < prompt.txt
191
192
  ```
192
193
 
194
+ `call --pure` is a hostile-input completion boundary available on Claude only.
195
+ It uses an empty temporary cwd, an allowlisted child environment, no Delegate
196
+ prompt framing, and no session persistence or tools according to the engine
197
+ capability contract. Claude receives the prompt only on stdin and is
198
+ schema-capable via `--output-schema`; `delegate --json describe` reports
199
+ `pureCall`, `structuredOutput`, `noSessionPersistence`, `usageEvents`, and
200
+ `promptStdin` per engine. `--timeout SECONDS` applies to every call-mode engine.
201
+
193
202
  For multi-step fan-out or gated review flows, use Delegate Workflows. A workflow
194
203
  is a Python script that launches normal Delegate child runs, journals progress,
195
204
  can pause on approval gates, and can resume from cached child results:
@@ -261,6 +270,9 @@ cancelled, and `124` on timeout. `cancel` signals the run's process group
261
270
  start-identity check against PID reuse; a cancelled run reports `cancelled`
262
271
  rather than a false success. Tag a batch of launches with `--group NAME` and
263
272
  `wait`, `runs`, and the worktree commands can select the whole group at once.
273
+ Commit between grouped feature waves that edit the same workspace. If features
274
+ need separate review or commits, give each a persistent worktree and integrate
275
+ them separately; `wait --group` warns about shared non-isolated workspaces.
264
276
 
265
277
  ## Task design: cluster related work into fewer, richer delegations
266
278
 
@@ -57,6 +57,13 @@ positional prompt input, and Kimi Code prompt mode currently uses `--prompt`,
57
57
  so those launches still use argv transport; Delegate redacts Cursor and Kimi
58
58
  prompt argv in dry-run output and run manifests.
59
59
 
60
+ Temporary safe isolation also re-roots absolute paths under the source workspace
61
+ when it transports the prompt, so `/source/repo/src/app.py` becomes the matching
62
+ path inside the isolated copy. Paths outside the workspace and prefix lookalikes
63
+ are left unchanged. The safe prompt asks the child to cite workspace-relative
64
+ paths in its report; consumers should not depend on temporary isolation paths.
65
+ Verbatim slash pass-through prompts are not rewritten.
66
+
60
67
  For launch and `dry-run` commands, `--json` and `--isolation auto|none|worktree`
61
68
  are unambiguous before inline prompt text starts and may appear with launch
62
69
  options, such as `delegate codex work --prompt-file task.md --json` or
@@ -125,6 +132,11 @@ A few boundaries are worth stating explicitly:
125
132
  create an orchestration manifest; it only enables selectors such as
126
133
  `delegate runs --group NAME`, `delegate wait --group NAME`, and worktree
127
134
  management filters. Group names must match `[A-Za-z0-9._-]{1,64}`.
135
+ If grouped feature waves run in the same non-isolated workspace, commit between
136
+ waves so later edits do not become interleaved with earlier work. When features
137
+ need separate review or commits, launch each in a persistent worktree and
138
+ integrate them separately; `wait --group` warns when it sees shared same-tree
139
+ work runs.
128
140
 
129
141
  `--auth-profile NAME` selects a top-level `profiles.definitions` entry and
130
142
  injects that profile's flat env map into child processes. It overrides ambient
@@ -139,7 +151,8 @@ auth/env selection happens.
139
151
  Usage:
140
152
 
141
153
  ```bash
142
- delegate [--json] [--isolation auto|none|worktree] codex {safe,work,call} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--fast|--no-fast] [--progress] [--forbid-commit] [--prompt-file PATH] [--output-schema FILE] [prompt...]
154
+ delegate [--json] [--isolation auto|none|worktree] codex {safe,work} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--fast|--no-fast] [--progress] [--forbid-commit] [--prompt-file PATH] [--output-schema FILE] [prompt...]
155
+ delegate [--json] codex call [--read-only] [--model <alias-or-model>] [--reasoning-effort LEVEL] [--fast|--no-fast] [--prompt-file PATH] [--output-schema FILE] [prompt...]
143
156
  ```
144
157
 
145
158
  - Safe mode reviews your **current working tree** — uncommitted tracked edits and untracked, non-ignored files are mirrored into an isolated throwaway copy (only gitignored paths are excluded), so you can review local changes without committing first or pasting a diff. Codex safe always uses `--sandbox read-only`. Under `--isolation auto`, Codex safe is the only safe harness that may opt out with `--isolation none`, because Codex still keeps its read-only sandbox active.
@@ -166,7 +179,8 @@ delegate --isolation worktree codex work "Implement the feature in a persistent
166
179
  Usage:
167
180
 
168
181
  ```bash
169
- delegate [--json] [--isolation auto|none|worktree] claude {safe,work,call} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--progress] [--forbid-commit] [--prompt-file PATH] [prompt...]
182
+ delegate [--json] [--isolation auto|none|worktree] claude {safe,work} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--progress] [--forbid-commit] [--prompt-file PATH] [prompt...]
183
+ delegate [--json] claude call [--read-only] [--model <alias-or-model>] [--reasoning-effort LEVEL] [--prompt-file PATH] [prompt...]
170
184
  ```
171
185
 
172
186
  - Safe mode reviews your **current working tree** — uncommitted tracked edits and untracked, non-ignored files are mirrored into an isolated throwaway copy (only gitignored paths are excluded), so you can review local changes without committing first or pasting a diff. Under `--isolation auto`, Claude safe uses `--permission-mode plan`, `--strict-mcp-config`, Read/Grep/Glob, and selected read-only Bash tools such as `git diff`/`git status`.
@@ -191,7 +205,8 @@ delegate --isolation worktree claude work "Implement the feature in a persistent
191
205
  Usage:
192
206
 
193
207
  ```bash
194
- delegate [--json] [--isolation auto|none|worktree] grok {safe,work,call} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--progress] [--forbid-commit] [--prompt-file PATH] [prompt...]
208
+ delegate [--json] [--isolation auto|none|worktree] grok {safe,work} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--progress] [--forbid-commit] [--prompt-file PATH] [prompt...]
209
+ delegate [--json] grok call [--read-only] [--model <alias-or-model>] [--reasoning-effort LEVEL] [--prompt-file PATH] [prompt...]
195
210
  ```
196
211
 
197
212
  - Safe mode reviews your **current working tree** in an isolated throwaway copy plus Grok read-only controls (`--sandbox read-only`, `--permission-mode dontAsk` by default). Delegate does not use Grok `plan` mode for safe review.
@@ -274,7 +289,8 @@ delegate --isolation worktree opencode work "Implement the feature in a persiste
274
289
  Usage:
275
290
 
276
291
  ```bash
277
- delegate [--json] [--isolation auto|none|worktree] kimi {safe,work,call} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--progress] [--forbid-commit] [--prompt-file PATH] [prompt...]
292
+ delegate [--json] [--isolation auto|none|worktree] kimi {safe,work} [--model <alias-or-model>] [--reasoning-effort LEVEL] [--progress] [--forbid-commit] [--prompt-file PATH] [prompt...]
293
+ delegate [--json] kimi call [--read-only] [--model <alias-or-model>] [--reasoning-effort LEVEL] [--prompt-file PATH] [prompt...]
278
294
  ```
279
295
 
280
296
  - Safe mode reviews your **current working tree** — uncommitted tracked edits and untracked, non-ignored files are mirrored into an isolated throwaway copy (only gitignored paths are excluded), so you can review local changes without committing first or pasting a diff. Under `--isolation auto`, Kimi safe uses a read-only safety prompt. Delegate intentionally avoids Kimi `--plan` in safe mode. Kimi prompt mode auto-approves tool actions, so the isolation is the effective write boundary; the safety prompt is advisory.
@@ -308,8 +324,8 @@ delegate [--json] workflow run --resume <wfId> [--budget N]
308
324
  delegate [--json] workflow status <wfId>
309
325
  delegate [--json] workflow events <wfId> [--since SEQ]
310
326
  delegate [--json] workflow watch <wfId> [--since SEQ]
311
- delegate [--json] workflow wait <wfId> [--timeout SEC]
312
- delegate [--json] workflow result <wfId>
327
+ delegate [--json] workflow wait [<wfId>] [--timeout SEC]
328
+ delegate [--json] workflow result [<wfId>] [--field KEY]
313
329
  delegate [--json] workflow approve <wfId>
314
330
  delegate [--json] workflow kill <wfId>
315
331
  delegate [--json] workflow list
@@ -319,9 +335,18 @@ delegate [--json] workflow save <script.py> --name NAME
319
335
  - `check` validates the workflow script, including literal preflight checks for
320
336
  unsupported `agent()` combinations.
321
337
  - `run` launches a detached supervisor; `--dry-run` renders planned stubs
322
- without launching child agents or consuming real budget.
338
+ without launching child agents or consuming real budget. Each entry in
339
+ `runTree.calls` includes the resolved `model`, `effort`, `fast`, `isolation`,
340
+ and UTF-8 `promptBytes`; Cursor/Kimi prompts over 102400 bytes add a warning
341
+ before their argv transport limit can fail a real run.
323
342
  - `--resume` replays the journal, adopts matching child runs by workflow agent
324
343
  key, and continues from missing work.
344
+ - `wait` and `result` accept an explicit workflow ID or, when omitted, resolve
345
+ the latest eligible workflow. JSON output for implicit selection includes the
346
+ selected `wfId` and `resolutionKind: "latest"`.
347
+ - `result --field KEY` extracts a top-level field from an object result. Text
348
+ mode prints strings directly and JSON-encodes other values; JSON mode returns
349
+ a field/value envelope.
325
350
  - `approve` releases a paused gate (and resumes). Do not also run
326
351
  `run --resume` for the same gate — approve already is that resume. `kill`
327
352
  validates the supervisor process group before signaling and always attempts
@@ -340,6 +365,7 @@ Codes raised as `DelegateError` from workflow commands (`workflows/commands.py`)
340
365
  | `invalid_workflow_name` | Saved-workflow `--name` is not a simple file stem. |
341
366
  | `invalid_workflow_script` | Script failed `check` / load validation. |
342
367
  | `missing_workflow` | A verb that needs `<wfId>` was invoked without one. |
368
+ | `missing_workflow_result_field` | `result --field` was invoked without a key. |
343
369
  | `missing_workflow_save_args` | `save` needs both `<script.py>` and `--name`. |
344
370
  | `missing_workflow_script` | `run`/`check` need `<script.py>` or `--name`. |
345
371
  | `unknown_workflow_action` | Unrecognized `workflow` subcommand. |
@@ -348,6 +374,8 @@ Codes raised as `DelegateError` from workflow commands (`workflows/commands.py`)
348
374
  | `workflow_not_found` | No workflow directory / status for that `wfId`. |
349
375
  | `workflow_not_gated` | `approve` on a workflow that is not paused on a gate. |
350
376
  | `workflow_result_missing` | `result` before `result.json` exists. |
377
+ | `workflow_result_field_missing` | The requested top-level result field does not exist. |
378
+ | `workflow_result_not_object` | `--field` was requested for a non-object result. |
351
379
  | `workflow_script_not_found` | Resolved script path is missing or not a file. |
352
380
 
353
381
  See [Delegate Workflows](delegate-workflows.md) for the DSL, caps, config, and
@@ -356,24 +384,26 @@ gate semantics.
356
384
  ### Stateless `call` mode
357
385
 
358
386
  `call` is the one-hop model-call form of Delegate: "work mode minus a repo." It
359
- gives a child runtime a prompt with no project tree to resolve, captures the
360
- final assistant text, and exits without creating a tracked run so you can call
361
- a model to *do something* (or to *judge something*) from anywhere, including a
362
- non-git directory.
387
+ gives a child runtime a prompt with no project tree to resolve and captures the
388
+ final assistant text, so you can call a model to *do something* (or to *judge
389
+ something*) from anywhere, including a non-git directory. Calls are untracked
390
+ by default; grouped calls are the narrow exception described below.
363
391
 
364
392
  ```bash
365
393
  delegate codex call "Write a Python script that finds the 500th prime and run it."
366
394
  delegate --json grok call --read-only --prompt-file rubric.md
367
395
  delegate --json codex call --read-only --output-schema verdict.json --prompt-file rubric.md
396
+ delegate --json claude call --pure --timeout 60 --output-schema verdict.json < rubric.md
368
397
  ```
369
398
 
370
399
  Call mode uses an empty temporary cwd instead of resolving the current repo, and
371
- it deletes that cwd after the child exits. It does not write `.delegate/runs`,
372
- create snapshots, inject safe/work skill or completion-report framing, emit
373
- progress heartbeats, or honor persistent worktree/commit policy options. JSON
400
+ it deletes that cwd after the child exits. It does not create snapshots, inject
401
+ safe/work skill or completion-report framing, emit progress heartbeats, or honor
402
+ persistent worktree/commit policy options. JSON
374
403
  output returns fields such as `ok`, `status`, `exitCode`, `engine`, `mode`,
375
- `model`, `text`, `textChars`, `textTruncated`, `stdoutBytes`, `stderrBytes`,
376
- reasoning metadata, and `warnings`. Failed calls include a redacted `stderrTail`.
404
+ `model`, `pure`, `structuredOutput`, `modelRequested`, `modelResolved`, `usage`,
405
+ `text`, `textChars`, `textTruncated`, `stdoutBytes`, `stderrBytes`, reasoning
406
+ metadata, and `warnings`. Failed calls include a redacted `stderrTail`.
377
407
  `textTruncated` is `true` when the returned
378
408
  `text` was bounded (large outputs keep the head and tail); `textChars` is the
379
409
  full untruncated character count.
@@ -392,11 +422,38 @@ changed files…" on a repo-flavored prompt. Pair it with `--output-schema` (Cod
392
422
  for structured verdicts. Use `--read-only` for any LLM-as-judge, grader, or
393
423
  oracle use where the text is the product and the model must not act.
394
424
 
395
- `--read-only` applies only to `call`; passing it with `safe`/`work` is rejected.
396
- Because call mode is stateless, `--cwd`, `--isolation`, `--pass-through`,
425
+ **`--pure` is the hostile-content completion boundary.** It is supported by
426
+ Claude only; other engines fail before launch with `unsupported_pure_call`. Pure
427
+ mode sends the prompt verbatim, drops ambient
428
+ environment variables outside the documented allowlist, and cannot be combined
429
+ with `--read-only`. Claude uses `--safe-mode`, disables every tool, ignores MCP
430
+ and ambient customization, disables session persistence, and receives the prompt
431
+ only on stdin. Use `--output-schema FILE` with Claude (schema contents inline)
432
+ or with ordinary Codex call mode (schema path).
433
+
434
+ `--timeout SECONDS` is a positive integer available on every call-mode engine.
435
+ On expiry Delegate terminates the whole child process group and returns
436
+ `call_timeout` with exit code 1.
437
+
438
+ `--read-only`, `--pure`, and `--timeout` apply only to `call`; passing them with
439
+ `safe`/`work` is rejected.
440
+ Because the child call is stateless, `--isolation`, `--pass-through`,
397
441
  `--progress`, `--forbid-commit`, and markdown completion reports are rejected.
398
- Use `safe` or `work` when the child should see the project tree or when you need
399
- run registry inspection.
442
+ An ordinary call also rejects `--cwd`.
443
+
444
+ **Grouped calls preserve workflow tracking without exposing the workspace to the
445
+ child.** `--group NAME` registers the call in the invocation workspace so
446
+ workflow kill/adopt and group selectors can find it. In that one combination,
447
+ `--cwd PATH` may select the registry/config workspace:
448
+
449
+ ```bash
450
+ delegate --cwd /path/to/project --group wf_0123abcdef45 codex call "Summarize this input."
451
+ ```
452
+
453
+ The child still executes in an empty temporary cwd; `--cwd` does not give it the
454
+ project tree. Dry-run accepts the same combination for faithful planning but
455
+ creates no run entry. Use `safe` or `work` when the child should see the project
456
+ tree.
400
457
 
401
458
  ### Dry-run
402
459
 
@@ -536,7 +593,7 @@ Supported input keys:
536
593
  - `progress`: optional boolean. `true` enables parent progress heartbeats on stderr; `false` disables them even when `progress.enabled` is true in config. When omitted, config `progress.enabled` applies (default `false`). `mode: "call"` rejects progress.
537
594
  - `forbidCommit`: optional boolean. `true` requires `mode: "work"` with persistent worktree isolation and fails the run if the child creates commits. `mode: "call"` rejects commit policy.
538
595
  - `includeDirty`: optional boolean. `true` requires `mode: "work"` with persistent worktree isolation and syncs tracked edits plus untracked non-ignored files into the new worktree before launch.
539
- - `outputSchema`: optional path to a JSON Schema for Codex's final message. Codex-only; same semantics as `--output-schema`. Other engines fail with `unsupported_output_schema`.
596
+ - `outputSchema`: optional path to a JSON Schema for the final message. Supported for Codex and Claude call mode (same semantics as `--output-schema`). Other engines fail with `unsupported_output_schema`.
540
597
  - `prompt`: required task prompt.
541
598
 
542
599
  `profile` is not accepted in run input JSON. Configure the Codex CLI config
@@ -734,7 +791,8 @@ signals the recorded process group with SIGTERM, waits a 5s grace period, then
734
791
  uses SIGKILL if needed. It never signals pid/pgid `<= 1`. Legacy runs without a
735
792
  recorded pgid fall back to the recorded pid with a warning. Cancel marks the run
736
793
  `cancelled` with `failureReason: cancelled_by_user` and records current captured
737
- stdout/stderr byte counts. `call` mode is untracked and therefore not cancellable.
794
+ stdout/stderr byte counts. Ungrouped `call` mode is untracked; grouped calls are
795
+ registered and can be selected for cancellation.
738
796
 
739
797
  Before sending any signal, cancel stamps a `cancelRequested: true` marker (with
740
798
  a `cancelRequestedAt` timestamp) on the run state under the registry lock, so
@@ -100,6 +100,35 @@ you need registry inspection.
100
100
  OpenCode `call --read-only` uses the same protected environment settings and
101
101
  `--pure` plugin restriction as OpenCode safe mode.
102
102
 
103
+ `call --pure` is a separate, stronger completion boundary. It is currently
104
+ supported on **Claude only**. Delegate sends the prompt verbatim on stdin, starts
105
+ the child in an empty temporary cwd, and builds the child environment from only
106
+ `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TMPDIR`, `LANG`, `LC_ALL`, `LC_CTYPE`,
107
+ and `TERM`, then applies trusted Delegate profile overrides. Claude additionally
108
+ uses `--safe-mode --tools "" --strict-mcp-config --no-session-persistence`, and the
109
+ result must carry an empty `permission_denials` list — a missing, null, or
110
+ non-empty value fails the call closed (`pure_boundary_unverified` /
111
+ `pure_boundary_violation`) rather than reporting success.
112
+
113
+ Codex and OpenCode are **not** pure-eligible; `<engine> call --pure` is rejected
114
+ before launch (`unsupported_pure_call`). They were disabled after review:
115
+
116
+ - **Codex** would need external OS confinement (a macOS Seatbelt profile) because
117
+ it stays a tool-using agent. The prototype handed the child an ephemeral
118
+ `CODEX_HOME` holding the resolved `auth.json`, but a single inherited Seatbelt
119
+ profile cannot distinguish a read by the Codex parent from a read by a
120
+ model-driven subprocess, so the credential was reachable inside the boundary it
121
+ was meant to protect. A credential transport the parent can use but model tools
122
+ cannot read is required before Codex pure is re-enabled. `sandbox-exec` is also
123
+ deprecated on macOS and would never cover other platforms.
124
+ - **OpenCode**'s native `--pure` only disables external plugins; it offers no
125
+ session non-persistence, no schema output, and no denial tripwire, so it does
126
+ not meet the hostile-input contract.
127
+
128
+ Fail-closed eligibility is intentional: an engine without a verified boundary
129
+ rejects `--pure` rather than presenting a weaker one under the same name. The
130
+ supported pure matrix may contain only Claude for some time.
131
+
103
132
  ## Reasoning-effort boundary
104
133
 
105
134
  `--reasoning-effort LEVEL` and JSON `reasoningEffort` request model thinking depth only. They do not change:
@@ -12,6 +12,10 @@ Use persistent worktree isolation when:
12
12
 
13
13
  Use the real workspace instead when the task depends on uncommitted local files that you do not want to commit or mirror into a worktree. If you do want a persistent worktree that starts from your current dirty checkout, use `--include-dirty`.
14
14
 
15
+ For grouped feature waves, commit between waves when they share the real
16
+ workspace. If each feature needs its own review or commit, use one persistent
17
+ worktree per feature and integrate those worktrees separately.
18
+
15
19
  ## Launch
16
20
 
17
21
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "delegate-agent-cli"
7
- version = "0.13.1"
7
+ version = "0.14.0"
8
8
  description = "A tiny CLI for delegating bounded agent tasks to Cursor, Droid, OpenAI Codex, Claude Code, Grok Build, or Kimi Code runtimes."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,5 +1,5 @@
1
1
  """Delegate Agent CLI package."""
2
2
 
3
- VERSION = "0.13.1"
3
+ VERSION = "0.14.0"
4
4
 
5
5
  __all__ = ["VERSION"]
@@ -125,6 +125,13 @@ def _grok_harness_bypass_enabled(config: JsonObject, mode: str) -> bool:
125
125
  return _harness_bypass_enabled(config, mode, "grok")
126
126
 
127
127
 
128
+ def _reject_pure(engine: str, mode: str, pure: bool, *, supported: bool = False) -> None:
129
+ if not pure:
130
+ return
131
+ if mode != MODE_CALL or not supported:
132
+ raise DelegateError("unsupported_pure_call", f"{engine} does not support pure call mode.")
133
+
134
+
128
135
  def build_cursor_argv(
129
136
  prefix: list[str],
130
137
  mode: str,
@@ -134,7 +141,9 @@ def build_cursor_argv(
134
141
  *,
135
142
  stream_capture: bool = True,
136
143
  call_read_only: bool = False,
144
+ pure: bool = False,
137
145
  ) -> list[str]:
146
+ _reject_pure("cursor", mode, pure)
138
147
  argv = [*prefix, "--workspace", workspace, "-p", "--trust"]
139
148
  if mode == MODE_WORK:
140
149
  argv.extend(["--approve-mcps", "--force"])
@@ -165,7 +174,9 @@ def build_droid_argv(
165
174
  reasoning_capability: reasoning.ReasoningCapability | None = None,
166
175
  prompt_transport: str = PROMPT_TRANSPORT_ARGV,
167
176
  call_read_only: bool = False,
177
+ pure: bool = False,
168
178
  ) -> list[str]:
179
+ _reject_pure("droid", mode, pure)
169
180
  argv = [binary, "exec", "--cwd", workspace]
170
181
  if mode == MODE_WORK:
171
182
  argv.append("--skip-permissions-unsafe")
@@ -202,7 +213,9 @@ def build_kimi_argv(
202
213
  prompt: str,
203
214
  *,
204
215
  stream_capture: bool = True,
216
+ pure: bool = False,
205
217
  ) -> list[str]:
218
+ _reject_pure("kimi", mode, pure)
206
219
  argv = [str(kimi["binary"])]
207
220
  if mode == MODE_SAFE:
208
221
  prompt = prefix_kimi_safe_prompt(prompt)
@@ -226,16 +239,35 @@ def build_claude_argv(
226
239
  reasoning_effort: str | None = None,
227
240
  allow_bypass_permissions: bool = False,
228
241
  call_read_only: bool = False,
242
+ pure: bool = False,
243
+ output_schema: str | None = None,
229
244
  ) -> list[str]:
245
+ _reject_pure("claude", mode, pure, supported=True)
246
+ if pure or output_schema is not None:
247
+ output_format = "json"
248
+ elif stream_capture:
249
+ output_format = "stream-json"
250
+ else:
251
+ output_format = "text"
230
252
  argv = [
231
253
  str(claude["binary"]),
232
254
  "-p",
233
255
  "--output-format",
234
- "stream-json" if stream_capture else "text",
256
+ output_format,
235
257
  "--input-format",
236
258
  "text",
237
259
  ]
238
- if mode == MODE_SAFE:
260
+ if pure:
261
+ argv.extend(
262
+ [
263
+ "--safe-mode",
264
+ "--tools",
265
+ "",
266
+ "--strict-mcp-config",
267
+ "--no-session-persistence",
268
+ ]
269
+ )
270
+ elif mode == MODE_SAFE:
239
271
  argv.extend(
240
272
  [
241
273
  "--permission-mode",
@@ -271,10 +303,12 @@ def build_claude_argv(
271
303
  argv.extend(["--permission-mode", str(claude.get("workPermissionMode", "auto"))])
272
304
  else:
273
305
  validate_mode(mode)
274
- if claude.get("noSessionPersistence", True) is True:
306
+ if not pure and claude.get("noSessionPersistence", True) is True:
275
307
  argv.append("--no-session-persistence")
276
- if claude.get("bare", False) is True:
308
+ if not pure and claude.get("bare", False) is True:
277
309
  argv.append("--bare")
310
+ if output_schema is not None:
311
+ argv.extend(["--json-schema", output_schema])
278
312
  if model:
279
313
  argv.extend(["--model", model])
280
314
  if reasoning_effort is not None:
@@ -294,7 +328,9 @@ def build_grok_argv(
294
328
  allow_bypass_permissions: bool = False,
295
329
  prompt_transport: str = PROMPT_TRANSPORT_FILE,
296
330
  call_read_only: bool = False,
331
+ pure: bool = False,
297
332
  ) -> list[str]:
333
+ _reject_pure("grok", mode, pure)
298
334
  argv = [str(grok["binary"]), "--cwd", workspace]
299
335
  if stream_capture:
300
336
  argv.extend(["--output-format", "streaming-json"])
@@ -358,7 +394,9 @@ def build_devin_argv(
358
394
  *,
359
395
  prompt_transport: str = PROMPT_TRANSPORT_FILE,
360
396
  call_read_only: bool = False,
397
+ pure: bool = False,
361
398
  ) -> list[str]:
399
+ _reject_pure("devin", mode, pure)
362
400
  argv = [str(devin["binary"])]
363
401
  if model:
364
402
  argv.extend(["--model", model])
@@ -394,8 +432,10 @@ def build_opencode_argv(
394
432
  variant: str | None,
395
433
  *,
396
434
  call_read_only: bool = False,
435
+ pure: bool = False,
397
436
  ) -> list[str]:
398
- read_only = mode == MODE_SAFE or (mode == MODE_CALL and call_read_only)
437
+ _reject_pure("opencode", mode, pure)
438
+ read_only = mode == MODE_SAFE or (mode == MODE_CALL and (call_read_only or pure))
399
439
  argv = [str(opencode["binary"])]
400
440
  if read_only:
401
441
  argv.append("--pure")
@@ -428,8 +468,39 @@ def build_codex_argv(
428
468
  prompt_transport: str = PROMPT_TRANSPORT_ARGV,
429
469
  output_schema: str | None = None,
430
470
  call_read_only: bool = False,
471
+ pure: bool = False,
431
472
  ) -> list[str]:
473
+ _reject_pure("codex", mode, pure)
432
474
  binary = str(codex["binary"])
475
+ if pure:
476
+ argv = [binary]
477
+ if model:
478
+ argv.extend(["--model", model])
479
+ if reasoning_capability is not None:
480
+ argv.extend(["-c", f'model_reasoning_effort="{reasoning_capability.effort}"'])
481
+ if fast is not None:
482
+ service_tier = "fast" if fast else "default"
483
+ argv.extend(["-c", f'service_tier="{service_tier}"'])
484
+ if fast:
485
+ argv.extend(["-c", "features.fast_mode=true"])
486
+ argv.extend(
487
+ [
488
+ "exec",
489
+ "--ignore-user-config",
490
+ "--ignore-rules",
491
+ "--skip-git-repo-check",
492
+ "--sandbox",
493
+ "read-only",
494
+ ]
495
+ )
496
+ if output_schema is not None:
497
+ argv.extend(["--output-schema", output_schema])
498
+ if stream_capture:
499
+ argv.extend(["--color", "never", "--json"])
500
+ if codex.get("ephemeral", True) is True:
501
+ argv.append("--ephemeral")
502
+ argv.append("-")
503
+ return argv
433
504
  argv = [binary]
434
505
  # Safe mode is read-only by contract: never emit the dangerous bypass flags,
435
506
  # even if a policy block somehow carries them. Config validation rejects such
@@ -35,4 +35,12 @@ def replace_workspace_arg_in_argv(engine: str, argv: list[str], value: str) -> l
35
35
 
36
36
  def public_argv(request: Request) -> list[str]:
37
37
  """Return the display-safe argv for a request (redacted form when present)."""
38
- return list(request.display_argv if request.display_argv is not None else request.argv)
38
+ argv = list(request.display_argv if request.display_argv is not None else request.argv)
39
+ output_schema = getattr(request, "output_schema", None)
40
+ if output_schema and request.engine == "claude" and "--json-schema" in argv:
41
+ redacted = list(argv)
42
+ idx = redacted.index("--json-schema")
43
+ if idx + 1 < len(redacted):
44
+ redacted[idx + 1] = f"<json-schema:{len(redacted[idx + 1])} bytes>"
45
+ return redacted
46
+ return argv