e3fcce413b
CI / lint (pull_request) Successful in 24s
CI / typecheck (pull_request) Successful in 59s
CI / security (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 36s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 27s
CI / integration_tests (pull_request) Successful in 5m30s
CI / benchmark-regression (pull_request) Successful in 25m38s
CI / unit_tests (pull_request) Successful in 36m51s
CI / docker (pull_request) Successful in 1m3s
CI / coverage (pull_request) Successful in 1h48m49s
CI / lint (push) Successful in 22s
CI / security (push) Successful in 58s
CI / typecheck (push) Successful in 1m3s
CI / quality (push) Successful in 46s
CI / build (push) Successful in 23s
CI / integration_tests (push) Successful in 5m23s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 15m5s
CI / unit_tests (push) Successful in 20m1s
CI / docker (push) Successful in 1m0s
CI / coverage (push) Successful in 1h45m36s
Add SQLite persistence for ChangeSet entries and ToolInvocation records via new changeset_repository module implementing the ChangeSetStore protocol. - Alembic migration d0_001 creates changeset_entries and tool_invocations tables - SqliteChangeSetStore, ChangeSetEntryRepository, ToolInvocationRepository - PlanApplyService.cleanup_changeset() for cancel/failure cleanup - Behave tests (17 scenarios), Robot tests (5 cases), ASV benchmarks - Reference documentation in docs/reference/changeset.md Closes #163
157 lines
5.0 KiB
Markdown
157 lines
5.0 KiB
Markdown
# ChangeSet Persistence
|
|
|
|
## Overview
|
|
|
|
ChangeSets track every file mutation made during the Execute phase of a plan.
|
|
Each mutation is recorded as a `ChangeEntry` with content hashes, operation
|
|
type, and tool provenance. Tool executions are recorded as `ToolInvocation`
|
|
records for full auditability.
|
|
|
|
## Persistence Fields
|
|
|
|
### changeset_entries table
|
|
|
|
| Column | Type | Description |
|
|
|---------------|--------------|--------------------------------------|
|
|
| entry_id | String(26) | ULID primary key |
|
|
| changeset_id | String(26) | ULID of the owning changeset |
|
|
| plan_id | String(26) | ULID of the owning plan |
|
|
| resource_id | String(26) | ULID of the affected resource |
|
|
| tool_name | String(255) | Namespaced tool name |
|
|
| operation | String(20) | create, modify, delete, or rename |
|
|
| path | String(1024) | Repo-relative file path |
|
|
| before_hash | String(64) | SHA-256 before change (nullable) |
|
|
| after_hash | String(64) | SHA-256 after change (nullable) |
|
|
| before_mode | Integer | File mode before change (nullable) |
|
|
| after_mode | Integer | File mode after change (nullable) |
|
|
| timestamp | String(30) | ISO-8601 UTC timestamp |
|
|
|
|
### tool_invocations table
|
|
|
|
| Column | Type | Description |
|
|
|---------------------|--------------|----------------------------------|
|
|
| invocation_id | String(26) | ULID primary key |
|
|
| changeset_id | String(26) | Optional owning changeset |
|
|
| plan_id | String(26) | Owning plan ULID |
|
|
| tool_name | String(255) | Namespaced tool name |
|
|
| skill_name | String(255) | Skill that provided the tool |
|
|
| arguments_json | Text | JSON input parameters |
|
|
| result_json | Text | JSON output payload |
|
|
| error | Text | Error message on failure |
|
|
| success | Boolean | Whether execution succeeded |
|
|
| duration_ms | Float | Execution time in milliseconds |
|
|
| started_at | String(30) | ISO-8601 start timestamp |
|
|
| completed_at | String(30) | ISO-8601 completion timestamp |
|
|
| change_ids_json | Text | JSON list of ChangeEntry IDs |
|
|
| sequence_number | Integer | Ordering within a plan |
|
|
| sandbox_path | String(1024) | Sandbox root path |
|
|
| resource_refs_json | Text | JSON list of resource IDs |
|
|
| provider_metadata_json | Text | JSON provider info |
|
|
|
|
## Diff Output Examples
|
|
|
|
### Plain format (`agents plan diff <plan_id> --format plain`)
|
|
|
|
```text
|
|
ChangeSet: 01HXYZ123456789ABCDEFG
|
|
Plan: 01HABCDEF123456789XYZ
|
|
Total changes: 3
|
|
|
|
--- a/src/new_module.py
|
|
+++ b/src/new_module.py
|
|
@@ new file @@
|
|
+ hash: abc123def456...
|
|
|
|
--- a/src/existing.py
|
|
+++ b/src/existing.py
|
|
@@ modified @@
|
|
- hash: old_hash_val...
|
|
+ hash: new_hash_val...
|
|
|
|
--- a/src/removed.py
|
|
+++ b/src/removed.py
|
|
@@ deleted @@
|
|
- hash: file_hash_be...
|
|
```
|
|
|
|
### JSON format (`agents plan diff <plan_id> --format json`)
|
|
|
|
```json
|
|
{
|
|
"changeset_id": "01HXYZ123456789ABCDEFG",
|
|
"plan_id": "01HABCDEF123456789XYZ",
|
|
"total_changes": 3,
|
|
"summary": {
|
|
"total": 3,
|
|
"creates": 1,
|
|
"modifies": 1,
|
|
"deletes": 1,
|
|
"renames": 0,
|
|
"paths_changed": 3,
|
|
"resources_involved": 1
|
|
},
|
|
"entries": [
|
|
{
|
|
"path": "src/new_module.py",
|
|
"operation": "create",
|
|
"before_hash": null,
|
|
"after_hash": "abc123def456",
|
|
"resource_id": "01HRES001",
|
|
"tool_name": "file-write",
|
|
"timestamp": "2026-02-24T12:00:00+00:00"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Artifacts output (`agents plan artifacts <plan_id>`)
|
|
|
|
```json
|
|
{
|
|
"plan_id": "01HABCDEF123456789XYZ",
|
|
"phase": "execute",
|
|
"processing_state": "complete",
|
|
"changeset_id": "01HXYZ123456789ABCDEFG",
|
|
"sandbox_refs": ["sandbox-001"],
|
|
"changeset_summary": {
|
|
"total": 3,
|
|
"creates": 1,
|
|
"modifies": 1,
|
|
"deletes": 1,
|
|
"renames": 0
|
|
},
|
|
"files_changed": [
|
|
{"path": "src/new_module.py", "operation": "create"},
|
|
{"path": "src/existing.py", "operation": "modify"},
|
|
{"path": "src/removed.py", "operation": "delete"}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Cleanup Behaviour
|
|
|
|
Changeset artifacts are automatically cleaned up when:
|
|
|
|
- **Plan cancel**: `cleanup_changeset()` deletes all persisted entries and
|
|
invocations for the cancelled plan.
|
|
- **Apply failure**: `cleanup_changeset()` is called after
|
|
`handle_merge_failure()` to free storage for failed plans.
|
|
|
|
## API
|
|
|
|
### SqliteChangeSetStore
|
|
|
|
Implements the `ChangeSetStore` protocol with SQLite persistence.
|
|
|
|
```python
|
|
from cleveragents.infrastructure.database.changeset_repository import (
|
|
SqliteChangeSetStore,
|
|
)
|
|
|
|
store = SqliteChangeSetStore(session_factory)
|
|
changeset_id = store.start(plan_id)
|
|
store.record(changeset_id, entry)
|
|
changeset = store.get(changeset_id)
|
|
store.delete_for_plan(plan_id)
|
|
```
|