6.3 KiB
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 withinadd_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
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")