# 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") ```