delegate-agent-cli 0.22.0__tar.gz → 0.23.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 (146) hide show
  1. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/CHANGELOG.md +11 -0
  2. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/PKG-INFO +2 -1
  3. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/README.md +1 -0
  4. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/cli-reference.md +3 -0
  5. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/delegate-workflows.md +2 -0
  6. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/pyproject.toml +1 -1
  7. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/__init__.py +1 -1
  8. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/cli.py +6 -1
  9. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/cli_parser.py +35 -1
  10. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/command_help.py +30 -1
  11. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/inspection_commands.py +31 -0
  12. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/rendering.py +18 -0
  13. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/run_registry.py +155 -3
  14. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent_cli.egg-info/PKG-INFO +2 -1
  15. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_delegate_help_cli.py +1 -0
  16. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_delegate_parser.py +24 -0
  17. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_inspection_commands.py +170 -1
  18. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/CONTRIBUTING.md +0 -0
  19. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/LICENSE +0 -0
  20. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/MANIFEST.in +0 -0
  21. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/SECURITY.md +0 -0
  22. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/bin/delegate-profile-shim +0 -0
  23. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/bin/delegate.py +0 -0
  24. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/config.example.json +0 -0
  25. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/agent-setup.md +0 -0
  26. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/assets/delegate-agent-header.png +0 -0
  27. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/configuration.md +0 -0
  28. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/development.md +0 -0
  29. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/live-runtime.md +0 -0
  30. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/publishing-checklist.md +0 -0
  31. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/security-model.md +0 -0
  32. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/troubleshooting.md +0 -0
  33. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/docs/worktrees.md +0 -0
  34. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/examples/task.claude.json +0 -0
  35. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/examples/task.codex.json +0 -0
  36. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/examples/task.cursor.json +0 -0
  37. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/examples/task.droid.json +0 -0
  38. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/examples/task.grok.json +0 -0
  39. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/examples/task.judge.json +0 -0
  40. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/examples/task.kimi.json +0 -0
  41. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/setup.cfg +0 -0
  42. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/archived_logs.py +0 -0
  43. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/argv_builders.py +0 -0
  44. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/argv_utils.py +0 -0
  45. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/bundled_models.py +0 -0
  46. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/capability_commands.py +0 -0
  47. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/child_failures.py +0 -0
  48. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/command_errors.py +0 -0
  49. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/config.py +0 -0
  50. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/config_commands.py +0 -0
  51. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/constants.py +0 -0
  52. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/describe_payload.py +0 -0
  53. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/errors.py +0 -0
  54. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/git_utils.py +0 -0
  55. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/harness_discovery.py +0 -0
  56. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/harness_events.py +0 -0
  57. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/isolation.py +0 -0
  58. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/json_types.py +0 -0
  59. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/log_output.py +0 -0
  60. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/model_discovery.py +0 -0
  61. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/private_io.py +0 -0
  62. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/profile_commands.py +0 -0
  63. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/profile_guard.py +0 -0
  64. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/profiles.py +0 -0
  65. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/prompt_instructions.py +0 -0
  66. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/prompt_transport.py +0 -0
  67. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/reasoning.py +0 -0
  68. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/redaction.py +0 -0
  69. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/request_build.py +0 -0
  70. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/request_models.py +0 -0
  71. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/retention.py +0 -0
  72. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/run_metadata.py +0 -0
  73. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/run_output_commands.py +0 -0
  74. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/run_status.py +0 -0
  75. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/runner.py +0 -0
  76. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/safe_workspace.py +0 -0
  77. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/seatbelt.py +0 -0
  78. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/setup_commands.py +0 -0
  79. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/snapshot_view.py +0 -0
  80. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/structured_output.py +0 -0
  81. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/wait_cancel_commands.py +0 -0
  82. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/workflows/__init__.py +0 -0
  83. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/workflows/commands.py +0 -0
  84. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/workflows/registry.py +0 -0
  85. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/workflows/runtime.py +0 -0
  86. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/workflows/schema.py +0 -0
  87. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/workflows/script.py +0 -0
  88. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/worktree_commands.py +0 -0
  89. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/worktree_execution.py +0 -0
  90. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/worktree_gc.py +0 -0
  91. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/worktree_mgmt.py +0 -0
  92. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/worktree_records.py +0 -0
  93. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/worktree_remove.py +0 -0
  94. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/worktree_summary.py +0 -0
  95. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent/wsl.py +0 -0
  96. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent_cli.egg-info/SOURCES.txt +0 -0
  97. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent_cli.egg-info/dependency_links.txt +0 -0
  98. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent_cli.egg-info/entry_points.txt +0 -0
  99. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent_cli.egg-info/requires.txt +0 -0
  100. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/src/delegate_agent_cli.egg-info/top_level.txt +0 -0
  101. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_capability_commands.py +0 -0
  102. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_child_failures.py +0 -0
  103. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_codex_pure_sandbox.py +0 -0
  104. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_codex_thread_resilience.py +0 -0
  105. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_command_help.py +0 -0
  106. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_config_commands.py +0 -0
  107. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_delegate_isolation.py +0 -0
  108. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_delegate_validation.py +0 -0
  109. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_end_to_end_tracking.py +0 -0
  110. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_engine_argv.py +0 -0
  111. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_execution_argv_and_prompt.py +0 -0
  112. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_execution_dry_run.py +0 -0
  113. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_execution_worktree_failure_cleanup.py +0 -0
  114. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_execution_worktree_preflight.py +0 -0
  115. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_execution_worktree_run.py +0 -0
  116. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_harness_discovery.py +0 -0
  117. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_harness_events.py +0 -0
  118. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_kimi_commands.py +0 -0
  119. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_model_discovery.py +0 -0
  120. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_model_selection_wave1a.py +0 -0
  121. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_model_selection_wave1b.py +0 -0
  122. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_omp_read_only_behavior.py +0 -0
  123. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_packaging.py +0 -0
  124. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_profiles.py +0 -0
  125. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_pure_call.py +0 -0
  126. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_reasoning_capabilities.py +0 -0
  127. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_retention.py +0 -0
  128. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_run_registry.py +0 -0
  129. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_runner_capture.py +0 -0
  130. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_safe_workspace_isolation.py +0 -0
  131. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_setup_commands.py +0 -0
  132. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_slash_passthrough.py +0 -0
  133. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_snapshot_redaction.py +0 -0
  134. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_snapshot_rendering.py +0 -0
  135. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_snapshot_run_output.py +0 -0
  136. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_snapshot_view.py +0 -0
  137. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_structured_output.py +0 -0
  138. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_tracked_timeout.py +0 -0
  139. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_utility_modules.py +0 -0
  140. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_wait_cancel_commands.py +0 -0
  141. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_wave4_launch_features.py +0 -0
  142. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_workflow_commands.py +0 -0
  143. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_worktree_list_show.py +0 -0
  144. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_worktree_prune_gc.py +0 -0
  145. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_worktree_remove.py +0 -0
  146. {delegate_agent_cli-0.22.0 → delegate_agent_cli-0.23.0}/tests/test_wsl_guardrails.py +0 -0
