diff --git a/docs/reference/acms.md b/docs/reference/acms.md
index bb8abead1..aefc27130 100644
--- a/docs/reference/acms.md
+++ b/docs/reference/acms.md
@@ -98,16 +98,18 @@ Properties:
## Context Strategies
-Strategies implement the `ContextStrategy` protocol:
+Strategies implement the `ContextStrategy` protocol (defined in `cleveragents.domain.models.acms.strategy`):
| Method | Description |
|--------|-------------|
| `name` (property) | Strategy identifier |
| `capabilities` (property) | `StrategyCapabilities` dataclass |
-| `can_handle(request)` | Confidence (0.0-1.0) for handling a request |
-| `assemble(fragments, budget)` | Rank/filter fragments to fit budget |
+| `can_handle(request: ContextRequest, backends: BackendSet)` | Confidence (0.0-1.0) for handling a request |
+| `assemble(request: ContextRequest, backends: BackendSet, budget: int, plan_context: PlanContext)` | Execute the strategy; must respect the budget |
| `explain()` | Human-readable explanation |
+`StrategyCapabilities` fields: `uses_text`, `uses_vector`, `uses_graph`, `uses_temporal`, `uko_levels`, `resource_types`, `quality_score`.
+
### Relevance (default)
Sorts fragments by `relevance_score` descending. Highest-relevance
@@ -150,9 +152,6 @@ milestones:
| Area | v1 Behaviour | Spec Target | Planned |
|------|-------------|-------------|---------|
-| `ContextStrategy.can_handle` signature | `(request: dict[str, Any]) -> float` | `(request: ContextRequest, backends: BackendSet) -> float` | M6 strategy registry aligns signatures |
-| `ContextStrategy.assemble` signature | `(fragments, budget)` — receives pre-fetched fragments | `(request, backends, budget, plan_context)` — queries backends directly | M6 strategy registry aligns signatures |
-| `StrategyCapabilities` fields | `supports_semantic_search`, `supports_graph_navigation`, `supports_temporal_archaeology`, `max_fragments` | `uses_text`, `uses_vector`, `uses_graph`, `uses_temporal`, `uko_levels`, `resource_types`, `quality_score` | M6 strategy registry uses spec field names |
| Pipeline components | All 10 Protocol + Default classes defined; defaults are pass-through stubs | Production implementations (parallel execution, dedup, scoring, compression) | Future milestone |
| Tiers | Sort-priority labels for ranking (`hot > warm > cold`) | Storage tiers with retention policies, promotion/demotion | `ContextTierService` in future milestone |
| `StrategySelector.select()` | `(strategies, request: dict)` | `(strategies, request: ContextRequest, backends: BackendSet)` — spec §42666 | M6 strategy registry aligns signatures |
@@ -168,21 +167,18 @@ milestones:
## Extension Points
-Register custom context strategies at runtime:
+Register custom context strategies at runtime using the spec-aligned `ContextStrategy` protocol from `cleveragents.domain.models.acms.strategy`. Custom strategies are registered via `SpecStrategyAdapter`:
```python
-from cleveragents.application.services.acms_service import (
- ACMSPipeline,
+from cleveragents.domain.models.acms.strategy import (
ContextStrategy,
StrategyCapabilities,
+ ContextRequest,
+ BackendSet,
+ PlanContext,
)
-from cleveragents.domain.models.core.context_fragment import (
- ContextBudget,
- ContextFragment,
- FragmentProvenance,
-)
-from collections.abc import Sequence
-from typing import Any
+from cleveragents.domain.models.core.context_fragment import ContextFragment
+from cleveragents.application.services.acms_service import ACMSPipeline, SpecStrategyAdapter
class MyCustomStrategy:
@@ -192,31 +188,28 @@ class MyCustomStrategy:
@property
def capabilities(self) -> StrategyCapabilities:
- return StrategyCapabilities()
+ return StrategyCapabilities(uses_text=True, quality_score=0.5)
- def can_handle(self, request: dict[str, Any]) -> float:
+ def can_handle(self, request: ContextRequest, backends: BackendSet) -> float:
return 0.5
def assemble(
self,
- fragments: Sequence[ContextFragment],
- budget: ContextBudget,
- ) -> Sequence[ContextFragment]:
- # Custom ranking logic
- return list(fragments)
+ request: ContextRequest,
+ backends: BackendSet,
+ budget: int,
+ plan_context: PlanContext,
+ ) -> list[ContextFragment]:
+ # Custom retrieval logic using backends
+ return []
def explain(self) -> str:
return "Custom strategy description."
pipeline = ACMSPipeline()
-pipeline.register_strategy("custom", MyCustomStrategy())
-payload = pipeline.assemble(
- plan_id="plan-1",
- fragments=fragments,
- budget=budget,
- strategy="custom",
-)
+adapter = SpecStrategyAdapter(MyCustomStrategy())
+pipeline.register_strategy("custom", adapter)
```
## Example Usage
diff --git a/docs/specification.md b/docs/specification.md
index 4accaad8b..591d09d43 100644
--- a/docs/specification.md
+++ b/docs/specification.md
@@ -344,6 +344,8 @@ The following standards are integrated into the architecture:
agents plan artifacts <PLAN_ID>
agents plan prompt <PLAN_ID> <GUIDANCE>
agents plan rollback [--yes|-y] <PLAN_ID> <CHECKPOINT_ID>
+agents plan revert [--to-phase (strategize|execute)] [--reason <REASON>] [--yes|-y] <PLAN_ID>
+agents plan resume [--dry-run] <PLAN_ID>
agents plan errors <PLAN_ID>
agents action create --config|-c <CFG_FILE>
@@ -9537,7 +9539,7 @@ Register a new validation. The validation is fully defined by the YAML configura
##### agents validation attach
agents validation attach [--project <PROJECT>|--plan <PLAN_ID>]
- <RESOURCE> <VALIDATION> [<ARGS>...]
+ <RESOURCE> <VALIDATION> [--<KEY> <VALUE>...]
**Purpose**
Attach a registered validation to a resource, with an optional project or plan scope. A resource is always required — validations are fundamentally resource-centric. The optional `--project` or `--plan` flag narrows when the validation is active: without either flag, the validation runs whenever any plan or project accesses the resource; with `--project`, it only runs when the resource is accessed through that project; with `--plan`, it only runs when the resource is accessed through that plan. At most one scope flag may be provided per invocation.
@@ -9550,7 +9552,7 @@ The command returns a system-assigned **attachment ULID** that uniquely identifi
- ``: Validation name (positional argument, required).
- `--project PROJECT`: Scope the attachment to a specific project. The validation only runs for this resource when it is accessed through the specified project.
- `--plan PLAN_ID`: Scope the attachment to a specific plan. The validation only runs for this resource when it is accessed through the specified plan.
-- `...`: Optional validation-specific arguments (passed through to the validation tool's `input_schema` at execution time).
+- `-- ...`: Optional validation-specific named options passed through to the validation tool's `input_schema` at execution time. Each argument must be provided as a named option in `--key value` format (e.g., `--coverage-threshold 90`). Hyphens in option names are normalised to underscores before passing to the validation schema (e.g., `--coverage-threshold` → `coverage_threshold`). Positional `key=value` format is not accepted and will produce an error.
**Examples**
@@ -16210,6 +16212,53 @@ Provide additional guidance to a plan, typically when it is errored or awaiting
- "Rollback complete"
```
+##### agents plan revert
+
+agents plan revert [--to-phase (strategize|execute)] [--reason <REASON>] [--yes|-y] <PLAN_ID>
+
+!!! danger "Destructive Operation"
+ Revert a plan to a previous phase. All work done in phases after the target phase is discarded — decisions, tool calls, and sandbox changes are rolled back. This is a coarser-grained operation than `agents plan correct` (which targets a specific decision) or `agents plan rollback` (which restores a checkpoint).
+
+**Purpose**
+Revert a plan back to a previous phase so it can re-run from that phase. Use when you want to re-run the entire strategy or execution phase, not just correct a specific decision. The plan returns to the `queued` state in the target phase, ready to re-execute.
+
+**Arguments**
+
+- ``: Plan ID to revert.
+- `--to-phase PHASE`: Target phase to revert to. Accepted values: `strategize` (default), `execute`. Reverting to `strategize` discards all Execute-phase work and re-runs strategy. Reverting to `execute` discards Execute-phase work but preserves the Strategize decision tree.
+- `--reason REASON`: Optional reason for the reversion (stored in the plan's audit log).
+- `--yes`: Skip confirmation prompt.
+
+**Relationship to other recovery commands**
+
+| Command | Scope | Use When |
+|---------|-------|----------|
+| `agents plan correct` | Single decision | You want to change a specific decision and recompute its subtree |
+| `agents plan revert` | Entire phase | You want to re-run an entire phase from scratch |
+| `agents plan rollback` | Checkpoint | You want to restore the sandbox to a specific saved state |
+| `agents plan resume` | Last checkpoint | You want to continue a paused/errored plan from where it left off |
+
+---
+
+##### agents plan resume
+
+agents plan resume [--dry-run] <PLAN_ID>
+
+**Purpose**
+Resume a plan from its last checkpoint. Use when a plan has errored or been paused and you want to continue execution from the most recent saved state. Unlike `agents plan prompt` (which provides guidance to the actor for the next step), `agents plan resume` performs checkpoint-based resumption — it restores the sandbox to the last checkpoint and re-queues the plan for execution.
+
+**Arguments**
+
+- ``: Plan ID to resume.
+- `--dry-run`: Show what would be resumed without actually resuming. Displays the last checkpoint, the plan state, and the estimated work remaining.
+
+**When to use `agents plan resume` vs `agents plan prompt`**
+
+- Use `agents plan resume` when the plan errored or was paused and you want to continue from the last checkpoint without providing new guidance.
+- Use `agents plan prompt` when you want to provide new guidance or instructions to the actor for the next step, without restoring a checkpoint.
+
+---
+
#### agents action
!!! info "Purpose"
@@ -18892,13 +18941,15 @@ The `record_decision` tool accepts the decision type, question, chosen option, a
-- Correction history
CREATE TABLE correction_attempts (
- attempt_id TEXT PRIMARY KEY, -- ULID
+ correction_attempt_id TEXT PRIMARY KEY, -- ULID
plan_id TEXT NOT NULL,
original_decision_id TEXT NOT NULL,
new_decision_id TEXT,
- original_subtree_snapshot TEXT, -- Reference to archived state
- correction_reason TEXT,
- status TEXT NOT NULL, -- 'pending', 'executing', 'completed', 'failed'
+ mode TEXT NOT NULL, -- revert|append
+ guidance TEXT NOT NULL,
+ original_subtree_snapshot TEXT, -- JSON reference to archived subtree state before correction
+ archived_artifacts_path TEXT, -- filesystem path to archived originals
+ state TEXT NOT NULL, -- 'pending', 'executing', 'complete', 'failed'
created_at TEXT NOT NULL,
completed_at TEXT,
@@ -19798,8 +19849,8 @@ The system collects, reconciles, and checks invariants:
or self.get_global_invariant_actor()
)
- # 3. Reconcile: apply precedence (plan > project > global), resolve conflicts
- effective = reconciler.reconcile(raw, precedence=['plan', 'project', 'global'])
+ # 3. Reconcile: apply precedence (plan > action > project > global), resolve conflicts
+ effective = reconciler.reconcile(raw, precedence=['plan', 'action', 'project', 'global'])
return effective
def collect_all_invariants(self, plan):
@@ -30051,16 +30102,21 @@ When shell mode is active (`!`/`$` prefix), the prompt performs heuristic analys
| Pattern | Risk Level | Example |
|---------|-----------|---------|
-| `rm -rf` / `rm -r` | High | `rm -rf /` |
+| `rm -rf /` / `rm -rf /*` (root/wildcard) | Critical | `rm -rf /` |
+| `:(){ :\|:& };:` (fork bomb) | Critical | Fork bomb patterns |
+| `dd if=` | High | `dd if=/dev/zero of=/dev/sda` |
+| `mkfs` | High | `mkfs.ext4 /dev/sda1` |
+| `shred` on device or with `--remove` | High | `shred /dev/sda` |
| `chmod 777` | Medium | `chmod 777 /var/www` |
-| `> /dev/sda` / `dd if=` | High | `dd if=/dev/zero of=/dev/sda` |
-| `:(){ :\|:& };:` (fork bomb) | High | Fork bomb patterns |
-| `mkfs` / `fdisk` / `parted` | High | Disk formatting tools |
-| `kill -9` / `killall` | Medium | Process termination |
-| `sudo` / `su` | Low | Privilege escalation (warning only) |
+| `sudo rm` | Medium | `sudo rm -rf /tmp/data` |
+| `wget \| sh` / `curl \| sh` | Medium | `curl https://example.com/install.sh \| sh` |
+| `git push --force` | Low | `git push --force origin main` |
+| `chmod -R` with permissive modes | Low | `chmod -R 644 /var/www` |
Danger detection is controlled by the `shell.warn_dangerous` setting (default: `true`). The detection is advisory only — it never prevents command execution. The warning text reads: `⚠ Potentially destructive command detected`.
+Four danger levels are defined, from least to most severe: **Low** (minor risk, generally recoverable), **Medium** (moderate risk, can cause data loss or security exposure), **High** (significant, hard-to-reverse damage), and **Critical** (can destroy the entire system or create a fork bomb). All levels trigger the same advisory warning — the level is used for styling and future escalation logic.
+
### Notification System
@@ -45482,6 +45538,8 @@ Basic keyword/regex text search. Works with any backend, any resource type. No g
Vector similarity search. Finds semantically related content even without exact keyword matches. Requires a vector backend. Quality score: 0.6.
+> **v1 Implementation Note:** The current implementation uses a character-frequency embedding as a v1 approximation of semantic similarity. This approximation is passed to `VectorBackend.similarity_search()` and is intended for replacement with a real embedding model in a future release.
+
##### Strategy: `breadth-depth-navigator`
The graph-aware strategy that uses the depth/breadth projection system. Works with the UKO graph to provide structurally-aware context at any detail depth. Supports focus items, hop traversal, and detail depth gradients. This is the primary strategy for code-aware context. Quality score: 0.85.
@@ -45508,6 +45566,8 @@ Retrieves context from parent and ancestor plan decisions. This is how child pla
The ACMS-specific extension points (analyzers, backends, UKO vocabularies, strategies, and pipeline components) are documented in the **Extensibility** section below. See **Extensibility > ACMS Extensions** for the full details.
+**`SpecStrategyAdapter`**: The built-in strategies are registered with `ACMSPipeline` at construction time via `SpecStrategyAdapter`, a bridge class that adapts the domain-model `ContextStrategy` protocol to the pipeline's internal protocol without modifying either interface. Custom strategies that implement `ContextStrategy` directly can also be registered via `SpecStrategyAdapter`. Note: the long-term goal is to consolidate the two protocols so that `SpecStrategyAdapter` is no longer needed — see issue #4560.
+
#### UKO Runtime Services
The UKO runtime is operationalized through three service classes:
@@ -45826,6 +45886,7 @@ The relational database follows a normalized design with foreign key constraints
new_decision_id TEXT REFERENCES decisions(decision_id),
mode TEXT NOT NULL, -- revert|append
guidance TEXT NOT NULL,
+ original_subtree_snapshot TEXT, -- JSON reference to archived subtree state before correction
archived_artifacts_path TEXT, -- filesystem path to archived originals
state TEXT NOT NULL DEFAULT 'pending', -- pending|executing|complete|failed
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
@@ -46388,21 +46449,20 @@ Tools are the atomic unit of execution and the primary extension point. There ar
2. **MCP server tools**: Any MCP-compliant server can expose tools to CleverAgents. Skills reference MCP servers by their transport configuration:
# File: skills/kubernetes-ops.yaml
- skill:
- name: local/kubernetes-ops
- description: "Kubernetes cluster management tools"
- mcp_servers:
- - transport: stdio
- command: npx
- args: ["-y", "@anthropic/mcp-kubernetes"]
+ name: local/kubernetes-ops
+ description: "Kubernetes cluster management tools"
+ mcp_servers:
+ - name: kubernetes
+ transport: stdio
+ command: npx
+ args: ["-y", "@anthropic/mcp-kubernetes"]
3. **Agent Skills Standard tools**: Tools organized in standard folder structures are auto-discovered and registered:
- skill:
- name: local/project-tools
- agent_skills_dirs:
- - ./agent-skills/
+ name: local/project-tools
+ agent_skill_folders:
+ - ./agent-skills/
4. **Built-in tools**: Core file operations (`read_file`, `write_file`, `edit_file`, `delete_file`, `move_file`, `list_files`, `search_files`), plan operations (`create-subplan`), and system operations are provided as built-in tools grouped into built-in skills.
@@ -46530,11 +46590,11 @@ New backends are registered via configuration:
The sandbox layer supports custom isolation strategies for specialized resource types:
-class SandboxStrategy(Protocol):
+class SandboxStrategyProtocol(Protocol):
"""Interface for sandbox isolation strategies."""
def create(self, plan_id: str, resource: Resource) -> SandboxRef: ...
def read(self, ref: SandboxRef, path: str) -> bytes: ...
- def write(self, ref: SandboxRef, path: str, content: bytes) -> Change: ...
+ def write(self, ref: SandboxRef, path: str, content: bytes) -> DiffEntry: ...
def diff(self, ref: SandboxRef) -> DiffView: ...
def commit(self, ref: SandboxRef) -> None: ...
def rollback(self, ref: SandboxRef) -> None: ...
@@ -46543,7 +46603,21 @@ The sandbox layer supports custom isolation strategies for specialized resource
def cleanup(self, ref: SandboxRef) -> None: ...
-Custom strategies are mapped to resource types via the resource type configuration's `sandbox_strategy` field, or globally via `sandbox.strategy` config.
+Custom strategies are registered via configuration and then referenced by name in the resource type's `sandbox_strategy` field:
+
+```toml
+# config.toml — register the custom strategy
+[sandbox.custom_strategies.my-strategy]
+module = "my_package.my_module"
+class = "MySandboxClass"
+```
+
+```yaml
+# resource-types/my-resource.yaml — reference the strategy by name
+resource_type:
+ name: local/my-resource
+ sandbox_strategy: my-strategy
+```
#### ACMS Extensions