144 lines
6.3 KiB
Markdown
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")
|
|
```
|