@@ -5,6 +5,16 @@ 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.23.0] - 2026-07-30
9
+
10
+ ### Added
11
+
12
+ - Added `delegate runs prune` with a 30-day default age threshold, an explicit `--older-than` override, and `--dry-run` preview output. It prunes terminal Run records and their associated Registry artifacts (per-Run directory, logs, Completion Report, retained raw-log archive) without touching genuinely running Runs. Persistent-worktree Runs are skipped unless their worktree is recorded as removed or missing — unknown or unrecorded worktree status is treated as possibly present, mirroring `worktree gc` — so live worktrees never lose their Registry records. The ambient retention pass is bypassed on the prune command path so a dry run never mutates state.
13
+
14
+ ### Fixed
15
+
16
+ - Focused unittest invocations (`python3 -m unittest tests.<module>`) no longer fail on the src layout import: `tests/__init__.py` now puts `src` on `sys.path` before any test module import runs.
17
+
8
18
  ## [0.22.0] - 2026-07-26
9
19
 
10
20
  ### Changed
@@ -759,6 +769,7 @@ Usage-audit fix wave: 82 sessions and 1,241 delegate invocations from one week o
759
769
 
760
770
  - Releases before 0.1.3 predate this changelog.
761
771
 
772
+ [0.23.0]: https://github.com/treygoff24/delegate-agent/compare/v0.22.0...v0.23.0
762
773
  [0.22.0]: https://github.com/treygoff24/delegate-agent/compare/v0.21.0...v0.22.0
