pub struct Store {
pub(super) conn: Mutex<Connection>,
registry: Mutex<Option<Arc<Registry>>>,
def_registry: Mutex<Option<Arc<DefinitionStore>>>,
registry_agents_base: Mutex<Option<PathBuf>>,
}Expand description
SQLite-backed object store for StoreObj types.
Fields§
§conn: Mutex<Connection>pub(super) so sibling subsystem modules (e.g. memory_bundles)
can take the lock. Each per-subsystem file adds methods to Store
via impl Store {} and needs the connection.
registry: Mutex<Option<Arc<Registry>>>Cross-version named-agent registry. None for in-memory test
stores; Some for production srv. Mutations to
db_agent_instances parallel-write to this registry when set.
See docs/specs/SPEC_SHARED_AGENT_REGISTRY_2026_05_12.md.
def_registry: Mutex<Option<Arc<DefinitionStore>>>GLOBAL (cross-channel) agent-definition store. None for in-memory
test stores and when the shared dir can’t be resolved; Some for
production srv. Definition mutations mirror to it so an agent created
in one channel is visible in every channel (cross-channel agent
persistence, docs/specs/SPEC_CROSS_CHANNEL_AGENT_PERSISTENCE_2026-06-13.md).
registry_agents_base: Mutex<Option<PathBuf>>Base directory that named-instance working_directory values are
expressed relative to in the instance registry (write side:
registry_mirror; read side: the listnamedagents handler).
This is the current channel’s agents dir (channels/<ch>/agents,
i.e. AGENTMUX_AGENTS_DIR). It must be tracked separately from the
registry’s own file location because P0.3 re-roots the registry to the
global ~/.agentmux/shared/agents/registry/ — once the registry no
longer sits under channels/<ch>/agents/, its parent (agents_root())
stops coinciding with the channel agents dir, and using it to strip /
re-join working_directory would drop every live instance.
In production it is wired from AGENTMUX_AGENTS_DIR in main.rs
(P0.3b), atomically with the re-root to the global shared registry —
which is why the wiring waited for the re-root: setting it earlier would
diverge in dev mode, where AGENTMUX_AGENTS_DIR ≠ the (then
channel-local) registry parent. None only for in-memory test stores
and odd envs where the var is unset; the accessor then falls back to the
registry’s parent (which equals the channel agents dir in the pre-re-root
layout), so existing mirror tests are unchanged. When set, it is passed
in explicitly — never read from ambient env inside the Store — so tests
running inside an AgentMux pane don’t pick up the host’s
AGENTMUX_AGENTS_DIR. See
docs/specs/SPEC_CROSS_CHANNEL_AGENT_PERSISTENCE_2026-06-13.md (P0.3).
Implementations§
Source§impl Store
impl Store
Sourcepub fn user_clone_defs_for_template(
&self,
template_id: &str,
) -> Result<Vec<AgentDefinition>, StoreError>
pub fn user_clone_defs_for_template( &self, template_id: &str, ) -> Result<Vec<AgentDefinition>, StoreError>
List all agent definitions, most-recently-used first.
Reads from the consolidated db_agents table, ordered by
updated_at DESC then created_at ASC. Dual-write keeps
db_agents.updated_at fresh on every definition mutation AND every
instance lifecycle touch, so recency on a row tracks the last time
the agent was either edited or launched.
Result-set shape: every db_agents row is returned — templates
(is_template = 1) and user-clone projections (is_template = 0)
each appear once. parent_id is sourced from
db_agents.parent_template_id.
Find user-clone definitions for a given seeded template (rows
in db_agent_definitions with is_seeded = 0 and
parent_id = <template.id>). Returns the most-recent-first.
Reads db_agent_definitions directly — NOT the db_agents
consolidated view — because the latter surfaces template-
instance projection rows under the same
is_template = 0 AND parent_template_id = <tpl> shape as
user-clone defs, which would conflate two distinct things.
Sole production caller today is the template_promote
migration’s “did the user delete the deterministic-id
clone?” diagnostic logging and its tests. Kept public so
follow-up callers (e.g. a cleanup pass that GCs orphaned
pre-deterministic-id clones from earlier migration code)
can use it without re-deriving the schema.
Sourcepub fn agent_def_get(
&self,
id: &str,
) -> Result<Option<AgentDefinition>, StoreError>
pub fn agent_def_get( &self, id: &str, ) -> Result<Option<AgentDefinition>, StoreError>
Fetch a single agent definition by primary key. Reads
db_agent_definitions directly (not the db_agents
consolidated view that agent_def_list reads), so it
returns user-clone definitions and seeded templates, never
template-instance projection rows.
Used by the template_promote migration’s deterministic-id
idempotency check (see
migrate_promote_template_sessions_v1): every retry asks
“does the promote-target clone for this template already
exist?” and either reuses it or inserts it.
pub fn agent_def_list(&self) -> Result<Vec<AgentDefinition>, StoreError>
Sourcepub fn agent_def_count(&self) -> Result<i64, StoreError>
pub fn agent_def_count(&self) -> Result<i64, StoreError>
Count agent rows (used by seed engine to check if seeding is needed).
Reads from the consolidated db_agents table. The seed engine only
cares about == 0 to decide “fresh database, seed templates”.
Sourcepub(super) fn agent_def_exists_local(
&self,
id: &str,
) -> Result<bool, StoreError>
pub(super) fn agent_def_exists_local( &self, id: &str, ) -> Result<bool, StoreError>
Whether a definition with id exists in the LOCAL channel’s SQLite
(db_agents). Gates the cross-channel content/skills fallback: a
locally-known agent with genuinely empty content/skills must NOT
resurrect them from the global record. (reagent P1 on #1385.)
Sourcepub fn agent_def_delete_seeded(&self) -> Result<usize, StoreError>
pub fn agent_def_delete_seeded(&self) -> Result<usize, StoreError>
Delete all seeded agents (is_seeded=1). Used by reseed to clear built-in agents.
Sourcepub fn agent_def_insert(
&self,
agent: &mut AgentDefinition,
) -> Result<(), StoreError>
pub fn agent_def_insert( &self, agent: &mut AgentDefinition, ) -> Result<(), StoreError>
Insert a new agent definition. Auto-derives slug from name if empty,
resolves collisions by appending -2, -3, etc., and mutates
agent.slug so the caller sees the resolved value (important
for handlers that serialize the struct back to the frontend
after insert).
The collision check + insert run under a single mutex lock, so this is race-safe against concurrent inserts on the same connection.
Sourcepub fn agent_def_find_or_insert(
&self,
agent: &mut AgentDefinition,
) -> Result<Option<AgentDefinition>, StoreError>
pub fn agent_def_find_or_insert( &self, agent: &mut AgentDefinition, ) -> Result<Option<AgentDefinition>, StoreError>
Atomic check-then-insert for agent.define.
Looks up an existing user-owned definition by name (case-insensitive)
or derived slug under the SAME mutex guard that protects the INSERT —
preventing TOCTOU when two concurrent agent.define calls arrive for
the same name.
Returns:
Ok(Some(def))— an existing row matched;agentwas NOT inserted.Ok(None)— no match;agentwas inserted andagent.slugnow holds the collision-resolved slug.
Set the user_hidden flag on a single agent definition. Phase 2
of the two-tier picker (Q2 Decision Y). Returns:
Ok(true) — row updated.
Ok(false) — no row with that id exists.
Err(...) — the row exists but is NOT a seeded template
(is_seeded != 1). User-owned definitions go
through agent_def_delete, not hide.
Does NOT bump updated_at: hide is a per-user view-state flag,
not a definition-content edit. Keeps updated_at faithful to the
agent’s payload (the manifest re-sync compares description etc.
against the canonical row).
Sourcepub fn agent_def_update(
&self,
agent: &mut AgentDefinition,
) -> Result<bool, StoreError>
pub fn agent_def_update( &self, agent: &mut AgentDefinition, ) -> Result<bool, StoreError>
Update an existing agent definition (all fields except id, created_at, is_seeded).
parent_id and branch_label are NOT updatable post-insert — they
describe the agent’s provenance; renaming or re-branching is done by
creating a new fork, not mutating the original.
Self-stamps updated_at with the current time and writes it back into
agent.updated_at, so the caller’s struct (e.g. an RPC response body)
reflects exactly what landed in the database.
Sourcepub fn agent_def_delete(&self, id: &str) -> Result<bool, StoreError>
pub fn agent_def_delete(&self, id: &str) -> Result<bool, StoreError>
Delete a agent definition by id. Returns true if a row was deleted.
Source§impl Store
impl Store
Sourcepub fn instance_list(
&self,
definition_id: Option<&str>,
status: Option<&str>,
) -> Result<Vec<AgentInstance>, StoreError>
pub fn instance_list( &self, definition_id: Option<&str>, status: Option<&str>, ) -> Result<Vec<AgentInstance>, StoreError>
List instances. Both filters are optional — pass None to scan
all instances. Ordered by updated_at descending, with
created_at as a tiebreaker (most recent activity first; the
dual-write bumps updated_at on every launch / continuation,
so a continued older agent ranks ahead of a brand-new untouched
one).
Reads from the consolidated db_agents table (is_template = 0,
user_hidden = 0) for the no-status case.
Continuation chains pre-collapse — one row per logical agent.
The definition_id filter, when supplied, matches the agent’s
own id only (templates aren’t agents and user-clones derived
from a template are SEPARATE agents — see the implementation
note below for why parent_template_id traversal was dropped).
Field mapping for fields with no consolidated-row analog:
block_id,session_id,status,ended_at,parent_instance_id→ type defaults (""/0). Truly transient per-launch state; not modelled ondb_agents.started_at→db_agents.created_at. Same proxy used byinstance_get_by_name(3b.2); the consolidated row’s creation IS the agent’s launch moment in the new model.display_hidden→ alwaysfalse. The WHERE clause filtersuser_hidden = 0, so hidden rows never surface here.
Callers passing a
status filter need transient runtime state that db_agents
doesn’t model. Route those to the legacy db_agent_instances
path so existing semantics are preserved until the
updateagentinstance handler’s “fetch + merge transient fields”
pattern is refactored. (Currently no production caller passes
status — listagentinstances RPC frontends call with empty
filters — so the legacy path is exercised only by tests.)
Spec: docs/specs/SPEC_AGENT_ARCHITECTURE_2026_05_27.md §3b.3.
Sourcefn instance_list_legacy(
&self,
definition_id: Option<&str>,
status: Option<&str>,
) -> Result<Vec<AgentInstance>, StoreError>
fn instance_list_legacy( &self, definition_id: Option<&str>, status: Option<&str>, ) -> Result<Vec<AgentInstance>, StoreError>
Legacy db_agent_instances read — preserved for the
status-filter case (transient state). Will retire when the
updateagentinstance handler’s fetch-and-merge pattern is
refactored. Do NOT add new callers; use
instance_list instead.
pub fn instance_get( &self, id: &str, ) -> Result<Option<AgentInstance>, StoreError>
Sourcepub fn instance_create(&self, inst: &AgentInstance) -> Result<(), StoreError>
pub fn instance_create(&self, inst: &AgentInstance) -> Result<(), StoreError>
Insert a new instance row. Caller is responsible for the id (UUID).
Set the display_hidden flag on an existing instance row. Used
by the “Forget agent” affordance — soft-delete only; the row +
working directory remain on disk for audit + recovery.
Cross-version case: an agent migrated into the registry from
another version’s SQLite won’t have a row in the current
version’s SQLite. The UPDATE returns 0 rows, but the registry
still needs to flip — otherwise “Forget agent” silently no-ops
on cross-version entries. Returns true if either side acted.
Sourcepub fn instance_list_named(
&self,
limit: usize,
definition_id: Option<&str>,
identity_id: Option<&str>,
include_continuations: bool,
) -> Result<Vec<AgentInstance>, StoreError>
pub fn instance_list_named( &self, limit: usize, definition_id: Option<&str>, identity_id: Option<&str>, include_continuations: bool, ) -> Result<Vec<AgentInstance>, StoreError>
List named instances for the launch-modal “Continue agent”
dropdown (include_continuations = false) or the picker
“My Agents” surface (include_continuations = true). Filters
to non-hidden + named rows, sorted by started_at DESC,
capped by limit.
definition_id, when provided, restricts the result to
instances of that definition. Server-side filtering is
necessary because the launch modal opens per-definition: a
user with 200+ named agents across many definitions could
have the current definition’s older instances cut off by a
purely global limit otherwise.
include_continuations controls whether rows with
parent_instance_id != '' (continuation chains) are
returned:
-
false(legacy “head-of-chain only”). Pre-Option-E semantics: hides continuation rows so the launch-modal dropdown shows one entry per chain root.listnamedagentsALSO uses this mode for its same-version SQLite enrichment of registry-sourced rows — the registry mirror filter atregistry_upsert_if_namedexcludes continuations symmetrically, and breaking that symmetry under alimittruncation would let continuation rows displace registry-head rows in the top-N and miss the merge-by-id enrichment. (Codex P1 on PR #1016 first cut: regresses running-state badges and focus-existing-pane hints for any user whose latest instance is a continuation.) -
true(Option-E “include continuations”). For the picker’slistrecentsessionsflow and thetemplate_promotemigration’s instance-name lookup. Under Option E the session zone is anchored ondefinition_id, so a continuation row is simply the most-recent named instance of an agent the user actively used — exactly what those callers want visible. Excluding them hides real agents (the original 2026-05-24 “Maks doesn’t appear under My Agents” report) and makestemplate_promote’s name lookup miss the realinstance_name, falling back to the template name.
Sourcepub fn instance_get_by_name(
&self,
instance_name: &str,
) -> Result<Option<AgentInstance>, StoreError>
pub fn instance_get_by_name( &self, instance_name: &str, ) -> Result<Option<AgentInstance>, StoreError>
Look up the canonical named-agent row matching instance_name.
Used by the launch modal to detect name collisions (“did you
mean to continue?”) and by ContinueNamedAgentCommand to
resolve the consolidated row when the caller only knows the
name. Hidden rows are excluded.
Reads from the consolidated db_agents table —
is_template = 0 (named user agent), instance_name matches,
user_hidden = 0. Continuation chains are pre-collapsed in
db_agents (one row per logical agent), so this returns the
canonical agent regardless of how many launches its chain has.
MRU tiebreak is by updated_at (the dual-write touches it on
every continuation), then created_at for stable order.
The legacy db_agent_instances carried per-launch runtime
state (block_id, session_id, status, started_at,
ended_at, parent_instance_id) that has no analog in
db_agents — those fields are returned as their AgentInstance
defaults (empty strings, 0). Callers wanting transient state
should consult runtime sources (the controller, the block
row); none of the documented use cases need it (collision
detection only cares about identity / cwd; ContinueNamed only
cares about id + bindings).
Spec: docs/specs/SPEC_AGENT_ARCHITECTURE_2026_05_27.md §3b.
Sourcepub fn instance_update_partial(
&self,
id: &str,
upd: &InstanceUpdate,
) -> Result<Option<AgentInstance>, StoreError>
pub fn instance_update_partial( &self, id: &str, upd: &InstanceUpdate, ) -> Result<Option<AgentInstance>, StoreError>
Partial update of an instance’s mutable runtime fields. Only
Some fields are written; None leaves that column untouched.
Replaces the updateagentinstance handler’s fetch-and-merge
(read the full row → fill the unspecified fields → write the
whole struct back). That read was the only production caller
that needed instance_get’s transient per-launch fields, which
pinned instance_get to the legacy db_agent_instances table.
With a partial write the handler no longer reads the row at all.
See docs/specs/SPEC_UPDATEAGENTINSTANCE_PARTIAL_UPDATE_2026_05_29.md.
Returns the post-update row so callers that need definition_id
for an event scope — or want to echo the row back — get it from
the same authoritative reload this method already runs to refresh
the registry mirror + dual-write. Those consumers read
only non-transient fields, so the reload survives a future
instance_get → db_agents flip.
None is reserved for not-found (the id doesn’t exist). An
all-None update on an existing id is a no-op that returns the
unchanged row — so callers can distinguish “nothing to change”
from “no such instance”.
Sourcepub fn instance_update(&self, inst: &AgentInstance) -> Result<bool, StoreError>
pub fn instance_update(&self, inst: &AgentInstance) -> Result<bool, StoreError>
Update mutable instance fields. id, definition_id,
parent_instance_id, started_at, created_at are immutable
after insert (they describe provenance, not state).
Retained as a full-struct convenience for store tests + internal
callers; the updateagentinstance handler now uses
Self::instance_update_partial so it no longer reads the row
to merge.
Sourcepub fn instance_repoint_definition(
&self,
old_def_id: &str,
new_def_id: &str,
) -> Result<usize, StoreError>
pub fn instance_repoint_definition( &self, old_def_id: &str, new_def_id: &str, ) -> Result<usize, StoreError>
Repoint every instance currently referencing old_def_id to
new_def_id. Used by the Phase 1 two-tier-picker migration
(SPEC_AGENT_PICKER_TWO_TIER_2026_05_24.md): when a seeded
template has been used directly (carries an agent:<id>:current
zone), the migration clones the template into a user agent and
repoints any instances so the existing reattach flow
(continueOfInstanceId) keeps working against the new
definition_id. Returns the number of rows updated.
definition_id is declared immutable post-insert on the normal
instance_update path. This is the migration escape hatch.
pub fn instance_delete(&self, id: &str) -> Result<bool, StoreError>
Sourcepub fn instance_backfill_identity_id(
&self,
new_identity_id: &str,
) -> Result<usize, StoreError>
pub fn instance_backfill_identity_id( &self, new_identity_id: &str, ) -> Result<usize, StoreError>
Back-fill db_agent_instances.identity_id for legacy rows that
have either the empty string (post-v7 default before the launch
modal required Identity) or the literal "blank" sentinel
(pre-v8 placeholder for “use ambient creds”). Both shapes map
to “no Identity bundle assigned” and the OAuth-bundles startup
migration (PR E, spec §5) routes them to the newly-seeded
Default bundle so the resolver can inject env vars from the
captured ambient credentials at the next spawn.
Returns the number of rows touched. Caller must verify that
new_identity_id is a real db_identity_bundles.id — this
method does NOT enforce FK validity (the column has no FK
constraint per the v7 migration). Mis-use would orphan the
rows to a non-existent bundle; the OAuth-bundles migration
guards against this by only calling here when it just upserted
the bundle row.
Sourcepub fn instance_get_active_for_block(
&self,
block_id: &str,
) -> Result<Option<AgentInstance>, StoreError>
pub fn instance_get_active_for_block( &self, block_id: &str, ) -> Result<Option<AgentInstance>, StoreError>
Resolve the agent bindings tied to a block.
Resolve through block.meta.agentId (or legacy
agent:id) against db_agents for the user-clone case;
fall back to the legacy db_agent_instances lookup by
block_id for seeded-template launches and any other block
the consolidated path can’t satisfy.
We deliberately do NOT consult block.meta.agentInstanceId:
codex P1 on PR #1114 round 3 surfaced that pane reuse
(backToPicker clears agentId but not agentInstanceId)
and quick-launch (no instance-id stamp) leave the key stale.
Trusting it would silently bleed the prior agent’s identity
across reopens — exactly the regression the legacy active-
instance query avoided. The agentId-then-legacy path covers
every launch shape without needing instance-id stamping:
- User-clone: db_agents.id == def.id, hits is_template=0.
- Template direct-launch: agentId points at a template (is_template=1, filtered out). Legacy fallback finds the active instance row keyed on block_id.
- Pane reuse: stale
agentInstanceIdis ignored; currentagentIdwins.
Replaces the legacy “find most recent active instance for this block” as the PRIMARY path for user-clones; the legacy query remains as fallback for templates + edge cases. Retires fully when Phase 3c drops the legacy table. Spec: docs/specs/SPEC_AGENT_ARCHITECTURE_2026_05_27.md §3b.
user_hidden is NOT filtered — hiding a named agent
(“forget”) is a picker-visibility concept; the pane bound to
that agent must keep resolving credentials. Codex P2 on PR
#1114 round 2.
Used by the identity resolver to pull identity_id /
memory_id for environment injection on every command
dispatch. Caller only reads identity_id from the returned
AgentInstance — transient per-launch fields (status,
session_id, started_at, ended_at, parent_instance_id) come
back as type defaults. block_id echoes back the caller’s
argument.
Source§impl Store
impl Store
pub fn agent_content_get( &self, agent_id: &str, content_type: &str, ) -> Result<Option<AgentContent>, StoreError>
Sourcepub fn agent_content_set(
&self,
content: &AgentContent,
) -> Result<(), StoreError>
pub fn agent_content_set( &self, content: &AgentContent, ) -> Result<(), StoreError>
Upsert a content blob for an agent.
Sourcepub(super) fn agent_content_get_all_local(
&self,
agent_id: &str,
) -> Result<Vec<AgentContent>, StoreError>
pub(super) fn agent_content_get_all_local( &self, agent_id: &str, ) -> Result<Vec<AgentContent>, StoreError>
Get all content blobs for an agent.
LOCAL channel’s content blobs only — NO cross-channel fallback. Used
by the def-registry mirror (which always operates on a local agent, so
must never read the global record) and by agent_content_get_all.
pub fn agent_content_get_all( &self, agent_id: &str, ) -> Result<Vec<AgentContent>, StoreError>
Sourcepub fn agent_content_delete(
&self,
agent_id: &str,
content_type: &str,
) -> Result<bool, StoreError>
pub fn agent_content_delete( &self, agent_id: &str, content_type: &str, ) -> Result<bool, StoreError>
Delete a specific content blob. Returns true if a row was deleted.
Source§impl Store
impl Store
Sourcepub(super) fn registry_def_upsert(&self, def_id: &str)
pub(super) fn registry_def_upsert(&self, def_id: &str)
Mirror a definition (by id) into the global store, reading its full row + content + skills from SQLite. Best-effort: a missing global store or any failure is logged, never propagated (SQLite stays authoritative). No-op when the definition no longer exists.
upsert itself refuses to resurrect a tombstoned id, so a stale
mirror call for a deleted agent won’t un-delete it.
Sourcepub(super) fn registry_def_retire(&self, def_id: &str) -> bool
pub(super) fn registry_def_retire(&self, def_id: &str) -> bool
Mirror a user-agent deletion as a global tombstone (retired/) so
another channel’s stale SQLite can’t resurrect it via upsert.
Best-effort.
Sourcepub(super) fn registry_def_update_definition_fields(
&self,
agent: &AgentDefinition,
) -> bool
pub(super) fn registry_def_update_definition_fields( &self, agent: &AgentDefinition, ) -> bool
Apply a definition edit directly to the global record’s definition
fields, PRESERVING its existing content + skills. Used when editing a
cross-channel agent that has no local SQLite row to update. Returns
whether a global record was found + updated. Best-effort. Tombstoned
(retired) agents are not resurrected — get reads only the active tree.
Source§impl Store
impl Store
Sourcepub(crate) fn agents_dual_write_definition_upsert(
&self,
def: &AgentDefinition,
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_definition_upsert( &self, def: &AgentDefinition, ) -> Result<(), StoreError>
Mirror a db_agent_definitions row into db_agents as the
canonical row for that definition.
is_seeded = 1rows becomeis_template = 1(canonical template; bindings stay empty).is_seeded = 0rows becomeis_template = 0withparent_template_id = parent_id(user-cloned from a template; bindings come from the matching instance, if any — handled byagents_dual_write_instance_createupdating in place).
Idempotent: uses INSERT … ON CONFLICT(id) DO UPDATE. Existing
bindings on the row (written previously by an instance dual-write)
are preserved — only definition-side fields are overwritten.
Phase 3b: returns Err on failure (previously logged + continued).
Phase 3b readers see db_agents, so a silent dual-write failure
would leak stale data into the picker.
Sourcepub(crate) fn agents_dual_write_definition_delete(
&self,
def_id: &str,
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_definition_delete( &self, def_id: &str, ) -> Result<(), StoreError>
Mirror a db_agent_definitions DELETE into db_agents. The
definition row itself is removed; any user-cloned children (rows
with parent_template_id = old_id) are left intact because the
FK cascade on the OLD schema only deletes instances, not other
definitions.
Sourcepub(crate) fn agents_dual_write_seeded_delete(
&self,
cascaded_inst_ids: &[String],
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_seeded_delete( &self, cascaded_inst_ids: &[String], ) -> Result<(), StoreError>
Bulk dual-write: mirror agent_def_delete_seeded. Deletes:
- every
is_template = 1row (the template projections), AND - every
db_agentsrow whoseidis in thecascaded_inst_idsset (template-instance projections that were just removed by the FK cascade ondb_agent_instances).
User-clone DEFINITION projections (is_template = 0, id is a
def_id in db_agent_definitions) are NOT touched — those rows
represent persistent user agents and live or die with their
db_agent_definitions row, not with the seeded-template bulk
delete. Reagent P1 round 4 on #1013: the previous version
scoped by parent_template_id and over-deleted user-clone DEF
projections too. Idempotent.
Sourcepub(crate) fn agents_dual_write_instance_create(
&self,
inst: &AgentInstance,
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_instance_create( &self, inst: &AgentInstance, ) -> Result<(), StoreError>
Mirror a db_agent_instances INSERT into db_agents. Always
creates a NEW row in db_agents whose id == instance id.
The row’s identity comes from the instance (id, name, bindings).
Its template-config fields are copied from the parent definition;
parent_template_id points at that definition.
Continuations (parent_instance_id non-empty) mirror their
new bindings into the chain’s existing db_agents row rather
than creating a separate one. The chain root is resolved via
agents_projection_key_for_inst. Codex P2 on PR #1110 — without
this, a named-agent rebind (continue with different identity,
memory, cwd, or github context) never reaches the consolidated
row and Phase 3b readers see stale data.
Sourcepub(crate) fn agents_dual_write_instance_update(
&self,
inst: &AgentInstance,
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_instance_update( &self, inst: &AgentInstance, ) -> Result<(), StoreError>
Mirror a db_agent_instances UPDATE into db_agents. Touches
only the fields that instance_update writes (block + session +
status + github_context + ended_at) — name/bindings come from
the original create.
Continuations flow through here too — agents_projection_key_for_inst
walks up the chain to the head’s id, so a continuation’s
github_context refresh lands on the canonical row (codex P2 on
PR #1110, paired with the create-path fix in
agents_dual_write_instance_create).
Mirror instance_set_hidden into db_agents.user_hidden.
Sourcepub(crate) fn agents_dual_write_instance_repoint(
&self,
old_def_id: &str,
new_def_id: &str,
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_instance_repoint( &self, old_def_id: &str, new_def_id: &str, ) -> Result<(), StoreError>
Mirror instance_repoint_definition into db_agents. The
parent_template_id of every user-clone row that pointed at
old_def_id is updated to new_def_id.
Sourcepub(crate) fn agents_dual_write_instance_delete(
&self,
id: &str,
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_instance_delete( &self, id: &str, ) -> Result<(), StoreError>
Mirror instance_delete into db_agents.
Sourcepub(crate) fn agents_dual_write_backfill_identity(
&self,
new_identity_id: &str,
) -> Result<(), StoreError>
pub(crate) fn agents_dual_write_backfill_identity( &self, new_identity_id: &str, ) -> Result<(), StoreError>
Mirror instance_backfill_identity_id into db_agents. Same
filter (empty or "blank" identity_id) restricted to user-clone
rows.
Sourcefn agents_projection_key_for_inst(
conn: &Connection,
inst_id: &str,
) -> Option<(String, bool)>
fn agents_projection_key_for_inst( conn: &Connection, inst_id: &str, ) -> Option<(String, bool)>
Reagent P1 round 4 on #1013 — fold-aware projection key lookup.
Resolves “for an instance with this id, which db_agents row
represents it post-create?”. Returns Some((key, is_folded)):
is_folded = true→ key is the parent definition id (the user-clone-def projection absorbs the instance’s bindings).is_folded = false→ key is the instance id (template-instance projection is its own row). ReturnsNoneif the instance no longer exists indb_agent_instances(e.g. already deleted by FK cascade).
Sourcefn load_definition_for_dual_write(
conn: &Connection,
id: &str,
) -> Result<Option<AgentDefinition>>
fn load_definition_for_dual_write( conn: &Connection, id: &str, ) -> Result<Option<AgentDefinition>>
Helper: re-read a definition row from inside an active connection
lock (used by agents_dual_write_instance_create to avoid
re-locking the mutex recursively).
Source§impl Store
impl Store
Sourcepub fn agent_history_append(
&self,
agent_id: &str,
entry: &str,
) -> Result<AgentHistory, StoreError>
pub fn agent_history_append( &self, agent_id: &str, entry: &str, ) -> Result<AgentHistory, StoreError>
Append a history entry for an agent. Auto-sets session_date (today) and timestamp.
Sourcepub fn agent_history_list(
&self,
agent_id: &str,
session_date: Option<&str>,
limit: i64,
offset: i64,
) -> Result<Vec<AgentHistory>, StoreError>
pub fn agent_history_list( &self, agent_id: &str, session_date: Option<&str>, limit: i64, offset: i64, ) -> Result<Vec<AgentHistory>, StoreError>
List history entries for an agent, with optional date filter and pagination.
Sourcepub fn agent_history_search(
&self,
agent_id: &str,
query: &str,
limit: i64,
) -> Result<Vec<AgentHistory>, StoreError>
pub fn agent_history_search( &self, agent_id: &str, query: &str, limit: i64, ) -> Result<Vec<AgentHistory>, StoreError>
Search history entries for an agent using LIKE-based matching.
Source§impl Store
impl Store
Sourcepub fn identity_list(
&self,
provider: Option<&str>,
) -> Result<Vec<IdentityAccount>, StoreError>
pub fn identity_list( &self, provider: Option<&str>, ) -> Result<Vec<IdentityAccount>, StoreError>
List identity accounts. If provider is Some, filter to that
provider; otherwise return every account, ordered by most recent
update first (so the identity panel shows live accounts on top).
pub fn identity_get( &self, id: &str, ) -> Result<Option<IdentityAccount>, StoreError>
Sourcepub fn identity_upsert(
&self,
account: &IdentityAccount,
) -> Result<(), StoreError>
pub fn identity_upsert( &self, account: &IdentityAccount, ) -> Result<(), StoreError>
Upsert an identity account. If account.id is empty the caller
must generate one first (we don’t silently mint ids here — callers
should know whether they’re creating vs updating).
pub fn identity_delete(&self, id: &str) -> Result<bool, StoreError>
Sourcepub fn agent_identity_link(
&self,
agent_id: &str,
account_id: &str,
provider: &str,
) -> Result<(), StoreError>
pub fn agent_identity_link( &self, agent_id: &str, account_id: &str, provider: &str, ) -> Result<(), StoreError>
Link an agent to an identity for a given provider. Overwrites any existing link for the same (agent_id, provider) — each agent has at most one account per provider.
Sourcepub fn agent_identity_unlink(
&self,
agent_id: &str,
provider: &str,
) -> Result<bool, StoreError>
pub fn agent_identity_unlink( &self, agent_id: &str, provider: &str, ) -> Result<bool, StoreError>
Remove the identity link for a given (agent_id, provider). Returns true iff a link existed.
Sourcepub fn agent_identity_list_for_agent(
&self,
agent_id: &str,
) -> Result<Vec<AgentIdentityLink>, StoreError>
pub fn agent_identity_list_for_agent( &self, agent_id: &str, ) -> Result<Vec<AgentIdentityLink>, StoreError>
List all (agent_id, account_id, provider) triples for an agent.
Sourcepub fn bundle_identity_list(&self) -> Result<Vec<Identity>, StoreError>
pub fn bundle_identity_list(&self) -> Result<Vec<Identity>, StoreError>
List all Identity bundles, blank singleton last so the picker shows user-defined bundles first.
pub fn bundle_identity_get( &self, id: &str, ) -> Result<Option<Identity>, StoreError>
Sourcepub fn bundle_identity_upsert(
&self,
identity: &Identity,
) -> Result<(), StoreError>
pub fn bundle_identity_upsert( &self, identity: &Identity, ) -> Result<(), StoreError>
Upsert an Identity bundle. Caller mints the id (no silent generation).
The is_blank flag is reserved for the seeded singleton — callers
should pass false for user-created identities.
Sourcepub fn bundle_identity_delete(&self, id: &str) -> Result<bool, StoreError>
pub fn bundle_identity_delete(&self, id: &str) -> Result<bool, StoreError>
Delete an Identity bundle. Refuses to delete the blank singleton — the launch UI depends on it as the always-present default option.
Sourcepub fn bundle_identity_bind(
&self,
identity_id: &str,
provider: &str,
account_id: &str,
) -> Result<(), StoreError>
pub fn bundle_identity_bind( &self, identity_id: &str, provider: &str, account_id: &str, ) -> Result<(), StoreError>
Set the account for (identity_id, provider). Overwrites any
existing binding for the same (identity, provider).
Sourcepub fn bundle_identity_unbind(
&self,
identity_id: &str,
provider: &str,
) -> Result<bool, StoreError>
pub fn bundle_identity_unbind( &self, identity_id: &str, provider: &str, ) -> Result<bool, StoreError>
Remove the binding for (identity_id, provider). Returns whether a
row was deleted.
Sourcepub fn bundle_identity_bindings(
&self,
identity_id: &str,
) -> Result<Vec<IdentityBinding>, StoreError>
pub fn bundle_identity_bindings( &self, identity_id: &str, ) -> Result<Vec<IdentityBinding>, StoreError>
List bindings for an Identity bundle.
Source§impl Store
impl Store
pub fn bundle_memory_list(&self) -> Result<Vec<Memory>, StoreError>
Sourcepub fn bundle_memory_list_global(&self) -> Result<Vec<Memory>, StoreError>
pub fn bundle_memory_list_global(&self) -> Result<Vec<Memory>, StoreError>
Returns only the global bundles (is_global = 1), in explicit
sort_order (then name as a stable tiebreak). Called at agent launch
to inject workspace-wide rules into every agent in the order the user
arranged them in the Trust Center Brain tab.
pub fn bundle_memory_get(&self, id: &str) -> Result<Option<Memory>, StoreError>
pub fn bundle_memory_upsert(&self, memory: &Memory) -> Result<(), StoreError>
Sourcepub fn bundle_memory_delete(&self, id: &str) -> Result<bool, StoreError>
pub fn bundle_memory_delete(&self, id: &str) -> Result<bool, StoreError>
Delete a Memory bundle. Refuses to delete the blank singleton.
Sourcepub fn bundle_memory_reorder(
&self,
ordered_ids: &[String],
) -> Result<usize, StoreError>
pub fn bundle_memory_reorder( &self, ordered_ids: &[String], ) -> Result<usize, StoreError>
Assign sort_order to the given bundle ids in the order supplied
(position 0, 1, 2, …). Drives the Trust Center global brain ordering,
which in turn controls CLAUDE.md injection order. Ids not present in
the table are skipped silently (a concurrently-deleted section is not
an error). Runs in a single transaction so a partial reorder never
lands. Returns the number of rows updated.
Source§impl Store
impl Store
pub fn muxbus_load(&self) -> Result<Option<MuxBusCredentials>, StoreError>
pub fn muxbus_save(&self, creds: &MuxBusCredentials) -> Result<(), StoreError>
pub fn muxbus_clear(&self) -> Result<(), StoreError>
Source§impl Store
impl Store
Sourcepub(super) fn registry_upsert_if_named(&self, inst: &AgentInstance)
pub(super) fn registry_upsert_if_named(&self, inst: &AgentInstance)
Mirror a db_agent_instances mutation into the cross-version
registry. Only fires for named rows. Routes by
display_hidden so the registry file ends up in the tree
matching SQLite’s dropdown filter:
- hidden = true → upsert (atomic write to active/) then
retire (atomic rename to retired/). Net: file lives in
retired/<id>.jsonwith the freshest content. Preventsinstance_updateon a previously-hidden row from resurrecting an active registry file, AND keeps the retired tombstone’s content current. - hidden = false → unretire (no-op if not retired) then
upsert. Net: file in
active/<id>.json, no orphan retired.
Failures are logged, never propagated: SQLite remains authoritative.
Source§impl Store
impl Store
Sourcepub(super) fn agent_skill_list_local(
&self,
agent_id: &str,
) -> Result<Vec<AgentSkill>, StoreError>
pub(super) fn agent_skill_list_local( &self, agent_id: &str, ) -> Result<Vec<AgentSkill>, StoreError>
List all skills for an agent, ordered by created_at ascending.
LOCAL channel’s skills only — NO cross-channel fallback. Used by the
def-registry mirror (which always operates on a local agent) and by
agent_skill_list.
pub fn agent_skill_list( &self, agent_id: &str, ) -> Result<Vec<AgentSkill>, StoreError>
Sourcepub fn agent_skill_get(
&self,
id: &str,
) -> Result<Option<AgentSkill>, StoreError>
pub fn agent_skill_get( &self, id: &str, ) -> Result<Option<AgentSkill>, StoreError>
Get a single skill by id.
Sourcepub fn agent_skill_insert(&self, skill: &AgentSkill) -> Result<(), StoreError>
pub fn agent_skill_insert(&self, skill: &AgentSkill) -> Result<(), StoreError>
Insert a new skill.
Sourcepub fn agent_skill_update(&self, skill: &AgentSkill) -> Result<bool, StoreError>
pub fn agent_skill_update(&self, skill: &AgentSkill) -> Result<bool, StoreError>
Update an existing skill (all fields except id, agent_id, created_at).
Sourcepub fn agent_skill_delete(&self, id: &str) -> Result<bool, StoreError>
pub fn agent_skill_delete(&self, id: &str) -> Result<bool, StoreError>
Delete a skill by id. Returns true if a row was deleted.
Source§impl Store
impl Store
Sourcepub fn open(path: &Path) -> Result<Self, StoreError>
pub fn open(path: &Path) -> Result<Self, StoreError>
Open a Store backed by a file on disk. Configures WAL mode and 5s busy timeout (matching Go).
Sourcepub fn open_in_memory() -> Result<Self, StoreError>
pub fn open_in_memory() -> Result<Self, StoreError>
Open an in-memory Store for testing.
Sourcepub(crate) fn conn(&self) -> &Mutex<Connection>
pub(crate) fn conn(&self) -> &Mutex<Connection>
Crate-internal accessor for sibling modules that maintain their
own per-table CRUD via the DroneStore extension trait
pattern (see agentmux-srv/src/drone/storage.rs). Outside
callers must use the typed methods on this impl.
Sourcepub fn run_agents_consolidate(
&self,
data_dir: Option<&Path>,
) -> Result<ConsolidateStats, StoreError>
pub fn run_agents_consolidate( &self, data_dir: Option<&Path>, ) -> Result<ConsolidateStats, StoreError>
Run the db_agents consolidation backfill under the wstore’s
exclusive connection lock. Idempotent — gated by a marker file in
data_dir (skip with None for tests).
Sourcepub fn repair_agent_def_gaps(&self) -> Result<usize, StoreError>
pub fn repair_agent_def_gaps(&self) -> Result<usize, StoreError>
Backfill any db_agent_definitions rows that are missing from
db_agents. Not marker-gated — runs cheaply on every startup.
See agents_consolidate::repair_def_gaps for details.
fn configure_and_migrate(conn: Connection) -> Result<Self, StoreError>
Sourcepub fn set_registry(&self, registry: Arc<Registry>)
pub fn set_registry(&self, registry: Arc<Registry>)
Attach a shared cross-version agent registry. Called once on
srv startup after Store::open and before the store is
wrapped in Arc. Mutations to db_agent_instances will then
parallel-write to the registry; the SQLite table remains the
authoritative read path for PR A.
pub(super) fn registry(&self) -> Option<Arc<Registry>>
Sourcepub fn set_registry_agents_base(&self, base: PathBuf)
pub fn set_registry_agents_base(&self, base: PathBuf)
Set the channel agents dir that instance working_directory values
are stored relative to (see the registry_agents_base field). Wired
from AGENTMUX_AGENTS_DIR in main.rs in P0.3b (atomically with the
registry re-root); until then it is exercised only by tests.
Sourcepub fn registry_agents_base(&self) -> Option<PathBuf>
pub fn registry_agents_base(&self) -> Option<PathBuf>
The base dir for instance working_directory relative paths.
Returns the explicitly-set channel agents dir
([set_registry_agents_base]) when present; otherwise falls back to
the registry’s parent (agents_root()), which equals the channel
agents dir in the pre-re-root layout. Used symmetrically by the write
mirror and the read handler so the two never disagree on the anchor.
Public accessor for the cross-version named-agent registry.
Returns None when the registry couldn’t be resolved at
startup (CI / unusual envs); callers must handle the absent
case by falling back to SQLite.
Sourcepub fn set_def_registry(&self, def_registry: Arc<DefinitionStore>)
pub fn set_def_registry(&self, def_registry: Arc<DefinitionStore>)
Attach the GLOBAL (cross-channel) agent-definition store. Called
once on srv startup after Store::open, before the store is
wrapped in Arc. Definition mutations then mirror to it.
Public accessor for the global agent-definition store. None when
it couldn’t be resolved at startup (CI / unusual envs / in-memory
tests); callers fall back to SQLite.
Sourcefn table_name<T: StoreObj>() -> String
fn table_name<T: StoreObj>() -> String
Table name for a StoreObj type: db_<otype>.
Sourcepub fn get<T: StoreObj>(&self, oid: &str) -> Result<Option<T>, StoreError>
pub fn get<T: StoreObj>(&self, oid: &str) -> Result<Option<T>, StoreError>
Get a single object by OID. Returns None if not found.
Sourcepub fn must_get<T: StoreObj>(&self, oid: &str) -> Result<T, StoreError>
pub fn must_get<T: StoreObj>(&self, oid: &str) -> Result<T, StoreError>
Get a single object, returning StoreError::NotFound if missing.
Sourcepub fn get_raw(
&self,
otype: &str,
oid: &str,
) -> Result<Option<Value>, StoreError>
pub fn get_raw( &self, otype: &str, oid: &str, ) -> Result<Option<Value>, StoreError>
Get a single object as raw JSON Value by otype and OID. Used by GetObject/GetObjects to return data without strict struct deserialization.
Sourcepub fn exists_raw(&self, otype: &str, oid: &str) -> Result<bool, StoreError>
pub fn exists_raw(&self, otype: &str, oid: &str) -> Result<bool, StoreError>
Check if an object exists (by otype and OID).
Sourcepub fn insert<T: StoreObj>(&self, obj: &mut T) -> Result<(), StoreError>
pub fn insert<T: StoreObj>(&self, obj: &mut T) -> Result<(), StoreError>
Insert a new object. Sets version to 1.
Sourcepub fn update<T: StoreObj>(&self, obj: &mut T) -> Result<i64, StoreError>
pub fn update<T: StoreObj>(&self, obj: &mut T) -> Result<i64, StoreError>
Update an existing object. Increments version atomically. Returns the new version number.
Sourcepub fn update_raw(
&self,
otype: &str,
oid: &str,
value: &Value,
) -> Result<i64, StoreError>
pub fn update_raw( &self, otype: &str, oid: &str, value: &Value, ) -> Result<i64, StoreError>
Update an object using raw JSON (bypasses struct deserialization). Used by UpdateObject where the frontend sends the full replacement object. This matches Go’s generic map-based UpdateObject behavior.
Sourcepub fn delete<T: StoreObj>(&self, oid: &str) -> Result<(), StoreError>
pub fn delete<T: StoreObj>(&self, oid: &str) -> Result<(), StoreError>
Delete an object by OID.
Sourcepub fn delete_by_otype(&self, otype: &str, oid: &str) -> Result<(), StoreError>
pub fn delete_by_otype(&self, otype: &str, oid: &str) -> Result<(), StoreError>
Delete by otype string and OID (for dynamic dispatch).
Validates otype against VALID_OTYPES to prevent SQL injection.
Sourcepub fn get_all<T: StoreObj>(&self) -> Result<Vec<T>, StoreError>
pub fn get_all<T: StoreObj>(&self) -> Result<Vec<T>, StoreError>
Get all objects of a given type.
Sourcepub fn with_tx<F, R>(&self, f: F) -> Result<R, StoreError>
pub fn with_tx<F, R>(&self, f: F) -> Result<R, StoreError>
Execute multiple operations in a single SQLite transaction. Acquires the Mutex once, wraps all operations in BEGIN/COMMIT. On error, rolls back and returns the error.
This is the key performance primitive — reduces N lock acquisitions and N fsyncs to 1 each.
Trait Implementations§
Source§impl DroneStore for Store
impl DroneStore for Store
fn drone_list(&self) -> Result<Vec<DroneDefinition>, StoreError>
fn drone_get(&self, id: &str) -> Result<Option<DroneDefinition>, StoreError>
fn drone_upsert(&self, wf: &DroneDefinition) -> Result<(), StoreError>
fn drone_delete(&self, id: &str) -> Result<bool, StoreError>
fn drone_run_insert(&self, run: &DroneRun) -> Result<(), StoreError>
Source§fn drone_run_update(&self, run: &DroneRun) -> Result<usize, StoreError>
fn drone_run_update(&self, run: &DroneRun) -> Result<usize, StoreError>
run.id). Used to flip a placeholder
running row into its terminal state once the drain task
completes, so the row exists from the moment the RPC returns.fn drone_runs_for( &self, drone_id: &str, limit: i64, ) -> Result<Vec<DroneRun>, StoreError>
Auto Trait Implementations§
impl !Freeze for Store
impl RefUnwindSafe for Store
impl Send for Store
impl Sync for Store
impl Unpin for Store
impl UnwindSafe for Store
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more