Make built-in actors virtual (resolved on-demand from provider registry, not persisted to database) #10923

Closed
opened 2026-04-29 05:20:18 +00:00 by hurui200320 · 2 comments
Member

Metadata

Commit Message: feat(actor): make built-in actors virtual, resolved on-demand from provider registry
Branch: feature/m3-virtual-built-in-actors

Background and Context

Built-in actors (e.g. openai/gpt-4o, anthropic/claude-sonnet-4-20250514) represent LLM providers configured via API keys. They are conceptually derived data — their existence and configuration come from the ProviderRegistry, not from user-created definitions.

The specification (docs/specification.md) shows agents actor list output that includes built-in actors alongside custom actors, implying they should always be visible when providers are configured. However, the current implementation has an architectural contradiction:

  1. ensure_built_in_actors() persists built-in actors to the SQLite database via upsert_actor() (database write)
  2. list() and list_actors() intentionally skip calling ensure_built_in_actors() to avoid triggering writes from a read-only operation (bug #797)
  3. Result: after agents init --yes, running agents actor list shows "No actors configured" even though providers are configured with valid API keys

The built-in actors only appear after a write operation (e.g. actor set-default, actor add) triggers ensure_built_in_actors() as a side effect.

Current Behavior

  • agents init --yes creates the database but does not materialize built-in actors
  • agents actor list returns "No actors configured" because it does not call ensure_built_in_actors()
  • Built-in actors only appear in the database after a write operation triggers lazy materialization
  • The ActorRegistry.list() method has a comment explicitly stating it does not call ensure_built_in_actors() to avoid database writes from read-only operations (bug #797)

Expected Behavior

Built-in actors should be truly virtual — resolved on-demand from the ProviderRegistry at query time and merged with persisted custom actors in memory. No database writes should occur for built-in actors.

Actor resolution order (when looking up an actor by name):

  1. Search the database for a custom actor with the exact name
  2. If not found, resolve from configured providers as a virtual built-in actor
  3. If still not found, throw an error

This allows user-registered actors to take precedence over built-in ones (though the spec reserves provider namespaces like openai/ and anthropic/ for built-in actors, so registration of a custom actor with those namespaces should be rejected at actor add time — a pre-existing gap, not in scope here).

This means:

  • agents actor list shows built-in actors immediately after init (if providers are configured)
  • agents actor list output shows built-in actors with Built-in: ✓ column populated
  • agents actor show openai/gpt-4o works without prior materialization
  • agents actor run openai/gpt-4o "hello" works without prior materialization
  • The database only stores custom actors (those added via actor add)
  • Built-in actors cannot be removed or updated
  • set-default persists only the actor name string; the name can reference a virtual built-in actor that is resolved from the provider registry at runtime
  • All dead code and database schema artifacts related to persisting built-in actors are removed in this same change

Acceptance Criteria

  • agents actor list displays built-in actors from configured providers without any prior write operation
  • agents actor list output shows built-in actors with Built-in: ✓ column populated
  • agents actor show <provider>/<model> returns details for a built-in actor without it being persisted
  • agents actor run <provider>/<model> "prompt" executes using a virtual built-in actor
  • agents actor remove <provider>/<model> returns an error (built-in actors cannot be removed)
  • agents actor set-default <provider>/<model> persists the actor name and resolves it to the virtual built-in actor at runtime
  • agents actor get-default returns the virtual built-in actor when the persisted default name matches a provider-registry actor
  • Actor resolution follows DB-first order: database → virtual built-in → error
  • The ActorRegistry.list() and list_actors() methods resolve virtual actors without calling any persistence method
  • The ensure_built_in_actors() method is removed entirely — no code path persists built-in actors to the database
  • Custom actors (added via actor add) continue to be persisted and listed correctly
  • The is_built_in flag on the Actor model is preserved for virtual actors returned from the provider registry
  • ActorRepository.upsert_built_in() is removed (no longer needed)
  • The is_built_in column is removed from the actors database table via a new Alembic migration
  • All references to is_built_in in the repository layer are cleaned up
  • All existing Behave scenarios for actor listing pass with the new virtual behavior
  • Coverage remains ≥ 97% via nox -s coverage_report
  • Full nox suite passes with zero errors

Supporting Information

  • Specification: docs/specification.md lines 5497-5644 (agents actor list examples show built-in actors in output)
  • Specification: docs/specification.md lines 1330-1487 (agents init output does NOT include built-in actors)
  • Current code: src/cleveragents/actor/registry.py lines 164-231 (ensure_built_in_actors() persists to DB — to be removed)
  • Current code: src/cleveragents/actor/registry.py lines 439-466 (list() skips ensure_built_in_actors() — to be changed)
  • Bug reference: Bug #797 — the fix that removed ensure_built_in_actors() calls from list() to prevent read-triggered writes
  • Related: src/cleveragents/infrastructure/database/repositories.pyActorRepository has is_built_in column and upsert_built_in() method (to be removed)
  • Related: src/cleveragents/domain/models/core/actor.pyActor model has is_built_in field (keep for virtual resolution, remove from DB mapping)
  • Related: src/cleveragents/application/services/actor_service.py — service methods that persist built-in actors (to be reviewed)
  • Related: src/cleveragents/infrastructure/database/migrations/ — new migration needed to drop is_built_in column

Subtasks

  • Design: Define the virtual actor resolution strategy — how ProviderRegistry.get_configured_providers() maps to Actor domain objects in memory, including yaml_text generation for v3 compatibility
  • Refactor: Remove ensure_built_in_actors() from ActorRegistry and all call sites that invoke it
  • Refactor: Remove ActorRepository.upsert_built_in() and all repository-level built-in actor persistence logic
  • Implement: Add _resolve_virtual_builtin_actors() method to ActorRegistry that generates virtual Actor objects from configured providers (in-memory only)
  • Implement: Update ActorRegistry.list() / list_actors() to merge virtual built-in actors with persisted custom actors
  • Implement: Update ActorRegistry.get() / get_actor() to resolve virtual built-in actors by name before falling back to the database
  • Implement: Ensure set_default_actor() persists only the actor name string (no actor row created for built-ins)
  • Implement: Ensure get_default_actor() resolves the persisted name to a virtual built-in actor if it matches a configured provider
  • Implement: Ensure remove_actor() rejects built-in actor names with a clear error message
  • Implement: Ensure update() rejects built-in actor names with a clear error message
  • DB Migration: Create Alembic migration to drop is_built_in column from the actors table
  • Cleanup: Remove all dead code references to is_built_in persistence across the codebase (repository, service, registry, tests)
  • Tests (Behave): Add scenarios for virtual built-in actor listing, showing, and running
  • Tests (Behave): Add scenarios confirming built-in actors cannot be removed or updated
  • Tests (Behave): Add scenarios for set-default and get-default with virtual built-in actors
  • Tests (Robot): Add integration test confirming no database writes occur during actor list
  • Verify coverage ≥ 97% via nox -s coverage_report
  • Run full nox suite, fix any errors

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line matches the Commit Message in Metadata exactly.
  • The commit is pushed to the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a PR to master, reviewed, and merged.
## Metadata ``` Commit Message: feat(actor): make built-in actors virtual, resolved on-demand from provider registry Branch: feature/m3-virtual-built-in-actors ``` ## Background and Context Built-in actors (e.g. `openai/gpt-4o`, `anthropic/claude-sonnet-4-20250514`) represent LLM providers configured via API keys. They are conceptually derived data — their existence and configuration come from the `ProviderRegistry`, not from user-created definitions. The specification (`docs/specification.md`) shows `agents actor list` output that includes built-in actors alongside custom actors, implying they should always be visible when providers are configured. However, the current implementation has an architectural contradiction: 1. `ensure_built_in_actors()` **persists** built-in actors to the SQLite database via `upsert_actor()` (database write) 2. `list()` and `list_actors()` intentionally **skip** calling `ensure_built_in_actors()` to avoid triggering writes from a read-only operation (bug #797) 3. Result: after `agents init --yes`, running `agents actor list` shows "No actors configured" even though providers are configured with valid API keys The built-in actors only appear after a write operation (e.g. `actor set-default`, `actor add`) triggers `ensure_built_in_actors()` as a side effect. ## Current Behavior - `agents init --yes` creates the database but does not materialize built-in actors - `agents actor list` returns "No actors configured" because it does not call `ensure_built_in_actors()` - Built-in actors only appear in the database after a write operation triggers lazy materialization - The `ActorRegistry.list()` method has a comment explicitly stating it does not call `ensure_built_in_actors()` to avoid database writes from read-only operations (bug #797) ## Expected Behavior Built-in actors should be **truly virtual** — resolved on-demand from the `ProviderRegistry` at query time and merged with persisted custom actors in memory. No database writes should occur for built-in actors. **Actor resolution order** (when looking up an actor by name): 1. Search the database for a custom actor with the exact name 2. If not found, resolve from configured providers as a virtual built-in actor 3. If still not found, throw an error This allows user-registered actors to take precedence over built-in ones (though the spec reserves provider namespaces like `openai/` and `anthropic/` for built-in actors, so registration of a custom actor with those namespaces should be rejected at `actor add` time — a pre-existing gap, not in scope here). This means: - `agents actor list` shows built-in actors immediately after `init` (if providers are configured) - `agents actor list` output shows built-in actors with `Built-in: ✓` column populated - `agents actor show openai/gpt-4o` works without prior materialization - `agents actor run openai/gpt-4o "hello"` works without prior materialization - The database only stores **custom** actors (those added via `actor add`) - Built-in actors cannot be removed or updated - `set-default` persists only the actor name string; the name can reference a virtual built-in actor that is resolved from the provider registry at runtime - All dead code and database schema artifacts related to persisting built-in actors are removed in this same change ## Acceptance Criteria - [x] `agents actor list` displays built-in actors from configured providers without any prior write operation - [x] `agents actor list` output shows built-in actors with `Built-in: ✓` column populated - [x] `agents actor show <provider>/<model>` returns details for a built-in actor without it being persisted - [x] `agents actor run <provider>/<model> "prompt"` executes using a virtual built-in actor - [x] `agents actor remove <provider>/<model>` returns an error (built-in actors cannot be removed) - [x] `agents actor set-default <provider>/<model>` persists the actor name and resolves it to the virtual built-in actor at runtime - [x] `agents actor get-default` returns the virtual built-in actor when the persisted default name matches a provider-registry actor - [x] Actor resolution follows DB-first order: database → virtual built-in → error - [x] The `ActorRegistry.list()` and `list_actors()` methods resolve virtual actors without calling any persistence method - [x] The `ensure_built_in_actors()` method is removed entirely — no code path persists built-in actors to the database - [x] Custom actors (added via `actor add`) continue to be persisted and listed correctly - [x] The `is_built_in` flag on the `Actor` model is preserved for virtual actors returned from the provider registry - [x] `ActorRepository.upsert_built_in()` is removed (no longer needed) - [x] The `is_built_in` column is removed from the `actors` database table via a new Alembic migration - [x] All references to `is_built_in` in the repository layer are cleaned up - [x] All existing Behave scenarios for actor listing pass with the new virtual behavior - [x] Coverage remains ≥ 97% via `nox -s coverage_report` - [x] Full `nox` suite passes with zero errors ## Supporting Information - **Specification**: `docs/specification.md` lines 5497-5644 (`agents actor list` examples show built-in actors in output) - **Specification**: `docs/specification.md` lines 1330-1487 (`agents init` output does NOT include built-in actors) - **Current code**: `src/cleveragents/actor/registry.py` lines 164-231 (`ensure_built_in_actors()` persists to DB — to be removed) - **Current code**: `src/cleveragents/actor/registry.py` lines 439-466 (`list()` skips `ensure_built_in_actors()` — to be changed) - **Bug reference**: Bug #797 — the fix that removed `ensure_built_in_actors()` calls from `list()` to prevent read-triggered writes - **Related**: `src/cleveragents/infrastructure/database/repositories.py` — `ActorRepository` has `is_built_in` column and `upsert_built_in()` method (to be removed) - **Related**: `src/cleveragents/domain/models/core/actor.py` — `Actor` model has `is_built_in` field (keep for virtual resolution, remove from DB mapping) - **Related**: `src/cleveragents/application/services/actor_service.py` — service methods that persist built-in actors (to be reviewed) - **Related**: `src/cleveragents/infrastructure/database/migrations/` — new migration needed to drop `is_built_in` column ## Subtasks - [x] Design: Define the virtual actor resolution strategy — how `ProviderRegistry.get_configured_providers()` maps to `Actor` domain objects in memory, including `yaml_text` generation for v3 compatibility - [x] Refactor: Remove `ensure_built_in_actors()` from `ActorRegistry` and all call sites that invoke it - [x] Refactor: Remove `ActorRepository.upsert_built_in()` and all repository-level built-in actor persistence logic - [x] Implement: Add `_resolve_virtual_builtin_actors()` method to `ActorRegistry` that generates virtual `Actor` objects from configured providers (in-memory only) - [x] Implement: Update `ActorRegistry.list()` / `list_actors()` to merge virtual built-in actors with persisted custom actors - [x] Implement: Update `ActorRegistry.get()` / `get_actor()` to resolve virtual built-in actors by name before falling back to the database - [x] Implement: Ensure `set_default_actor()` persists only the actor name string (no actor row created for built-ins) - [x] Implement: Ensure `get_default_actor()` resolves the persisted name to a virtual built-in actor if it matches a configured provider - [x] Implement: Ensure `remove_actor()` rejects built-in actor names with a clear error message - [x] Implement: Ensure `update()` rejects built-in actor names with a clear error message - [x] DB Migration: Create Alembic migration to drop `is_built_in` column from the `actors` table - [x] Cleanup: Remove all dead code references to `is_built_in` persistence across the codebase (repository, service, registry, tests) - [x] Tests (Behave): Add scenarios for virtual built-in actor listing, showing, and running - [x] Tests (Behave): Add scenarios confirming built-in actors cannot be removed or updated - [x] Tests (Behave): Add scenarios for `set-default` and `get-default` with virtual built-in actors - [x] Tests (Robot): Add integration test confirming no database writes occur during `actor list` - [x] Verify coverage ≥ 97% via `nox -s coverage_report` - [x] Run full `nox` suite, fix any errors ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the first line matches the Commit Message in Metadata exactly. - The commit is pushed to the branch matching the Branch in Metadata exactly. - The commit is submitted as a PR to master, reviewed, and merged.
hurui200320 added this to the v3.2.0 milestone 2026-04-29 05:23:17 +00:00
Author
Member

Implementation Notes

Architecture Decision: Virtual Resolution Pattern

Built-in actors are now truly virtual — resolved on-demand from ProviderRegistry.get_configured_providers() and never written to the database.

Key design choices:

  1. _resolve_virtual_builtin_actors() (new method on ActorRegistry): Generates Actor domain objects in-memory from configured providers, sorted alphabetically. Returns is_built_in=True, id=None, no persistence.

  2. list() merge strategy: Calls service.list_actors() for custom DB actors, then adds virtual built-ins for names not already in the DB. Custom actors win when names collide. Result sorted alphabetically.

  3. get() resolution order: DB first (via service.get_actor), virtual built-in fallback, NotFoundError if neither.

  4. remove() guard: Rejects virtual built-in names with ValidationError("...is a built-in actor and cannot be removed").

  5. Default actor preference via actor_preferences table: set_default_actor now stores just the actor name string in a new singleton preferences table (id=1, default_actor_name). For virtual built-ins, no actor row is created. get_default_actor reads the preference name, resolves via the DB→virtual chain, and returns the actor with is_default=True. Backwards-compatible with existing DBs via fallback to is_default=True actor row.

  6. ensure_built_in_actors() removed: All 20+ call sites removed. plan.py (two actor_registry.ensure_built_in_actors() calls) cleaned up, unused actor_registry variables removed.

Migration: m10_001_virtual_builtin_actors

  • Drops is_built_in column from actors table (batch_alter for SQLite 3.35 compat)
  • Adds actor_preferences table with id (singleton PK=1) and default_actor_name VARCHAR(255)
  • Chains from a5_006_action_invariants_unique_constraint (the actual head)

Files Changed

Core logic:

  • src/cleveragents/actor/registry.py — full rewrite with virtual resolution
  • src/cleveragents/application/services/actor_service.py — new set_default_actor_name(), get_default_actor_name() methods
  • src/cleveragents/infrastructure/database/repositories.pyActorRepository updated: removed upsert_built_in(), is_built_in DB persistence; added get_default_name(), set_default_name()
  • src/cleveragents/infrastructure/database/models.py — removed is_built_in from ActorModel, added ActorPreferencesModel
  • src/cleveragents/cli/commands/plan.py — removed 2 ensure_built_in_actors() calls

New migration:

  • src/cleveragents/infrastructure/database/migrations/versions/m10_001_virtual_builtin_actors.py

Tests:

  • tests/actor/test_registry_builtin_yaml.py — rewrote TestEnsureBuiltInActorsWithYaml as TestResolveVirtualBuiltinActors; added TestListActors, TestGetActor, TestRemoveActor, TestDefaultActor
  • features/virtual_builtin_actors.feature + steps — 8 new scenarios covering listing, show, remove, set-default, get-default, no-DB-writes
  • Updated 15+ existing step files and 5 feature files to remove ensure_built_in_actors() calls and adapt assertions to virtual behavior

Quality Gate Results

  • nox -e lint: ✓ PASS
  • nox -e typecheck: ✓ PASS (0 errors, 3 pre-existing warnings in providers/registry.py)
  • nox -e unit_tests: ✓ PASS (671 features, 15674 scenarios)
  • nox -e coverage_report: ✓ PASS — 97.10% (threshold: 97%)
  • nox -e integration_tests: ✓ PASS — 1997 tests, 0 failed
  • nox -e e2e_tests: ⚠️ 1 pre-existing failure (Wf07 CI profile config, unrelated to actors)
## Implementation Notes ### Architecture Decision: Virtual Resolution Pattern Built-in actors are now **truly virtual** — resolved on-demand from `ProviderRegistry.get_configured_providers()` and never written to the database. **Key design choices:** 1. **`_resolve_virtual_builtin_actors()`** (new method on `ActorRegistry`): Generates `Actor` domain objects in-memory from configured providers, sorted alphabetically. Returns `is_built_in=True`, `id=None`, no persistence. 2. **`list()` merge strategy**: Calls `service.list_actors()` for custom DB actors, then adds virtual built-ins for names not already in the DB. Custom actors win when names collide. Result sorted alphabetically. 3. **`get()` resolution order**: DB first (via `service.get_actor`), virtual built-in fallback, `NotFoundError` if neither. 4. **`remove()` guard**: Rejects virtual built-in names with `ValidationError("...is a built-in actor and cannot be removed")`. 5. **Default actor preference via `actor_preferences` table**: `set_default_actor` now stores just the actor name string in a new singleton preferences table (`id=1`, `default_actor_name`). For virtual built-ins, no actor row is created. `get_default_actor` reads the preference name, resolves via the DB→virtual chain, and returns the actor with `is_default=True`. Backwards-compatible with existing DBs via fallback to `is_default=True` actor row. 6. **`ensure_built_in_actors()` removed**: All 20+ call sites removed. `plan.py` (two `actor_registry.ensure_built_in_actors()` calls) cleaned up, unused `actor_registry` variables removed. ### Migration: `m10_001_virtual_builtin_actors` - Drops `is_built_in` column from `actors` table (batch_alter for SQLite 3.35 compat) - Adds `actor_preferences` table with `id` (singleton PK=1) and `default_actor_name VARCHAR(255)` - Chains from `a5_006_action_invariants_unique_constraint` (the actual head) ### Files Changed **Core logic:** - `src/cleveragents/actor/registry.py` — full rewrite with virtual resolution - `src/cleveragents/application/services/actor_service.py` — new `set_default_actor_name()`, `get_default_actor_name()` methods - `src/cleveragents/infrastructure/database/repositories.py` — `ActorRepository` updated: removed `upsert_built_in()`, `is_built_in` DB persistence; added `get_default_name()`, `set_default_name()` - `src/cleveragents/infrastructure/database/models.py` — removed `is_built_in` from `ActorModel`, added `ActorPreferencesModel` - `src/cleveragents/cli/commands/plan.py` — removed 2 `ensure_built_in_actors()` calls **New migration:** - `src/cleveragents/infrastructure/database/migrations/versions/m10_001_virtual_builtin_actors.py` **Tests:** - `tests/actor/test_registry_builtin_yaml.py` — rewrote `TestEnsureBuiltInActorsWithYaml` as `TestResolveVirtualBuiltinActors`; added `TestListActors`, `TestGetActor`, `TestRemoveActor`, `TestDefaultActor` - `features/virtual_builtin_actors.feature` + steps — 8 new scenarios covering listing, show, remove, set-default, get-default, no-DB-writes - Updated 15+ existing step files and 5 feature files to remove `ensure_built_in_actors()` calls and adapt assertions to virtual behavior ### Quality Gate Results - `nox -e lint`: ✓ PASS - `nox -e typecheck`: ✓ PASS (0 errors, 3 pre-existing warnings in providers/registry.py) - `nox -e unit_tests`: ✓ PASS (671 features, 15674 scenarios) - `nox -e coverage_report`: ✓ PASS — **97.10%** (threshold: 97%) - `nox -e integration_tests`: ✓ PASS — 1997 tests, 0 failed - `nox -e e2e_tests`: ⚠️ 1 pre-existing failure (Wf07 CI profile config, unrelated to actors)
Owner

Implementation Attempt — Tier 0: qwen — Failed

PR #10923 does not exist in the repository.

The item referenced by work_number=10923 is Issue #10923 ("Make built-in actors virtual (resolved on-demand from provider registry, not persisted to database)"), which is already State/Completed and was merged as PR #10927.

No open or closed PR with number 10923 exists in cleveragents/cleveragents-core. A full search across all pages of PRs found zero matches for number 10923.

Cannot proceed with pr_fix because the target PR is not resolvable via the Forgejo API.


Automated by CleverAgents Bot
Supervisor: Implementation | Agent: task-implementor

**Implementation Attempt** — Tier 0: qwen — Failed PR #10923 does not exist in the repository. The item referenced by work_number=10923 is Issue #10923 ("Make built-in actors virtual (resolved on-demand from provider registry, not persisted to database)"), which is already State/Completed and was merged as PR #10927. No open or closed PR with number 10923 exists in cleveragents/cleveragents-core. A full search across all pages of PRs found zero matches for number 10923. Cannot proceed with pr_fix because the target PR is not resolvable via the Forgejo API. --- Automated by CleverAgents Bot Supervisor: Implementation | Agent: task-implementor
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#10923
No description provided.