763
774
  [0.21.0]: https://github.com/treygoff24/delegate-agent/compare/v0.20.0...v0.21.0
764
775
  [0.20.0]: https://github.com/treygoff24/delegate-agent/compare/v0.19.0...v0.20.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: delegate-agent-cli
3
- Version: 0.22.0
3
+ Version: 0.23.0
4
4
  Summary: A tiny CLI for delegating bounded agent tasks across coding-agent runtimes.
5
5
  Author: Trey Goff
6
6
  License-Expression: MIT
@@ -332,6 +332,7 @@ delegate runs --recent
332
332
  delegate ps # active and stale tracked runs
333
333
  delegate snapshot <alias-or-runId>
334
334
  delegate run-output <alias-or-runId>
335
+ delegate runs prune --dry-run # preview GC of terminal run records older than 30 days
335
336
  ```
336
337
 
337
338
  Aliases are always numbered (`codex-1`, `cursor-2`); a **bare harness name
@@ -299,6 +299,7 @@ delegate runs --recent
299
299
  delegate ps # active and stale tracked runs
300
300
  delegate snapshot <alias-or-runId>
301
301
  delegate run-output <alias-or-runId>
302
+ delegate runs prune --dry-run # preview GC of terminal run records older than 30 days
302
303
  ```
303
304
 
304
305
  Aliases are always numbered (`codex-1`, `cursor-2`); a **bare harness name
@@ -931,6 +931,7 @@ Tracked runs return bounded parent-facing output and store local metadata under
931
931
 
