[AUTO-DOCS-2] Add decision tree, invariant, checkpoint, and plan correction API docs (v3.2.0/v3.3.0) #8957
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.2
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.3
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ temperature: 0.2
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
color: success
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.2
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.2
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -7,7 +7,11 @@ hidden: true
|
||||
temperature: 0.3
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -7,7 +7,11 @@ temperature: 0.2
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
color: "#059669"
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
@@ -61,6 +65,7 @@ You manually fix a specific pull request. The user tells you which PR to fix. Yo
|
||||
4. Fix the code — address both CI failures and review feedback.
|
||||
5. Run quality gates locally (`nox -e lint`, `nox -e typecheck`, `nox -e unit_tests`, `nox -e integration_tests`).
|
||||
6. Commit and push using `git-commit-helper`.
|
||||
7. Clean up the isolated clone using `repo-isolator`.
|
||||
|
||||
## Rules
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ hidden: true
|
||||
temperature: 0.1
|
||||
# No model specified — tier is set by the supervisor via tier selectors
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.2
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ temperature: 0.2
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
color: warning
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ temperature: 0.1
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
color: warning
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -9,7 +9,11 @@ temperature: 0.1
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
color: warning
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
@@ -46,7 +50,7 @@ permission:
|
||||
|
||||
# PR CI Test Fixer
|
||||
|
||||
You fix failing CI checks on a PR branch. You work in an isolated clone directory.
|
||||
You fix failing CI checks on a PR branch. You work in an isolated clone directory — `$WORK_DIR` is always a path inside `/tmp/` created by `repo-isolator`. All file edits and every git operation (`add`, `commit`, `push`, branch switching) must be performed inside `$WORK_DIR`, never against `/app`.
|
||||
|
||||
## What You Do
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: >
|
||||
PR merge supervisor. Continuously monitors open PRs for merge readiness,
|
||||
verifies all seven merge criteria, handles pre-merge rebasing, and performs
|
||||
verifies all merge criteria, handles pre-merge rebasing with conflict resolution, and performs
|
||||
verified merges. Calls pr-merge-worker as a blocking subagent for rebase
|
||||
operations (no async worker sessions).
|
||||
mode: subagent
|
||||
@@ -10,7 +10,11 @@ temperature: 0.1
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
color: "#059669"
|
||||
permission:
|
||||
edit: deny
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: deny
|
||||
bash:
|
||||
"*": deny
|
||||
@@ -53,7 +57,7 @@ permission:
|
||||
|
||||
# PR Merge Supervisor
|
||||
|
||||
You are a supervisor that monitors open PRs for merge readiness, verifies all criteria are met, and merges them. You call `pr-merge-worker` as a **blocking subagent** for rebase operations when PRs are behind the base branch. Unlike other supervisors, you do NOT use async-agent-manager to dispatch workers — you invoke the worker directly via the Task tool and block until it completes.
|
||||
You are a supervisor that monitors open PRs for merge readiness, verifies all criteria are met, rebases stale PR, resolves conflicts, and merges them. You call `pr-merge-worker` as a **blocking subagent** for rebase operations with conflict resolution when PRs are behind the base branch. Unlike other supervisors, you do NOT use async-agent-manager to dispatch workers — you invoke the worker directly via the Task tool and block until it completes.
|
||||
|
||||
## What You Receive
|
||||
|
||||
@@ -99,9 +103,11 @@ Before merging any PR, the following must be true:
|
||||
5. **No `needs feedback` label** — the PR is not waiting for human input
|
||||
6. **Not blocked** — no `Blocked` label
|
||||
|
||||
**Note:** The above criteria do not need to be satisfied before dispatching a `pr-merge-worker`. While they must be satisfied before the actual merge, the rebasing and conflict resolution steps are still useful even if it isnt ready to be merged.
|
||||
|
||||
## Workers
|
||||
|
||||
You call `pr-merge-worker` as a **blocking subagent** (via the Task tool) for rebase operations when PRs are behind the base branch. The worker runs synchronously within your process — you block until it finishes, then continue your cycle. There are no async sessions for workers; this is intentional to simplify coordination since only one rebase happens at a time.
|
||||
You call `pr-merge-worker` as a **blocking subagent** (via the Task tool) for rebase operations with conflict resolution when PRs are behind the base branch. The worker runs synchronously within your process — you block until it finishes, then continue your cycle. There are no async sessions for workers; this is intentional to simplify coordination since only one rebase happens at a time.
|
||||
|
||||
## Main Loop
|
||||
|
||||
@@ -109,9 +115,9 @@ Poll every 5 minutes using `bash("sleep 300", timeout=360000)`.
|
||||
|
||||
Each cycle:
|
||||
1. List all open PRs. (the forgejo task paginates so be sure to go through every page)
|
||||
2. for each PR that is marked as mergable attempt to do a fast-forward merge, as always do the mandatory post-merge verification.
|
||||
2. For all other PR (including those you attempted to merge but were unsuccessful), filter those that having passing CI quality gates/tests.
|
||||
4. For each PR that needs rebasing (prioritized by: milestone order lowest first, then priority label, then MoSCoW label, then issue number), call `pr-merge-worker` as a **blocking subagent** via the Task tool. Pass the PR number, repository info, and credentials in the prompt. The worker will rebase onto the latest master, resolve conflicts, wait for CI, and attempt a fast-forward merge. You block until the worker finishes before processing the next PR.
|
||||
2. for each PR that can be merged (has a passing CI,, isn't stale, and has at least 1 approval review) do a fast-forward or rebase merge, as always do the mandatory post-merge verification.
|
||||
3. For all other PR (including those you attempted to merge but were unsuccessful) order them as follows: 1) CI passes and has one or more review approval 2) CI is failing without conflicts and has one or more review approval 3) CI is failing with conflicts and has one or more review approval 4) All other PRs, within each of those four categories sort by priority label with the most critical label ("Priority/CI Blocking") coming first.
|
||||
4. For each PR, in the order specified in step 2 above, call `pr-merge-worker` as a **blocking subagent** via the Task tool. Pass the PR number, repository info, and credentials in the prompt. The worker will rebase onto the latest master, resolve conflicts, wait for CI, and attempt a fast-forward or rebase merge. You block until the worker finishes before processing the next PR.
|
||||
5. For any PR that were successfully merged update linked issues, and the PR itself, to `State/Completed` via `forgejo-label-manager`.
|
||||
|
||||
## Tracking
|
||||
@@ -131,6 +137,5 @@ Each cycle:
|
||||
**Automated by CleverAgents Bot**
|
||||
Supervisor: PR Merge | Agent: pr-merge-pool-supervisor
|
||||
```
|
||||
|
||||
6. **Apply labels via `forgejo-label-manager`.** Never apply labels directly or using the Forgejo MCP/task. All label operations must go through `forgejo-label-manager`.
|
||||
7. **Exhaustive pagination for all list results.** Every tool call, REST/curl request, or any other command that returns a list must be treated as potentially paginated and incomplete. Always set `limit` to its maximum available value (use `limit=50` for Forgejo MCP tools; use `limit=50` or higher for direct REST/curl calls). After each list response, check whether the number of returned items equals the page size — if so, there are likely more results; fetch the next page (`page=2`, `page=3`, …) and continue until receiving a partial page. Never assume the first response is the complete result. This rule applies to every list-returning call without exception. *Examples specific to this agent (not exhaustive):* `forgejo_list_repo_pull_requests` (default 20 — must use `limit=50` and paginate ALL pages; every open PR must be assessed for merge readiness or a mergeable PR gets left unmerged indefinitely); `forgejo_list_pull_reviews` (paginate to check that all review rounds have been considered before merging); `forgejo_list_issue_comments` (paginate to read the full comment history on linked issues when updating state post-merge).
|
||||
5. **Apply labels via `forgejo-label-manager`.** Never apply labels directly or using the Forgejo MCP/task. All label operations must go through `forgejo-label-manager`.
|
||||
6. **Exhaustive pagination for all list results.** Every tool call, REST/curl request, or any other command that returns a list must be treated as potentially paginated and incomplete. Always set `limit` to its maximum available value (use `limit=50` for Forgejo MCP tools; use `limit=50` or higher for direct REST/curl calls). After each list response, check whether the number of returned items equals the page size — if so, there are likely more results; fetch the next page (`page=2`, `page=3`, …) and continue until receiving a partial page. Never assume the first response is the complete result. This rule applies to every list-returning call without exception. *Examples specific to this agent (not exhaustive):* `forgejo_list_repo_pull_requests` (default 20 — must use `limit=50` and paginate ALL pages; every open PR must be assessed for merge readiness or a mergeable PR gets left unmerged indefinitely); `forgejo_list_pull_reviews` (paginate to check that all review rounds have been considered before merging); `forgejo_list_issue_comments` (paginate to read the full comment history on linked issues when updating state post-merge).
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
---
|
||||
description: >
|
||||
PR merge worker. Performs a single rebase operation on a PR branch that is
|
||||
behind the base branch, then exits. Called as a blocking subagent by the
|
||||
PR merge worker. Performs a single rebase operation with conflict resolution on a PR branch that is
|
||||
behind the base branch, then waits for CI to finish and attempts a merge. Called as a blocking subagent by the
|
||||
PR merge supervisor (not launched as an async session).
|
||||
mode: subagent
|
||||
hidden: true
|
||||
temperature: 0.1
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
"external_directory":
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
@@ -46,19 +50,19 @@ You perform a single rebase operation on a PR branch, resolve any conflicts, and
|
||||
## Procedure
|
||||
|
||||
Your prompt tells you which PR to rebase. You must:
|
||||
1. Create an isolated clone using the `repo-isolator` subagent ensuring you pass it the branch used by the PR.
|
||||
1. Create an isolated clone using the `repo-isolator` subagent ensuring you pass it the branch used by the PR. Make sure all work is done within this clone's directory.
|
||||
2. Rebase it onto the base branch (usually `master`).
|
||||
3. Resolve any conflicts that arise by reviewing the recent git history and using that to fix the conflicts
|
||||
4. Force-push with lease using `git-commit-helper`
|
||||
5. Clean up the clone.
|
||||
6. Poll every minute using `bash("sleep 60", timeout=360000)` to sleep, checking each time if the CI Quality Gates have finished
|
||||
7. Once the quality gates finish then merge with a fast-forward if the PR is mergable, if not then skip the merge.
|
||||
7. Once the quality gates finish then merge with a fast-forward or rebase merge if the PR is mergable, if not then skip the merge.
|
||||
8. Report back with any relevant details.
|
||||
|
||||
## Rules
|
||||
|
||||
1. **One task, then exit.** Do not loop. Do not sleep. Do not look for more work.
|
||||
2. **Force-push with lease only.** Never use `--force` without `--lease`.
|
||||
3. **Clean up your clone.** Delete the temporary directory before exiting.
|
||||
3. **Clean up your clone.** Delete the temporary clone directory before exiting.
|
||||
4. **Apply labels via `forgejo-label-manager`.** Never apply labels directly or using the Forgejo MCP/task. All label operations must go through `forgejo-label-manager`.
|
||||
5. **Exhaustive pagination for all list results.** Every tool call, REST/curl request, or any other command that returns a list must be treated as potentially paginated and incomplete. Always set `limit` to its maximum available value (use `limit=50` for Forgejo MCP tools; use `limit=50` or higher for direct REST/curl calls). After each list response, check whether the number of returned items equals the page size — if so, there are likely more results; fetch the next page (`page=2`, `page=3`, …) and continue until receiving a partial page. Never assume the first response is the complete result. This rule applies to every list-returning call without exception. *Examples specific to this agent (not exhaustive):* `git log` listing commits during conflict resolution must be fully read; any future REST/curl calls returning JSON arrays must be paginated.
|
||||
|
||||
@@ -9,7 +9,11 @@ temperature: 0.2
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
color: primary
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
@@ -29,6 +33,8 @@ permission:
|
||||
task:
|
||||
"*": deny
|
||||
"forgejo-label-manager": allow
|
||||
"repo-isolator": allow
|
||||
"git-commit-helper": allow
|
||||
"forgejo_*": allow
|
||||
# CRITICAL: Never list repo-level labels — use org labels via forgejo-label-manager
|
||||
"forgejo_list_repo_labels": deny
|
||||
@@ -60,5 +66,6 @@ You set up project infrastructure from scratch. Your caller provides the product
|
||||
1. **Detect before creating.** Always check if something exists before creating it.
|
||||
2. **Never overwrite.** If a file or label already exists, skip it.
|
||||
3. **Credentials from prompt.** All Forgejo PAT, git identity, etc. come from the caller's prompt.
|
||||
4. **Apply labels via `forgejo-label-manager`.** Never apply labels directly or using the Forgejo MCP/task. All label operations must go through `forgejo-label-manager`.
|
||||
5. **Exhaustive pagination for all list results.** Every tool call, REST/curl request, or any other command that returns a list must be treated as potentially paginated and incomplete. Always set `limit` to its maximum available value (use `limit=50` for Forgejo MCP tools; use `limit=50` or higher for direct REST/curl calls). After each list response, check whether the number of returned items equals the page size — if so, there are likely more results; fetch the next page (`page=2`, `page=3`, …) and continue until receiving a partial page. Never assume the first response is the complete result. This rule applies to every list-returning call without exception. *Examples specific to this agent (not exhaustive):* any `forgejo_list_*` calls used when checking for existing labels, milestones, or workflows must be paginated — missing one means re-creating something that already exists.
|
||||
4. **Work in an isolated clone.** Use `repo-isolator` to create an isolated clone of the target repository in `/tmp/`. All file creation and edits must be done in the clone — never directly in `/app`. Use `git-commit-helper` to commit and push changes. Clean up the isolated clone using `repo-isolator` when done.
|
||||
5. **Apply labels via `forgejo-label-manager`.** Never apply labels directly or using the Forgejo MCP/task. All label operations must go through `forgejo-label-manager`.
|
||||
6. **Exhaustive pagination for all list results.** Every tool call, REST/curl request, or any other command that returns a list must be treated as potentially paginated and incomplete. Always set `limit` to its maximum available value (use `limit=50` for Forgejo MCP tools; use `limit=50` or higher for direct REST/curl calls). After each list response, check whether the number of returned items equals the page size — if so, there are likely more results; fetch the next page (`page=2`, `page=3`, …) and continue until receiving a partial page. Never assume the first response is the complete result. This rule applies to every list-returning call without exception. *Examples specific to this agent (not exhaustive):* any `forgejo_list_*` calls used when checking for existing labels, milestones, or workflows must be paginated — missing one means re-creating something that already exists.
|
||||
|
||||
@@ -9,7 +9,9 @@ temperature: 0.1
|
||||
model: openai/gpt-5-codex
|
||||
color: "#6B7280"
|
||||
permission:
|
||||
edit: deny
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
webfetch: deny
|
||||
bash:
|
||||
"*": deny
|
||||
@@ -37,6 +39,8 @@ permission:
|
||||
# CRITICAL: DO NOT use forgejo_add_issue_labels directly
|
||||
# Always delegate to forgejo-label-manager for label operations
|
||||
"forgejo_add_issue_labels": deny
|
||||
"external_directory":
|
||||
"/tmp/**": allow
|
||||
---
|
||||
|
||||
# Repository Isolator
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.2
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.2
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -11,7 +11,11 @@ temperature: 0.1
|
||||
model: openai/gpt-5-codex
|
||||
color: accent
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -9,7 +9,11 @@ temperature: 0.2
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
color: warning
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -7,7 +7,11 @@ hidden: true
|
||||
temperature: 0.0
|
||||
model: openai/gpt-5-codex
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: deny
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -7,7 +7,11 @@ hidden: true
|
||||
temperature: 0.0
|
||||
model: anthropic/claude-haiku-4-5
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: deny
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -7,7 +7,11 @@ hidden: true
|
||||
temperature: 0.0
|
||||
model: anthropic/claude-opus-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: deny
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -7,7 +7,11 @@ hidden: true
|
||||
temperature: 0.0
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: deny
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.1
|
||||
model: anthropic/claude-sonnet-4-6
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
@@ -53,6 +57,7 @@ Your prompt provides the current milestone status data and the timeline file for
|
||||
3. Add new entries — never overwrite existing ones.
|
||||
4. Commit using `git-commit-helper` with a Conventional Changelog message.
|
||||
5. Push and exit. (No PR needed — timeline updates go directly to master.)
|
||||
6. Clean up the isolated clone using `repo-isolator`.
|
||||
|
||||
## Rules
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.1
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -8,7 +8,11 @@ hidden: true
|
||||
temperature: 0.2
|
||||
# NO MODEL SPECIFIED - inherits from caller (tier selector)
|
||||
permission:
|
||||
edit: allow
|
||||
edit:
|
||||
"*": deny
|
||||
"/tmp/**": allow
|
||||
external_directory:
|
||||
"/tmp/**": allow
|
||||
webfetch: allow
|
||||
bash:
|
||||
"*": deny
|
||||
|
||||
@@ -5,6 +5,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add `docs/api/decisions.md` — decision recording and tree CLI reference (v3.2.0)
|
||||
- Add `docs/api/invariants.md` — invariant management CLI reference (v3.2.0)
|
||||
- Add `docs/api/checkpoints.md` — checkpoint and rollback CLI reference (v3.3.0)
|
||||
- Add `docs/api/plan-corrections.md` — plan correction modes and subplan system overview (v3.2.0/v3.3.0)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Automation Profile Silent Fallback** (#8232): `_resolve_profile_for_plan` in
|
||||
@@ -163,6 +170,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
`sandbox_root=.cleveragents/sandbox/`, so LLM file output (`FILE:` blocks)
|
||||
is written to disk during the execute phase. (#4222)
|
||||
|
||||
- **SubplanExecutionService fail_fast cancellation** (#7582): Fixed a race condition where
|
||||
already-running parallel subplans were not cancelled when `fail_fast` fired. Previously,
|
||||
`Future.cancel()` only prevented queued futures from starting but had no effect on
|
||||
in-flight futures that completed after `stop_flag` was set — their `COMPLETE` results
|
||||
were incorrectly included in the merge output. The fix adds a post-completion guard that
|
||||
overrides any non-`ERRORED`/non-`CANCELLED` result to `CANCELLED` when `stop_flag` is
|
||||
active, and clears the associated output to prevent it from entering the merge. Also
|
||||
replaces the O(n) linear `status` lookup in the `as_completed()` loop with an O(1)
|
||||
`status_map` dict pre-computed before the executor block.
|
||||
|
||||
- **Robot Framework TDD Listener Guards** (#5436): Added three guard conditions to the
|
||||
`tdd_expected_fail_listener` `end_test()` function to prevent blindly inverting ALL test
|
||||
failures to passes, which was masking infrastructure errors and causing flaky CI behavior.
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
# Checkpoint and Rollback API (v3.3.0)
|
||||
|
||||
CleverAgents supports checkpointing and rollback to snapshot sandbox state during plan
|
||||
execution and restore it later. This page documents the CLI commands and Python API for
|
||||
managing checkpoints.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Checkpointing introduced in **v3.3.0** allows operators to:
|
||||
|
||||
- Snapshot sandbox state at key points during plan execution.
|
||||
- Roll back to a previous snapshot to undo tool side-effects.
|
||||
- Integrate with the decision-correction revert flow for targeted re-execution.
|
||||
|
||||
Checkpoints are immutable records stored in the `checkpoint_metadata` SQLite table and
|
||||
backed by a `CheckpointRepository`. The `CheckpointService` is registered in the DI
|
||||
container and injected into `ToolRunner`, `SubplanExecutionService`, and `PlanExecutor`.
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### `agents plan checkpoint list`
|
||||
|
||||
List all checkpoints for a plan.
|
||||
|
||||
```bash
|
||||
agents plan checkpoint list <PLAN_ID> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# List checkpoints for a plan
|
||||
agents plan checkpoint list 01HXYZ1234567890ABCDEFGH
|
||||
|
||||
# JSON output for scripting
|
||||
agents plan checkpoint list 01HXYZ1234567890ABCDEFGH --format json
|
||||
```
|
||||
|
||||
Each checkpoint entry shows:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `checkpoint_id` | ULID identifier |
|
||||
| `checkpoint_type` | `pre_write`, `post_step`, or `manual` |
|
||||
| `decision_id` | Optional decision this checkpoint is aligned to |
|
||||
| `sandbox_ref` | Git commit hash or patch reference |
|
||||
| `size_bytes` | Size of the checkpoint data |
|
||||
| `created_at` | UTC creation timestamp |
|
||||
|
||||
---
|
||||
|
||||
### `agents plan rollback`
|
||||
|
||||
Roll back a plan's sandbox to a previously captured checkpoint.
|
||||
|
||||
```bash
|
||||
agents plan rollback [--yes|-y] <PLAN_ID> <CHECKPOINT_ID>
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--yes`, `-y` | Skip the interactive confirmation prompt |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Rollback with confirmation prompt
|
||||
agents plan rollback 01HXYZ1234567890ABCDEFGH 01HABC1234567890ABCDEFGH
|
||||
|
||||
# Skip confirmation (for scripts/CI)
|
||||
agents plan rollback --yes 01HXYZ1234567890ABCDEFGH 01HABC1234567890ABCDEFGH
|
||||
```
|
||||
|
||||
**JSON output envelope** (when using `--format json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"rollback_summary": {
|
||||
"plan_id": "...",
|
||||
"from_checkpoint_id": "...",
|
||||
"restored_files_count": 3
|
||||
},
|
||||
"changes_reverted": ["path/to/file1.py", "path/to/file2.py"],
|
||||
"impact": {
|
||||
"files_affected": 3
|
||||
},
|
||||
"post_rollback_state": {
|
||||
"active_checkpoint": "...",
|
||||
"plan_id": "..."
|
||||
},
|
||||
"timing": {
|
||||
"elapsed_seconds": 0.042
|
||||
},
|
||||
"messages": ["Rollback completed successfully."]
|
||||
}
|
||||
```
|
||||
|
||||
**Error cases:**
|
||||
|
||||
| Error | Cause |
|
||||
|-------|-------|
|
||||
| `Rollback blocked: plan is already applied` | Plan has reached terminal `applied` state |
|
||||
| `Rollback blocked: sandbox is missing` | Sandbox was cleaned up before rollback |
|
||||
| `Not found: checkpoint` | Checkpoint ID does not exist |
|
||||
| `Validation Error` | Checkpoint does not belong to the specified plan |
|
||||
|
||||
---
|
||||
|
||||
## Checkpoint Lifecycle
|
||||
|
||||
### Checkpoint Types
|
||||
|
||||
| Type | When Created |
|
||||
|------|-------------|
|
||||
| `pre_write` | Automatically before each write-tool execution |
|
||||
| `post_step` | Automatically after each write-tool execution |
|
||||
| `manual` | Via `CheckpointService.create_checkpoint()` directly |
|
||||
|
||||
### Automatic Checkpoint Triggers (v3.8.0+)
|
||||
|
||||
The execution engine supports four automatic triggers, configurable via
|
||||
`core.checkpoints.auto_create_on`:
|
||||
|
||||
| Trigger | When | Component |
|
||||
|---------|------|-----------|
|
||||
| `on_tool_write` | Before each write-tool execution | `ToolRunner` |
|
||||
| `on_tool_write_complete` | After each write-tool execution | `ToolRunner` |
|
||||
| `on_subplan_spawn` | Before first subplan execution attempt | `SubplanExecutionService` |
|
||||
| `on_error` | When the Execute phase fails | `PlanExecutor` |
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```toml
|
||||
[core.checkpoints]
|
||||
auto_create_on = ["on_tool_write", "on_tool_write_complete", "on_subplan_spawn", "on_error"]
|
||||
```
|
||||
|
||||
To disable all automatic checkpoints:
|
||||
|
||||
```toml
|
||||
[core.checkpoints]
|
||||
auto_create_on = []
|
||||
```
|
||||
|
||||
### Retention Policy
|
||||
|
||||
A `CheckpointRetentionPolicy` governs how many checkpoints a plan may keep:
|
||||
|
||||
| Field | Default | Range |
|
||||
|-------|---------|-------|
|
||||
| `max_checkpoints` | 50 | 1–100 |
|
||||
| `auto_prune` | `true` | — |
|
||||
|
||||
When `auto_prune` is enabled and the count exceeds `max_checkpoints`, the oldest
|
||||
**interior** checkpoints are removed. The first (earliest) and most recent checkpoints
|
||||
are always preserved.
|
||||
|
||||
---
|
||||
|
||||
## Checkpoint Data Model
|
||||
|
||||
Each checkpoint stores:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `checkpoint_id` | ULID | Unique identifier |
|
||||
| `plan_id` | ULID | The plan that owns this checkpoint |
|
||||
| `sandbox_ref` | str | Git commit hash or patch reference |
|
||||
| `decision_id` | ULID \| None | Optional decision alignment |
|
||||
| `checkpoint_type` | str | `pre_write`, `post_step`, or `manual` |
|
||||
| `resource_id` | ULID \| None | Optional resource association |
|
||||
| `filesystem_path` | str | Relative path within the checkpoint directory |
|
||||
| `size_bytes` | int | Size of the checkpoint data in bytes |
|
||||
| `created_at` | datetime | UTC creation timestamp |
|
||||
| `metadata` | dict | Audit metadata: `reason`, `source_tool`, `phase`, `extra` |
|
||||
|
||||
---
|
||||
|
||||
## Python API
|
||||
|
||||
The `CheckpointService` is obtained from the DI container:
|
||||
|
||||
```python
|
||||
from cleveragents.application.container import get_container
|
||||
|
||||
checkpoint_service = get_container().checkpoint_service()
|
||||
|
||||
# Create a manual checkpoint
|
||||
checkpoint = checkpoint_service.create_checkpoint(
|
||||
plan_id="01HV...",
|
||||
sandbox_ref="abc123",
|
||||
checkpoint_type="manual",
|
||||
metadata={"reason": "pre-correction snapshot", "phase": "execute"},
|
||||
)
|
||||
|
||||
# List checkpoints for a plan
|
||||
checkpoints = checkpoint_service.list_checkpoints(plan_id="01HV...")
|
||||
|
||||
# Roll back to a checkpoint
|
||||
result = checkpoint_service.rollback_to_checkpoint(
|
||||
plan_id="01HV...",
|
||||
checkpoint_id="01HABC...",
|
||||
)
|
||||
print(f"Restored {result.restored_files_count} files")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback Guards
|
||||
|
||||
The rollback operation enforces two guards:
|
||||
|
||||
1. **Plan is applied** — Once a plan reaches the `applied` terminal state, rollback is
|
||||
rejected with a `BusinessRuleViolation`.
|
||||
2. **Sandbox is missing** — If the sandbox has been cleaned up, rollback is rejected
|
||||
because there is nothing to restore.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [`docs/reference/checkpointing.md`](../reference/checkpointing.md) — Full checkpointing domain model reference
|
||||
- [`docs/api/plan-corrections.md`](plan-corrections.md) — Plan correction modes (revert uses checkpoints)
|
||||
- [ADR-015: Sandbox & Checkpoint](../adr/ADR-015-sandbox-and-checkpoint.md)
|
||||
- [ADR-035: Decision Tree Rollback & Replay](../adr/ADR-035-decision-tree-rollback-and-replay.md)
|
||||
@@ -0,0 +1,348 @@
|
||||
# Decision Recording and Tree API (v3.2.0)
|
||||
|
||||
The decision subsystem records every choice point during a plan's Strategize and Execute
|
||||
phases as a persistent tree of **Decision** nodes. This page documents the CLI commands
|
||||
and Python API for inspecting and navigating that tree.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
During the **Strategize** phase, the strategy actor records each significant choice as a
|
||||
`Decision` node. Decisions are linked parent-to-child to form a tree rooted at the
|
||||
`prompt_definition` node (the original plan prompt). The tree is persisted to SQLite via
|
||||
`DecisionRepository` and can be queried at any time — even after the plan has completed.
|
||||
|
||||
Key capabilities introduced in **v3.2.0**:
|
||||
|
||||
- Automatic decision recording for every strategy and execution choice.
|
||||
- Context snapshots (SHA-256 hash + storage pointer) captured alongside each decision.
|
||||
- Alternatives-considered tracking for every decision node.
|
||||
- Full tree rendering via `agents plan tree`.
|
||||
- Per-decision explanation via `agents plan explain`.
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### `agents plan tree`
|
||||
|
||||
Display the decision tree for a plan.
|
||||
|
||||
```bash
|
||||
agents plan tree <PLAN_ID> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` (default: `rich`) |
|
||||
| `--show-superseded` | Include superseded decisions in the tree |
|
||||
| `--depth` | Maximum tree depth to render (0 = unlimited, default: 0) |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Default rich tree view
|
||||
agents plan tree 01HXYZ1234567890ABCDEFGH
|
||||
|
||||
# Table format
|
||||
agents plan tree 01HXYZ1234567890ABCDEFGH --format table
|
||||
|
||||
# Include superseded decisions (e.g. after a correction)
|
||||
agents plan tree 01HXYZ1234567890ABCDEFGH --show-superseded
|
||||
|
||||
# Limit depth to 2 levels
|
||||
agents plan tree 01HXYZ1234567890ABCDEFGH --depth 2
|
||||
|
||||
# JSON output for scripting
|
||||
agents plan tree 01HXYZ1234567890ABCDEFGH --format json
|
||||
```
|
||||
|
||||
**Rich output** renders an indented tree with decision type labels, sequence numbers,
|
||||
and full 26-character ULIDs so that IDs can be copied directly into follow-up commands
|
||||
such as `agents plan explain` or `agents plan correct`.
|
||||
|
||||
---
|
||||
|
||||
### `agents plan explain`
|
||||
|
||||
Show full details for a single decision node, including alternatives considered,
|
||||
context snapshot, and actor reasoning.
|
||||
|
||||
```bash
|
||||
agents plan explain <DECISION_ID> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` |
|
||||
| `--show-context` | Include context snapshot details (hash, storage ref, resources) |
|
||||
| `--show-reasoning` | Include rationale and raw actor reasoning trace |
|
||||
|
||||
Alternatives considered are **always** included in the output regardless of flags.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Default rich output
|
||||
agents plan explain 01HXYZ1234567890ABCDEFGH
|
||||
|
||||
# JSON with full context snapshot
|
||||
agents plan explain 01HXYZ1234567890ABCDEFGH --format json --show-context
|
||||
|
||||
# Show actor reasoning trace
|
||||
agents plan explain 01HXYZ1234567890ABCDEFGH --show-reasoning
|
||||
|
||||
# YAML with all details
|
||||
agents plan explain 01HXYZ1234567890ABCDEFGH --format yaml \
|
||||
--show-context --show-reasoning
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Data Model
|
||||
|
||||
Each decision node stores the following fields.
|
||||
|
||||
### Identity
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `decision_id` | ULID | Auto-generated unique identifier |
|
||||
| `plan_id` | ULID | Parent plan |
|
||||
| `parent_decision_id` | ULID \| None | Parent node; `None` for the root |
|
||||
| `sequence_number` | int | Monotonic order within the plan (0-indexed, never reused) |
|
||||
|
||||
### Classification
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `decision_type` | `DecisionType` | One of 11 enum values (see table below) |
|
||||
|
||||
**Decision types:**
|
||||
|
||||
| Type | Phase | Description |
|
||||
|------|-------|-------------|
|
||||
| `prompt_definition` | Strategize | Root decision — the plan prompt |
|
||||
| `invariant_enforced` | Strategize | An invariant constraint was applied |
|
||||
| `strategy_choice` | Strategize | High-level approach chosen |
|
||||
| `implementation_choice` | Execute | How to implement a specific task |
|
||||
| `resource_selection` | Execute | Which resources to read / modify |
|
||||
| `subplan_spawn` | Strategize | Decision to create a child plan |
|
||||
| `subplan_parallel_spawn` | Strategize | Spawn a group of child plans in parallel |
|
||||
| `tool_invocation` | Execute | Which skill / tool to use |
|
||||
| `error_recovery` | Execute | How to handle a failure |
|
||||
| `validation_response` | Execute | Response to a validation failure |
|
||||
| `user_intervention` | Any | User-provided guidance / correction |
|
||||
|
||||
### Content
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `question` | str | What question was being answered |
|
||||
| `chosen_option` | str | The option that was selected |
|
||||
| `alternatives_considered` | list[str] | Other options that were evaluated |
|
||||
| `confidence_score` | float \| None | 0.0–1.0 confidence, or `None` |
|
||||
|
||||
### Context Snapshot
|
||||
|
||||
Every decision captures a `ContextSnapshot` for replay and correction:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `hot_context_hash` | str | SHA-256 hash of the context window at decision time |
|
||||
| `hot_context_ref` | str | Storage pointer to the full serialised context |
|
||||
| `relevant_resources` | list[ResourceRef] | Resources in scope at decision time |
|
||||
| `actor_state_ref` | str | LangGraph actor checkpoint reference |
|
||||
|
||||
### Rationale
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `rationale` | str | Human-readable explanation |
|
||||
| `actor_reasoning` | str \| None | Raw LLM reasoning trace |
|
||||
|
||||
### Downstream Impact
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `downstream_decision_ids` | list[ULID] | Decisions that depend on this one |
|
||||
| `downstream_plan_ids` | list[ULID] | Child plans spawned from this decision |
|
||||
| `artifacts_produced` | list[ArtifactRef] | Artifacts created by this decision |
|
||||
|
||||
### Correction Metadata
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `is_correction` | bool | `True` if this decision corrects another |
|
||||
| `corrects_decision_id` | ULID \| None | Original decision being corrected |
|
||||
| `correction_reason` | str \| None | Why the correction was made |
|
||||
| `superseded_by` | ULID \| None | Decision that replaced this one |
|
||||
|
||||
---
|
||||
|
||||
## Tree Structure
|
||||
|
||||
Decisions form a tree via `parent_decision_id`:
|
||||
|
||||
```
|
||||
prompt_definition (root, parent=None)
|
||||
├── invariant_enforced
|
||||
├── strategy_choice
|
||||
│ ├── implementation_choice
|
||||
│ │ ├── resource_selection
|
||||
│ │ └── tool_invocation
|
||||
│ └── subplan_spawn
|
||||
└── strategy_choice
|
||||
```
|
||||
|
||||
The `prompt_definition` type is always the root and must have
|
||||
`parent_decision_id = None`. The **current tree** consists of all decisions where
|
||||
`superseded_by IS NULL`. Superseded decisions are hidden by default in `agents plan tree`
|
||||
but can be shown with `--show-superseded`.
|
||||
|
||||
---
|
||||
|
||||
## Decision Persistence
|
||||
|
||||
Decisions are persisted to SQLite via `DecisionRepository` in the
|
||||
`cleveragents.infrastructure.database` package. The service layer
|
||||
(`DecisionService`) supports two modes:
|
||||
|
||||
| Mode | UnitOfWork | Storage |
|
||||
|------|------------|---------|
|
||||
| In-memory | `None` | Internal dicts |
|
||||
| Persisted | provided | SQLite via `DecisionRepository` + in-memory write-through cache |
|
||||
|
||||
When a `UnitOfWork` is wired, mutations are written to the database first, then the
|
||||
in-memory cache is updated (write-through).
|
||||
|
||||
**Python API — recording a decision:**
|
||||
|
||||
```python
|
||||
from cleveragents.application.services.decision_service import DecisionService
|
||||
from cleveragents.domain.models.core.decision import DecisionType
|
||||
|
||||
svc = DecisionService(unit_of_work=uow)
|
||||
|
||||
decision = svc.record_decision(
|
||||
plan_id="01HV...",
|
||||
decision_type=DecisionType.STRATEGY_CHOICE,
|
||||
question="Which approach should we take?",
|
||||
chosen_option="Build a REST API",
|
||||
alternatives_considered=["GraphQL API", "gRPC service"],
|
||||
confidence_score=0.85,
|
||||
rationale="REST is simpler and better supported by existing tooling.",
|
||||
)
|
||||
```
|
||||
|
||||
**Python API — retrieving the tree:**
|
||||
|
||||
```python
|
||||
# BFS from root(s), level by level
|
||||
tree = svc.get_tree(plan_id="01HV...")
|
||||
|
||||
# Walk from a decision up to the root
|
||||
path = svc.get_path_to_root(decision_id="01HXYZ...")
|
||||
|
||||
# List all decisions for a plan, ordered by sequence number
|
||||
decisions = svc.list_decisions(plan_id="01HV...")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [`docs/reference/decision_model.md`](../reference/decision_model.md) — Full domain model reference
|
||||
- [`docs/reference/decision_service.md`](../reference/decision_service.md) — `DecisionService` API reference
|
||||
- [`docs/api/plan-corrections.md`](plan-corrections.md) — Correction modes and subplan system
|
||||
- [ADR-007: Decision Tree & Correction](../adr/ADR-007-decision-tree-and-correction.md)
|
||||
- [ADR-033: Decision Recording Protocol](../adr/ADR-033-decision-recording-protocol.md)
|
||||
- [ADR-034: Decision Tree Versioning & History](../adr/ADR-034-decision-tree-versioning-and-history.md)
|
||||
diff --git a/docs/api/invariants.md b/docs/api/invariants.md
|
||||
new file mode 100644
|
||||
index 0000000..2222222
|
||||
--- /dev/null
|
||||
+++ b/docs/api/invariants.md
|
||||
@@ -0,0 +1,274 @@
|
||||
# Invariant Management API (v3.2.0)
|
||||
|
||||
Invariants are natural-language constraints that govern plan execution. They are
|
||||
evaluated at the start of the Strategize phase by the **Invariant Reconciliation Actor**
|
||||
and recorded in the decision tree as `invariant_enforced` nodes.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Invariants introduced in **v3.2.0** provide a declarative way to constrain what a plan
|
||||
may do. They are scoped (global, project, action, or plan), merged by precedence, and
|
||||
de-duplicated before enforcement. Violations block the phase transition and emit
|
||||
`INVARIANT_VIOLATED` events.
|
||||
|
||||
Key capabilities:
|
||||
|
||||
- Four scope levels: `GLOBAL`, `PROJECT`, `ACTION`, `PLAN`.
|
||||
- Merge precedence: plan > project > global (action invariants are promoted to plan scope).
|
||||
- Automatic enforcement at every phase transition via `InvariantReconciliationActor`.
|
||||
- CLI commands for adding, listing, and removing invariants.
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### `agents invariant add`
|
||||
|
||||
Create a new invariant constraint.
|
||||
|
||||
```bash
|
||||
agents invariant add <NAME> --description <DESC> [SCOPE_FLAG]
|
||||
```
|
||||
|
||||
**Scope flags:**
|
||||
|
||||
| Flag | Scope | Description |
|
||||
|------|-------|-------------|
|
||||
| `--global` | `GLOBAL` | Applies to every plan in the system |
|
||||
| `--project <NAME>` | `PROJECT` | Applies to plans targeting the named project |
|
||||
| `--plan <PLAN_ID>` | `PLAN` | Attached directly to a specific plan |
|
||||
| `--action <ACTION>` | `ACTION` | Defined in an action template; promoted on `plan use` |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Global invariant
|
||||
agents invariant add --global "Never delete production data"
|
||||
|
||||
# Project-scoped invariant
|
||||
agents invariant add --project myapp "All API changes need tests"
|
||||
|
||||
# Plan-specific invariant
|
||||
agents invariant add --plan 01HXYZ... "Use Python 3.13 only"
|
||||
|
||||
# Action-scoped invariant
|
||||
agents invariant add --action local/code-coverage "Minimum 80% coverage"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `agents invariant list`
|
||||
|
||||
Display invariants, optionally filtered by scope or project.
|
||||
|
||||
```bash
|
||||
agents invariant list [PATTERN] [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--global` | Show only global invariants |
|
||||
| `--project <NAME>` | Show only invariants for the named project |
|
||||
| `--effective --project <NAME>` | Show the merged effective set for a project |
|
||||
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` |
|
||||
@@ -0,0 +1,245 @@
|
||||
# Invariant Management API (v3.2.0)
|
||||
|
||||
Invariants are natural-language constraints that govern plan execution. They are
|
||||
evaluated at the start of the Strategize phase by the **Invariant Reconciliation Actor**
|
||||
and recorded in the decision tree as `invariant_enforced` nodes.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Invariants introduced in **v3.2.0** provide a declarative way to constrain what a plan
|
||||
may do. They are scoped (global, project, action, or plan), merged by precedence, and
|
||||
de-duplicated before enforcement. Violations block the phase transition and emit
|
||||
`INVARIANT_VIOLATED` events.
|
||||
|
||||
Key capabilities:
|
||||
|
||||
- Four scope levels: `GLOBAL`, `PROJECT`, `ACTION`, `PLAN`.
|
||||
- Merge precedence: plan > project > global (action invariants are promoted to plan scope).
|
||||
- Automatic enforcement at every phase transition via `InvariantReconciliationActor`.
|
||||
- CLI commands for adding, listing, and removing invariants.
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### `agents invariant add`
|
||||
|
||||
Create a new invariant constraint.
|
||||
|
||||
```bash
|
||||
agents invariant add <NAME> --description <DESC> [SCOPE_FLAG]
|
||||
```
|
||||
|
||||
**Scope flags:**
|
||||
|
||||
| Flag | Scope | Description |
|
||||
|------|-------|-------------|
|
||||
| `--global` | `GLOBAL` | Applies to every plan in the system |
|
||||
| `--project <NAME>` | `PROJECT` | Applies to plans targeting the named project |
|
||||
| `--plan <PLAN_ID>` | `PLAN` | Attached directly to a specific plan |
|
||||
| `--action <ACTION>` | `ACTION` | Defined in an action template; promoted on `plan use` |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Global invariant
|
||||
agents invariant add --global "Never delete production data"
|
||||
|
||||
# Project-scoped invariant
|
||||
agents invariant add --project myapp "All API changes need tests"
|
||||
|
||||
# Plan-specific invariant
|
||||
agents invariant add --plan 01HXYZ... "Use Python 3.13 only"
|
||||
|
||||
# Action-scoped invariant
|
||||
agents invariant add --action local/code-coverage "Minimum 80% coverage"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `agents invariant list`
|
||||
|
||||
Display invariants, optionally filtered by scope or project.
|
||||
|
||||
```bash
|
||||
agents invariant list [PATTERN] [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--global` | Show only global invariants |
|
||||
| `--project <NAME>` | Show only invariants for the named project |
|
||||
| `--effective --project <NAME>` | Show the merged effective set for a project |
|
||||
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# List all active invariants
|
||||
agents invariant list
|
||||
|
||||
# Filter by scope
|
||||
agents invariant list --global
|
||||
agents invariant list --project myapp
|
||||
|
||||
# Show merged effective set for a project
|
||||
agents invariant list --effective --project myapp
|
||||
|
||||
# Filter by regex pattern
|
||||
agents invariant list "data.*safe"
|
||||
|
||||
# JSON output
|
||||
agents invariant list --format json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `agents invariant remove`
|
||||
|
||||
Remove (soft-delete) an invariant by its ULID.
|
||||
|
||||
```bash
|
||||
agents invariant remove <INVARIANT_ID> [--yes]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--yes` | Skip the interactive confirmation prompt |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# With confirmation prompt
|
||||
agents invariant remove 01HXYZ...
|
||||
|
||||
# Skip confirmation
|
||||
agents invariant remove --yes 01HXYZ...
|
||||
```
|
||||
|
||||
Removal sets `active=False` on the invariant record; it is not hard-deleted. This
|
||||
preserves the audit trail for enforcement records that reference the invariant.
|
||||
|
||||
---
|
||||
|
||||
## Scope Hierarchy and Merge Precedence
|
||||
|
||||
When computing the effective invariant set for a plan, the precedence chain is:
|
||||
|
||||
```
|
||||
plan > project > global
|
||||
```
|
||||
|
||||
- **Plan-level** invariants take highest precedence.
|
||||
- **Project-level** invariants apply next.
|
||||
- **Global-level** invariants are lowest precedence.
|
||||
- **Action-level** invariants are promoted to plan scope when `plan use` is called.
|
||||
|
||||
### De-duplication
|
||||
|
||||
Invariants are de-duplicated by text (case-insensitive). When the same constraint text
|
||||
appears at multiple scopes, only the highest-precedence copy is kept.
|
||||
|
||||
### Merge Example
|
||||
|
||||
Given:
|
||||
|
||||
- Global: "Never delete production data", "Log all changes"
|
||||
- Project (`myapp`): "All API changes need tests", "Log all changes"
|
||||
- Plan: "Use Python 3.13 only"
|
||||
|
||||
The effective set for a plan targeting `myapp` would be:
|
||||
|
||||
1. "Use Python 3.13 only" (plan)
|
||||
2. "All API changes need tests" (project)
|
||||
3. "Log all changes" (project — shadows the global duplicate)
|
||||
4. "Never delete production data" (global)
|
||||
|
||||
---
|
||||
|
||||
## Invariant Enforcement During Strategize
|
||||
|
||||
At the start of the Strategize phase, the `InvariantReconciliationActor`:
|
||||
|
||||
1. Calls `InvariantService.get_effective_invariants(plan_id, project_name)` to collect
|
||||
the merged invariant set.
|
||||
2. Evaluates each invariant against the current plan context.
|
||||
3. Creates an `InvariantEnforcementRecord` for each invariant.
|
||||
4. Records an `invariant_enforced` decision in the decision tree.
|
||||
5. Emits `INVARIANT_VIOLATED` for each violated invariant.
|
||||
6. Blocks the phase transition with `ReconciliationBlockedError` if any invariant fails.
|
||||
|
||||
### Enforcement Record
|
||||
|
||||
Each record contains:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `invariant_id` | ULID of the invariant |
|
||||
| `enforced` | Whether the invariant was successfully enforced |
|
||||
| `actor_response` | Response text from the reconciliation actor |
|
||||
| `decision_id` | ULID of the associated `invariant_enforced` decision node |
|
||||
|
||||
### Violation Model
|
||||
|
||||
When an invariant is violated, an `InvariantViolation` is created:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `invariant_id` | ULID of the violated invariant |
|
||||
| `violated_text` | The invariant text that was violated |
|
||||
| `severity` | `error`, `warning`, or `info` |
|
||||
| `details` | Additional violation context |
|
||||
|
||||
---
|
||||
|
||||
## Python API
|
||||
|
||||
The `InvariantService` is registered as a Singleton in the DI container. Obtain it via
|
||||
`container.invariant_service()` rather than constructing it directly.
|
||||
|
||||
```python
|
||||
from cleveragents.application.services.invariant_service import InvariantService
|
||||
from cleveragents.domain.models.core.invariant import InvariantScope
|
||||
|
||||
service = InvariantService(event_bus=event_bus)
|
||||
|
||||
# Add an invariant
|
||||
inv = service.add_invariant(
|
||||
text="All output files must be UTF-8 encoded",
|
||||
scope=InvariantScope.PROJECT,
|
||||
source_name="my-project",
|
||||
)
|
||||
|
||||
# List effective invariants for a plan
|
||||
effective = service.get_effective_invariants(
|
||||
plan_id="01HV...",
|
||||
project_name="my-project",
|
||||
)
|
||||
|
||||
# Remove an invariant (soft-delete)
|
||||
service.remove_invariant(invariant_id="01HXYZ...")
|
||||
```
|
||||
|
||||
**Key methods:**
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `add_invariant(text, scope, source_name)` | `Invariant` | Add a new invariant |
|
||||
| `list_invariants(scope, source_name, effective)` | `list[Invariant]` | Filter invariants |
|
||||
| `remove_invariant(invariant_id)` | `Invariant` | Soft-delete (sets `active=False`) |
|
||||
| `get_effective_invariants(plan_id, project_name)` | `list[Invariant]` | Merged precedence chain |
|
||||
| `enforce_invariants(plan_id, invariants, actor_response, violated_ids)` | `list[InvariantEnforcementRecord]` | Create enforcement records |
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [`docs/reference/invariants.md`](../reference/invariants.md) — Full invariant domain model reference
|
||||
- [`docs/api/decisions.md`](decisions.md) — Decision recording and tree CLI reference
|
||||
- [ADR-016: Invariant System](../adr/ADR-016-invariant-system.md)
|
||||
@@ -0,0 +1,272 @@
|
||||
# Plan Correction API (v3.2.0 / v3.3.0)
|
||||
|
||||
The correction subsystem allows operators to modify a plan's decision tree after
|
||||
execution by either **reverting** a subtree of decisions or **appending** new guidance
|
||||
as a child plan. This page documents the CLI commands, correction modes, and the
|
||||
subplan system that powers append-mode corrections.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Plan corrections introduced across **v3.2.0** and **v3.3.0** provide two complementary
|
||||
modes for adapting a plan's strategy without discarding accumulated context:
|
||||
|
||||
| Mode | Effect |
|
||||
|------|--------|
|
||||
| `revert` | Invalidates the targeted decision and all descendants; re-executes from that point |
|
||||
| `append` | Preserves the original decision; spawns a new child plan with operator guidance |
|
||||
|
||||
Corrections never mutate existing decisions. Instead, a new `Decision` is created with
|
||||
`is_correction=True` and `corrects_decision_id` pointing to the original. The original
|
||||
decision has its `superseded_by` field set to the new decision's ID.
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### `agents plan correct`
|
||||
|
||||
Apply a correction to a plan's decision tree.
|
||||
|
||||
```bash
|
||||
agents plan correct <PLAN_ID> --decision <DECISION_ID> --mode <MODE> --guidance <TEXT> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--decision` | ULID of the decision to correct (required) |
|
||||
| `--mode` | Correction mode: `revert` or `append` (required) |
|
||||
| `--guidance` | Operator guidance text (1–10,000 characters, required) |
|
||||
| `--dry-run` | Preview impact without making changes |
|
||||
| `--yes`, `-y` | Skip the interactive confirmation prompt |
|
||||
| `--format`, `-f` | Output format: `json`, `yaml`, `plain`, `table`, `rich` |
|
||||
|
||||
**Revert mode — re-execute from a targeted decision point:**
|
||||
|
||||
```bash
|
||||
agents plan correct 01HXYZ... \
|
||||
--decision 01HABC... \
|
||||
--mode=revert \
|
||||
--guidance "Use a safer database migration approach"
|
||||
```
|
||||
|
||||
This invalidates the targeted decision and every descendant reachable via BFS traversal.
|
||||
Associated artifacts are archived and affected child plans are rolled back. The plan then
|
||||
re-executes from the targeted decision point using the operator's guidance.
|
||||
|
||||
**Append mode — add guidance without recomputing:**
|
||||
|
||||
```bash
|
||||
agents plan correct 01HXYZ... \
|
||||
--decision 01HABC... \
|
||||
--mode=append \
|
||||
--guidance "Add input validation to the API endpoint"
|
||||
```
|
||||
|
||||
This preserves the original decision and spawns a **new child plan** rooted at the target
|
||||
node. The child plan carries the operator's guidance and produces additional decisions
|
||||
without disturbing the existing tree.
|
||||
|
||||
**Dry-run preview:**
|
||||
|
||||
```bash
|
||||
agents plan correct 01HXYZ... \
|
||||
--decision 01HABC... \
|
||||
--mode=revert \
|
||||
--guidance "..." \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
The dry-run report includes:
|
||||
|
||||
- **impact** — Affected decisions, files, child plans, estimated cost, and risk level.
|
||||
- **decisions_to_invalidate** — Decision IDs that *would* be marked invalid (revert only).
|
||||
- **child_plans_to_rollback** — Child plans that *would* be rolled back.
|
||||
- **estimated_recompute_time_seconds** — Wall-clock estimate.
|
||||
- **warnings** — Human-readable cautions (e.g. high risk).
|
||||
|
||||
---
|
||||
|
||||
## Correction Modes in Detail
|
||||
|
||||
### Revert Mode
|
||||
|
||||
Invalidates the targeted decision and every descendant reachable via BFS traversal:
|
||||
|
||||
```
|
||||
┌─── D1 (target) ◄── revert starts here
|
||||
│ │
|
||||
│ ┌──┴──┐
|
||||
│ D2 D3 ← all invalidated
|
||||
│ │
|
||||
│ D4 ← also invalidated
|
||||
```
|
||||
|
||||
**Risk classification** based on affected decision count:
|
||||
|
||||
| Affected Count | Risk Level |
|
||||
|----------------|------------|
|
||||
| ≤ 3 | low |
|
||||
| 4 – 10 | medium |
|
||||
| > 10 | high |
|
||||
|
||||
### Append Mode
|
||||
|
||||
Preserves the original decision and spawns a new child plan:
|
||||
|
||||
```
|
||||
D1 (target)
|
||||
│
|
||||
┌──┴──────────┐
|
||||
D2 (original) CP-new ← child plan appended
|
||||
```
|
||||
|
||||
The child plan runs in its own sandbox and its results are merged back into the parent
|
||||
plan using the configured merge strategy.
|
||||
|
||||
---
|
||||
|
||||
## Correction Status Lifecycle
|
||||
|
||||
```
|
||||
PENDING → ANALYZING → EXECUTING → APPLIED
|
||||
→ FAILED
|
||||
PENDING → CANCELLED
|
||||
ANALYZING → CANCELLED
|
||||
```
|
||||
|
||||
Each execution of a correction is tracked as a `CorrectionAttemptRecord`. Multiple
|
||||
attempts may exist for a single correction (e.g. if a first attempt fails and the
|
||||
operator retries).
|
||||
|
||||
---
|
||||
|
||||
## Subplan System Overview
|
||||
|
||||
The **append** correction mode relies on the subplan system to spawn and execute child
|
||||
plans. Subplans are also used directly during Strategize when the strategy actor decides
|
||||
to decompose work into parallel or sequential child plans.
|
||||
|
||||
### Spawning Child Plans
|
||||
|
||||
When a `subplan_spawn` or `subplan_parallel_spawn` decision is recorded during
|
||||
Strategize, the `SubplanService` handles the spawn workflow:
|
||||
|
||||
1. Extract spawn decisions from `DecisionService`.
|
||||
2. Build `SpawnEntry` objects from those decisions.
|
||||
3. Validate resource scopes, merge strategy, and parallelism bounds.
|
||||
4. Create `SubplanStatus` and `SpawnMetadata` for each entry.
|
||||
5. Return the result for the caller to persist on the parent plan.
|
||||
|
||||
### Execution Modes
|
||||
|
||||
| Mode | Description |
|
||||
|------|-------------|
|
||||
| `sequential` | Execute one at a time in order |
|
||||
| `parallel` | Execute concurrently (up to `max_parallel`, default 5) |
|
||||
| `dependency_ordered` | Respect DAG dependencies via topological sort |
|
||||
|
||||
### Three-Way Merge
|
||||
|
||||
After subplans complete, their sandbox outputs are merged using the configured
|
||||
`SubplanMergeStrategy`:
|
||||
|
||||
| Strategy | Description |
|
||||
|----------|-------------|
|
||||
| `git_three_way` | Three-way merge via `git merge-file` (default) |
|
||||
| `sequential_apply` | Apply changes in completion order |
|
||||
| `fail_on_conflict` | Raise `MergeConflictError` on any conflict |
|
||||
| `last_wins` | Final subplan's output overwrites earlier ones |
|
||||
|
||||
The `git_three_way` strategy uses `git merge-file` to combine non-overlapping changes
|
||||
from different subplans automatically. Overlapping changes produce conflict markers in
|
||||
the output.
|
||||
|
||||
### Subplan Configuration
|
||||
|
||||
```yaml
|
||||
subplan_config:
|
||||
execution_mode: parallel # sequential | parallel | dependency_ordered
|
||||
merge_strategy: git_three_way # git_three_way | sequential_apply | fail_on_conflict | last_wins
|
||||
max_parallel: 5 # 1-50, for parallel mode
|
||||
fail_fast: false # stop all on first failure
|
||||
timeout_per_subplan_seconds: ~ # optional per-subplan timeout
|
||||
retry_failed: true # auto-retry failed subplans
|
||||
max_retries: 2 # 0-5, max retry attempts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase Reversion
|
||||
|
||||
When corrections cannot be resolved within the current strategy, the plan may revert to
|
||||
an earlier lifecycle phase:
|
||||
|
||||
| Source Phase | Target Phase | Trigger |
|
||||
|-------------|-------------|---------|
|
||||
| Execute | Strategize | Validation failures block apply; constraints too restrictive |
|
||||
| Apply (constrained) | Strategize | Cannot proceed within current strategy constraints |
|
||||
|
||||
Phase reversion is subject to a **loop guard**: each plan may revert at most **3 times**
|
||||
(`Plan.MAX_REVERSIONS`). Once this limit is reached, both automatic and manual
|
||||
reversions are blocked.
|
||||
|
||||
Manual reversion via CLI:
|
||||
|
||||
```bash
|
||||
agents plan revert <plan_id> --to-phase strategize --reason "constraints too strict"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Python API
|
||||
|
||||
```python
|
||||
from cleveragents.application.services.correction_service import CorrectionService
|
||||
from cleveragents.domain.models.core.correction import CorrectionMode
|
||||
|
||||
correction_service = CorrectionService(unit_of_work=uow)
|
||||
|
||||
# Request a correction
|
||||
correction = correction_service.request_correction(
|
||||
plan_id="01HV...",
|
||||
original_decision_id="01HABC...",
|
||||
mode=CorrectionMode.REVERT,
|
||||
guidance="Use a safer database migration approach",
|
||||
)
|
||||
|
||||
# Preview impact (dry run)
|
||||
report = correction_service.generate_dry_run_report(correction.id)
|
||||
print(f"Risk: {report.impact.risk_level}, Affected: {len(report.decisions_to_invalidate)}")
|
||||
|
||||
# Execute the correction
|
||||
correction_service.execute_correction(correction.id)
|
||||
```
|
||||
|
||||
**Key service methods:**
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `request_correction()` | Create a new correction request |
|
||||
| `analyze_impact()` | BFS impact analysis on the decision tree |
|
||||
| `generate_dry_run_report()` | Full report without side effects |
|
||||
| `execute_revert()` | Invalidate subtree + archive artifacts |
|
||||
| `execute_append()` | Spawn child plan preserving original decision |
|
||||
| `execute_correction()` | Dispatch to revert or append based on mode |
|
||||
| `get_correction()` | Retrieve a correction by ID |
|
||||
| `list_corrections()` | List corrections (optional plan_id filter) |
|
||||
| `cancel_correction()` | Cancel a pending/analyzing correction |
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [`docs/reference/decision_correction.md`](../reference/decision_correction.md) — Full correction domain model reference
|
||||
- [`docs/reference/subplans.md`](../reference/subplans.md) — Subplan execution and merge strategies
|
||||
- [`docs/reference/phase_reversion.md`](../reference/phase_reversion.md) — Phase reversion state machine
|
||||
- [`docs/api/decisions.md`](decisions.md) — Decision recording and tree CLI reference
|
||||
- [`docs/api/checkpoints.md`](checkpoints.md) — Checkpoint and rollback CLI reference
|
||||
- [ADR-007: Decision Tree & Correction](../adr/ADR-007-decision-tree-and-correction.md)
|
||||
@@ -258,6 +258,15 @@ Feature: Subplan Execution and Merge
|
||||
Then the first subplan should be errored
|
||||
And the remaining subplans should have CANCELLED status
|
||||
|
||||
@parallel @cancel_status
|
||||
Scenario: Parallel fail_fast marks in-flight futures as CANCELLED
|
||||
Given a parent plan with 3 subplans in parallel mode with fail_fast
|
||||
And the subplan executor will block for 1 seconds
|
||||
And the first subplan will fail with "ValidationError: schema mismatch"
|
||||
When the subplans are executed
|
||||
Then the first subplan should be errored
|
||||
And the remaining subplans should have CANCELLED status
|
||||
|
||||
# --- Dependency-ordered concurrent execution ---
|
||||
|
||||
@dependency_ordered @concurrent
|
||||
|
||||
@@ -23,6 +23,10 @@ nav:
|
||||
- Configuration: api/config.md
|
||||
- AI Providers: api/providers.md
|
||||
- TUI: api/tui.md
|
||||
- Decisions: api/decisions.md
|
||||
- Invariants: api/invariants.md
|
||||
- Checkpoints: api/checkpoints.md
|
||||
- Plan Corrections: api/plan-corrections.md
|
||||
- Modules:
|
||||
- Shell Safety: modules/shell-safety.md
|
||||
- UKO Provenance Tracking: modules/uko-provenance.md
|
||||
|
||||
@@ -301,6 +301,7 @@ class SubplanExecutionService:
|
||||
completion_order: list[str] = []
|
||||
stop_flag = False
|
||||
timeout = self._config.timeout_per_subplan_seconds
|
||||
status_map = {status.subplan_id: status for status in statuses}
|
||||
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as pool:
|
||||
future_to_id: dict[Future[tuple[SubplanStatus, dict[str, str]]], str] = {}
|
||||
@@ -315,20 +316,26 @@ class SubplanExecutionService:
|
||||
|
||||
for future in as_completed(future_to_id):
|
||||
subplan_id = future_to_id[future]
|
||||
original_status = status_map[subplan_id]
|
||||
try:
|
||||
result_status, output = future.result()
|
||||
except CancelledError:
|
||||
result_status = self._cancel_status(
|
||||
next(s for s in statuses if s.subplan_id == subplan_id)
|
||||
)
|
||||
result_status = self._cancel_status(original_status)
|
||||
output = {}
|
||||
except Exception as exc: # pragma: no cover - defensive
|
||||
result_status = self._error_status(
|
||||
next(s for s in statuses if s.subplan_id == subplan_id),
|
||||
original_status,
|
||||
str(exc),
|
||||
)
|
||||
output = {}
|
||||
|
||||
if stop_flag and result_status.status not in (
|
||||
ProcessingState.ERRORED,
|
||||
ProcessingState.CANCELLED,
|
||||
):
|
||||
result_status = self._cancel_status(original_status)
|
||||
output = {}
|
||||
|
||||
results_map[subplan_id] = (result_status, output)
|
||||
completion_order.append(subplan_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user