Add Alembic migration m4_002_skill_flattened_tools to extend the skills table with five new columns: flattened_tools_json, includes_json, capability_summary_json, yaml_text, and flattening_hash (SHA-256). A defence-in-depth uniqueness constraint (uq_skills_name) is also added. Update SkillModel with the new column definitions and extend SkillRepository with update_flattened_tools(), get_flattened_tools(), needs_refresh(), recompute_flattening_hash(), and invalidate_cached_summaries() methods. The existing update() method now nulls all cached fields on mutation (hash-based invalidation). All new repository methods follow the session-factory pattern with @database_retry and flush-but-don-t-commit semantics. Structured logging via structlog records cache updates and invalidations. Database schema docs updated with the new skills table columns and a persistence-field-to-domain-model mapping table. Tests: - 6 Behave scenarios covering create, invalidation, hash staleness, refresh recomputation, uniqueness constraint, and namespace filtering - 2 Robot Framework smoke tests (round-trip and invalidation) - 3 ASV benchmarks (persist, refresh check, namespace list) ISSUES CLOSED: #166
20 KiB
Database Schema Reference
Overview
CleverAgents uses SQLAlchemy ORM models with SQLite as the default backend.
All tables are created via Base.metadata.create_all through the
init_database() helper in
src/cleveragents/infrastructure/database/models.py.
Resource Registry Tables
The resource registry consists of three core tables introduced in Stage B1:
| Table | Model | Description |
|---|---|---|
resource_types |
ResourceTypeModel |
Schema-level resource type defs |
resources |
ResourceModel |
Registered resource instances |
resource_edges |
ResourceEdgeModel |
Parent-child DAG edges |
resource_types
Primary key: name (namespaced, e.g. builtin/git-checkout).
Stores kind (physical/virtual), handler references, JSON argument
schemas, allowed parent/child types, auto-discovery config, and
capabilities.
resources
Primary key: resource_id (26-char ULID).
FK to resource_types.name. Stores optional namespaced name, location,
JSON properties/metadata, and content hash for equivalence tracking.
resource_edges
Composite primary key: (parent_id, child_id).
Both columns FK to resources.resource_id with CASCADE delete.
Stores link_type (contains, references, derived_from) and a
self-loop check constraint.
Robot Migration Smoke Suite
A Robot Framework smoke suite validates that schema creation produces the expected resource registry tables with correct columns.
Running the suite
# Via nox (recommended — runs all Robot integration tests):
nox -s integration_tests
# Directly with robot:
robot --outputdir build/reports/robot robot/resource_registry_migration.robot
Test cases
| Test Case | What it checks |
|---|---|
| Schema Creation Produces Resource Registry Tables | resource_types, resources, resource_edges exist |
| Resource Registry Tables Have Expected Columns | Core columns present on each table |
| Migration Is Idempotent | Calling init_database twice is safe |
The helper script (robot/helper_resource_registry_migration.py) uses
init_database() with a temporary SQLite file and validates via
sqlalchemy.inspect.
Behave BDD Tests
The Behave feature file features/resource_registry_tables.feature
contains comprehensive scenarios covering:
- Table existence after schema creation
- CRUD operations for
ResourceTypeModel,ResourceModel,ResourceEdgeModel - Constraint enforcement (uniqueness, FK, check constraints)
- ORM relationship navigation
- Migration smoke verification
Run Behave tests via:
nox -s unit_tests
ASV Benchmarks
Performance benchmarks for resource registry operations live in
benchmarks/resource_registry_migration_bench.py and measure:
- Schema creation time
- Insert throughput for types, resources, and edges
- DAG query performance
Run benchmarks via:
nox -s benchmark
Tool and Validation Registry Tables
Tables
tools
Stores registered tool and validation definitions.
| Column | Type | Constraints | Description |
|---|---|---|---|
tool_id |
TEXT | PRIMARY KEY | ULID identifier |
name |
TEXT | NOT NULL, UNIQUE | Namespaced name (namespace/short_name) |
description |
TEXT | Human-readable description | |
tool_type |
TEXT | NOT NULL, CHECK (tool/validation) |
Discriminator for registry queries |
source_type |
TEXT | NOT NULL, CHECK (see below) | Implementation source |
input_schema |
TEXT | JSON Schema for tool inputs | |
output_schema |
TEXT | JSON Schema for tool outputs | |
read_only |
BOOLEAN | NOT NULL, DEFAULT FALSE | Tool only reads, never writes |
writes |
BOOLEAN | NOT NULL, DEFAULT FALSE | Tool can write to resources |
checkpointable |
BOOLEAN | NOT NULL, DEFAULT FALSE | Tool supports checkpoint/rollback |
side_effects |
BOOLEAN | NOT NULL, DEFAULT FALSE | Tool has known side effects |
config_yaml |
TEXT | Raw YAML config for reconstruction | |
created_at |
TEXT | NOT NULL | ISO-8601 timestamp |
updated_at |
TEXT | NOT NULL | ISO-8601 timestamp |
Check constraints:
ck_tools_tool_type:tool_type IN ('tool', 'validation')ck_tools_source_type:source_type IN ('mcp', 'agent_skill', 'builtin', 'custom', 'wrapped')
Indexes:
ix_tools_nameon(name)ix_tools_tool_typeon(tool_type)
tool_bindings
Stores resource slot bindings for tools.
| Column | Type | Constraints | Description |
|---|---|---|---|
binding_id |
TEXT | PRIMARY KEY | ULID identifier |
tool_id |
TEXT | NOT NULL, FK → tools.tool_id CASCADE |
Parent tool reference |
slot_name |
TEXT | NOT NULL | Named resource slot |
resource_type |
TEXT | NOT NULL | Expected resource type |
binding_mode |
TEXT | NOT NULL, CHECK (see below) | How the slot is resolved |
access_level |
TEXT | NOT NULL, DEFAULT read_only |
Access level on bound resource |
created_at |
TEXT | NOT NULL | ISO-8601 timestamp |
Check constraints:
ck_tool_bindings_mode:binding_mode IN ('context', 'static', 'parameter')ck_tool_bindings_access:access_level IN ('read_only', 'read_write')
Unique constraints:
uq_tool_bindings_tool_slot:UNIQUE(tool_id, slot_name)
validation_attachments
Links validations to resources with mode semantics.
| Column | Type | Constraints | Description |
|---|---|---|---|
attachment_id |
TEXT | PRIMARY KEY | ULID identifier |
resource_id |
TEXT | NOT NULL, FK → resources.resource_id CASCADE |
Target resource |
validation_name |
TEXT | NOT NULL | References tools.name (validation) |
mode |
TEXT | NOT NULL, DEFAULT required |
required or informational |
created_at |
TEXT | NOT NULL | ISO-8601 timestamp |
Check constraints:
ck_validation_attachments_mode:mode IN ('required', 'informational')
Unique constraints:
uq_validation_attachments_resource_validation:UNIQUE(resource_id, validation_name)
Indexes:
ix_validation_attachments_resource_idon(resource_id)
Relationships
tools 1 ──< tool_bindings (tool_id FK, CASCADE delete)
resources 1 ──< validation_attachments (resource_id FK, CASCADE delete)
validation_attachments.validation_name → tools.name (logical, not enforced by FK)
actors
Stores actor configurations with YAML text retention, schema version tracking, and optional compiled metadata.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
INTEGER | PRIMARY KEY, AUTOINCREMENT | Auto-generated row ID |
name |
TEXT(255) | NOT NULL, UNIQUE | Namespaced name (namespace/identifier) |
provider |
TEXT(255) | NOT NULL | Provider identifier (e.g. openai) |
model |
TEXT(255) | NOT NULL | Model identifier (e.g. gpt-4) |
config_blob |
JSON | NOT NULL, DEFAULT {} |
Canonical actor configuration blob |
config_hash |
TEXT(128) | NOT NULL | SHA-256 hash of config_blob |
graph_descriptor |
JSON | Adapter-produced graph descriptor | |
yaml_text |
TEXT | Original YAML source text for the actor config | |
schema_version |
TEXT(20) | NOT NULL, DEFAULT '1.0' |
Actor config schema version |
compiled_metadata |
JSON | Compiler-produced metadata (graph topology etc) | |
unsafe |
BOOLEAN | NOT NULL, DEFAULT FALSE | True when actor is marked unsafe |
is_built_in |
BOOLEAN | NOT NULL, DEFAULT FALSE | Generated from provider registry |
is_default |
BOOLEAN | NOT NULL, DEFAULT FALSE | Designated default actor |
created_at |
DATETIME | NOT NULL | Row creation timestamp |
updated_at |
DATETIME | NOT NULL | Last update timestamp |
Notes:
yaml_textpreserves the original YAML source so that actors can be reconstructed or exported without loss.schema_versiontracks which version of the actor YAML schema was used to create the configuration. Defaults to1.0.compiled_metadatastores JSON output from the actor compiler (graph topology, resolved tools, etc.).namefollows thenamespace/identifierconvention enforced by the domain model validator (e.g.local/my-actor,openai/gpt-4).
Skill Registry Tables
The skill registry stores namespaced skill definitions with cached flattened tool sets for fast resolution.
| Table | Model | Description |
|---|---|---|
skills |
SkillModel |
Registered skill definitions |
skill_items |
SkillItemModel |
Child items (tools, includes, etc) |
skills
Primary key: name (namespaced, e.g. local/code-tools).
| Column | Type | Constraints | Description |
|---|---|---|---|
name |
String(255) | PK, UNIQUE | Namespaced name (namespace/short_name) |
namespace |
String(100) | NOT NULL | Namespace extracted from name |
short_name |
String(150) | NOT NULL | Short name extracted from name |
description |
Text | NOT NULL | Human-readable description |
version |
String(50) | Optional version string | |
metadata_json |
Text | JSON overrides metadata | |
flattened_tools_json |
Text | JSON-serialized flattened tool descriptors | |
includes_json |
Text | JSON-serialized includes list | |
capability_summary_json |
Text | JSON-serialized capability summary | |
yaml_text |
Text | Original YAML text of the skill definition | |
flattening_hash |
String(64) | SHA-256 hash for refresh invalidation | |
created_at |
String(30) | NOT NULL | ISO-8601 timestamp |
updated_at |
String(30) | NOT NULL | ISO-8601 timestamp |
Unique constraints:
uq_skills_name:UNIQUE(name)(defence-in-depth — PK already unique)
Indexes:
ix_skills_namespaceon(namespace)— used for filtered skill listing
skill_items
Child table linking skill components to their parent skill.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
Integer | PK, AUTOINCREMENT | Auto-generated row ID |
skill_name |
String(255) | NOT NULL, FK -> skills.name CASCADE | Parent skill reference |
item_type |
String(30) | NOT NULL | Item kind (tool_ref, include, etc) |
item_name |
String(500) | NOT NULL | Item identifier |
item_config |
Text | JSON configuration blob | |
item_order |
Integer | NOT NULL | Stable ordering index |
created_at |
String(30) | NOT NULL | ISO-8601 timestamp |
Indexes:
ix_skill_items_skill_nameon(skill_name)ix_skill_items_item_typeon(item_type)
Persistence field mapping
| Database Column | Domain Model Property |
|---|---|
flattened_tools_json |
ResolvedToolEntry list (JSON) |
includes_json |
SkillInclude list (JSON) |
capability_summary_json |
SkillCapabilitySummary (JSON) |
yaml_text |
Original YAML source text |
flattening_hash |
SHA-256 digest of yaml_text |
Decision Tree Tables
The decision tree subsystem consists of two tables introduced in Stage M3.2:
| Table | Model | Description |
|---|---|---|
decisions |
DecisionModel |
Decision tree nodes |
decision_dependencies |
DecisionDependencyModel |
Decision influence DAG edges |
decisions
Primary key: decision_id (26-char ULID).
FK to v3_plans.plan_id (CASCADE delete). Self-referential FKs for
parent_decision_id, corrects_decision_id, and superseded_by (SET NULL).
| Column | Type | Constraints | Description |
|---|---|---|---|
decision_id |
String(26) | PRIMARY KEY | ULID identifier |
plan_id |
String(26) | NOT NULL, FK -> v3_plans CASCADE | Parent plan reference |
parent_decision_id |
String(26) | FK -> decisions SET NULL | Parent decision in tree |
sequence_number |
Integer | NOT NULL | Monotonic order within plan |
decision_type |
String(30) | NOT NULL, CHECK (11 enum values) | Classification of the decision |
question |
Text | NOT NULL | What question was being answered |
chosen_option |
Text | NOT NULL | The option that was chosen |
alternatives_considered_json |
Text | JSON array of alternative options | |
confidence_score |
Float | CHECK (0.0-1.0 or NULL) | Confidence in the decision |
context_snapshot_json |
Text | NOT NULL | JSON blob of ContextSnapshot |
rationale |
Text | Human-readable rationale | |
actor_reasoning |
Text | Raw LLM reasoning trace | |
downstream_decision_ids_json |
Text | JSON array of dependent decision ULIDs | |
downstream_plan_ids_json |
Text | JSON array of spawned plan ULIDs | |
artifacts_produced_json |
Text | JSON array of ArtifactRef objects | |
created_at |
String(30) | NOT NULL | ISO-8601 timestamp |
is_correction |
Boolean | NOT NULL, DEFAULT FALSE | Whether this is a correction |
corrects_decision_id |
String(26) | FK -> decisions SET NULL | Decision being corrected |
correction_reason |
Text | Why the correction was made | |
superseded_by |
String(26) | FK -> decisions SET NULL | Decision that supersedes this one |
Check constraints:
ck_decisions_type:decision_type IN ('prompt_definition', 'invariant_enforced', 'strategy_choice', 'implementation_choice', 'resource_selection', 'subplan_spawn', 'subplan_parallel_spawn', 'tool_invocation', 'error_recovery', 'validation_response', 'user_intervention')ck_decisions_confidence:confidence_score IS NULL OR (confidence_score >= 0.0 AND confidence_score <= 1.0)
Indexes:
ix_decisions_planon(plan_id)ix_decisions_parenton(parent_decision_id)ix_decisions_typeon(decision_type)ix_decisions_createdon(created_at)ix_decisions_plan_seqon(plan_id, sequence_number)
decision_dependencies
Composite primary key: (source_decision_id, target_decision_id).
Both columns FK to decisions.decision_id with CASCADE delete.
Stores relationship_type (default influences) and a self-loop
check constraint.
| Column | Type | Constraints | Description |
|---|---|---|---|
source_decision_id |
String(26) | PK, FK -> decisions CASCADE | Decision that influences |
target_decision_id |
String(26) | PK, FK -> decisions CASCADE | Decision that is influenced |
relationship_type |
String(30) | NOT NULL, DEFAULT 'influences' | Type of influence relationship |
created_at |
String(30) | NOT NULL | ISO-8601 timestamp |
Check constraints:
ck_decision_deps_no_self_loop:source_decision_id != target_decision_id
Indexes:
ix_decision_deps_sourceon(source_decision_id)ix_decision_deps_targeton(target_decision_id)
Migration
- Revision:
c1_001_tool_registry - Depends on:
b0_001_projects - File:
alembic/versions/c1_001_tool_registry.py