932
932
  ```bash
933
933
  delegate runs [--active|--running|--stale|--recent] [--harness HARNESS] [--group NAME] [--limit N]
934
+ delegate runs prune [--older-than DAYS] [--dry-run]
934
935
  delegate ps [--harness HARNESS] [--group NAME] [--limit N]
935
936
  delegate snapshot [--latest HARNESS] [--no-redact] <handle>
936
937
  delegate run-output [--latest HARNESS] <handle> [--completion-report] [--stdout] [--stderr] [--tail N] [--max-chars N] [--raw] [--no-redact]
@@ -942,6 +943,8 @@ delegate cancel <handle>...
942
943
  `delegate ps` is the shorter first-class form of `delegate runs --active` and
943
944
  accepts the same harness, group, and limit selectors.
944
945
 
946
+ `delegate runs prune` removes old terminal Run records from the workspace Registry so they stop accumulating forever. Only runs whose effective status is terminal (`succeeded`, `failed`, `cancelled`, or `stale` from a dead child) and whose last Registry activity is older than the threshold (default 30 days; override with `--older-than DAYS`) are eligible. Effectively running runs are always skipped, and persistent-worktree runs are skipped unless their worktree is recorded as removed or missing — pruning the record of a live worktree would orphan it from `worktree list`/`show`/`remove`. Pruning deletes the per-Run directory (Snapshot, Manifest, logs, events, Completion Report) and the retained raw-log archive; worktree paths on disk are never touched. `--dry-run` reports what would be removed without changing anything. JSON output uses schema `delegate.runs-prune.v1` with `planned`, `removed`, `skipped` (each entry carries a `reason`), and `errors` sections.
947
+
945
948
  Run-scoped handles (`snapshot`, `run-output`, `wait`, and `cancel`)
946
949
  resolve exact run IDs and numbered aliases first. A bare harness name such as
947
950
  `codex` resolves to that harness's latest run, and `harness:modelAlias` (for
@@ -175,6 +175,7 @@ engines = args.get("engines", ["codex", "claude", "cursor"])
175
175
  models = args.get("models", {})
176
176
  prompt = args["prompt"]
177
177
 
178
+
178
179
  def reviewer(engine):
179
180
  return lambda: agent(
180
181
  prompt,
@@ -184,6 +185,7 @@ def reviewer(engine):
184
185
  phase=f"Review ({engine})",
185
186
  )
186
187
 
188
+
187
189
  reviews = parallel([reviewer(engine) for engine in engines])
188
190
  return {"reviews": dict(zip(engines, reviews))}
189
191
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "delegate-agent-cli"
7
- version = "0.22.0"
7
+ version = "0.23.0"
8
8
  description = "A tiny CLI for delegating bounded agent tasks across coding-agent 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.22.0"
3
+ VERSION = "0.23.0"
4
4
 
5
5
  __all__ = ["VERSION"]
@@ -1180,7 +1180,12 @@ def main(
1180
1180
  "workflow",
1181
1181
  }:
1182
1182
  existing_registry = run_registry.registry_root_if_exists(Path(workspace.path))
1183
- if existing_registry is not None:
1183
+ pruning_runs = (
1184
+ parsed.subcommand == "runs"
1185
+ and parsed.runs is not None
1186
+ and parsed.runs.action == "prune"
1187
+ )
1188
+ if existing_registry is not None and not pruning_runs:
1184
1189
  maybe_run_retention_pass(existing_registry, config)
1185
1190
  if parsed.subcommand == "snapshot":
1186
1191
  return emit_snapshot(parsed, workspace, stdout)
@@ -1380,7 +1380,10 @@ def parse_runs(
1380
1380
  # front-ends (ps) do not report errors against flags nobody entered.
1381
1381
  rest, json_mode = consume_json_option(rest, json_mode)
1382
1382
  if any(command_help.is_help_token(token) for token in rest):
1383
- return help_command(json_mode, "runs")
1383
+ topic = "runs prune" if rest and rest[0] == "prune" else "runs"
1384
+ return help_command(json_mode, topic)
1385
+ if rest and rest[0] == "prune":
1386
+ return parse_runs_prune(rest[1:], json_mode, cwd)
1384
1387
  active = False
1385
1388
  recent = False
1386
1389
  running = False
@@ -1468,6 +1471,37 @@ def parse_runs(
1468
1471
  )
1469
1472
 
1470
1473
 
1474
+ def parse_runs_prune(rest: list[str], json_mode: bool, cwd: str | None) -> ParsedCommand:
1475
+ rest, json_mode = consume_json_option(rest, json_mode)
1476
+ if any(command_help.is_help_token(token) for token in rest):
1477
+ return help_command(json_mode, "runs prune")
1478
+ older_than_days = None
1479
+ dry_run = False
1480
+ i = 0
1481
+ while i < len(rest):
1482
+ token = rest[i]
1483
+ if token == "--older-than":
1484
+ value = _require_option_value(rest, i, token)
1485
+ older_than_days = parse_non_negative_int(value, option="runs prune --older-than")
1486
+ i += 2
1487
+ continue
1488
+ if token == "--dry-run":
1489
+ dry_run = True
1490
+ i += 1
1491
+ continue
1492
+ raise DelegateError("unknown_option", f"runs prune does not support option: {token}")
1493
+ return ParsedCommand(
1494
+ "runs",
1495
+ global_options=GlobalOptions(json_mode=json_mode, cwd=cwd),
1496
+ runs=inspection_commands.RunsCommand(
1497
+ action="prune",
1498
+ older_than_days=older_than_days,
1499
+ dry_run=dry_run,
1500
+ json_mode=json_mode,
1501
+ ),
1502
+ )
1503
+
1504
+
1471
1505
  def parse_ps(rest: list[str], json_mode: bool, cwd: str | None) -> ParsedCommand:
1472
1506
  rest, json_mode = consume_json_option(rest, json_mode)
1473
1507
  if any(command_help.is_help_token(token) for token in rest):
@@ -16,6 +16,7 @@ from dataclasses import dataclass, field
16
16
  from delegate_agent import VERSION
17
17
  from delegate_agent.constants import ENGINES_PROSE
18
18
  from delegate_agent.json_types import JsonObject
19
+ from delegate_agent.run_registry import DEFAULT_RUN_PRUNE_DAYS
19
20
 
20
21
 
21
22
  @dataclass(frozen=True)
@@ -818,7 +819,34 @@ COMMAND_SPECS: dict[str, CommandSpec] = {
818
819
  "delegate runs --group wave4",
819
820
  ),
820
821
  notes=("--active, --running, --stale, and --recent are mutually exclusive.",),
821
- see_also=("ps", "snapshot", "run-output"),
822
+ see_also=("ps", "runs prune", "snapshot", "run-output"),
823
+ unsupported_global_options=("--auth-profile",),
824
+ ),
825
+ "runs prune": CommandSpec(
826
+ name="runs prune",
827
+ summary="Prune old terminal Run records from the workspace Registry.",
828
+ usage=("delegate [--cwd PATH] [--json] runs prune [--older-than DAYS] [--dry-run]",),
829
+ options=(
830
+ OptionSpec(
831
+ "--older-than",
832
+ "DAYS",
833
+ f"Prune Runs older than DAYS by Registry activity time (defaults to {DEFAULT_RUN_PRUNE_DAYS}; non-negative integer).",
834
+ ),
835
+ OptionSpec(
836
+ "--dry-run",
837
+ None,
838
+ "Report planned removals without changing the Registry, logs, or artifacts.",
839
+ ),
840
+ ),
841
+ examples=(
842
+ "delegate runs prune --dry-run",
843
+ "delegate runs prune --older-than 14",
844
+ ),
845
+ notes=(
846
+ "Only terminal effective statuses are eligible: succeeded, failed, cancelled, and stale from a dead child; effective running Runs are always skipped.",
847
+ "Runs with a registered present persistent worktree are skipped, so their worktree remains manageable. Pruning removes the per-Run directory, including its Snapshot, Manifest, logs, events, and Completion Report, plus the retained raw-log archive.",
848
+ ),
849
+ see_also=("runs", "snapshot", "run-output"),
822
850
  unsupported_global_options=("--auth-profile",),
823
851
  ),
824
852
  "ps": CommandSpec(
@@ -1745,6 +1773,7 @@ def render_overview_text() -> str:
1745
1773
  "delegate [--cwd PATH] [--json] snapshot [--latest HARNESS] [--no-redact] <handle>",
1746
1774
  "delegate [--cwd PATH] [--json] runs "
1747
1775
  "[--active|--running|--stale|--recent] [--harness HARNESS] [--limit N]",
1776
+ "delegate [--cwd PATH] [--json] runs prune [--older-than DAYS] [--dry-run]",
1748
1777
  "delegate [--cwd PATH] [--json] ps [--harness HARNESS] [--group NAME] [--limit N]",
1749
1778
  "delegate [--cwd PATH] [--json] run-output <handle> "
1750
1779
  "[--completion-report] [--stdout] [--stderr] [--tail N] [--max-chars N] "
@@ -19,12 +19,15 @@ class SnapshotCommand:
19
19
 
20
20
  @dataclass(frozen=True)
21
21
  class RunsCommand:
22
+ action: str | None = None
22
23
  active: bool = False
23
24
  running: bool = False
24
25
  stale: bool = False
25
26
  harness: str | None = None
26
27
  group: str | None = None
27
28
  limit: int | None = None
29
+ older_than_days: int | None = None
30
+ dry_run: bool = False
28
31
  json_mode: bool = False
29
32
 
30
33
 
@@ -64,6 +67,34 @@ def emit_snapshot(command: SnapshotCommand, *, workspace_path: str, stdout: Text
64
67
 
65
68
  def emit_runs(command: RunsCommand, *, workspace_path: str, stdout: TextIO) -> int:
66
69
  registry_root = run_registry.registry_root_if_exists(Path(workspace_path))
70
+ if command.action == "prune":
71
+ older_than_days = (
72
+ command.older_than_days
73
+ if command.older_than_days is not None
74
+ else run_registry.DEFAULT_RUN_PRUNE_DAYS
75
+ )
76
+ payload = (
77
+ run_registry.prune_runs(
78
+ registry_root,
79
+ older_than_days=older_than_days,
80
+ dry_run=command.dry_run,
81
+ )
82
+ if registry_root is not None
83
+ else run_registry.empty_run_prune_payload(
84
+ older_than_days=older_than_days,
85
+ dry_run=command.dry_run,
86
+ )
87
+ )
88
+ if command.json_mode:
89
+ delegate_rendering.print_json(payload, stdout)
90
+ else:
91
+ delegate_rendering.render_runs_prune_text(payload, stdout)
92
+ if payload.get("ok") is False:
93
+ exit_code = payload.get("exitCode")
94
+ return exit_code if isinstance(exit_code, int) else 1
95
+ return 0
96
+ if command.action is not None:
97
+ raise InspectionError("unknown_runs_action", f"Unknown runs action: {command.action}")
67
98
  limit = command.limit or run_registry.DEFAULT_RUNS_LIMIT
68
99
  if command.running:
69
100
  mode = "running"
@@ -554,6 +554,24 @@ def render_worktree_prune_text(payload: JsonObject, stdout: TextIO) -> None:
554
554
  print(f" - {label} {detail}", file=stdout)
555
555
 
556
556
 
557
+ def render_runs_prune_text(payload: JsonObject, stdout: TextIO) -> None:
558
+ print(f"older than: {payload.get('olderThanDays', '?')} days", file=stdout)
559
+ if payload.get("dryRun") is True:
560
+ print("dry run: Registry, logs, and artifacts unchanged", file=stdout)
561
+ for section in ("planned", "removed", "skipped", "errors"):
562
+ items = payload.get(section)
563
+ count = len(items) if isinstance(items, list) else 0
564
+ print(f"{section}: {count}", file=stdout)
565
+ if isinstance(items, list):
566
+ for item in items[:20]:
567
+ if isinstance(item, dict):
568
+ label = item.get("alias") or item.get("runId") or "?"
569
+ detail = (
570
+ item.get("reason") or item.get("code") or item.get("effectiveStatus") or ""
571
+ )
572
+ print(f" - {label} {detail}", file=stdout)
573
+
574
+
557
575
  def render_worktree_gc_text(payload: JsonObject, stdout: TextIO) -> None:
558
576
  print(f"reconciled: {payload.get('reconciled', 0)}", file=stdout)
559
577
  if payload.get("dryRun") is True:
@@ -6,16 +6,17 @@ import os
6
6
  import re
7
7
  import secrets
8
8
  import shlex
9
+ import shutil
9
10
  import subprocess
10
11
  import time
11
12
  from collections.abc import Iterator
12
13
  from contextlib import contextmanager
13
14
  from dataclasses import dataclass
14
- from datetime import UTC, datetime
15
+ from datetime import UTC, datetime, timedelta
15
16
  from pathlib import Path
16
17
 
17
- from delegate_agent import private_io
18
- from delegate_agent.json_types import JsonObject
18
+ from delegate_agent import archived_logs, private_io
19
+ from delegate_agent.json_types import JsonObject, is_non_negative_int
19
20
  from delegate_agent.private_io import ( # noqa: F401 # re-exported
20
21
  RegistryJsonError,
21
22
  ensure_private_dir,
@@ -47,6 +48,9 @@ STATE_SCHEMA = "delegate.state.v1"
47
48
  SNAPSHOT_SCHEMA = "delegate.snapshot.v1"
48
49
  RUNS_SCHEMA = "delegate.runs.v1"
49
50
  RUN_OUTPUT_SCHEMA = "delegate.run-output.v1"
51
+ RUNS_PRUNE_SCHEMA = "delegate.runs-prune.v1"
52
+ DEFAULT_RUN_PRUNE_DAYS = 30
53
+ RUN_PRUNE_ERROR_EXIT_CODE = 1
50
54
  REGISTRY_LOCK_NAME = ".registry.lock"
51
55
  REGISTRY_LOCK_TIMEOUT_SECONDS = 30.0
52
56
  REGISTRY_LOCK_POLL_SECONDS = 0.05
@@ -832,3 +836,151 @@ from delegate_agent.run_status import ( # noqa: E402, F401 # re-exported
832
836
  stale_next_actions,
833
837
  status_fields,
834
838
  )
839
+
840
+
841
+ def _run_prune_ref(
842
+ index: JsonObject,
843
+ run_id: str,
844
+ status: str,
845
+ *,
846
+ reason: str | None = None,
847
+ ) -> JsonObject:
848
+ entry: JsonObject = {
849
+ "alias": alias_for_run(index, run_id),
850
+ "runId": run_id,
851
+ "effectiveStatus": status,
852
+ }
853
+ if reason is not None:
854
+ entry["reason"] = reason
855
+ return entry
856
+
857
+
858
+ def _remove_run_record_artifacts(registry_root: Path, run_id: str) -> None:
859
+ run_path = run_directory(registry_root, run_id)
860
+ if run_path.is_symlink():
861
+ raise OSError(f"refusing to prune symlinked run directory: {run_path}")
862
+ if run_path.exists():
863
+ shutil.rmtree(run_path)
864
+ archived_logs.archive_path(registry_root, run_id).unlink(missing_ok=True)
865
+
866
+
867
+ def empty_run_prune_payload(*, older_than_days: int, dry_run: bool) -> JsonObject:
868
+ return {
869
+ "schema": RUNS_PRUNE_SCHEMA,
870
+ "ok": True,
871
+ "olderThanDays": older_than_days,
872
+ "dryRun": dry_run,
873
+ "planned": [],
874
+ "removed": [],
875
+ "skipped": [],
876
+ "errors": [],
877
+ }
878
+
879
+
880
+ def prune_runs(
881
+ registry_root: Path,
882
+ *,
883
+ older_than_days: int = DEFAULT_RUN_PRUNE_DAYS,
884
+ dry_run: bool = False,
885
+ now: datetime | None = None,
886
+ ) -> JsonObject:
887
+ """Prune old terminal Run records and their per-Run artifacts."""
888
+ if not is_non_negative_int(older_than_days):
889
+ raise ValueError("older_than_days must be a non-negative integer")
890
+ # worktree_records imports this module, so keep this dependency local.
891
+ from delegate_agent import worktree_records
892
+
893
+ cutoff = (now or datetime.now(UTC)) - timedelta(days=older_than_days)
894
+ planned: list[JsonObject] = []
895
+ removed: list[JsonObject] = []
896
+ skipped: list[JsonObject] = []
897
+ errors: list[JsonObject] = []
898
+ index_changed = False
899
+
900
+ with registry_lock(registry_root):
901
+ index = load_index(registry_root)
902
+ # Like worktree gc, treat unknown/unrecorded status as possibly present:
903
+ # only records whose worktree is affirmatively gone are prunable.
904
+ present_persistent_worktree_run_ids = {
905
+ record["runId"]
906
+ for record in worktree_records.load_persistent_records(registry_root)
907
+ if record.get("registryWorktreeStatus")
908
+ not in (worktree_records.STATUS_REMOVED, worktree_records.STATUS_MISSING)
909
+ }
910
+ for run_id, index_entry in list(index.get("runs", {}).items()):
911
+ if not isinstance(run_id, str) or not isinstance(index_entry, dict):
912
+ continue
913
+ if not RUN_ID_RE.fullmatch(run_id):
914
+ skipped.append(
915
+ _run_prune_ref(
916
+ index, run_id, run_status.STATUS_UNKNOWN, reason="invalid_run_id"
917
+ )
918
+ )
919
+ continue
920
+ state = load_run_state_or_none(registry_root, run_id)
921
+ effective_status = run_status.effective_status(state)
922
+ if run_id in present_persistent_worktree_run_ids:
923
+ skipped.append(
924
+ _run_prune_ref(
925
+ index,
926
+ run_id,
927
+ effective_status,
928
+ reason="persistent_worktree",
929
+ )
930
+ )
931
+ continue
932
+ if effective_status == run_status.STATUS_RUNNING:
933
+ skipped.append(_run_prune_ref(index, run_id, effective_status, reason="running"))
934
+ continue
935
+ if effective_status not in run_status.TERMINAL_STATUSES | {run_status.STATUS_STALE}:
936
+ skipped.append(
937
+ _run_prune_ref(index, run_id, effective_status, reason="non_terminal")
938
+ )
939
+ continue
940
+ manifest = load_run_manifest_or_none(registry_root, run_id)
941
+ activity = run_status.activity_datetime(state, manifest, run_id)
942
+ if activity is None:
943
+ skipped.append(
944
+ _run_prune_ref(index, run_id, effective_status, reason="invalid_activity")
945
+ )
946
+ continue
947
+ if activity >= cutoff:
948
+ skipped.append(
949
+ _run_prune_ref(index, run_id, effective_status, reason="not_yet_old_enough")
950
+ )
951
+ continue
952
+ candidate = _run_prune_ref(index, run_id, effective_status)
953
+ candidate["activityAt"] = run_status.activity_timestamp(state, manifest, run_id)
954
+ planned.append(candidate)
955
+ if dry_run:
956
+ continue
957
+ try:
958
+ _remove_run_record_artifacts(registry_root, run_id)
959
+ except OSError as exc:
960
+ errors.append({**candidate, "code": "record_remove_failed", "message": str(exc)})
961
+ continue
962
+ for alias, target_run_id in list(index.get("aliases", {}).items()):
963
+ if target_run_id != run_id:
964
+ continue
965
+ index["aliases"].pop(alias, None)
966
+ if isinstance(alias, str) and alias and alias not in {".", ".."}:
967
+ claim_path = aliases_dir(registry_root) / alias
968
+ if Path(alias).name == alias and (
969
+ claim_path.is_file() or claim_path.is_symlink()
970
+ ):
971
+ claim_path.unlink(missing_ok=True)
972
+ index["runs"].pop(run_id, None)
973
+ removed.append(candidate)
974
+ index_changed = True
975
+ if index_changed:
976
+ save_index(registry_root, index)
977
+
978
+ payload = empty_run_prune_payload(older_than_days=older_than_days, dry_run=dry_run)
979
+ payload["planned"] = planned
980
+ payload["removed"] = removed
981
+ payload["skipped"] = skipped
982
+ payload["errors"] = errors
983
+ if errors:
984
+ payload["ok"] = False
985
+ payload["exitCode"] = RUN_PRUNE_ERROR_EXIT_CODE
986
+ return payload
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: delegate-agent-cli
3
- Version: 0.22.0
3
+ Version: 0.23.0
4
4
  Summary: A tiny CLI for delegating bounded agent tasks across coding-agent runtimes.
5
5
  Author: Trey Goff
6
6
  License-Expression: MIT
@@ -332,6 +332,7 @@ delegate runs --recent
332
332
  delegate ps # active and stale tracked runs
333
333
  delegate snapshot <alias-or-runId>
334
334
  delegate run-output <alias-or-runId>
335
+ delegate runs prune --dry-run # preview GC of terminal run records older than 30 days
335
336
  ```
