Move alembic configuration and migration files from repository root into the Python package structure to ensure they are included in the wheel distribution. This fix resolves the FileNotFoundError when running `agents init` in Docker containers or any environment using the built wheel distribution. Changes: - Move alembic/ directory from repo root to src/cleveragents/infrastructure/database/migrations/ - Move alembic.ini to the same new location and update script_location setting - Update MigrationRunner._find_alembic_ini() to search from the new canonical location within the package - Update create_template_db.py to point to the new alembic.ini location - Update documentation references to reflect new migration file locations - Create __init__.py for migrations package - The env.py file is imported when running tests that verify all modules can be imported without errors. However, context.config is only available when alembic is actually running migrations, not during normal module imports. This caused an AttributeError when the test tried to import the migrations.env module. - Fix by using getattr() with a default value to safely access context.config, and guard all code that uses config with None checks. This allows the module to be safely imported while still functioning correctly during migrations. Testing: - Verified MigrationRunner can locate alembic.ini in new location - Tested agents init succeeds in creating project with database - Template database creation works correctly - All migration tests should pass without changes Alembic files now follow standard Python packaging conventions, making them automatically included in wheel distributions without special configuration. ISSUES CLOSED: #4180
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:
src/cleveragents/infrastructure/database/migrations/versions/c1_001_tool_registry.py