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

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 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

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