336
337
 
337
338
  Aliases are always numbered (`codex-1`, `cursor-2`); a **bare harness name
@@ -101,6 +101,7 @@ class MultiLevelHelpTests(HelpCliTestBase):
101
101
  (["dry-run", "droid", "--help"], "dry-run"),
102
102
  (["worktree", "remove", "--help"], "worktree remove"),
103
103
  (["worktree", "prune", "--help"], "worktree prune"),
104
+ (["runs", "prune", "--help"], "runs prune"),
104
105
  (["worktree", "list", "--help"], "worktree list"),
105
106
  (["worktree", "show", "--help"], "worktree show"),
106
107
  (["worktree", "gc", "--help"], "worktree gc"),
@@ -1100,6 +1100,30 @@ class ParserTests(unittest.TestCase):
1100
1100
  self.delegate.parse_cli(["runs", "--limit", "0"])
1101
1101
  self.assertEqual(ctx.exception.error, "invalid_limit")
1102
1102
 
1103
+ def test_runs_prune_parses_age_override_and_dry_run(self):
1104
+ parsed = self.delegate.parse_cli(
1105
+ [
1106
+ "--json",
1107
+ "--cwd",
1108
+ "/tmp/repo",
1109
+ "runs",
1110
+ "prune",
1111
+ "--older-than",
1112
+ "14",
1113
+ "--dry-run",
1114
+ ]
1115
+ )
1116
+ self.assertEqual(parsed.subcommand, "runs")
1117
+ self.assertEqual(parsed.runs.action, "prune")
1118
+ self.assertEqual(parsed.runs.older_than_days, 14)
1119
+ self.assertTrue(parsed.runs.dry_run)
1120
+ self.assertTrue(parsed.runs.json_mode)
1121
+
1122
+ def test_runs_prune_rejects_negative_age(self):
1123
+ with self.assertRaises(self.delegate.DelegateError) as ctx:
1124
+ self.delegate.parse_cli(["runs", "prune", "--older-than", "-1"])
1125
+ self.assertEqual(ctx.exception.error, "missing_option_value")
1126
+
1103
1127
  def test_has_misplaced_global_option_detects_exact_tokens(self):
1104
1128
  self.assertFalse(self.delegate.has_misplaced_global_option([]))
1105
1129
  self.assertFalse(self.delegate.has_misplaced_global_option(["--jsonish"]))