Files
cleveragents-core/docs/reference/change_tracking.md
T

144 lines
6.3 KiB
Markdown

# Change Tracking (C5.model)
This document describes the ChangeSet domain models, ToolInvocation
tracking, and the SkillInvocationTracker used for auditability of
tool executions during the Execute phase.
## Overview
CleverAgents uses a **tool-based change model**: LLMs call tools
directly, tools modify sandbox state, and a `ChangeSet` is built from
those tool invocations. Every resource modification is explicit and
tracked.
## Models
### ChangeOperation (enum)
| Value | Description |
|----------|---------------------------|
| `create` | New file created |
| `modify` | Existing file changed |
| `delete` | File removed |
| `rename` | File moved or renamed |
`ChangeType` is an alias for `ChangeOperation`.
### ChangeEntry
A single recorded change from a tool execution.
| Field | Type | Required | Description |
|---------------|--------------------|----------|-----------------------------------|
| `entry_id` | `str` (ULID) | auto | Unique identifier |
| `plan_id` | `str` (ULID) | yes | Owning plan |
| `resource_id` | `str` (ULID) | yes | Affected resource |
| `tool_name` | `str` | yes | Namespaced tool name |
| `operation` | `ChangeOperation` | yes | Type of change |
| `path` | `str` | yes | Repo-relative file path |
| `before_hash` | `str \| None` | no | SHA-256 before change |
| `after_hash` | `str \| None` | no | SHA-256 after change |
| `before_mode` | `int \| None` | no | File mode before change |
| `after_mode` | `int \| None` | no | File mode after change |
| `timestamp` | `datetime` | auto | UTC timestamp |
**Validation rules:**
- CREATE must not have `before_hash` (no prior content).
- DELETE must not have `after_hash` (no resulting content).
**Integrity check:** `has_integrity_hashes` property returns `True` when
the operation-appropriate hashes are all present.
### SpecChangeSet
Accumulated set of ChangeEntry records for a plan.
| Field | Type | Description |
|----------------|---------------------|-------------------------------|
| `changeset_id` | `str` (ULID) | Unique identifier |
| `plan_id` | `str` (ULID) | Owning plan |
| `entries` | `list[ChangeEntry]` | Ordered change entries |
| `created_at` | `datetime` | UTC creation timestamp |
**Methods:**
- `summary()` -- flat counts (total, creates, modifies, deletes, renames)
- `sorted_entries()` -- deterministic order: `(resource_id, path, timestamp)`
- `grouped_by_resource()` -- dict grouped by resource_id, sorted within
- `add_change(entry)` -- append a change entry
### ToolInvocation
Record of a single tool execution for auditability.
| Field | Type | Description |
|--------------------|-------------------------|------------------------------------|
| `invocation_id` | `str` (ULID) | Unique identifier |
| `plan_id` | `str` (ULID) | Plan that triggered the call |
| `tool_name` | `str` | Namespaced tool name |
| `skill_name` | `str \| None` | Skill that provided the tool |
| `arguments` | `dict` | Input parameters |
| `result` | `dict \| None` | Output payload |
| `error` | `str \| None` | Error message on failure |
| `success` | `bool` | Whether execution succeeded |
| `duration_ms` | `float` | Wall-clock time in milliseconds |
| `started_at` | `datetime` | UTC start timestamp |
| `completed_at` | `datetime \| None` | UTC end timestamp |
| `change_ids` | `list[str]` | ChangeEntry IDs produced |
| `sequence_number` | `int` | Ordering within a plan |
| `sandbox_path` | `str \| None` | Sandbox root used |
| `resource_refs` | `list[str]` | Resource IDs involved |
| `provider_metadata`| `dict \| None` | Provider info (model, latency) |
### InvocationTracker (Protocol)
| Method | Returns | Description |
|---------------------------------|------------------------|---------------------------------|
| `track(invocation)` | `None` | Record a tool invocation |
| `get_invocations(plan_id)` | `list[ToolInvocation]` | All invocations for a plan |
| `get_invocations_for_skill(...)`| `list[ToolInvocation]` | Filter by skill within a plan |
| `get_changes(plan_id)` | `list[str]` | All change IDs for a plan |
### InMemoryInvocationTracker
In-memory implementation of the `InvocationTracker` protocol.
Returns invocations sorted by `sequence_number`.
## Path Normalization
`normalize_change_path(path, repo_root)` converts absolute paths to
repo-relative POSIX paths by stripping the repo root prefix.
## Usage
```python
from cleveragents.domain.models.core.change import (
ChangeEntry, ChangeOperation, SpecChangeSet,
ToolInvocation, InMemoryInvocationTracker,
normalize_change_path,
)
# Create and populate a changeset
cs = SpecChangeSet(plan_id="plan-123")
cs.add_change(ChangeEntry(
plan_id="plan-123",
resource_id="res-1",
tool_name="builtin/file-write",
operation=ChangeOperation.CREATE,
path=normalize_change_path("/sandbox/src/new.py", "/sandbox"),
after_hash="abc123",
))
# Track a tool invocation
tracker = InMemoryInvocationTracker()
tracker.track(ToolInvocation(
plan_id="plan-123",
tool_name="builtin/file-write",
skill_name="local/file-ops",
change_ids=[cs.entries[0].entry_id],
))
# Get grouped view for plan diff
for resource_id, entries in cs.grouped_by_resource().items():
print(f"Resource {resource_id}: {len(entries)} changes")
```