pub struct Store {
pub(super) conn: Mutex<Connection>,
registry: Mutex<Option<Arc<Registry>>>,
def_registry: Mutex<Option<Arc<DefinitionStore>>>,
pub(super) token_index: Mutex<Option<Arc<TokenIndex>>>,
wan_identity: Mutex<Option<Arc<WanIdentityStore>>>,
registry_agents_base: Mutex<Option<PathBuf>>,
pub(super) muxbus_save_lock: Mutex<()>,
global_memory_observer: Mutex<Option<Arc<dyn Fn(GlobalMemoryChange) + Send + Sync>>>,
}Expand description
SQLite-backed object store for StoreObj types.
Fields§
§conn: Mutex<Connection>pub(super) so sibling subsystem modules (e.g. 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).
token_index: Mutex<Option<Arc<TokenIndex>>>token → UID for attributing requests (identity M4a). None except on
the channel’s object store at runtime; see attach_token_index.
wan_identity: Mutex<Option<Arc<WanIdentityStore>>>The channel-wide WAN identity store (wan-identity/wan.db). None
except on the channel’s object store at runtime, and when the file
couldn’t be opened — WAN signing is then off. See wan_identity.rs.
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).
muxbus_save_lock: Mutex<()>Serializes muxbus_save end-to-end (keychain read, keychain write,
SQL write, and any rollback) — reagent P1 on #2260: two concurrent
muxbus_save calls (the muxbus.login RPC handler and the broker’s
registered refresh closure can each call it independently) would
otherwise race on the keychain’s “previous state” snapshot; if the
second call’s SQL write then failed, its rollback could restore a
stale pre-first-call blob, silently reverting a credential the first
call already committed. self.conn’s own lock only covers the SQL
portion, not the keychain read/write either side of it, so a
dedicated lock is needed rather than reusing that one. pub(super)
like conn — muxbus.rs is a sibling module under backend::storage.
global_memory_observer: Mutex<Option<Arc<dyn Fn(GlobalMemoryChange) + Send + Sync>>>Told about every change to a Global Memory entry or its order, after
it commits — the Global Memory record’s write-through
(backend::global_memory_record,
SPEC_MEMORY_FOLLOWS_THE_AGENT_2026_09_24.md §2.1.6). None except on
the channel’s Global Memory store at runtime.
Implementations§
Source§impl Store
impl Store
pub fn agent_activity_summary_get( &self, definition_id: &str, ) -> Result<Option<AgentActivitySummary>, StoreError>
Sourcepub fn agent_activity_summary_set(
&self,
definition_id: &str,
summary: &str,
updated_at: i64,
) -> Result<(), StoreError>
pub fn agent_activity_summary_set( &self, definition_id: &str, summary: &str, updated_at: i64, ) -> Result<(), StoreError>
Upsert — a definition’s summary is generated at most once in the common case, but an upsert (rather than plain INSERT) keeps this safe against the rare double-generation race the Ambient Model Call gateway doesn’t fully close (two independent process starts, or a restart mid-generation).
Source§impl Store
impl Store
Sourcepub fn agent_bundle_ids(
&self,
agent_id: &str,
) -> Result<Vec<String>, StoreError>
pub fn agent_bundle_ids( &self, agent_id: &str, ) -> Result<Vec<String>, StoreError>
The bundles picked for agent_id, in order. Not the agent’s own.
Sourcefn agent_bundles_fold_startup_pick(&self, agent_id: &str)
fn agent_bundles_fold_startup_pick(&self, agent_id: &str)
Append an old Startup-tab pick to the list, then remove it. Best effort: on a failure the pick stays, to be folded on a later read.
fn agent_bundle_ids_stored( &self, agent_id: &str, ) -> Result<Vec<String>, StoreError>
Sourcepub fn agent_bundles_set(
&self,
agent_id: &str,
bundle_ids: &[String],
) -> Result<Vec<String>, StoreError>
pub fn agent_bundles_set( &self, agent_id: &str, bundle_ids: &[String], ) -> Result<Vec<String>, StoreError>
Replace agent_id’s picks with bundle_ids, in that order. Blank ids,
repeats and the agent’s own bundle are dropped. Returns what was stored.
Sourcepub fn agent_bundles_forget_bundle(
&self,
bundle_id: &str,
) -> Result<usize, StoreError>
pub fn agent_bundles_forget_bundle( &self, bundle_id: &str, ) -> Result<usize, StoreError>
Take a deleted bundle out of every agent’s list. Returns the rows removed.
Sourcepub fn agent_bundle_chain(&self, agent_id: &str) -> Vec<String>
pub fn agent_bundle_chain(&self, agent_id: &str) -> Vec<String>
The agent’s whole list: its own bundle first (when it has one), then its picks. What launch unions skills and MCP servers over.
Source§impl Store
impl Store
pub fn agent_credential_load( &self, agent_id: &str, ) -> Result<Option<AgentCredential>, StoreError>
Sourcepub fn agent_credential_save(
&self,
agent_id: &str,
client_id: &str,
client_secret: &str,
token_endpoint: &str,
) -> Result<(), StoreError>
pub fn agent_credential_save( &self, agent_id: &str, client_id: &str, client_secret: &str, token_endpoint: &str, ) -> Result<(), StoreError>
Save a newly-provisioned client_id/client_secret (access_token/expires_at left at their defaults — the caller fetches a token separately via agent_credential_save_token).
Sourcepub fn agent_credential_save_token(
&self,
agent_id: &str,
access_token: &str,
expires_at: i64,
) -> Result<(), StoreError>
pub fn agent_credential_save_token( &self, agent_id: &str, access_token: &str, expires_at: i64, ) -> Result<(), StoreError>
Cache a freshly-fetched client_credentials access token against an already-provisioned agent. No-op if the agent has no row yet (the caller should provision first).
Sourcepub fn agent_credential_invalidate_token(
&self,
agent_id: &str,
) -> Result<(), StoreError>
pub fn agent_credential_invalidate_token( &self, agent_id: &str, ) -> Result<(), StoreError>
Clear a per-agent credential’s cached access token (client_id/secret are left intact — only the token is suspect). Used when the server rejects a request with 401 despite our cached expires_at still looking valid (revoked/rotated out-of-band): forces the next ensure_agent_credential call to re-fetch via fetch_m2m_token instead of retrying the exact same rejected token. No-op if the agent has no row. reagentx P1 on PR #2342.
Sourcepub fn agent_credential_delete(&self, agent_id: &str) -> Result<(), StoreError>
pub fn agent_credential_delete(&self, agent_id: &str) -> Result<(), StoreError>
Delete one agent’s credential row outright — for a client the cloud
no longer recognizes (invalid_client), where keeping the
client_id/secret would only fail again. The next
ensure_agent_credential re-provisions it. No-op if there is no row.
docs/specs/SPEC_CLOUD_SETTINGS_DISCOVERY_2026_09_27.md §3.4.
Sourcepub fn agent_credentials_clear_all(&self) -> Result<(), StoreError>
pub fn agent_credentials_clear_all(&self) -> Result<(), StoreError>
Wipe every cached per-agent M2M credential. db_agent_credentials
carries no account/user_sub column of its own — each row is only
ever meaningful under the muxbus account that provisioned it — so a
genuine account switch (muxbus_save with a different user_sub) or a
logout (muxbus_clear) must wipe this cache wholesale; otherwise a
stale row keeps authenticating this agent_id’s requests as the
PREVIOUS account after the human has switched to a different one.
Re-provisioning is cheap and idempotent on the cloud side, so clearing on every account transition —
not just the ones that turn out to matter — is the safe default.
reagentx P0 on PR #2342.
Sourcepub fn agent_credentials_purge_agent(
&self,
agent_id: &str,
) -> Result<usize, StoreError>
pub fn agent_credentials_purge_agent( &self, agent_id: &str, ) -> Result<usize, StoreError>
Every channel’s row for one agent: what deleting the agent removes.
Sourcefn agent_credentials_clear_prefix(&self, prefix: &str) -> Result<(), StoreError>
fn agent_credentials_clear_prefix(&self, prefix: &str) -> Result<(), StoreError>
The rows behind prefix (a channel’s <channel>/), or, with an empty
prefix, the unprefixed ones.
Source§impl Store
impl Store
pub fn agent_group_create( &self, id: &str, name: &str, member_ids: &[String], created_at: i64, ) -> Result<(), StoreError>
pub fn agent_group_list(&self) -> Result<Vec<AgentGroup>, StoreError>
pub fn agent_group_get( &self, id: &str, ) -> Result<Option<AgentGroup>, StoreError>
Sourcepub fn agent_group_update(
&self,
id: &str,
name: Option<&str>,
member_ids: Option<&[String]>,
) -> Result<bool, StoreError>
pub fn agent_group_update( &self, id: &str, name: Option<&str>, member_ids: Option<&[String]>, ) -> Result<bool, StoreError>
Partial update: None fields are left unchanged. Returns false if
no row matched id (caller should surface a not-found error).
pub fn agent_group_delete(&self, id: &str) -> Result<bool, StoreError>
Source§impl Store
impl Store
Sourcepub fn agent_jekt_key_load(
&self,
agent_id: &str,
) -> Result<Option<Vec<u8>>, StoreError>
pub fn agent_jekt_key_load( &self, agent_id: &str, ) -> Result<Option<Vec<u8>>, StoreError>
Load this agent’s signing key if one has already been minted, without creating one. Returns raw key bytes (decoded from the stored base64).
Sourcepub fn agent_jekt_key_expires_at(
&self,
agent_id: &str,
) -> Result<Option<i64>, StoreError>
pub fn agent_jekt_key_expires_at( &self, agent_id: &str, ) -> Result<Option<i64>, StoreError>
When this agent’s jekt key expires (unix seconds): its creation plus
JEKT_KEY_TTL_SECS. Until then no ensure rotates it, so a holder
of the key needs nothing newer before this time (identity M4d-3).
Sourcefn agent_jekt_key_load_with_created_at(
&self,
agent_id: &str,
) -> Result<Option<(Vec<u8>, i64)>, StoreError>
fn agent_jekt_key_load_with_created_at( &self, agent_id: &str, ) -> Result<Option<(Vec<u8>, i64)>, StoreError>
Load this agent’s signing key together with its mint/last-rotation time, if one has already been minted.
Sourcepub fn agent_jekt_key_ensure(
&self,
agent_id: &str,
) -> Result<Vec<u8>, StoreError>
pub fn agent_jekt_key_ensure( &self, agent_id: &str, ) -> Result<Vec<u8>, StoreError>
Return this agent’s signing key, minting and persisting a fresh
random one on first use. Race-safe under concurrent first-use: the
insert is OR IGNORE, and the winning row (whichever call actually
landed first) is always what gets read back and returned, so two
concurrent callers for the same never-before-seen agent_id agree on
one key instead of each minting and using a different one.
A key older than JEKT_KEY_TTL_SECS is rotated here rather than
returned as-is — see docs/specs/SPEC_JEKT_HOST_KEY_TTL_ROTATION_2026_09_14.md.
Source§impl Store
impl Store
Sourcepub fn attach_token_index(&self) -> Result<Arc<TokenIndex>, StoreError>
pub fn attach_token_index(&self) -> Result<Arc<TokenIndex>, StoreError>
Build this store’s TokenIndex from db_agent_tokens and attach
it, so mint and revoke keep it current from here on. Called once, on
the channel’s object store at boot — never on the shared, identity or
migration-time stores, which must not grow an index of their own.
Sourcepub fn token_index(&self) -> Option<Arc<TokenIndex>>
pub fn token_index(&self) -> Option<Arc<TokenIndex>>
The attached TokenIndex, if this store has one.
Sourcepub fn agent_token_load(&self, uid: &str) -> Result<Option<String>, StoreError>
pub fn agent_token_load(&self, uid: &str) -> Result<Option<String>, StoreError>
This agent’s token if one has been minted, without minting.
The verification read (M4) is the production caller; until it lands
only tests read this, hence the dead_code allowance outside test
builds. Deletion does not go through here — purge_agent_dependents
covers db_agent_tokens directly.
Sourcepub fn agent_token_ensure(&self, uid: &str) -> Result<String, StoreError>
pub fn agent_token_ensure(&self, uid: &str) -> Result<String, StoreError>
This agent’s token, minting and persisting one on first use.
Race-safe under concurrent first use, the same way
agent_jekt_key_ensure is: the insert is OR IGNORE and the row is
re-read regardless of whether this call’s insert won, so two spawns
of a never-before-seen agent agree on one token instead of each
holding a different one. An empty uid is refused rather than given
a token: a token keyed by "" would be shared by every caller that
failed to resolve an identity, which is the shared-credential defect
this table exists to remove.
Source§impl Store
impl Store
Sourcepub fn agent_lan_key_load(
&self,
agent_id: &str,
) -> Result<Option<AgentLanKeypair>, StoreError>
pub fn agent_lan_key_load( &self, agent_id: &str, ) -> Result<Option<AgentLanKeypair>, StoreError>
Load this agent’s LAN keypair if one has already been minted, without creating one.
Sourcepub fn agent_lan_public_key_load(
&self,
agent_id: &str,
) -> Result<Option<String>, StoreError>
pub fn agent_lan_public_key_load( &self, agent_id: &str, ) -> Result<Option<String>, StoreError>
Load only this agent’s PUBLIC key — the half safe to hand to a LAN
peer’s pubkey-lookup query (see LanDiscovery::find_agent_lan_pubkey
and the GET /agentmux/reactive/agent handler it calls). Never loads
or touches the private half.
Sourcepub fn agent_lan_key_ensure(
&self,
agent_id: &str,
) -> Result<AgentLanKeypair, StoreError>
pub fn agent_lan_key_ensure( &self, agent_id: &str, ) -> Result<AgentLanKeypair, StoreError>
Return this agent’s LAN keypair, minting and persisting a fresh
random one on first use. Race-safe under concurrent first-use — same
INSERT OR IGNORE + re-read pattern as agent_jekt_key_ensure: two
concurrent callers for the same never-before-seen agent_id agree on
one keypair instead of each minting and using a different one.
Source§impl Store
impl Store
Sourcepub fn agent_uid_keys_ensure(
&self,
uid: &str,
) -> Result<Option<(AgentUidKeys, [UidKeyOrigin; 2])>, StoreError>
pub fn agent_uid_keys_ensure( &self, uid: &str, ) -> Result<Option<(AgentUidKeys, [UidKeyOrigin; 2])>, StoreError>
The UID’s LAN and WAN keypairs, copied or minted on first use (§6.5.10
M4d-2). One critical section: the row is re-read inside the lock, so a
row purged since the caller read it gets nothing (Ok(None)) and no
private key is left without an owner.
Sourcepub fn agent_uid_lan_public_key_load(
&self,
uid: &str,
) -> Result<Option<String>, StoreError>
pub fn agent_uid_lan_public_key_load( &self, uid: &str, ) -> Result<Option<String>, StoreError>
The UID’s LAN public key, if it has one; never creates one.
Source§impl Store
impl Store
Sourcepub fn agent_wan_key_load(
&self,
agent_id: &str,
) -> Result<Option<AgentWanKeypair>, StoreError>
pub fn agent_wan_key_load( &self, agent_id: &str, ) -> Result<Option<AgentWanKeypair>, StoreError>
Load this agent’s WAN keypair if one has already been minted, without creating one.
Sourcepub fn agent_wan_key_ensure(
&self,
agent_id: &str,
) -> Result<AgentWanKeypair, StoreError>
pub fn agent_wan_key_ensure( &self, agent_id: &str, ) -> Result<AgentWanKeypair, StoreError>
Return this agent’s WAN keypair, minting and persisting a fresh random
one on first use. Race-safe under concurrent first-use — same
INSERT OR IGNORE + re-read pattern as agent_lan_key_ensure: two
concurrent callers for the same never-before-seen agent_id agree on one
keypair instead of each minting and using a different one.
Note there is deliberately no TTL/rotation branch here yet, unlike
agent_jekt_key_ensure. Rotation needs the continuity chain described
in the spec’s §3.6 (a new key signed by its predecessor) to be
distinguishable from a registry rewrite; rotating before that exists
would trade one gap for a worse one.
Source§impl Store
impl Store
Sourcepub fn agent_native_memory_list_all_rows(
&self,
) -> Result<Vec<NativeMemoryMirrorRowWithAgent>, StoreError>
pub fn agent_native_memory_list_all_rows( &self, ) -> Result<Vec<NativeMemoryMirrorRowWithAgent>, StoreError>
List every db_agent_native_memory row across every agent, content
included — used only by the one-time version-history backfill
migration (migrations::m0023_native_memory_versions_backfill).
Not for any live RPC path (those are always scoped to one
agent_id, and this loads full content for every row at once).
Sourcepub fn agent_native_memory_list_meta(
&self,
agent_id: &str,
) -> Result<Vec<NativeMemoryMirrorRow>, StoreError>
pub fn agent_native_memory_list_meta( &self, agent_id: &str, ) -> Result<Vec<NativeMemoryMirrorRow>, StoreError>
List every mirrored file for agent_id (no content — each row’s
content is empty, so a list view never loads full file bodies;
agent_native_memory_read fetches one file’s body).
Sourcepub fn agent_native_memory_read(
&self,
agent_id: &str,
filename: &str,
) -> Result<Option<String>, StoreError>
pub fn agent_native_memory_read( &self, agent_id: &str, filename: &str, ) -> Result<Option<String>, StoreError>
Read one mirrored file’s content by (agent_id, filename). Used as
the read-path fallback when the file is missing from the live FS
(e.g. a different channel wrote it, or the live folder was wiped).
Sourcepub fn agent_native_memory_upsert(
&self,
agent_id: &str,
filename: &str,
content: &str,
metadata_type: Option<&str>,
last_seen_path: &str,
size_bytes: i64,
mtime_ms: i64,
) -> Result<(), StoreError>
pub fn agent_native_memory_upsert( &self, agent_id: &str, filename: &str, content: &str, metadata_type: Option<&str>, last_seen_path: &str, size_bytes: i64, mtime_ms: i64, ) -> Result<(), StoreError>
Upsert a file’s current live-FS content into the mirror. Called from
every agent:memory:list/read_file/write_file RPC on every file
it touches — cheap (one local SQLite write already on the request),
and is what makes native memory durable across a channel switch.
size_bytes is the file’s REAL on-disk size, not necessarily
content.len() — content may be truncated at the RPC layer’s
MAX_MEMORY_FILE_BYTES cap for a file Claude Code wrote directly
(bypassing write_file’s own size guard). Storing the real size (not
the capped one) is what lets agent:memory:list’s change-detection
via (size_bytes, last_seen_mtime_ms) still work correctly for an
oversized file — comparing against a capped mirror size would never
match the live (larger) size, forcing a full re-read + rewrite on
every single list() call forever (reagent P1 on PR #2459, fourth pass).
mtime_ms is the live file’s own on-disk modified time (0 if
unavailable, e.g. a write_file call that hasn’t re-stat’d the file
it just wrote) — stored as last_seen_mtime_ms for the same
change-detection purpose.
Source§impl Store
impl Store
Sourcepub fn agent_native_memory_version_insert(
&self,
agent_id: &str,
filename: &str,
content: &str,
source: &str,
source_detail: &str,
session_id: &str,
) -> Result<NativeMemoryVersion, StoreError>
pub fn agent_native_memory_version_insert( &self, agent_id: &str, filename: &str, content: &str, source: &str, source_detail: &str, session_id: &str, ) -> Result<NativeMemoryVersion, StoreError>
Record a new version of (agent_id, filename)’s content, chained
onto whatever version was previously latest for this file (None
for the first write ever recorded). Always inserts, even when
content is byte-identical to the previous version — simplicity
over cleverness; dedup-by-hash is a possible future optimization,
not this one.
reagent P2 (re-review): the read-latest-then-insert sequence is a
single connection-lock acquisition, not two — an earlier revision
released the lock between reading parent_version_id and
inserting, so two truly concurrent calls for the same (agent_id, filename) (e.g. two panes of the same agent identity writing at
once) could both read the same latest id and insert sibling
versions instead of a linear chain.
reagent P2 (second re-review, PR #2674): an in-process Mutex only
serializes callers sharing this one Store/Connection — it does
nothing for two SEPARATE srv processes (e.g. two channels/
instances of AgentMux) each holding their own Connection to the
same shared-store SQLite file. Those two connections’ SELECT-latest
and INSERT could still interleave across processes, producing
sibling versions with the same parent_version_id. Wrapping the
read+insert in a BEGIN IMMEDIATE transaction closes that gap at
the SQLite level: it acquires the RESERVED lock up front (before
the SELECT runs), so a second connection’s own BEGIN IMMEDIATE
blocks (up to busy_timeout, already configured at open) until
this one commits — true cross-process mutual exclusion, unlike a
plain deferred transaction (whose lock isn’t acquired until the
first write, after the read has already happened).
Sourcepub fn agent_native_memory_version_insert_if_changed(
&self,
agent_id: &str,
filename: &str,
content: &str,
source: &str,
source_detail: &str,
session_id: &str,
) -> Result<Option<NativeMemoryVersion>, StoreError>
pub fn agent_native_memory_version_insert_if_changed( &self, agent_id: &str, filename: &str, content: &str, source: &str, source_detail: &str, session_id: &str, ) -> Result<Option<NativeMemoryVersion>, StoreError>
Atomic compare-and-insert: within a SINGLE connection-lock
acquisition, reads the latest version’s hash for (agent_id, filename) and inserts a new version only if content’s hash
differs (or no version exists yet). Returns Ok(None) when nothing
was inserted (content unchanged).
Exists specifically for native_memory_drift.rs’s two concurrent
detectors (fast fs-watch path, slow reconciliation sweep) — reagent
P2 on PR #2675: a separate agent_native_memory_version_latest
call followed by a separate agent_native_memory_version_insert
call (each independently acquiring and releasing the connection
lock) is NOT atomic as a compound operation, even though each
individual call is serialized — two concurrent callers can both
observe the same stale “latest” between their own read and write,
producing duplicate version rows for one actual out-of-band change.
A caller that doesn’t need this compare-and-swap guarantee (e.g. an
explicit RPC write, where every call is expected to record a new
version regardless — see agent_native_memory_version_insert’s own
doc) should keep using the plain method above instead.
Same cross-connection hardening as agent_native_memory_version_ insert above (reagent P2 on PR #2674, applied here proactively for
the identical read-then-insert shape): the read+insert runs inside
a single BEGIN IMMEDIATE transaction, not just a single connection
lock, so two separate srv processes/channels racing this same
compare-and-insert can’t both observe the same stale “latest” either.
Sourcepub fn agent_native_memory_version_list(
&self,
agent_id: &str,
filename: &str,
) -> Result<Vec<NativeMemoryVersionSummary>, StoreError>
pub fn agent_native_memory_version_list( &self, agent_id: &str, filename: &str, ) -> Result<Vec<NativeMemoryVersionSummary>, StoreError>
List every version of (agent_id, filename), newest first — no
content (list-view; see NativeMemoryVersionSummary’s own doc).
Sourcepub fn agent_native_memory_version_latest(
&self,
agent_id: &str,
filename: &str,
) -> Result<Option<NativeMemoryVersion>, StoreError>
pub fn agent_native_memory_version_latest( &self, agent_id: &str, filename: &str, ) -> Result<Option<NativeMemoryVersion>, StoreError>
The most recently recorded version of (agent_id, filename), or
None if no version has ever been recorded. Used both to derive a
new write’s parent_version_id and (§4.5 drift detection, not yet
built) to compare a live file’s current hash against the last known
version.
Sourcepub fn agent_native_memory_versions_for_agent(
&self,
agent_id: &str,
) -> Result<Vec<NativeMemoryVersion>, StoreError>
pub fn agent_native_memory_versions_for_agent( &self, agent_id: &str, ) -> Result<Vec<NativeMemoryVersion>, StoreError>
Every version of every file of agent_id, with content, oldest
first — the one-time import into the agent’s memory record
(SPEC_MEMORY_FOLLOWS_THE_AGENT_2026_09_24.md §2.1.1).
Sourcepub fn agent_native_memory_version_list_distinct_files(
&self,
) -> Result<Vec<(String, String)>, StoreError>
pub fn agent_native_memory_version_list_distinct_files( &self, ) -> Result<Vec<(String, String)>, StoreError>
Every distinct (agent_id, filename) pair with at least one recorded
version — the retention sweep’s (native_memory_retention.rs) work
list, so it doesn’t need its own separate source of “which files
have history” (the version table itself is authoritative for that).
Sourcepub fn agent_native_memory_version_relabel_rollout_first_sight(
&self,
) -> Result<usize, StoreError>
pub fn agent_native_memory_version_relabel_rollout_first_sight( &self, ) -> Result<usize, StoreError>
Prune old versions of (agent_id, filename) per the hybrid
age + min-count-floor retention policy
(native_memory_retention.rs, spec §7.1): a version is deleted only
if it is BOTH older than max_age_ms AND ranked beyond the
min_keep most-recent versions for this file — so a rarely-touched
file never loses its entire history purely because every version
happens to be old, and a hyperactive file never grows unbounded
purely because every version happens to be recent. Returns the
number of rows deleted.
Repair the mislabeled rollout backfill — see
migrations::m0024_native_memory_backfill_from_fs for the full
story. In short: m0023 was supposed to tag every pre-existing
memory file agent_inferred, but it iterated the
db_agent_native_memory MIRROR, which is populated lazily and is
empty on a typical install — so it backfilled nothing, and the
drift sweep (which reads the real filesystem) claimed those same
files milliseconds later as external_fs_write, i.e. “detected
outside AgentMux — provenance unknown”. Innocent files, alarming
label.
The predicate is deliberately narrow, because relabeling a provenance claim is not something to do speculatively. All four must hold:
source = 'external_fs_write'anddetected_via = 'reconciliation_sweep'— the slow path, which is the only one that can observe a file it has never seen before at startup;parent_version_id IS NULL— a FIRST-EVER version for that(agent_id, filename), i.e. genuine first sight rather than an observed change to something already tracked;created_atinside a short window starting atm0023’s ownapplied_at— the load-bearing clause, since a memory file created out-of-band next week is also first-sight-via-sweep and that one is a real external write whose label must stand.
§The limit of the window, stated plainly
Inside the window this cannot tell the rollout burst from a genuinely new out-of-band file, and will relabel both (ReAgent P2 on PR #3055 — an earlier revision of this comment claimed an absolute guarantee it does not deliver). Nothing in the row or the file distinguishes them: mtime is no help either, since a burst row’s file may have been legitimately edited since.
What makes the trade acceptable is the shape of the two errors, not their likelihood alone. Getting it wrong costs one just-created file a benign “Agent” label instead of “detected outside AgentMux”, on an install where the user is watching a fresh upgrade. Not acting costs every pre-existing memory file on every affected install a permanent security-flavored warning it did not earn — which is the bug this repairs. The window is kept as small as the evidence allows so the ambiguous region stays tiny.
source_detail keeps the original detected_via and records that
the row was relabeled, so the audit trail says what happened rather
than quietly presenting the new claim as if it were the original
observation.
Returns the number of rows relabeled. A no-op (returns 0) when
m0023 never ran — nothing to repair on a fresh install.
pub fn agent_native_memory_version_prune( &self, agent_id: &str, filename: &str, min_keep: u32, max_age_ms: i64, ) -> Result<usize, StoreError>
Sourcepub fn agent_native_memory_version_get(
&self,
version_id: &str,
) -> Result<Option<NativeMemoryVersion>, StoreError>
pub fn agent_native_memory_version_get( &self, version_id: &str, ) -> Result<Option<NativeMemoryVersion>, StoreError>
Read one version’s full content by id — for diff/revert, which need exactly one or two full bodies, not a whole file’s history.
Source§impl Store
impl Store
Sourcepub fn jekt_held_insert(
&self,
held: &HeldJekt,
) -> Result<HoldOutcome, StoreError>
pub fn jekt_held_insert( &self, held: &HeldJekt, ) -> Result<HoldOutcome, StoreError>
Hold held, unless its request_id is already held or a cap is
reached — decided under the one connection lock, so two concurrent
holds cannot both slip under a cap.
Sourcepub fn jekt_held_delete_expired(&self, now_ms: i64) -> Result<usize, StoreError>
pub fn jekt_held_delete_expired(&self, now_ms: i64) -> Result<usize, StoreError>
Delete held messages past their expiry; returns how many.
Sourcepub fn jekt_held_targets(&self) -> Result<Vec<String>, StoreError>
pub fn jekt_held_targets(&self) -> Result<Vec<String>, StoreError>
Every target UID with a held message, the one waiting longest first.
Sourcepub fn jekt_held_for_target(
&self,
target_uid: &str,
limit: usize,
) -> Result<Vec<HeldJekt>, StoreError>
pub fn jekt_held_for_target( &self, target_uid: &str, limit: usize, ) -> Result<Vec<HeldJekt>, StoreError>
One target’s held messages, oldest first, at most limit.
Sourcepub fn jekt_held_delete(&self, request_id: &str) -> Result<(), StoreError>
pub fn jekt_held_delete(&self, request_id: &str) -> Result<(), StoreError>
Remove one held message (delivered, or dropped).
Sourcepub fn jekt_held_record_failure(
&self,
request_id: &str,
error: &str,
) -> Result<i64, StoreError>
pub fn jekt_held_record_failure( &self, request_id: &str, error: &str, ) -> Result<i64, StoreError>
Count a failed replay; returns the new attempt count.
Source§impl Store
impl Store
Sourcepub fn lan_peer_pubkey_pin_get_or_set(
&self,
agent_id: &str,
observed_key_b64: &str,
) -> Result<String, StoreError>
pub fn lan_peer_pubkey_pin_get_or_set( &self, agent_id: &str, observed_key_b64: &str, ) -> Result<String, StoreError>
Get-or-pin: if a pin already exists for this agent_id, returns it
UNCHANGED regardless of what observed_key_b64 says (the whole
point — a later, different key must never silently overwrite an
established pin). If no pin exists yet, pins observed_key_b64 and
returns it. Callers compare the return value against
observed_key_b64: equal means “trusted, use it”; a mismatch means
“this claimed sender’s key just changed unexpectedly” — a red flag,
not a value to fall back to. Race-safe under concurrent first-use
via the same INSERT OR IGNORE + re-read pattern
agent_jekt_key_ensure/agent_lan_key_ensure already establish.
Source§impl Store
impl Store
Sourcepub fn lan_peer_uid_pin_get_or_set(
&self,
agent_id: &str,
uid: &str,
observed_key_b64: &str,
) -> Result<String, StoreError>
pub fn lan_peer_uid_pin_get_or_set( &self, agent_id: &str, uid: &str, observed_key_b64: &str, ) -> Result<String, StoreError>
Get-or-pin, as lan_peer_pubkey_pin_get_or_set: an existing pin for
(agent_id, uid) is returned unchanged, otherwise observed_key_b64
is pinned and returned. A return that differs from observed_key_b64
means the UID’s key changed under that name.
Source§impl Store
impl Store
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_agents
directly (the same source agent_def_list reads, via the shared
private agent_row_get) — as of the definition flip this is no longer
scoped to definitions/templates only: any db_agents row, including
a bare template launch with no db_agent_definitions counterpart,
resolves here, matching what agent_def_list already returns for
it. Falls back to the global cross-channel registry when the row
doesn’t exist locally at all.
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.
Sourcepub(crate) fn agent_def_get_strict(
&self,
id: &str,
) -> Result<Option<AgentDefinition>, String>
pub(crate) fn agent_def_get_strict( &self, id: &str, ) -> Result<Option<AgentDefinition>, String>
agent_def_get, but a failed read of the global definition registry
is an error rather than “not found”. For callers that must refuse
instead of acting on a partial answer — the name resolver treats “no
such UID” as “send the bare name”, which a hidden fault must not
trigger (Codex P2 on #3568).
The global cross-channel definition for id, errors preserved.
Sourcepub fn agent_def_list(&self) -> Result<Vec<AgentDefinition>, StoreError>
pub fn agent_def_list(&self) -> 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. Every definition mutation
AND every instance lifecycle touch bumps db_agents.updated_at, 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.
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(in backend::storage) fn agent_def_exists_local(
&self,
id: &str,
) -> Result<bool, StoreError>
pub(in backend::storage) 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.
No project-instruction cleanup here, unlike agent_def_delete: this
removes template rows, and a template is never launched — observations
are recorded by agent.open, so a template has none to forget (Codex,
PR #3162).
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(in backend::storage) fn agent_def_insert_local_only(
&self,
agent: &mut AgentDefinition,
updated_at_override: Option<i64>,
) -> Result<(), StoreError>
pub(in backend::storage) fn agent_def_insert_local_only( &self, agent: &mut AgentDefinition, updated_at_override: Option<i64>, ) -> Result<(), StoreError>
Insert into db_agents only — does NOT mirror to the global
registry. Used directly by agent_def_insert (which mirrors right
after) and by agent_def_backfill_local_from_registry (which must
NOT mirror — see that function’s doc comment for why re-mirroring
immediately after this call would wipe the registry’s real
content/skills).
updated_at_override: None stamps updated_at = created_at
(the original, unchanged behavior for genuinely new definitions).
Some(ts) stamps updated_at = ts instead — used by the backfill
path to preserve the registry record’s real updated_at rather
than resetting it.
is_seeded = 1 rows become is_template = 1 (a template; bindings
stay empty). is_seeded = 0 rows become is_template = 0 with
parent_template_id = parent_id — same is_template/parent_template_id
derivation the old dual-write mirror used (frozen intent, no longer
a second write).
Sourcepub(in backend::storage) fn agent_def_backfill_local_from_registry(
&self,
record: &DefinitionRecord,
) -> Result<(), StoreError>
pub(in backend::storage) fn agent_def_backfill_local_from_registry( &self, record: &DefinitionRecord, ) -> Result<(), StoreError>
Materialize a local shadow of a registry-only definition — this
channel’s db_agents row plus a best-effort local copy of
content/skills — so instance_create’s FK can succeed for an agent
whose definition exists cross-channel but was never created in THIS
channel’s SQLite.
Deliberately does NOT call registry_def_upsert (unlike
agent_def_insert, which does): the registry is already
authoritative for this record. registry_def_upsert rebuilds
the registry record from agent_content_get_all_local/
agent_skill_list_local — LOCAL-only reads, by design (see that
function’s own comment) — so calling it immediately after this
insert-with-no-content-yet would overwrite the registry’s real
content/skills with empty arrays. Content/skill rows are instead
copied here via direct INSERTs (bypassing agent_content_set/
agent_skill_insert, which each call registry_def_refresh_if_mirrored
themselves) so nothing re-mirrors mid-backfill.
Copy failures for content/skills are logged and otherwise
ignored — the definition row alone satisfies the FK, and reads
still resolve content/skills correctly via the existing
cross-channel fallback even if this best-effort local copy is
incomplete. A slug collision against an unrelated local agent
will rename the local slug (existing agent_def_insert_local_only
behavior) — low-probability and non-fatal, since slug isn’t the
FK target.
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_has_local_row(&self, id: &str) -> Result<bool, StoreError>
pub fn agent_def_has_local_row(&self, id: &str) -> Result<bool, StoreError>
Whether this channel’s store has its own row for definition id, as
opposed to the definition only resolving through the global registry
overlay. Only a local row can have its default_memory_id set.
Sourcepub fn agent_def_set_memory_id_if_empty(
&self,
id: &str,
memory_id: &str,
) -> Result<bool, StoreError>
pub fn agent_def_set_memory_id_if_empty( &self, id: &str, memory_id: &str, ) -> Result<bool, StoreError>
Set db_agents.default_memory_id on a LOCAL agent — but only if
it’s currently empty. Exists because agent_def_update’s SET clause
deliberately never touches this column (readonly-after-creation, see
its own field doc comment) — this is the one narrow, explicit path
allowed to set it, and only the FIRST time, for m0021’s backfill
and definition-time provisioning to use. Returns Ok(true) if the
row existed and had an empty default_memory_id (write applied),
Ok(false) if the row doesn’t exist locally OR already has a
non-empty value (no-op, not an error — matches the “set once”
semantic silently rather than requiring every caller to pre-check).
LOCAL ONLY, deliberately — a cross-channel (global-registry-only)
definition can’t be reached this way today because
DefinitionRecordV1 doesn’t carry memory_id yet (same accepted gap
as model_vendor_base_url, see def_registry_mirror.rs). Callers
backfilling across the whole agent population must check
agent_def_get first and skip (with a log) anything that only
resolves via the global registry.
Sourcepub(crate) fn resolve_effective_vendor(
provider: &str,
model_vendor_base_url: &str,
) -> String
pub(crate) fn resolve_effective_vendor( provider: &str, model_vendor_base_url: &str, ) -> String
The provider’s default vendor, or "custom" when the agent has a
non-empty model_vendor_base_url override — same rule the
frontend’s resolveEffectiveVendor
(frontend/app/view/agent/providers/catalog.ts) already uses for
the dual-icon vendor badge. Falls back to the provider id itself
if the provider isn’t in the registry (e.g. a stale/custom
provider string), same fallback the frontend uses.
Derived wherever it’s needed rather than stored: a Claude agent
pointed at a custom endpoint is "custom", not "anthropic"
(#2587).
Sourcepub fn resolve_effective_provider_id(&self, agent: &AgentDefinition) -> String
pub fn resolve_effective_provider_id(&self, agent: &AgentDefinition) -> String
The harness/provider id agent runs: its own provider. A bundle
carries no harness (SPEC_AGENT_BUNDLE_FORMAT_V0_3_2026_10_05.md §3.1);
the agent owns it, read-only after creation (updateagent and
agent.define refuse a change). The bound bundle’s provider is a
hint and is consulted only when the agent has none at all, a
definition the §3.3 migration couldn’t reach; there it is the value
the agent has always run with.
Call this on the EFFECTIVE identity/memory store
(AppState.id_store), never on the per-channel mstore — that
fallback reads the bundle, which lives in the shared store when one is
configured. One shared implementation for spawn (agent_open.rs),
the credential gate (identity/resolver/inject.rs), clone, fork and
export, so they can’t disagree.
Sourcepub fn bundle_provision_for_new_agent(
&self,
agent: &AgentDefinition,
now: i64,
) -> Result<String, StoreError>
pub fn bundle_provision_for_new_agent( &self, agent: &AgentDefinition, now: i64, ) -> Result<String, StoreError>
Provision a fresh, dedicated ABF bundle for a NEW agent definition —
the definition-time half of
docs/specs/ARCHITECTURE_MANDATORY_ABF_RETHINK_2026_08_14.md §3.2
(m0021 is the backfill half, for agents that already existed before
this shipped). The bundle carries no harness: the agent owns its
provider (SPEC_AGENT_BUNDLE_FORMAT_V0_3_2026_10_05.md §3.2); the agent
is passed for the bundle’s name.
Call this on the EFFECTIVE identity/memory store
(AppState.id_store), never on the per-channel mstore directly.
Bundles live in the shared store when one is configured (the normal
case) — every real bundle-read path (listmemories/getmemory/
the Memory editor/the bundle-summary panel) reads through
id_store, so a bundle created via mstore.bundle_upsert
directly would be written to a different SQLite file and be
invisible everywhere else (P1 finding, Codex review on PR #2587,
live in the shipped code this fixes: agent_def_provision_and_ bind_bundle’s callers all used to invoke this method ON mstore).
Does NOT mutate agent or touch db_agent_definitions — returns
the new bundle’s id; callers set agent.memory_id themselves before
their own insert (so the existing agent_def_insert* INSERT
statements, which already include memory_id, pick it up with no
separate write).
Sourcepub(crate) fn resolve_unique_bundle_name(
&self,
base_name: &str,
) -> Result<String, StoreError>
pub(crate) fn resolve_unique_bundle_name( &self, base_name: &str, ) -> Result<String, StoreError>
Resolve a db_bundles.name guaranteed not to collide with an
existing row — that column is TEXT NOT NULL UNIQUE, but only
AgentDefinition.slug is guaranteed unique, not the display
name, so a naive "{agent.name} — ABF" collides whenever two
agents share a display name. Appends a numeric suffix on
collision, same shape agent_def_insert_local_only already uses
for slug collisions.
P1 fix (2026-08-15, ReAgent review on PR #2587 round 4): before
this, a collision here silently left a runtime-provisioned agent
unbound (the caller’s best-effort error handling swallowed it) and
— worse — unconditionally ABORTED m0021’s entire backfill loop
via ? on the very first collision, permanently stalling backfill
for every agent processed after it, on every subsequent boot,
until the underlying name collision was manually resolved.
Sourcepub fn agent_def_provision_and_bind_bundle(
&self,
bundle_store: &Store,
agent: &mut AgentDefinition,
now: i64,
)
pub fn agent_def_provision_and_bind_bundle( &self, bundle_store: &Store, agent: &mut AgentDefinition, now: i64, )
Provision + bind a fresh bundle to an ALREADY-INSERTED agent
definition — the two-step, post-insert counterpart to
bundle_provision_for_new_agent above. Deliberately separate from
the insert itself (rather than setting memory_id on the struct
before calling agent_def_insert*/agent_def_find_or_insert):
agent_def_find_or_insert in particular uses its AgentDefinition
argument as BOTH the lookup key and the conditional-insert payload,
so a bundle built up-front would go to waste (leaked, unbound) on
every if_exists=skip/update call against an already-existing
name — and agent.define is meant to be called repeatedly/
idempotently. Binding after the fact, only once the caller has
confirmed a row was genuinely freshly inserted, avoids that leak.
Best-effort by design (matches createagent’s own color-assignment
comment: a failure here shouldn’t fail agent creation) — logs and
returns on either step’s failure rather than propagating, since the
agent row itself is already durably committed by the time this
runs, and a still-unbound agent is exactly the pre-existing
memory_id='' state m0021 already knows how to backfill later.
Takes &mut AgentDefinition and writes the new bundle id back into
agent.memory_id on success — same “caller’s struct reflects what
landed” convention agent_def_update documents just below — so RPC
handlers that serialize agent straight back to the caller (e.g.
createagent, importagentfromclaw) return the real value instead
of the empty string the struct held before this call.
self (the definition store, i.e. mstore) and bundle_store
(the effective identity/memory store, i.e. AppState.id_store) are
deliberately two SEPARATE parameters, not the same store used for
both writes — see bundle_provision_for_new_agent’s own doc
comment for why (P1 fix, Codex review on PR #2587: they used to be
the same store, silently writing every provisioned bundle
somewhere the rest of the app can’t see it).
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).
parent_id is NOT updatable post-insert — it describes the agent’s
lineage; re-parenting is done by creating a new fork, not mutating the
original.
branch_label IS written here (unlike parent_id) — this storage-
layer function persists whatever is on agent. Immutability for most
callers is enforced one layer up, at the RPC handler: updateagent
always passes back old.branch_label.clone() unchanged, so it’s a
no-op for that caller. renameagentdefinitiontitle is the one
deliberate exception that supplies a real change — see
crates/srv/src/server/agent_handlers/template.rs and
docs/specs/SPEC_PANE_TAB_STRIP_COMPACT_SIZING_AND_RENAME_2026_07_22.md §4.
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.
Sourcepub(in backend::storage) fn scope_for_row(
conn: &Connection,
id: &str,
) -> RecordScope
pub(in backend::storage) fn scope_for_row( conn: &Connection, id: &str, ) -> RecordScope
The RecordScope every registry operation on id must use.
Read this before adding another registry call. The wide scope
matches records by definition_id, and a TEMPLATE’s id is the
definition_id of every legacy, never-re-keyed launch record
belonging to a REAL agent launched from it — so a wide match on a
template deletes / retires / renames those agents’ records along
with the template, making them vanish from the picker. ReAgent found
exactly that omission at four separate call sites on PR #3262
(agent_def_delete, instance_delete, instance_set_hidden,
instance_rename), which is why the rule lives here rather than as
a fourth hand-written if.
An id with no row at all is NOT a template: the global definition
store only holds is_seeded == 0 user agents, so a cross-channel id
is always an agent — which is what lets the delete paths still sweep
on a retry after the local row is already gone.
Takes the connection rather than &self because every caller needs
this read in the SAME critical section as its own UPDATE/DELETE —
otherwise the row could change kind in between. A &self wrapper
existed briefly and was dead on arrival for exactly that reason
(ReAgent P2 on PR #3262).
Sourcepub fn agent_dependents_purge(&self, id: &str) -> Result<usize, StoreError>
pub fn agent_dependents_purge(&self, id: &str) -> Result<usize, StoreError>
Purge this store’s rows keyed to a deleted agent, without touching
db_agents itself. For the OTHER physical database an agent has
rows in: agent_def_delete covers the object store it runs against,
and the deleteagent handler calls this on state.identity_store,
which is where the live db_agent_identity_links /
db_agent_credentials / native-memory rows actually are
(SPEC_IDENTITY_STORE_SPLIT_2026_08_17.md). See
purge_agent_dependents for the table list and why one list
serves both stores. Returns rows removed.
Sourcepub(in backend::storage) fn purge_agent_side_effects(
&self,
id: &str,
caller: &'static str,
scope: RecordScope,
)
pub(in backend::storage) fn purge_agent_side_effects( &self, id: &str, caller: &'static str, scope: RecordScope, )
The non-SQLite half of deleting an agent row: observations keyed to
it, and its records in the host-global instance registry. Shared by
Self::agent_def_delete and Self::instance_delete — the two
names one deletion is reached by since the definition flip (see
instance_delete’s own doc comment). Best-effort throughout:
SQLite has already committed, and neither side failing is a reason
to report the delete as failed. caller only labels the logs.
scope must come from scope_for_row, read
BEFORE the row was deleted — afterwards there is nothing left to ask.
Sourcepub fn agents_grandfather_ambient_login(
&self,
linked_agent_ids: &HashSet<String>,
) -> Result<(usize, usize), StoreError>
pub fn agents_grandfather_ambient_login( &self, linked_agent_ids: &HashSet<String>, ) -> Result<(usize, usize), StoreError>
One-shot grandfather pass for the layer-3 ambient-login opt-in (m0017, spec §2.4 of SPEC_ACCOUNT_DELETE_DEAUTH_LAYERS_2_4_2026_07_14.md).
Agents WITHOUT any db_agent_identity_links row at migration time
were de-facto ambient users → use_ambient_login = 1; agents WITH
links opted into managed accounts → 0 (honest failure is the new
behavior for them). linked_agent_ids comes from the SHARED store
(the live links table); this method writes db_agents — the
definition flip’s only remaining caller of this is m0017 on an
install upgrading through the whole migration chain for the first
time, where db_agents is already the sole live source. Returns
(rows set to 1, rows set to 0).
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 agents as instances. Both filters are optional — pass None
to scan all. Ordered by updated_at descending, created_at as a
tiebreaker (most recent activity first — every launch, continuation
and lifecycle write bumps updated_at).
The definition_id filter matches the agent’s own id only:
templates are not agents and user agents derived from one template
are separate agents (see instance_list_named for the one caller
that wants “everything derived from this template”). The status
filter reads the row’s latest launch status (v29).
Sourcepub fn instance_get(
&self,
id: &str,
) -> Result<Option<AgentInstance>, StoreError>
pub fn instance_get( &self, id: &str, ) -> Result<Option<AgentInstance>, StoreError>
One agent by id, as an instance. None for a template or an
unknown id.
Sourcepub fn agent_last_runtime_get(&self, id: &str) -> Result<String, StoreError>
pub fn agent_last_runtime_get(&self, id: &str) -> Result<String, StoreError>
The Runtime menu choices the user last made for agent id (JSON, see
normalize_last_runtime), or ‘’ when there are none or the agent is
unknown or a template.
Sourcepub fn agent_last_runtime_set(
&self,
id: &str,
runtime: &str,
) -> Result<bool, StoreError>
pub fn agent_last_runtime_set( &self, id: &str, runtime: &str, ) -> Result<bool, StoreError>
Replace agent id’s remembered runtime. Returns false for an unknown
agent or a template (templates are not agents anyone continues, so
they remember nothing). Deliberately leaves updated_at alone: this is
a note about how the agent was last run, not an edit of its definition.
Sourcepub(in backend::storage) fn agent_row_get(
&self,
id: &str,
) -> Result<Option<AgentDefinition>, StoreError>
pub(in backend::storage) fn agent_row_get( &self, id: &str, ) -> Result<Option<AgentDefinition>, StoreError>
A single db_agents row in the AgentDefinition shape, no registry
overlay — the definition a launch resolves against. Templates and
user agents alike.
Sourcepub fn instance_create(
&self,
inst: &AgentInstance,
) -> Result<AgentInstance, StoreError>
pub fn instance_create( &self, inst: &AgentInstance, ) -> Result<AgentInstance, StoreError>
Record a launch. Returns the agent row the launch now lives on, as an
instance — its id may differ from inst.id: a launch of a user
agent folds into that agent’s row, and a continuation (non-empty
parent_instance_id naming an existing row) folds into the row it
continues. Only a fresh launch of a TEMPLATE creates a new row, keyed
by inst.id. Callers must persist the returned id, not the one they
passed (the frontend stores it as the block’s agentInstanceId).
inst.definition_id must name a db_agents row; when it only exists
in the global cross-channel definition registry (an agent created in
another channel), the local definition is backfilled first, exactly
as before (REPORT_AGENT_DEFINITION_DB_GAP_2026_07_27.md). A
definition that exists nowhere is StoreError::NotFound — the error
the legacy FK used to raise, minus the FK.
Sourcefn monotonic_updated_at(conn: &Connection, floor: i64) -> i64
fn monotonic_updated_at(conn: &Connection, floor: i64) -> i64
An updated_at strictly greater than any already on db_agents, so
recency ordering survives fast successive writes within one
millisecond (reagent P2 round 3 on #1013 — the rule the dual-write
used; inherited here).
Set the hidden flag on an agent 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 for those. Returns true
when either side acted.
Sourcepub fn instance_rename(
&self,
id: &str,
instance_name: &str,
) -> Result<bool, StoreError>
pub fn instance_rename( &self, id: &str, instance_name: &str, ) -> Result<bool, StoreError>
Rename the agent’s PICKER-VISIBLE display name (instance_name),
in SQLite and across every registry record for it.
renameagentdefinitiontitle on its own writes name (or
branch_label for a fork) — the fields the pane tab strip’s
titleOf() reads. “My Agents” rows render
instance_name || definition_name instead, and instance_name is
non-empty for every launched or registry-backed row, so a rename
closed the editor, refetched, and redisplayed the OLD name; for a
fork it changed branch_label, which no picker row shows at all
(codex P1 on PR #3262). Renaming an agent has to move the name the
user is actually looking at, so the handler calls this too.
Scoped to rows that ALREADY have an instance_name: writing one
onto an agent that has never had a named launch would make it appear
in instance_list_named (which filters instance_name <> '') —
conjuring a picker row rather than renaming one. Those rows fall
back to definition_name, which the title update already moved.
Returns whether anything changed, counting the registry: a cross-channel agent has no local row to update but does have records.
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>
Named agents for the launch modal’s “Continue agent” dropdown and the
picker’s “My Agents” surface: non-hidden rows with an instance_name,
newest launch first, capped by limit.
definition_id restricts the result to that agent itself OR every
agent derived from it (parent_template_id) — the launch modal opens
per template, and what a user wants to continue there is any agent
they launched from it. identity_id filters on the row’s identity
binding.
include_continuations is accepted for API compatibility but no
longer changes the query: chains are pre-collapsed (one row per
agent carrying its latest launch), so the legacy “heads only” and
“latest per chain” modes both yield exactly this list.
Sourcepub fn instance_get_by_slug(
&self,
slug: &str,
) -> Result<Option<AgentInstance>, StoreError>
pub fn instance_get_by_slug( &self, slug: &str, ) -> Result<Option<AgentInstance>, StoreError>
Resolve an agent by its persisted slug — the App-API self-lookup
(AGENTMUX_AGENT_ID is the slug) — only if exactly one agent has
it. Matches ONLY slug; deliberately single-purpose after reagentx
P1 on PR #2428 (round 2): a lookup that also matched another column
let a coincidental cross-namespace collision return an unrelated
agent’s memory, accounts or bundle. Hidden rows are excluded.
None means “no agent” or “more than one agent” — callers already
treat both as “fall back to another source”, so they are not
distinguished. The fallbacks are themselves fail-closed
(agent_registry_lookup::find_active_record_by_slug), so refusing
here routes to another refusal rather than to a guess.
Why ambiguity returns None rather than the most recently updated
row: db_agents.slug has no UNIQUE constraint — migrations.rs
declines the index deliberately. Both write paths now suffix-resolve
(#3497 §4), so neither mints a duplicate, but nothing at the schema
level prevents one: a future writer, a migration, or hand-edited data
still can. (An earlier revision of this comment claimed
instance_create copied its template’s slug onto every launch, so two
launches collided. That was never true of the real launch flow — see
the spec’s §2.5.2 — and is no longer true of the function either.) This
function used to ORDER BY updated_at DESC LIMIT 1 and hand the
winner’s definition_id and working_directory to the caller.
HistoryService::sessions_for_agent consults it to resolve an
agent’s conversation history, so guessing wrong does not degrade
gracefully — it discloses one agent’s transcripts to another, and
deterministically: the same wrong agent won every time. A slug simply
does not identify an agent when it collides, so the honest answer is
“unknown”. Same rule and same reasoning as
crate::backend::agent_registry_lookup::find_active_record_by_slug
(PR #3480), which this mirrors for db_agents.
Sourcepub fn agents_matching_name(
&self,
typed: &str,
) -> Result<Vec<AgentNameMatch>, StoreError>
pub fn agents_matching_name( &self, typed: &str, ) -> Result<Vec<AgentNameMatch>, StoreError>
Every non-template, non-hidden agent a typed name could mean (identity
M3, spec §5): exact slug, or name/instance_name case-insensitively.
Lists rather than picks — the caller decides what to do with several.
Sourcepub fn rows_answering_to(
&self,
name: &str,
) -> Result<Vec<AgentNameMatch>, StoreError>
pub fn rows_answering_to( &self, name: &str, ) -> Result<Vec<AgentNameMatch>, StoreError>
Every row a name selects, templates and hidden rows included — the
identity M4a-2 ambiguity check (spec §6.5.3). Unlike
Self::agents_matching_name, which lists only the agents a typed
name could mean, this asks whether the name is anyone else’s: slug
collision suffixing (resolve_slug_collision) counts template rows,
so a “Claude” made from the “Claude” template is claude-2, and a name
its MCP sends as claude belongs to the template (#3573).
Sourcepub fn agent_names_by_id(
&self,
id: &str,
) -> Result<Option<AgentNameMatch>, StoreError>
pub fn agent_names_by_id( &self, id: &str, ) -> Result<Option<AgentNameMatch>, StoreError>
The names row id answers to — the by-id counterpart of
Self::agents_matching_name, for checking a body’s actor name
against the calling agent’s own row (identity M4a-2, spec §6.5.3).
Any row, template or hidden: the caller’s token already names it.
Sourcepub fn agent_was_deleted(&self, id: &str) -> bool
pub fn agent_was_deleted(&self, id: &str) -> bool
Whether id names an agent that was deleted: no local row (template
or not), and the shared definition registry holds a record for it that
is retired rather than active — deleting a user agent retires its
global record (registry_def_retire). A template, a removed template
and a live cross-channel agent all read false: templates are never
mirrored, and a cross-channel agent’s record is active. With no shared
registry this cannot be told and reads false. For the identity spawn
gate (#3577).
Sourcepub fn agent_row_ensure_local(&self, id: &str) -> Result<bool, StoreError>
pub fn agent_row_ensure_local(&self, id: &str) -> Result<bool, StoreError>
Ensure a user agent id has a local db_agents row, backfilling it
from the shared definition registry when it exists only there (a
cross-channel agent) — the same backfill instance_create runs, without
its fold. Identity M4b-4 (spec §6.5.8): so a block naming a
cross-channel agent resolves to a local row at its spawn. Returns
whether the row exists afterwards.
Sourcepub fn instance_record_launch(
&self,
id: &str,
block_id: &str,
started_at: i64,
) -> Result<bool, StoreError>
pub fn instance_record_launch( &self, id: &str, block_id: &str, started_at: i64, ) -> Result<bool, StoreError>
Record a launch of user agent id on block_id: a lifecycle-only
update — last_block_id, status = running, started_at,
ended_at = 0, a monotonic updated_at and the registry mirror — the
columns instance_create’s fold moves, and none of the ones it would
overwrite from a caller’s record (name, identity_id, memory_id,
instance_name, github_context, user_hidden). Identity M4b-4 (spec
§6.5.8), for agent.open, which has no such record. Returns false for
a template or a missing row.
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 agent’s launch state. Only Some fields are
written; None leaves that column untouched. Some("") explicitly
clears (the updateagentinstance command contract, and
poison_resume’s deliberate stale-session clear).
Returns the post-update row. None is reserved for not-found;
an all-None update on an existing id is a no-op that returns the
unchanged row, so callers can tell “nothing to change” from “no such
agent”. See SPEC_UPDATEAGENTINSTANCE_PARTIAL_UPDATE_2026_05_29.md.
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 agent derived from old_def_id at 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, the migration clones it into a user agent and
repoints its launches so the existing reattach flow keeps working.
Returns the number of rows updated.
Sourcepub fn instance_delete(&self, id: &str) -> Result<bool, StoreError>
pub fn instance_delete(&self, id: &str) -> Result<bool, StoreError>
Delete an agent row (never a template). The row IS the agent in the
consolidated model, so this removes the agent — previously a launch
row of a user-clone definition could be deleted while the definition
stayed; there is no such split any more. As of the definition flip,
this and agent_def_delete are the same operation reached from two
names: neither writes db_agent_definitions, and nothing reads it
per-boot any more to resurrect what either one deletes (the startup
gap-repair that once could is gone — codex P2 on #3080 closed the
resurrection path this way rather than by re-adding a legacy-table
delete here).
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.
Resolves through block.meta.agentId (or legacy agent:id) first —
the block says which agent it shows — returning that agent’s row
with block_id set to the asked-for block. Falls back to the agent
whose latest launch is on this block and still active, which is what
a block from before the block meta carried agentId needs.
block.meta.agentInstanceId never overrides agentId: codex P1 on
PR #1114 round 3 surfaced that pane reuse can leave a stale one
behind. It is consulted only for a block whose agentId names no
agent row, and only within what that block names, while it is still
the block’s latest launch (see the fallback below).
Sourcepub fn instance_get_by_block_id(
&self,
block_id: &str,
) -> Result<Option<AgentInstance>, StoreError>
pub fn instance_get_by_block_id( &self, block_id: &str, ) -> Result<Option<AgentInstance>, StoreError>
The agent whose latest launch is on block_id, regardless of status
— with its real session_id, so callers that write back to that
exact row (keeping session_id live as the CLI emits it — see
persist_session_id /
SPEC_PANE_CLOSE_REOPEN_CONTINUITY_GUARANTEE_2026_07_27.md §4.1) have
the right id to pass to instance_update_partial.
When a pane is reused, several rows can name the block. The one the
pane runs is the block’s own agentId (or legacy agent:id), while
its latest launch is still this block; otherwise the most recent
launch (started_at). Never the most recently updated row: a
rename or definition edit bumps updated_at, and a session id then
landed on the renamed agent instead of the one running (#3580).
Source§impl Store
impl Store
Sourcepub fn retired_agent_names(&self) -> Result<Vec<String>, StoreError>
pub fn retired_agent_names(&self) -> Result<Vec<String>, StoreError>
The names deleted agents were known by, lowercased as they were
tombstoned before each delete. A fork’s automatic number counts them,
so a deleted fork’s number is never handed out again, even the
highest one (suggest_fork_name).
Sourcepub fn agent_former_names(&self, id: &str) -> Result<Vec<String>, StoreError>
pub fn agent_former_names(&self, id: &str) -> Result<Vec<String>, StoreError>
The names agent id had before its renames. A lineage root’s former
names keep its deleted forks’ numbers counted after it is renamed
(suggest_fork_name).
Sourcepub fn agent_slugs_folded(&self, name: &str) -> Result<Vec<String>, StoreError>
pub fn agent_slugs_folded(&self, name: &str) -> Result<Vec<String>, StoreError>
The slugs of every row whose slug equals name trimmed and folded
(identity M4d-3’s drift count; slug uniqueness is case-sensitive, so
“Aria” and “aria” are two rows).
Source§impl Store
impl Store
Sourcepub fn agent_ids_for_slug_folded(
&self,
name: &str,
) -> Result<Vec<String>, StoreError>
pub fn agent_ids_for_slug_folded( &self, name: &str, ) -> Result<Vec<String>, StoreError>
The ids of every row whose slug is name, trimmed and folded with
Rust’s to_lowercase (identity M4d-6: which agent this instance knows
by a name a jekt claims).
Source§impl Store
impl Store
Sourcepub fn agent_defs_for_target(
&self,
target: &str,
) -> Result<Vec<AgentDefinition>, StoreError>
pub fn agent_defs_for_target( &self, target: &str, ) -> Result<Vec<AgentDefinition>, StoreError>
The definitions a message’s target names: the one whose slug it is,
else every one whose display name routes to it (agent_route_name: a
fork “Loap #3” is Loap-3, and a root Claude may hold the slug
claude-2) (#4744). Folded like the key tables.
Source§impl Store
impl Store
Sourcepub fn background_task_observe(
&self,
id: &str,
block_id: &str,
label: &str,
started_at_ms: i64,
now_ms: i64,
) -> Result<(), StoreError>
pub fn background_task_observe( &self, id: &str, block_id: &str, label: &str, started_at_ms: i64, now_ms: i64, ) -> Result<(), StoreError>
Create the row if it doesn’t exist yet (status running), and
refresh last_seen_ms — but only while the row is still running.
Never resurrects a row a concurrent background_task_complete call
already marked terminal; the terminal status is left untouched.
Sourcepub fn background_task_set_pid(
&self,
id: &str,
pid: i64,
) -> Result<bool, StoreError>
pub fn background_task_set_pid( &self, id: &str, pid: i64, ) -> Result<bool, StoreError>
Record the OS pid once known (bashwrap/process_tracker learn it
after the task has already been observed as declared-background).
No-op (returns false) if the row doesn’t exist.
Sourcepub fn background_task_complete(
&self,
id: &str,
status: BackgroundTaskStatus,
ended_at_ms: i64,
) -> Result<bool, StoreError>
pub fn background_task_complete( &self, id: &str, status: BackgroundTaskStatus, ended_at_ms: i64, ) -> Result<bool, StoreError>
Mark a task terminal. Idempotent — calling this again on an already- terminal row just overwrites the status/ended_at (last write wins), which only matters if two different terminal signals race, an edge case not worth a CAS for a status transition with no live consumer depending on “first terminal write wins” today.
pub fn background_task_get( &self, id: &str, ) -> Result<Option<BackgroundTask>, StoreError>
pub fn background_task_list_for_block( &self, block_id: &str, ) -> Result<Vec<BackgroundTask>, StoreError>
Sourcepub fn background_task_list_running(
&self,
) -> Result<Vec<BackgroundTask>, StoreError>
pub fn background_task_list_running( &self, ) -> Result<Vec<BackgroundTask>, StoreError>
Global — every still-running row across every block. Used by
Swarm-style fleet views and reconnect/history-restore, where the
caller doesn’t yet know which block(s) to ask about.
Sourcepub fn background_task_list_fleet(
&self,
ended_since_ms: i64,
) -> Result<Vec<BackgroundTask>, StoreError>
pub fn background_task_list_fleet( &self, ended_since_ms: i64, ) -> Result<Vec<BackgroundTask>, StoreError>
Every block’s running rows plus those that ended at or after
ended_since_ms — what a fleet view (Swarm) shows: live work, and
finished work for as long as it keeps showing a finished row.
Sourcepub fn background_task_describe(
&self,
id: &str,
label: &str,
owner_tool_use_id: Option<&str>,
) -> Result<bool, StoreError>
pub fn background_task_describe( &self, id: &str, label: &str, owner_tool_use_id: Option<&str>, ) -> Result<bool, StoreError>
Record what the CLI says about a task: its description (replacing a
generic label such as the renderer’s “Bash”) and the subagent that
owns it. The owner is only ever filled in, never cleared or changed.
No-op (false) if the row doesn’t exist.
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 fn conversation_trust_grant_check(
&self,
agent_id: &str,
requester_agent_id: &str,
tier: &str,
) -> Result<bool, StoreError>
pub fn conversation_trust_grant_check( &self, agent_id: &str, requester_agent_id: &str, tier: &str, ) -> Result<bool, StoreError>
True if agent_id has explicitly granted requester_agent_id access
to its conversation content on tier (“lan” or “wan”). A grant for
one tier’s cryptographic identity guarantee is never assumed to
cover another — a caller must check the grant for the ACTUAL tier
the incoming transcript_request arrived on.
Sourcepub fn conversation_trust_grant_add(
&self,
agent_id: &str,
requester_agent_id: &str,
tier: &str,
) -> Result<(), StoreError>
pub fn conversation_trust_grant_add( &self, agent_id: &str, requester_agent_id: &str, tier: &str, ) -> Result<(), StoreError>
Grant requester_agent_id access to agent_id’s conversation
content on tier. Idempotent — granting an already-granted peer is
a no-op, not an error or a duplicate row (the compound primary key
on (agent_id, granted_peer_agent_id, tier) enforces this at the
schema level too; INSERT OR REPLACE keeps this call site simple
rather than requiring callers to distinguish “insert” from
“already exists”).
Sourcepub fn conversation_trust_grant_revoke(
&self,
agent_id: &str,
requester_agent_id: &str,
tier: &str,
) -> Result<(), StoreError>
pub fn conversation_trust_grant_revoke( &self, agent_id: &str, requester_agent_id: &str, tier: &str, ) -> Result<(), StoreError>
Revoke a previously-granted peer. A no-op (not an error) if no such grant exists — revoking is idempotent, same as granting.
Sourcepub fn conversation_trust_grant_list(
&self,
agent_id: &str,
) -> Result<Vec<(String, String, i64)>, StoreError>
pub fn conversation_trust_grant_list( &self, agent_id: &str, ) -> Result<Vec<(String, String, i64)>, StoreError>
List every peer agent_id has granted access to, across all tiers —
for a settings UI/CLI to show the current allowlist.
Source§impl Store
impl Store
pub fn cron_create(&self, job: &CronJob) -> Result<(), StoreError>
pub fn cron_list(&self) -> Result<Vec<CronJob>, StoreError>
pub fn cron_list_enabled(&self) -> Result<Vec<CronJob>, StoreError>
pub fn cron_get(&self, id: &str) -> Result<Option<CronJob>, StoreError>
pub fn cron_set_enabled( &self, id: &str, enabled: bool, ) -> Result<bool, StoreError>
pub fn cron_record_fire(&self, id: &str, now: i64) -> Result<(), StoreError>
pub fn cron_delete(&self, id: &str) -> Result<bool, StoreError>
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_refresh_if_mirrored(&self, def_id: &str)
pub(super) fn registry_def_refresh_if_mirrored(&self, def_id: &str)
Same mirror as registry_def_upsert, but never CREATES a global
record — only refreshes one that already exists. Used by
agent_content_set/agent_content_delete/the skills equivalents:
their job is “keep an EXISTING mirror’s content/skills in sync,”
never “make this agent globally mirrored for the first time” (that
is agent_def_insert/agent_def_update/agent_def_find_or_insert’s
job, and their own registry_def_upsert calls already cover it).
Load-bearing as of the definition flip
(SPEC_AGENT_ARCHITECTURE_2026_05_27.md Phase 3d, reagent P1 x2 on
#3092): agent_def_get now resolves a template LAUNCH’s own row
too (is_seeded = 0, indistinguishable by column shape from a genuine
user agent — instance_create’s fresh-launch INSERT sets both
is_template/is_seeded to 0 the same way agent_def_insert does
for a real one). A launch never goes through agent_def_insert, so
it never has an existing global record — routing content/skill
writes through registry_def_upsert’s unconditional create-or-update
would mirror the launch into the shared cross-channel registry the
first time anything wrote its content (e.g. a routine ui:zoom
setting), making it appear as a new, permanent “My Agents” entry in
every other channel — one ghost per launch, accumulating forever.
This is not a template-vs-launch distinction this function has to
make: skipping creation entirely is correct for BOTH (a genuine
template’s own content writes are also a no-op here, same as
registry_def_upsert’s explicit is_seeded check already intends).
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.
Sourcepub(super) fn registry_def_update_content_field(
&self,
agent_id: &str,
content_type: &str,
value: Option<&str>,
) -> Result<(), StoreError>
pub(super) fn registry_def_update_content_field( &self, agent_id: &str, content_type: &str, value: Option<&str>, ) -> Result<(), StoreError>
Update a single content blob in the global registry record for a
cross-channel agent, preserving all other definition fields, content, and
skills. Used when the local channel has no db_agent_definitions row for
the agent (FK would prevent a local SQLite write).
value = Some(s) upserts the blob; value = None removes it.
Returns Err if the global registry is unavailable or the agent is absent.
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).
A row whose secret_ref/context JSON fails to parse is skipped
(logged with its account id) rather than aborting the whole query —
one corrupted row must never hide every other account. This is the
isolation the oauth_config_dir tag-alias comment above already
flagged as missing (“identity_list() aborts entirely on the first
unparseable row”) after that incident; this closes it at the source
instead of only patching the one wire-format mismatch that triggered
it. See ANALYSIS_ARMORY_STASH_CREDENTIAL_VISIBILITY_GAP_2026_08_04 (never committed to this repo).
pub fn identity_get( &self, id: &str, ) -> Result<Option<IdentityAccount>, StoreError>
Sourcepub fn identity_set_context_email(
&self,
id: &str,
email: &str,
) -> Result<bool, StoreError>
pub fn identity_set_context_email( &self, id: &str, email: &str, ) -> Result<bool, 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).
Set one account’s context.email, and nothing else: no other column,
no updated_at, and no row created — an account deleted since it was
read stays deleted, and a status or secret_ref written meanwhile
stands (SPEC_ACCOUNT_EMAIL_IN_ARMORY_2026_09_23.md §4’s backfill runs
inside a list). Returns whether a row was updated.
pub fn identity_upsert( &self, account: &IdentityAccount, ) -> Result<(), StoreError>
Sourcepub fn identity_upsert_with_mirror(
&self,
mirror: &Store,
account: &IdentityAccount,
) -> Result<(), StoreError>
pub fn identity_upsert_with_mirror( &self, mirror: &Store, account: &IdentityAccount, ) -> Result<(), StoreError>
identity_upsert(self) — the authoritative, primary write — followed
by a BEST-EFFORT mirror write into identity_store, so a link
resolved from the always-global identity store (§SPEC_IDENTITY_STORE_
SPLIT_2026_08_17.md) doesn’t dead-end resolving the account it
points at on a fresh, per-channel-isolated self after a version/
channel switch — see identity::resolver::inject::resolve_account.
Call this instead of plain identity_upsert at every LIVE account-
creation/update call site (reagentx P0 review on PR #2632 — the
original fix only backfilled the mirror once, via migration, so any
account created/updated AFTER the fix shipped still had no mirror
entry and reproduced the exact reported bug on its own next channel
switch).
self is the primary (id_store) — its result is authoritative and
propagated to the caller. mirror’s (identity_store’s) result is
logged on failure and otherwise ignored: the mirror existing at all
is a durability nicety for THIS specific continuity scenario, not a
correctness requirement the primary write should ever be blocked or
failed by.
Sourcepub fn identity_repair_malformed_secret_refs(&self) -> Result<usize, StoreError>
pub fn identity_repair_malformed_secret_refs(&self) -> Result<usize, StoreError>
Migration-only repair pass (m0019_repair_malformed_secret_ref):
scan every db_accounts.secret_ref for JSON that fails to parse and
attempt to fix the one known corruption shape — a Windows path
pasted into the JSON string with its backslashes never escaped
(found live: {"backend":"oauth_config_dir","dir":"C:\Users\..."},
not reproducible from any write path in this codebase — every real
caller goes through identity_upsert above, which always
serde_json::to_strings correctly; this shape only arises from an
out-of-band edit of the database file). Rows that still don’t parse
after the repair attempt are left untouched and logged — never
deleted, since a broken row may still be linked via
db_agent_identity_links and identity_list’s per-row skip (above)
already makes leaving it alone safe. Returns the number of rows
repaired.
Sourcepub fn identity_delete(
&self,
id: &str,
) -> Result<IdentityDeleteOutcome, StoreError>
pub fn identity_delete( &self, id: &str, ) -> Result<IdentityDeleteOutcome, StoreError>
Delete an identity account AND every db_agent_identity_links
row referencing it, atomically (single transaction under the
connection lock).
The cascade is EXPLICIT even though the current DDL carries
FOREIGN KEY (account_id) … ON DELETE CASCADE: databases whose
links table arrived via legacy ALTER TABLE … RENAME (forge era)
never got the FK clause retrofitted — CREATE TABLE IF NOT EXISTS
can’t alter an existing table — so on real user databases the
DDL-level cascade silently doesn’t exist and dangling links
survive the account row (auth-lifecycle gap §2.4,
docs/analysis/ANALYSIS_ACCOUNT_DELETE_AUTH_LIFECYCLE_GAP_2026_07_14.md).
db_identity_bindings (named by the report) was retired in Phase
4c and no longer exists; links are the sole junction on accounts.
Sourcepub fn agent_identity_unlink_by_account(
&self,
account_id: &str,
) -> Result<(usize, Vec<String>), StoreError>
pub fn agent_identity_unlink_by_account( &self, account_id: &str, ) -> Result<(usize, Vec<String>), StoreError>
Delete every db_agent_identity_links row for account_id, on
WHATEVER store this is called on — added for
docs/specs/SPEC_IDENTITY_STORE_SPLIT_2026_08_17.md: since links now
live in a different physical store (identity_store) than accounts
(id_store), identity_delete’s own same-connection cascade above
no longer reaches them (it only ever deleted rows in the SAME store
as the account, which was always true before this split). Callers
deleting an account must call this on identity_store in addition
to identity_delete on id_store — see the deleteidentityaccount
handler. Returns the same (count, affected_agents) shape
identity_delete did, captured before the delete in one transaction
for the same race-safety reason.
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_all(
&self,
) -> Result<Vec<AgentIdentityLink>, StoreError>
pub fn agent_identity_list_all( &self, ) -> Result<Vec<AgentIdentityLink>, StoreError>
List every (agent_id, account_id, provider) row in the table. Used by the startup backfill to seed the shared store.
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 agent_identity_link_provider_pairs(
&self,
) -> Result<Vec<(String, String)>, StoreError>
pub fn agent_identity_link_provider_pairs( &self, ) -> Result<Vec<(String, String)>, StoreError>
Every distinct (agent_id, provider) link pair. Used by the
m0017/m0018 ambient-login grandfather migrations, which must reason
about provider CLASS: only oauth-class links mean an agent “opted
into managed CLI login” (flag stays 0) — an api-key link (e.g. a
github PAT) is never spawn-gated, so it must not forfeit
grandfathering (spec §2.4 of
SPEC_ACCOUNT_DELETE_DEAUTH_LAYERS_2_4_2026_07_14.md). Class
filtering happens at the caller via
identity::resolver::provider_class; the storage layer stays
class-agnostic.
Source§impl Store
impl Store
Sourcefn managed_bound_ids<R: ManagedResource>(
&self,
owner: Owner,
owner_id: &str,
) -> Result<HashSet<String>, StoreError>
fn managed_bound_ids<R: ManagedResource>( &self, owner: Owner, owner_id: &str, ) -> Result<HashSet<String>, StoreError>
The ref ids owner_id holds for one primitive, on self.
Sourcefn managed_bound_counts<R: ManagedResource>(
&self,
) -> Result<HashMap<String, i64>, StoreError>
fn managed_bound_counts<R: ManagedResource>( &self, ) -> Result<HashMap<String, i64>, StoreError>
REF_COL -> count across every agent-level ref row, on self.
Sourcefn managed_catalog_global<R: ManagedResource>(
&self,
) -> Result<Vec<R>, StoreError>
fn managed_catalog_global<R: ManagedResource>( &self, ) -> Result<Vec<R>, StoreError>
Every global row of a primitive, on self acting as the CATALOG
store — newest first.
Sourcefn managed_catalog_global_and_ids<R: ManagedResource>(
&self,
ids: &HashSet<String>,
) -> Result<Vec<R>, StoreError>
fn managed_catalog_global_and_ids<R: ManagedResource>( &self, ids: &HashSet<String>, ) -> Result<Vec<R>, StoreError>
Every global row, plus (if ids is non-empty) every row in ids —
global or private — on self acting as the CATALOG store. Unordered;
callers sort as needed.
Sourcepub(super) fn managed_list<R: ManagedResource>(
&self,
catalog: &Store,
owner: Owner,
owner_id: &str,
) -> Result<Vec<(R, bool)>, StoreError>
pub(super) fn managed_list<R: ManagedResource>( &self, catalog: &Store, owner: Owner, owner_id: &str, ) -> Result<Vec<(R, bool)>, StoreError>
Rows visible to owner_id — its own referenced rows plus every global
row — each paired with whether this owner holds the ref. Global rows
first, newest first.
Sourcepub(super) fn managed_list_global_for_agent<R: ManagedResource>(
&self,
catalog: &Store,
agent_id: &str,
) -> Result<Vec<(R, bool)>, StoreError>
pub(super) fn managed_list_global_for_agent<R: ManagedResource>( &self, catalog: &Store, agent_id: &str, ) -> Result<Vec<(R, bool)>, StoreError>
Global rows only, each paired with whether agent_id holds the
agent-level ref. Newest first. Never returns private rows — the
caller-supplied agent_id is unverified on the catalog RPCs that
use this (reagentx P0 on PR #2329).
Sourcepub(super) fn managed_list_global<R: ManagedResource>(
&self,
catalog: &Store,
) -> Result<Vec<(R, i64)>, StoreError>
pub(super) fn managed_list_global<R: ManagedResource>( &self, catalog: &Store, ) -> Result<Vec<(R, i64)>, StoreError>
Global rows with how many agents hold an agent-level ref to each — the catalog’s “used by N agents” in Connectors and Memory. Newest first.
Sourcepub(super) fn managed_get<R: ManagedResource>(
&self,
id: &str,
) -> Result<Option<R>, StoreError>
pub(super) fn managed_get<R: ManagedResource>( &self, id: &str, ) -> Result<Option<R>, StoreError>
One row by id. Genuinely single-table — callers invoke this directly
on whichever store owns R::TABLE (catalog for Skill/McpServer).
Sourcepub(super) fn managed_delete<R: ManagedResource>(
&self,
catalog: &Store,
id: &str,
) -> Result<bool, StoreError>
pub(super) fn managed_delete<R: ManagedResource>( &self, catalog: &Store, id: &str, ) -> Result<bool, StoreError>
Delete the catalog row on catalog FIRST, then purge id’s ref rows
on self. True if a catalog row was deleted.
Order matters (Codex P1, PR #3183): with the ref-purge running first,
a catalog-delete failure AFTER the ref purge already committed (disk
full, the identity store locked, any I/O error) stranded the row —
still present in catalog, but no ref points at it and it is not
necessarily global, so no access-check path can find it again, and
the ordinary delete RPC’s own pre-check (skill_is_bound_to) now
rejects a retry too, since the ref that check depends on is already
gone. Catalog-first fixes the failure mode entirely: if the catalog
delete itself errors, ? returns before the ref purge ever runs, so
the row keeps its ref(s) and stays exactly as accessible/retryable as
before the call. If the catalog delete SUCCEEDS (including “0 rows,
already gone”) and the SUBSEQUENT ref purge then fails or a process
crashes between the two, the worst case is a dangling ref pointing at
an id the identity store no longer holds — the same already-accepted,
gracefully-degrading gap SPEC_DURABLE_BINDINGS_2026_09_10.md §5.4
documents for cross-channel dangling refs (skill_get on a missing
id returns None, which every read path already handles), not a new
failure mode.
Sourcepub(super) fn managed_unbind_all_for_bundle<R: ManagedResource>(
&self,
bundle_id: &str,
) -> Result<usize, StoreError>
pub(super) fn managed_unbind_all_for_bundle<R: ManagedResource>( &self, bundle_id: &str, ) -> Result<usize, StoreError>
Purge every bundle-level ref for bundle_id, for one resource kind.
The mirror of managed_delete’s ref purge, from the other side: that
one runs when a catalog row goes away, this one when a bundle does.
Neither can rely on foreign keys: the ref tables declare none to the
catalog rows or to db_bundles. The bundle side cannot have an
enforcing FK at all, since db_bundles lives in a different physical
database from the ref tables (see the db_bundle_skills_ref comment
in migrations.rs).
Returns the number of refs removed.
Sourcepub(super) fn managed_bind_agent<R: ManagedResource>(
&self,
catalog: &Store,
agent_id: &str,
id: &str,
) -> Result<(), StoreError>
pub(super) fn managed_bind_agent<R: ManagedResource>( &self, catalog: &Store, agent_id: &str, id: &str, ) -> Result<(), StoreError>
Insert the agent-level ref (idempotent). Errors if agent_id is not
a LOCAL agent: the ref table’s agent_id → db_agents FK would
otherwise swallow the INSERT OR IGNORE silently for a cross-channel
agent, which is indistinguishable by row count from “already bound”
(reagentx P1 on PR #2315; REPORT_ARMORY_SKILLS_MARKDOWN_AND_BIND_BUG_2026_07_27.md).
Checks db_agents, not db_agent_definitions: R::AGENT_REF_TABLE’s
FK targets db_agents as of Phase 3c (#3088), so checking the legacy
table here would reject a bind for any agent that exists ONLY as a
db_agents row (a template launch), even though the FK the INSERT
below actually depends on would accept it.
Additionally errors if id has no row in catalog — Part A of
SPEC_DURABLE_BINDINGS_2026_09_10.md §5.4 drops the ref table’s
skill_id/mcp_id → db_skills/db_mcp_servers FK entirely (it used
to target THIS channel’s local, now-stale catalog copy), so the
existence check that FK used to provide is now explicit here instead
— mirroring managed_bind_bundle’s existing id_store check.
Sourcepub(super) fn managed_bind_bundle<R: ManagedResource>(
&self,
catalog: &Store,
id_store: &Store,
bundle_id: &str,
id: &str,
) -> Result<(), StoreError>
pub(super) fn managed_bind_bundle<R: ManagedResource>( &self, catalog: &Store, id_store: &Store, bundle_id: &str, id: &str, ) -> Result<(), StoreError>
Insert the bundle-level ref (idempotent). Bundle existence is checked
in id_store, not self — bundles are authoritatively written through
the shared store, and self’s local db_bundles copy is essentially
always empty in production (reagentx P0 on PR #2639).
Also checks id exists in catalog — same Part A FK-removal reason
as managed_bind_agent’s new check (db_bundle_skills_ref also lost
its skill_id FK, and never had a bundle_id FK at all — see
migrations.rs’s own comment on these tables). Added now for the same
reason as the agent-level check, even though bundle_skill_bind’s
current call sites already pre-check existence via skill_get before
calling — see this crate’s report for why
this is still worth doing as a second line of defense at the store
layer rather than trusting every call site forever.
fn bundle_exists(&self, bundle_id: &str) -> Result<bool, StoreError>
Sourcepub(super) fn managed_unbind<R: ManagedResource>(
&self,
owner: Owner,
owner_id: &str,
id: &str,
) -> Result<bool, StoreError>
pub(super) fn managed_unbind<R: ManagedResource>( &self, owner: Owner, owner_id: &str, id: &str, ) -> Result<bool, StoreError>
Remove the owner-level ref. True if a row was removed.
Sourcepub(super) fn managed_upsert_unique<R: ManagedResource>(
&self,
catalog: &Store,
owner: Owner,
owner_id: &str,
row: &R,
bind_new: bool,
force_global: Option<bool>,
) -> Result<(), StoreError>
pub(super) fn managed_upsert_unique<R: ManagedResource>( &self, catalog: &Store, owner: Owner, owner_id: &str, row: &R, bind_new: bool, force_global: Option<bool>, ) -> Result<(), StoreError>
Upsert row into catalog enforcing owner-scoped name uniqueness (no
other row visible to the owner — bound or global — may share the
name), and bind it into self when bind_new.
No longer atomic (Phase 2 of SPEC_DURABLE_BINDINGS_2026_09_10.md §5.4, accepted interim gap): the catalog insert and the ref-table bind are two separate connections now, so a narrow same-name race between two concurrent upserts for a brand-new name is possible (single-owner, single-submit UI flows are not a realistic multi-writer scenario — not closed until Phase 3/6 restores single-connection atomicity). What this DOES still guarantee: if the ref-bind fails after the catalog insert already committed, a best-effort COMPENSATING DELETE removes the just-inserted catalog row before the bind error propagates, so the caller’s failure and the durable state agree (“nothing was created”) instead of leaving a permanent, undiscoverable orphan catalog row (no ordinary list/access/delete path can find a row that is neither bound nor global). The compensating delete’s own failure is logged, never allowed to mask the original bind error.
Sourcepub(super) fn managed_upsert_unique_for_bundle<R: ManagedResource>(
&self,
catalog: &Store,
id_store: &Store,
bundle_id: &str,
row: &R,
bind_new: bool,
) -> Result<(), StoreError>
pub(super) fn managed_upsert_unique_for_bundle<R: ManagedResource>( &self, catalog: &Store, id_store: &Store, bundle_id: &str, row: &R, bind_new: bool, ) -> Result<(), StoreError>
Bundle-scoped upsert: create a NEW, PRIVATE (never global) row bound
directly to a bundle, enforcing bundle-scoped name uniqueness. Bundle
existence is checked in id_store (see managed_bind_bundle).
managed_bind_bundle may only bind EXISTING global rows — binding an
existing private row would let one bundle “steal” read access to
whatever entity that row belongs to — but globals already reach every
agent, so this is how a bundle gets a genuinely bundle-specific row
that has never belonged to anyone else (reagentx P1 on PR #2639).
Sourcepub(super) fn managed_upsert_unique_global<R: ManagedResource>(
&self,
row: &R,
) -> Result<(), StoreError>
pub(super) fn managed_upsert_unique_global<R: ManagedResource>( &self, row: &R, ) -> Result<(), StoreError>
Atomically upsert a GLOBAL row enforcing catalog-wide name uniqueness
among every global row (not just those visible to one owner). Forces
is_global = 1 on INSERT; the caller is expected to have set it too.
Pure single-table operation — callers invoke this directly on
whichever store owns R::TABLE (identity_store in production).
Sourcepub(super) fn managed_is_accessible_to<R: ManagedResource>(
&self,
catalog: &Store,
owner: Owner,
owner_id: &str,
id: &str,
) -> Result<bool, StoreError>
pub(super) fn managed_is_accessible_to<R: ManagedResource>( &self, catalog: &Store, owner: Owner, owner_id: &str, id: &str, ) -> Result<bool, StoreError>
True when the row is global (in catalog) or the owner holds a ref
to it (in self) — the read/mutation access check.
Sourcepub(super) fn managed_is_bound_to<R: ManagedResource>(
&self,
owner: Owner,
owner_id: &str,
id: &str,
) -> Result<bool, StoreError>
pub(super) fn managed_is_bound_to<R: ManagedResource>( &self, owner: Owner, owner_id: &str, id: &str, ) -> Result<bool, StoreError>
True when the owner holds a direct ref to the row (global rows are
not implied) — the ownership check for edit/delete. Pure ref-table
operation on self.
Sourcepub(super) fn managed_union_bundle_refs<R: ManagedResource>(
&self,
catalog: &Store,
agent_id: &str,
visible: &mut Vec<R>,
)
pub(super) fn managed_union_bundle_refs<R: ManagedResource>( &self, catalog: &Store, agent_id: &str, visible: &mut Vec<R>, )
Union the rows referenced by agent_id’s bundles into visible, in
list order (its own bundle first, then its picks: agent_bundle_chain),
deduped by id, so the first bundle to name a row wins and a global row
already in visible isn’t added twice. Composable model v2 — without
this, bundle-level refs would be exactly as inert at launch as the
bundle’s old inline JSON columns were (GH issue #2024 item 3). Silent
on lookup failure. Skills only now: a bundle carries no MCP servers
(SPEC_BUNDLE_CONTENTS_MEMORY_NOT_MCP_2026_10_07.md §3.1).
Source§impl Store
impl Store
Sourcepub fn project_instructions_record(
&self,
agent_id: &str,
observations: &[ProjectInstructionObservation],
) -> Result<(), StoreError>
pub fn project_instructions_record( &self, agent_id: &str, observations: &[ProjectInstructionObservation], ) -> Result<(), StoreError>
Record what was observed, replacing the previous observation.
Last-write-wins per (agent_id, path): this is a “what did it look
like most recently” record, not a history. Version history for these
files would mean storing their content, which is exactly what this
table declines to do — the repository’s own version control already
owns that job for the repository’s own files.
Sourcepub fn project_instructions_list(
&self,
agent_id: &str,
) -> Result<Vec<ProjectInstructionObservation>, StoreError>
pub fn project_instructions_list( &self, agent_id: &str, ) -> Result<Vec<ProjectInstructionObservation>, StoreError>
Every recorded observation for agent_id, path-ordered.
Sourcepub fn project_instructions_forget(
&self,
agent_id: &str,
) -> Result<usize, StoreError>
pub fn project_instructions_forget( &self, agent_id: &str, ) -> Result<usize, StoreError>
Forget everything recorded for agent_id.
Called by agent_def_delete, beside the row these observations belong
to. There is no foreign key to enforce it — they record files an agent
reads, which is not a relationship SQLite can express — so without
that call the rows outlive the agent and a future agent reusing the id
inherits somebody else’s baseline, every file reporting unchanged
against observations never made about it (ReAgent, PR #3162).
Nothing else should call it: dropping observations resets every file to
FirstSeen, which silently discards the drift this table exists to
surface.
Source§impl Store
impl Store
Sourcepub fn bundle_unbind_all_components(
&self,
bundle_id: &str,
) -> Result<(usize, usize), StoreError>
pub fn bundle_unbind_all_components( &self, bundle_id: &str, ) -> Result<(usize, usize), StoreError>
Drop every skill and MCP ref belonging to bundle_id. Bundles no
longer carry MCP servers (m0036_drop_bundle_mcp), but an older build
sharing the channel can still write a bundle MCP ref, so those go too.
Call after deleting the bundle itself. bundle_delete lives on the
store that owns db_bundles, which does not have the ref tables at all
— they sit beside the catalog tables they key into — so the cleanup
cannot happen inside it and has to be driven from the handler, where
both stores are in scope. Without it a deleted bundle leaves its refs
behind, and a future bundle reusing that id would inherit them.
Returns (skill_refs_removed, mcp_refs_removed).
Source§impl Store
impl Store
Sourcepub fn mcp_server_list(
&self,
catalog: &Store,
agent_id: &str,
) -> Result<Vec<McpServerListItem>, StoreError>
pub fn mcp_server_list( &self, catalog: &Store, agent_id: &str, ) -> Result<Vec<McpServerListItem>, StoreError>
List all MCP servers visible to an agent: own (referenced) + global, each annotated with whether this specific agent holds the bind ref.
Sourcepub fn effective_mcp_servers(
&self,
catalog: &Store,
agent_id: &str,
container: bool,
) -> Vec<McpServer>
pub fn effective_mcp_servers( &self, catalog: &Store, agent_id: &str, container: bool, ) -> Vec<McpServer>
Every MCP server an agent launches with, on both launch paths
(agent.open and the picker’s writeagentconfig): its own private
servers, plus the catalog servers bound to it or switched on for all
agents. A container agent gets only the catalog servers switched on
for containers: a catalog server is usually a host command. Its own
servers are delivered as configured. Not its bundles’: a bundle
carries no MCP servers
(SPEC_BUNDLE_CONTENTS_MEMORY_NOT_MCP_2026_10_07.md §3.1).
SPEC_CONNECTORS_MCP_WHAT_AGENTS_RUN_2026_10_10.md §2.1.
Sourcepub fn mcp_server_reach_all(
&self,
) -> Result<HashMap<String, McpServerReach>, StoreError>
pub fn mcp_server_reach_all( &self, ) -> Result<HashMap<String, McpServerReach>, StoreError>
Every catalog server’s reach switches, by server id. Call on the
catalog store (the identity store), where db_mcp_server_reach sits
beside db_mcp_servers.
Sourcepub fn mcp_server_set_reach(
&self,
mcp_id: &str,
reach: McpServerReach,
now_ms: i64,
) -> Result<(), StoreError>
pub fn mcp_server_set_reach( &self, mcp_id: &str, reach: McpServerReach, now_ms: i64, ) -> Result<(), StoreError>
Set a catalog server’s reach switches. Call on the catalog store; the caller checks the server is a global row.
Sourcepub fn mcp_server_list_global(
&self,
catalog: &Store,
) -> Result<Vec<McpServerCatalogItem>, StoreError>
pub fn mcp_server_list_global( &self, catalog: &Store, ) -> Result<Vec<McpServerCatalogItem>, StoreError>
List every GLOBAL MCP server — Connectors → MCP servers. Unlike
mcp_server_list, this takes no agent_id and never includes an
agent’s private servers; it backs the window-scoped mcp.catalog.*
MuxAPI (no check_s1, so there is no agent context to scope by).
Each row carries bound_count — how many agents currently hold a
db_agent_mcp_ref to it — per SPEC_V1_MCP_SKILLS_PRIMITIVES_2026_06_30.md
§8 (“used by N agents”), tracked as gap #2 of #1960.
Sourcepub fn mcp_server_list_global_for_agent(
&self,
catalog: &Store,
agent_id: &str,
) -> Result<Vec<McpServerListItem>, StoreError>
pub fn mcp_server_list_global_for_agent( &self, catalog: &Store, agent_id: &str, ) -> Result<Vec<McpServerListItem>, StoreError>
Catalog-tier sibling of mcp_server_list (above) — same
bound_to_agent shape, but deliberately GLOBAL ROWS ONLY, unlike
mcp_server_list’s UNION with agent_id’s own private servers.
Backs mcp.catalog.list_for_agent, which — like every other
mcp.catalog.* command — has no check_s1, so agent_id here is
caller-supplied and unverified. Returning private server rows (whose
config can carry secrets: API keys, auth headers, env vars) for an
arbitrary caller-chosen agent_id would let any window connection
read any agent’s private server config. Global rows carry no
per-agent secret — they’re already fully visible via
mcp_server_list_global (Connectors → MCP servers) — so exposing them
alongside a caller-chosen agent’s bind status is safe.
reagentx P0 on PR #2329.
Sourcepub fn mcp_server_get(&self, id: &str) -> Result<Option<McpServer>, StoreError>
pub fn mcp_server_get(&self, id: &str) -> Result<Option<McpServer>, StoreError>
Get a standalone MCP server by id.
Sourcepub fn mcp_server_delete(
&self,
catalog: &Store,
id: &str,
) -> Result<bool, StoreError>
pub fn mcp_server_delete( &self, catalog: &Store, id: &str, ) -> Result<bool, StoreError>
Delete a standalone MCP server and purge ref rows (both agent- and
bundle-level — FK cascades may be off on some builds, same reasoning
as skill_delete). Returns true if deleted.
Sourcepub fn mcp_server_bind(
&self,
catalog: &Store,
agent_id: &str,
mcp_id: &str,
) -> Result<(), StoreError>
pub fn mcp_server_bind( &self, catalog: &Store, agent_id: &str, mcp_id: &str, ) -> Result<(), StoreError>
Bind an MCP server to an agent (insert ref row). Idempotent — binding an already-bound pair is a silent no-op success.
Errors if agent_id isn’t a LOCAL agent:
db_agent_mcp_ref.agent_id has an ON-enforced FK to
db_agents(id) (store.rs; was db_agent_definitions(id) before
Phase 3c, #3088), but Connectors’ agent
picker (ListAgentDefinitionsCommand → agent_def_list()) also
lists cross-channel agents that only exist in another channel’s
local database. Binding one of those would otherwise have the FK
silently swallow the INSERT OR IGNORE — indistinguishable, by
affected-row-count alone, from the equally-silent “already bound”
case — reporting success while creating nothing. Same fix as
skill_bind — see
docs/reports/REPORT_ARMORY_SKILLS_MARKDOWN_AND_BIND_BUG_2026_07_27.md.
Sourcepub fn mcp_server_unbind(
&self,
agent_id: &str,
mcp_id: &str,
) -> Result<bool, StoreError>
pub fn mcp_server_unbind( &self, agent_id: &str, mcp_id: &str, ) -> Result<bool, StoreError>
Unbind an MCP server from an agent. Returns true if a row was removed.
Sourcepub fn mcp_server_upsert_unique(
&self,
catalog: &Store,
agent_id: &str,
server: &McpServer,
bind_new: bool,
) -> Result<(), StoreError>
pub fn mcp_server_upsert_unique( &self, catalog: &Store, agent_id: &str, server: &McpServer, bind_new: bool, ) -> Result<(), StoreError>
Atomically upsert an MCP server enforcing per-agent name uniqueness, and
(when bind_new) bind it — all in one transaction so concurrent
mcp.upsert calls for the same name can’t both pass a check and insert
duplicate-named bindings. Returns an error if another server visible to
the agent (bound or global) already uses the name.
Sourcepub fn mcp_server_upsert_unique_global(
&self,
server: &McpServer,
) -> Result<(), StoreError>
pub fn mcp_server_upsert_unique_global( &self, server: &McpServer, ) -> Result<(), StoreError>
Atomically upsert a GLOBAL MCP server enforcing catalog-wide name
uniqueness (no agent_id — unlike mcp_server_upsert_unique, this
checks for a duplicate name among every global row, not just those
visible to one agent). Reagent P1 on #1948: agent_config.rs’s
build_mcp_config_from_refs merges servers into a JSON object keyed
by server.name — two same-named global servers would silently
clobber each other’s config for every agent that has either bound.
server.is_global must already be true; caller’s job.
pub(crate) fn mcp_server_list_all_raw( &self, ) -> Result<Vec<McpServer>, StoreError>
Sourcepub(crate) fn mcp_server_insert_raw(
&self,
server: &McpServer,
) -> Result<usize, StoreError>
pub(crate) fn mcp_server_insert_raw( &self, server: &McpServer, ) -> Result<usize, StoreError>
Returns 1 if a row was actually inserted, 0 if OR IGNORE found one
already there — see Skill::skill_insert_raw’s doc comment.
pub(crate) fn mcp_server_find_global_by_name( &self, name: &str, ) -> Result<Option<McpServer>, StoreError>
Sourcepub(crate) fn mcp_server_rewrite_ref_id(
&self,
old_id: &str,
new_id: &str,
) -> Result<usize, StoreError>
pub(crate) fn mcp_server_rewrite_ref_id( &self, old_id: &str, new_id: &str, ) -> Result<usize, StoreError>
Mirrors Skill::skill_rewrite_ref_id exactly, for the MCP ref tables
— including its insert-then-delete merge semantics (Codex P2, PR
#3183). Returns the number of ref rows actually repointed — see
Skill::skill_rewrite_ref_id’s doc comment.
Sourcepub fn mcp_server_is_accessible_to(
&self,
catalog: &Store,
agent_id: &str,
mcp_id: &str,
) -> Result<bool, StoreError>
pub fn mcp_server_is_accessible_to( &self, catalog: &Store, agent_id: &str, mcp_id: &str, ) -> Result<bool, StoreError>
Return true if the given MCP server is accessible to the agent (global or bound). Used for read and mutation access checks.
Sourcepub fn mcp_server_is_bound_to(
&self,
agent_id: &str,
mcp_id: &str,
) -> Result<bool, StoreError>
pub fn mcp_server_is_bound_to( &self, agent_id: &str, mcp_id: &str, ) -> Result<bool, StoreError>
Return true if the agent has a direct ref binding to this MCP server. Used for delete access — an agent may only delete servers it directly bound.
Source§impl Store
impl Store
pub fn bundle_list(&self) -> Result<Vec<Bundle>, StoreError>
Sourcepub fn bundle_list_global(&self) -> Result<Vec<Bundle>, StoreError>
pub fn bundle_list_global(&self) -> Result<Vec<Bundle>, StoreError>
Returns only the global bundles (is_global = 1), is_system rows
first (regardless of sort_order — see
docs/specs/SPEC_GLOBAL_MEMORY_SYSTEM_TIER_2026_08_24.md), then by
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 Memory → Global.
pub fn bundle_get(&self, id: &str) -> Result<Option<Bundle>, StoreError>
Sourcefn bundle_is_system(
&self,
conn: &Connection,
id: &str,
) -> Result<Option<bool>, StoreError>
fn bundle_is_system( &self, conn: &Connection, id: &str, ) -> Result<Option<bool>, StoreError>
Look up just the is_system flag for id, without decoding a full
Bundle row. Shared by every guard in this file that needs to know
“is the EXISTING row (if any) a system entry” before deciding
whether to allow a write through the generic path.
Sourcepub fn bundle_upsert(&self, memory: &Bundle) -> Result<(), StoreError>
pub fn bundle_upsert(&self, memory: &Bundle) -> Result<(), StoreError>
Self::bundle_upsert_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_upsert_unobserved(&self, memory: &Bundle) -> Result<(), StoreError>
fn bundle_upsert_unobserved(&self, memory: &Bundle) -> Result<(), StoreError>
Generic Global Bundle upsert — used by the ordinary Memory editor,
the per-agent Bundle editor, ABF import, and internal seeding.
Refuses outright to touch an existing is_system=1 row (content
included, not just the flag) — see
docs/specs/SPEC_GLOBAL_MEMORY_SYSTEM_TIER_2026_08_24.md §3.2. Use
bundle_upsert_system to create/edit a system entry.
Sourcepub fn bundle_upsert_with_version(
&self,
memory: &Bundle,
written_by: &str,
written_by_uid: &str,
source: &str,
source_detail: &str,
) -> Result<Option<BundleVersion>, StoreError>
pub fn bundle_upsert_with_version( &self, memory: &Bundle, written_by: &str, written_by_uid: &str, source: &str, source_detail: &str, ) -> Result<Option<BundleVersion>, StoreError>
Same as bundle_upsert, but when memory.is_global is true, also
appends a db_bundle_versions row for it — atomically, in the SAME
transaction as the db_bundles write, not as two separate calls.
codex P2, PR #3237: two separate lock/transaction acquisitions (the
original shape — call bundle_upsert, then separately call
bundle_version_insert) let a concurrent writer interleave between
them: writer A upserts, writer B upserts AND versions, then A’s own
(now-stale) version-insert lands last — the history would then claim
A’s content is current while the live row actually holds B’s. One
transaction makes that interleaving impossible.
written_by is the caller’s own TRUSTED identity (an agent’s real
AGENTMUX_AGENT_ID, or "armory-ui" for the human-facing Memory
editor) — see BundleVersion::written_by’s own doc comment for why
this is a separate field from source/source_detail.
written_by_uid is the writer’s UID from the request’s Caller
(identity M4c-1, spec §6.5.9) — "" when Unattributed, and for the
Memory pane. Returns
None (no version recorded) when memory.is_global is false —
versioning is scoped to Global Memory specifically, matching this
table’s whole purpose; a private, per-agent bundle upsert still
writes normally but has nothing to audit here.
Shares bundle_upsert’s exact guard (refuses an existing
is_system=1 row) and exact db_bundles SQL — kept as a literal
duplicate rather than having one call the other, since self.conn
is a plain (non-reentrant) Mutex: calling bundle_upsert (which
takes its own lock) from inside a transaction already holding that
same lock would deadlock.
Sourcepub fn bundle_upsert_with_version_based(
&self,
memory: &Bundle,
written_by: &str,
written_by_uid: &str,
source: &str,
source_detail: &str,
base_sha256: Option<&str>,
) -> Result<Option<BundleVersion>, StoreError>
pub fn bundle_upsert_with_version_based( &self, memory: &Bundle, written_by: &str, written_by_uid: &str, source: &str, source_detail: &str, base_sha256: Option<&str>, ) -> Result<Option<BundleVersion>, StoreError>
Self::bundle_upsert_with_version_based_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_upsert_with_version_based_unobserved(
&self,
memory: &Bundle,
written_by: &str,
written_by_uid: &str,
source: &str,
source_detail: &str,
base_sha256: Option<&str>,
) -> Result<Option<BundleVersion>, StoreError>
fn bundle_upsert_with_version_based_unobserved( &self, memory: &Bundle, written_by: &str, written_by_uid: &str, source: &str, source_detail: &str, base_sha256: Option<&str>, ) -> Result<Option<BundleVersion>, StoreError>
bundle_upsert_with_version with an optional base: when
base_sha256 is Some, the row’s CURRENT name + instructions
must still hash to it (bundle_versions::content_hash — the same
value as the latest version’s content_hash), checked inside the
same IMMEDIATE transaction as the write, else StoreError::Conflict
and nothing is written. A missing row is a conflict too. None
behaves exactly like bundle_upsert_with_version. Memory →
Global’s editor sends it (SPEC_MEMORY_FOLLOWS_THE_AGENT_2026_09_24.md §2.4).
Sourcepub fn bundle_upsert_system_if_changed(
&self,
memory: &Bundle,
written_by: &str,
source: &str,
source_detail: &str,
) -> Result<Option<BundleVersion>, StoreError>
pub fn bundle_upsert_system_if_changed( &self, memory: &Bundle, written_by: &str, source: &str, source_detail: &str, ) -> Result<Option<BundleVersion>, StoreError>
Same as bundle_upsert_system_with_version, but skips recording a
version when the write is byte-identical (name + instructions) to
what’s already stored — the case where an operator opens a seeded
Operator Config entry in Memory and clicks Save without
changing anything (the UI does not suppress this request). Recording
a version for a true no-op would permanently mark the row as
human-owned even though nothing actually changed, breaking bundle_ reseed_system_if_owned’s ownership signal for no reason (Codex P2,
PR #3244).
The existing-content comparison happens in the SAME transaction as
the resulting write, for the identical check-then-act reason bundle_ reseed_system_if_owned documents on itself — an earlier revision of
upsertsystemmemory (bundle.rs) composed this from a separate
bundle_get call plus a conditional branch, which raced a concurrent
writer between the read and the write (ReAgent P2, PR #3244).
Returns Ok(None) when the save was a no-op (still refreshes every
other field via the plain upsert; just records no version); Ok(Some( version)) otherwise, including for a brand-new row (never a no-op).
Sourcepub fn bundle_upsert_system_if_changed_based(
&self,
memory: &Bundle,
written_by: &str,
source: &str,
source_detail: &str,
base_sha256: Option<&str>,
) -> Result<Option<BundleVersion>, StoreError>
pub fn bundle_upsert_system_if_changed_based( &self, memory: &Bundle, written_by: &str, source: &str, source_detail: &str, base_sha256: Option<&str>, ) -> Result<Option<BundleVersion>, StoreError>
Self::bundle_upsert_system_if_changed_based_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_upsert_system_if_changed_based_unobserved(
&self,
memory: &Bundle,
written_by: &str,
source: &str,
source_detail: &str,
base_sha256: Option<&str>,
) -> Result<Option<BundleVersion>, StoreError>
fn bundle_upsert_system_if_changed_based_unobserved( &self, memory: &Bundle, written_by: &str, source: &str, source_detail: &str, base_sha256: Option<&str>, ) -> Result<Option<BundleVersion>, StoreError>
bundle_upsert_system_if_changed with the same optional base check
bundle_upsert_with_version_based documents — the system-tier half
of Memory → Global’s conditional save.
Sourcepub fn bundle_reseed_system_if_owned(
&self,
memory: &Bundle,
seeder_identity: &str,
source: &str,
source_detail: &str,
) -> Result<BundleReseedOutcome, StoreError>
pub fn bundle_reseed_system_if_owned( &self, memory: &Bundle, seeder_identity: &str, source: &str, source_detail: &str, ) -> Result<BundleReseedOutcome, StoreError>
Self::bundle_reseed_system_if_owned_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_reseed_system_if_owned_unobserved(
&self,
memory: &Bundle,
seeder_identity: &str,
source: &str,
source_detail: &str,
) -> Result<BundleReseedOutcome, StoreError>
fn bundle_reseed_system_if_owned_unobserved( &self, memory: &Bundle, seeder_identity: &str, source: &str, source_detail: &str, ) -> Result<BundleReseedOutcome, StoreError>
Atomically decide-and-apply an Operator Config reseed for one system
row — the ownership check (is this row still owned by AgentMux’s own
seeder, or has a human/another writer claimed it) and the resulting
write happen inside the SAME transaction, not as two separate Store
calls composed by the caller. A caller-composed version (read latest
version via bundle_version_list, decide, then separately call
bundle_upsert_system_with_version) is a check-then-act race: a
concurrent writer — a human editing this row in Memory, or
another AgentMux instance’s own startup reseed, since multiple
instances can run in parallel by design — could land a new version
between the read and the write, and the caller’s decision would be
based on data that was already stale by the time it acted (muxreview
P1, PR #3244).
seeder_identity is the reserved written_by value that marks a row
as still owned by the seeder (see operator_config_seed::WRITTEN_BY)
— a latest version written by anything else means a human (or some
other writer) has claimed this row, and it is left alone.
- No existing
db_bundlesrow at all -> (re)create, UNLESS the latest version for this id is aRETIREMENT_TOMBSTONE_SOURCEmarker whose recorded generation is newer thansource_detail’s own (SkippedRetiredin that case) — seebundle_delete_system_ if_owned’s own doc comment for why the tombstone exists: without it, an older AgentMux build (or a rollback) sharing this store.db would read “no row” as “never existed” and resurrect content a newer build’s prune deliberately retired (ReAgent/Codex P2, PR #3244). An ordinary human delete (bundle_delete_system, which records no version at all) leaves no tombstone, so a manually deleted entry still comes back exactly as before — only a generation-aware prune leaves a tombstone behind. - Existing row, latest version’s
written_by != seeder_identity->SkippedLocalEdit, no write at all. - Existing row, latest version’s
content_hashalready matches the target (name+instructions) ->Unchanged, no write. - Otherwise -> upsert + version insert,
Updated.
Sourcepub fn bundle_delete(&self, id: &str) -> Result<bool, StoreError>
pub fn bundle_delete(&self, id: &str) -> Result<bool, StoreError>
Self::bundle_delete_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_delete_unobserved(&self, id: &str) -> Result<bool, StoreError>
fn bundle_delete_unobserved(&self, id: &str) -> Result<bool, StoreError>
Delete a Bundle. Refuses to delete the blank singleton, a
seeded bundle, or (new) a system entry — use
bundle_delete_system for the last case.
Sourcepub fn bundle_delete_system(&self, id: &str) -> Result<bool, StoreError>
pub fn bundle_delete_system(&self, id: &str) -> Result<bool, StoreError>
Self::bundle_delete_system_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_delete_system_unobserved(&self, id: &str) -> Result<bool, StoreError>
fn bundle_delete_system_unobserved(&self, id: &str) -> Result<bool, StoreError>
The ONLY path that can remove an is_system=1 row — structurally
incapable of deleting anything else, even if misused.
Sourcepub fn bundle_list_system_ids(&self) -> Result<Vec<String>, StoreError>
pub fn bundle_list_system_ids(&self) -> Result<Vec<String>, StoreError>
All is_system=1 row ids — the full universe operator_config_seed’s
startup prune pass checks its manifest against, to find rows for an
entry AgentMux itself removed or renamed in a later release (Codex
P2, PR #3244). Ids only, not full Bundles: the prune pass needs
nothing else before deciding whether a given id is still in the
manifest.
Sourcepub fn bundle_delete_system_if_owned(
&self,
id: &str,
seeder_identity: &str,
caller_manifest_version: u32,
) -> Result<bool, StoreError>
pub fn bundle_delete_system_if_owned( &self, id: &str, seeder_identity: &str, caller_manifest_version: u32, ) -> Result<bool, StoreError>
Self::bundle_delete_system_if_owned_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_delete_system_if_owned_unobserved(
&self,
id: &str,
seeder_identity: &str,
caller_manifest_version: u32,
) -> Result<bool, StoreError>
fn bundle_delete_system_if_owned_unobserved( &self, id: &str, seeder_identity: &str, caller_manifest_version: u32, ) -> Result<bool, StoreError>
Atomically deletes an is_system=1 row ONLY if its latest version is
still owned by seeder_identity AND that version’s own recorded
manifest generation is <= caller_manifest_version — the delete-side
counterpart of bundle_reseed_system_if_owned’s ownership check,
used to prune a row whose manifest entry AgentMux itself removed in a
later release.
The generation gate exists for the same reason bundle_reseed_ system_if_owned has one: on a machine sharing store.db across
multiple AgentMux builds/versions, an OLDER build’s own manifest
simply doesn’t mention an entry a NEWER build already added — that
absence means “I don’t know about this yet,” not “this was removed.”
Without gating on generation, an older build’s prune pass would
delete a newer build’s own addition on every one of its startups.
Only a row whose generation this process’s OWN manifest has already
caught up to (or exceeds) is eligible for pruning at all.
A row a human has edited (written_by on the latest version is
anything else) is left in place — same “never clobber a local edit”
posture as the reseed path; an orphaned-but-human-owned entry is a
human’s to clean up, not this seeder’s. A row with NO version
history at all (should not normally happen — every write path that
can create an is_system row also records one) is conservatively
treated as NOT owned by the seeder and left alone, rather than
guessing. Returns true only when a deletion actually happened.
Also appends a RETIREMENT_TOMBSTONE_SOURCE-marked version row in
the SAME transaction as the delete — without it, an older AgentMux
build (or a rollback to one) sharing this store.db, whose own
manifest still lists this id, would see no db_bundles row and
treat that as “never existed, safe to create,” silently resurrecting
content this deletion deliberately retired. bundle_reseed_system_ if_owned’s missing-row branch checks for this tombstone before
recreating anything (ReAgent/Codex P2, PR #3244).
Also validates the LIVE row’s content against what the latest
version claims before trusting written_by at all — the delete-side
mirror of the same check bundle_reseed_system_if_owned gained
earlier in this PR. Without it, a compatible-schema OLDER build’s
unversioned write (still possible during a rolling upgrade — see
that method’s own doc comment) would leave written_by stale at
the seeder’s identity, and this method would DELETE the unrecorded
edit outright — strictly worse than the overwrite the reseed-path
fix was built to prevent, since deletion cannot be undone from
db_bundles alone. ReAgent P1, PR #3244.
Sourcepub fn bundle_reorder(
&self,
ordered_ids: &[String],
) -> Result<usize, StoreError>
pub fn bundle_reorder( &self, ordered_ids: &[String], ) -> Result<usize, StoreError>
Self::bundle_reorder_unobserved, then tell the Global Memory record’s
observer (GlobalMemoryChange).
Sourcefn bundle_reorder_unobserved(
&self,
ordered_ids: &[String],
) -> Result<usize, StoreError>
fn bundle_reorder_unobserved( &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 Memory → Global ordering,
which in turn controls CLAUDE.md injection order. Ids not present in
the table, OR present but is_system=1, are skipped silently — a
system row’s position is fixed (always first, see
bundle_list_global) and never disturbed by the generic
reorder command. Runs in a single transaction so a partial reorder
never lands. Returns the number of rows updated.
Source§impl Store
impl Store
Sourcepub fn bundle_version_list(
&self,
bundle_id: &str,
) -> Result<Vec<BundleVersionSummary>, StoreError>
pub fn bundle_version_list( &self, bundle_id: &str, ) -> Result<Vec<BundleVersionSummary>, StoreError>
History for one bundle, newest first — metadata only, no content
(mirrors agent_native_memory_version_list). Not called by the
Operator Config reseed path — Store::bundle_reseed_system_if_owned
(bundles.rs) performs its own inline SELECT written_by, content_hash, source_detail ... LIMIT 1 query instead, so its
ownership check and the write it gates happen in one transaction
(see that method’s own doc comment for why). Backs the
GlobalMemoryHistory MCP tool (global_memory_history_impl,
server/app_api/mod.rs) as of
SPEC_AGENT_FACING_GLOBAL_MEMORY_API_2026_09_15.md’s Phase-3
follow-up — also still exercised directly by this file’s own tests.
Sourcepub fn bundle_version_get(
&self,
version_id: &str,
) -> Result<Option<BundleVersion>, StoreError>
pub fn bundle_version_get( &self, version_id: &str, ) -> Result<Option<BundleVersion>, StoreError>
One version by id, full content included — the lookup diff/revert
build on (mirrors agent_native_memory_version_get). Called by
global_memory_diff_impl/global_memory_revert_impl
(app_api/mod.rs), which back the GlobalMemoryDiff/
GlobalMemoryRevert MCP tools.
Source§impl Store
impl Store
pub fn muxbus_load(&self) -> Result<Option<MuxBusCredentials>, StoreError>
Sourcepub fn muxbus_is_fresh(&self) -> bool
pub fn muxbus_is_fresh(&self) -> bool
Side-effect-free (no keychain/SQL WRITE, see allow_migration below)
freshness check: valid, non-empty access token that isn’t nearing
expiry. Safe for RefreshScheduler::register’s is_fresh closure,
called as it is under the per-id lock on every ensure_fresh/sweep
tick — the concern that closure must avoid (reagent P2 on #2260) is
an unexpected WRITE happening inside a nominally read-only check,
not lock acquisition itself; muxbus_load_impl below still takes
muxbus_save_lock (reagent P1 on this PR — see its comment) purely
to serialize this read against a concurrent writer, with no side
effect of its own performed while holding it. Any error (including a
genuinely corrupted keychain entry) is treated as “not fresh” rather
than propagated — a freshness check has no error channel to
propagate through, and “not fresh” just means the broker will
attempt a refresh, which surfaces the real problem through THAT path
instead.
Sourcefn muxbus_load_impl(
&self,
allow_migration: bool,
) -> Result<Option<MuxBusCredentials>, StoreError>
fn muxbus_load_impl( &self, allow_migration: bool, ) -> Result<Option<MuxBusCredentials>, StoreError>
allow_migration gates only the lazy-migration self-heal WRITES in
muxbus_load_tokens below (false for muxbus_is_fresh’s
side-effect-free contract, true for the normal muxbus_load
path) — it does NOT gate the lock below, which every call takes.
Sourcefn muxbus_load_tokens(
&self,
allow_migration: bool,
legacy_access: &str,
legacy_refresh: &str,
legacy_id: &str,
user_sub: &str,
) -> Result<MuxBusTokens, StoreError>
fn muxbus_load_tokens( &self, allow_migration: bool, legacy_access: &str, legacy_refresh: &str, legacy_id: &str, user_sub: &str, ) -> Result<MuxBusTokens, StoreError>
Resolve the three token fields, checking sources in order:
- the current split-entry keychain layout (fast path),
- the legacy (pre-2026-08-03) single combined-blob keychain entry — migrated in place to the split layout when found,
- legacy plaintext SQL columns from rows that predate keychain storage entirely — migrated in place to the split layout too. A keychain READ failure at any step falls back to the legacy plaintext SQL columns when they have data, same as the pre-split-entry code did — a transient storage failure must not present as a full logout when we still have usable data sitting right there.
Sourcepub fn muxbus_refresh_lock(
&self,
) -> Result<Option<CrossProcessGuard>, StoreError>
pub fn muxbus_refresh_lock( &self, ) -> Result<Option<CrossProcessGuard>, StoreError>
One process at a time refreshes this channel’s sign-in; the others wait, then find it already fresh. Its own lock, apart from the store’s, so reads aren’t held up by the token request.
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,
wrote_session: bool,
)
pub(super) fn registry_upsert_if_named( &self, inst: &AgentInstance, wrote_session: bool, )
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.
wrote_session says whether the caller’s own write set the row’s
session_id. When it didn’t (a launch), the row’s session id may be
older than one another channel has since mirrored, so a session id
the registry already holds is kept (#3586).
Failures are logged, never propagated: SQLite remains authoritative.
Sourcepub(super) fn registry_propagate_continuation_session_id(
&self,
inst: &AgentInstance,
)
pub(super) fn registry_propagate_continuation_session_id( &self, inst: &AgentInstance, )
Propagate a fresh session_id onto the chain-root registry record
when inst is a continuation row (parent_instance_id set).
registry_upsert_if_named above deliberately excludes continuation
rows from the mirror (see its filter comment — kept symmetric with
instance_list_named’s legacy dropdown mode so a resume chain
doesn’t fragment into N registry files). That’s correct for most
fields, but it leaves the registry’s session_id — the ONE field a
cross-channel/cross-build reader consults to --resume this agent —
stuck at whatever the chain head had when first mirrored, never
updated by any later resume. Root cause of
docs/retro/retro-agent-resumed-9-day-stale-session-2026-08-22.md:
reopening the agent in a different channel silently resumed a
stale-but-valid session instead of the live one.
Read-modify-write against the existing root record (never
constructs a partial one — Registry::upsert’s merge overwrites
every field present in the struct). Best-effort: logs and returns
on any lookup/write failure, since the SQLite write already
succeeded and remains authoritative.
Callers decide WHETHER to invoke this at all based on whether
inst.session_id reflects a genuine write, not just this
function — an empty session_id is ambiguous in isolation: it can
mean “never captured yet” (a fresh continuation — must NOT
propagate, would clobber a still-valid root pointer, reagent P1 on
PR #2755) or “deliberately cleared” (poison_resume’s stale-resume
handling, via persist_session_id(block_id, "", ...) on an
EXISTING row — MUST propagate, otherwise the shared registry keeps
serving a session_id already proven dead, reagent P1 round 2). Only
the caller knows which case it’s in (create vs. an explicit
InstanceUpdate.session_id write), so this function itself never
special-cases emptiness — it always mirrors whatever it’s given.
Sourcefn find_chain_root_id(&self, inst: &AgentInstance) -> String
fn find_chain_root_id(&self, inst: &AgentInstance) -> String
Walk parent_instance_id upward from inst to find the chain
root — the row with no parent. Bounded to guard against a
corrupted cyclic chain; real chains are a handful of user-driven
resumes deep.
When a lookup misses locally, returns current.parent_instance_id
(the id we couldn’t resolve), NOT current.id. Two cases produce a
local miss and both want that: (1) the id is a genuinely orphaned
continuation (its head was hard-deleted, no FK cascade) — the
registry has no file for it either, so the caller’s reg.get
harmlessly no-ops; (2) the id is the chain root living in a
DIFFERENT channel’s local SQLite — this store never has that row,
but the shared registry still does, under exactly this id. That
second case is the primary scenario this propagation exists for
(reopening an agent in another channel/build): current.id is a
continuation’s own id, which never has a registry file (reagent P1
on PR #2755 — the original fallback made cross-channel propagation
always no-op, defeating the fix for its own target case).
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 legacy skill by id. Returns true if a row was deleted.
Source§impl Store
impl Store
Sourcepub fn skill_list(
&self,
catalog: &Store,
agent_id: &str,
) -> Result<Vec<SkillListItem>, StoreError>
pub fn skill_list( &self, catalog: &Store, agent_id: &str, ) -> Result<Vec<SkillListItem>, StoreError>
List all skills visible to an agent: own (referenced) + global, each
annotated with whether this specific agent holds the bind ref.
catalog is where db_skills is authoritative — identity_store in
production (Phase 2 of SPEC_DURABLE_BINDINGS_2026_09_10.md §5.4).
Sourcepub fn effective_skills(
&self,
catalog: &Store,
agent_id: &str,
) -> Vec<AgentSkill>
pub fn effective_skills( &self, catalog: &Store, agent_id: &str, ) -> Vec<AgentSkill>
Effective skills for an agent at launch/config-materialization time:
this agent’s own ref-bound skills (via skill_list) take over
entirely when present (globals still included), otherwise fall back
to the legacy db_agent_skills table with globals layered on top. The
fallback decision is gated on own refs only — not the
global-inclusive list — so adding a global skill never discards a
legacy-only agent’s skills.
Single source of truth for two call sites that must stay consistent:
write_agent_config_files (the authoritative Rust materialization
path) and the listagentskills RPC handler, which the frontend’s
pre-launch ListAgentSkillsCommand call depends on for its own
buildConfigFiles mirror. Before this was extracted, listagentskills
returned only legacy skills — silently hiding every standalone/Memory →
Skills skill (including any Agent-Skills-format one, see
SKILL_TYPE_AGENT_SKILL) from the actual “click Launch” flow, since
that RPC is window-scoped (no check_s1) and can’t call the
agent-scoped skill.list RPC the way an already-running, authenticated
agent connection could (reagent P0 on PR #2322 — the launch UI is not
an authenticated agent connection, so it was never able to reach the
standalone Skill primitive via the RPC layer at all).
Sourcepub fn skill_list_global(
&self,
catalog: &Store,
) -> Result<Vec<SkillCatalogItem>, StoreError>
pub fn skill_list_global( &self, catalog: &Store, ) -> Result<Vec<SkillCatalogItem>, StoreError>
List every GLOBAL skill — Memory → Skills. Unlike
skill_list, this takes no agent_id and never includes an agent’s
private skills; it backs the window-scoped skill.catalog.* MuxAPI
(no check_s1, so there is no agent context to scope by). Each row
carries bound_count — how many agents currently hold a
db_agent_skills_ref to it — per
SPEC_V1_MCP_SKILLS_PRIMITIVES_2026_06_30.md §8 (“used by N agents”),
tracked as gap #2 of #1960.
Sourcepub fn skill_list_global_for_agent(
&self,
catalog: &Store,
agent_id: &str,
) -> Result<Vec<SkillListItem>, StoreError>
pub fn skill_list_global_for_agent( &self, catalog: &Store, agent_id: &str, ) -> Result<Vec<SkillListItem>, StoreError>
Catalog-tier sibling of skill_list (above) — same bound_to_agent
shape, but deliberately GLOBAL ROWS ONLY, unlike skill_list’s UNION
with agent_id’s own private skills. Backs skill.catalog.list_for_agent,
which — like every other skill.catalog.* command — has no
check_s1, so agent_id here is caller-supplied and unverified.
Returning private skill rows (whose content/description/trigger
can carry sensitive agent-authored material) for an arbitrary
caller-chosen agent_id would let any window connection read any
agent’s private skills. Global rows carry nothing per-agent-secret —
they’re already fully visible via skill_list_global (Memory →
Skills) — so exposing them alongside a caller-chosen agent’s bind
status is safe. reagentx P0 on PR #2329.
Sourcepub fn skill_get(&self, id: &str) -> Result<Option<Skill>, StoreError>
pub fn skill_get(&self, id: &str) -> Result<Option<Skill>, StoreError>
Get a standalone skill by id.
Sourcepub fn skill_delete(
&self,
catalog: &Store,
id: &str,
) -> Result<bool, StoreError>
pub fn skill_delete( &self, catalog: &Store, id: &str, ) -> Result<bool, StoreError>
Delete a standalone skill and purge its ref rows (both agent- and bundle-level). Returns true if deleted.
Sourcepub fn skill_bind(
&self,
catalog: &Store,
agent_id: &str,
skill_id: &str,
) -> Result<(), StoreError>
pub fn skill_bind( &self, catalog: &Store, agent_id: &str, skill_id: &str, ) -> Result<(), StoreError>
Bind a skill to an agent (insert ref row). Idempotent — binding an already-bound pair is a silent no-op success.
Errors if agent_id isn’t a LOCAL agent:
db_agent_skills_ref.agent_id has an ON-enforced FK to
db_agents(id) (store.rs; was db_agent_definitions(id) before
Phase 3c, #3088), but Memory’s agent
picker (ListAgentDefinitionsCommand → agent_def_list()) also
lists cross-channel agents that only exist in another channel’s
local database. Binding one of those would otherwise have the FK
silently swallow the INSERT OR IGNORE — indistinguishable, by
affected-row-count alone, from the equally-silent “already bound”
case — reporting success while creating nothing. reagentx P1, PR
#2315.
Sourcepub fn skill_unbind(
&self,
agent_id: &str,
skill_id: &str,
) -> Result<bool, StoreError>
pub fn skill_unbind( &self, agent_id: &str, skill_id: &str, ) -> Result<bool, StoreError>
Unbind a skill from an agent. Returns true if a row was removed.
Sourcepub fn skill_upsert_unique(
&self,
catalog: &Store,
agent_id: &str,
skill: &Skill,
bind_new: bool,
) -> Result<(), StoreError>
pub fn skill_upsert_unique( &self, catalog: &Store, agent_id: &str, skill: &Skill, bind_new: bool, ) -> Result<(), StoreError>
Atomically upsert a skill enforcing per-agent name uniqueness, and (when
bind_new) bind it to the agent — all in one transaction so concurrent
skill.upsert calls for the same name can’t both pass a separate check
and insert duplicates (the check+write is not split across lock releases).
Returns StoreError::Other (“… already bound to this agent”) if another
skill visible to the agent (bound or global) already uses the name.
Sourcepub fn skill_upsert_unique_global(
&self,
skill: &Skill,
) -> Result<(), StoreError>
pub fn skill_upsert_unique_global( &self, skill: &Skill, ) -> Result<(), StoreError>
Atomically upsert a GLOBAL skill enforcing catalog-wide name
uniqueness (no agent_id — unlike skill_upsert_unique, this checks
for a duplicate name among every global row, not just those visible
to one agent). Same defense-in-depth as
McpServer::mcp_server_upsert_unique_global (reagent P1 on #1948) —
two same-named global skills would at minimum produce a confusing
duplicate bullet in the assembled CLAUDE.md skills index.
skill.is_global must already be true; caller’s job.
Sourcepub(crate) fn skill_list_all_raw(&self) -> Result<Vec<Skill>, StoreError>
pub(crate) fn skill_list_all_raw(&self) -> Result<Vec<Skill>, StoreError>
Every row in this store’s db_skills, global and private alike, with
no accessibility filtering. Ordinary app code never needs this — it is
always scoped by agent/bundle accessibility — which is exactly why no
such method existed before the carry-across migration needed one.
Sourcepub(crate) fn skill_insert_raw(
&self,
skill: &Skill,
) -> Result<usize, StoreError>
pub(crate) fn skill_insert_raw( &self, skill: &Skill, ) -> Result<usize, StoreError>
Insert skill verbatim — including its is_global flag, which
skill_upsert_unique_global would otherwise force to true. INSERT OR IGNORE: idempotent against a re-run migrating the same row twice
(by id), never overwrites an existing row.
Returns 1 if a row was actually inserted, 0 if OR IGNORE found one
already there under this id — the caller’s signal for whether this
was a genuinely new carry or an idempotent re-run no-op.
Sourcepub(crate) fn skill_find_global_by_name(
&self,
name: &str,
) -> Result<Option<Skill>, StoreError>
pub(crate) fn skill_find_global_by_name( &self, name: &str, ) -> Result<Option<Skill>, StoreError>
The first existing GLOBAL row matching name alone, for the
migration’s first-wins dedup among global skills that aren’t a
recognized starter (those converge on starter_skill_id instead —
see skill_seed::starter_skill_trigger_for_name).
Name-only, not (name, skill_type) — matching idx_ids_skills_global_name
(IDENTITY_STORE_SCHEMA_VERSION v8, SPEC_DURABLE_BINDINGS_2026_09_10.md
§5.4, Codex P1 on PR #3183): a (name, skill_type)-scoped re-query
would miss the actual winner whenever two channels’ carries collide on
name but not type — the insert that failed did so because of the
name-only constraint, so the re-query must match the constraint that
actually fired, or it finds nothing, falls through to the
unrelated-PK-collision branch, and mints a fresh id whose insert
SILENTLY no-ops against the same name constraint (INSERT OR IGNORE),
leaving a local ref rewritten to an id the identity store never
actually holds — the skill vanishes the moment reads redirect there.
Sourcepub(crate) fn skill_rewrite_ref_id(
&self,
old_id: &str,
new_id: &str,
) -> Result<usize, StoreError>
pub(crate) fn skill_rewrite_ref_id( &self, old_id: &str, new_id: &str, ) -> Result<usize, StoreError>
Repoint every ref row (agent- and bundle-level) naming old_id to
new_id instead, on THIS store only. Called by the carry-across
migration after it decides a skill’s row must live under a different
id in the identity store than it does locally — without this, the ref
row would name an id that no longer resolves anywhere once catalog
reads redirect to the identity store (Phase 2’s whole point).
INSERT-survivor-then-DELETE-old, not a blind UPDATE: the PK
(agent_id, skill_id)/(bundle_id, skill_id) collides whenever this
exact owner already independently held a ref to new_id too — not
vanishingly unlikely once two DIFFERENT catalog rows (not just two
copies of the same one) can converge onto one survivor, which is
exactly what m0033_narrow_skill_global_uniqueness_index’s dedup
does (an owner can easily have bound BOTH of two same-name,
different-skill_type skills before the collision was discovered).
A plain UPDATE OR IGNORE left that case’s old-id row untouched
(documented at the time as an acceptable no-op — ReAgent P1, PR
#3181, round 2) — but a caller that then deletes the OLD catalog row
(as m0033 does, right after this rewrite) turns that “no-op” into a
permanently dangling ref pointing at an id nothing resolves anymore,
even within the current channel (Codex P2, PR #3183). Insert-then-
delete instead: INSERT OR IGNORE the survivor binding for every
owner who held old_id (already-bound owners no-op harmlessly, same
as before), then unconditionally DELETE every old_id row — so the
owner ends up bound to the survivor either way, never left pointing
at a name that’s about to stop existing. Returns the number of
old_id ref rows resolved (0 if none named old_id, distinct from
“the call itself no-oped”).
Sourcepub fn skill_is_accessible_to(
&self,
catalog: &Store,
agent_id: &str,
skill_id: &str,
) -> Result<bool, StoreError>
pub fn skill_is_accessible_to( &self, catalog: &Store, agent_id: &str, skill_id: &str, ) -> Result<bool, StoreError>
Return true if the given skill is accessible to the agent (global or bound). Used for read and delete access checks.
Sourcepub fn skill_is_bound_to(
&self,
agent_id: &str,
skill_id: &str,
) -> Result<bool, StoreError>
pub fn skill_is_bound_to( &self, agent_id: &str, skill_id: &str, ) -> Result<bool, StoreError>
Return true if the agent has a direct ref binding to this skill (for delete/mutation). Global skills are excluded — use the is_global guard separately.
Sourcepub fn bundle_skill_list(
&self,
catalog: &Store,
bundle_id: &str,
) -> Result<Vec<SkillBundleListItem>, StoreError>
pub fn bundle_skill_list( &self, catalog: &Store, bundle_id: &str, ) -> Result<Vec<SkillBundleListItem>, StoreError>
Bundle-level sibling of skill_list — this bundle’s own (referenced)
and global skills, each annotated with whether this specific bundle
holds the db_bundle_skills_ref row.
Sourcepub fn bundle_skill_bind(
&self,
catalog: &Store,
id_store: &Store,
bundle_id: &str,
skill_id: &str,
) -> Result<(), StoreError>
pub fn bundle_skill_bind( &self, catalog: &Store, id_store: &Store, bundle_id: &str, skill_id: &str, ) -> Result<(), StoreError>
Bind a skill to a bundle (insert ref row). Idempotent.
id_store (NOT self) is where bundle existence is checked: bundles
are written through the shared store, and self’s own db_bundles
copy is essentially always empty in production, so checking there made
“bundle not found” fire for every real bundle (PR #2639). catalog is where
db_skills is authoritative — see managed_bind_bundle’s doc
comment for why it now ALSO checks skill existence there (Part A of
SPEC_DURABLE_BINDINGS_2026_09_10.md §5.4 dropped this table’s
skill_id FK).
Sourcepub fn bundle_skill_upsert_unique(
&self,
catalog: &Store,
id_store: &Store,
bundle_id: &str,
skill: &Skill,
bind_new: bool,
) -> Result<(), StoreError>
pub fn bundle_skill_upsert_unique( &self, catalog: &Store, id_store: &Store, bundle_id: &str, skill: &Skill, bind_new: bool, ) -> Result<(), StoreError>
Atomically create a NEW, PRIVATE (never global) skill scoped and
bound directly to a bundle, enforcing bundle-scoped name uniqueness
— the bundle-level analog of skill_upsert_unique. Binding can only
reference global skills (binding another entity’s private one would
hand this bundle read access to it), and a global skill already
reaches every agent, so this is how a bundle gets a skill of its own
(PR #2639).
Sourcepub fn bundle_skill_unbind(
&self,
bundle_id: &str,
skill_id: &str,
) -> Result<bool, StoreError>
pub fn bundle_skill_unbind( &self, bundle_id: &str, skill_id: &str, ) -> Result<bool, StoreError>
Unbind a skill from a bundle. Returns true if a row was removed.
Sourcepub fn bundle_skill_is_accessible_to(
&self,
catalog: &Store,
bundle_id: &str,
skill_id: &str,
) -> Result<bool, StoreError>
pub fn bundle_skill_is_accessible_to( &self, catalog: &Store, bundle_id: &str, skill_id: &str, ) -> Result<bool, StoreError>
Return true if the given skill is accessible to the bundle (global or
bundle-bound). Mirrors skill_is_accessible_to.
Sourcepub fn bundle_skill_is_bound_to(
&self,
bundle_id: &str,
skill_id: &str,
) -> Result<bool, StoreError>
pub fn bundle_skill_is_bound_to( &self, bundle_id: &str, skill_id: &str, ) -> Result<bool, StoreError>
Return true if the bundle has a direct ref binding to this skill
(global excluded). Mirrors skill_is_bound_to — used for
edit/delete-ownership checks, as opposed to
bundle_skill_is_accessible_to’s broader read-access check.
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_read_only(path: &Path) -> Result<Self, StoreError>
pub fn open_read_only(path: &Path) -> Result<Self, StoreError>
Open an existing store read-only: SQLITE_OPEN_READ_ONLY, no
WAL/journal pragma, no schema setup, no version stamp — nothing that
could write. For doctor checks (migrate --verify, muxspect migrations) that need the typed read API against a store they must
not touch, including one on a read-only backup (codex P2 on #3070).
Any read of a table the store predates simply errors. Fails if the
file does not exist — a doctor must never conjure a store.
Sourcepub fn open_in_memory() -> Result<Self, StoreError>
pub fn open_in_memory() -> Result<Self, StoreError>
Open an in-memory Store for testing.
Sourcepub fn origin_id(&self) -> String
pub fn origin_id(&self) -> String
Which store this is, stable for its lifetime: the database file, or — for an in-memory store — its address. Lets a record written into a machine-wide zone say which channel’s store wrote it.
Open the GLOBAL shared store at path (~/.agentmux/shared/store.db).
Uses the same WAL + busy-timeout config as open() but runs
run_shared_store_schema instead of run_object_schema so only
the durable-user-content tables are created (identity, memory, drone
definitions, muxbus creds). Per-channel session tables are intentionally
absent — calling per-channel CRUD methods on this store will error.
Sourcepub fn open_identity_store(path: &Path) -> Result<Self, StoreError>
pub fn open_identity_store(path: &Path) -> Result<Self, StoreError>
Open the permanently-global identity store at path
(~/.agentmux/shared/identity-store.db) — see
docs/specs/SPEC_IDENTITY_STORE_SPLIT_2026_08_17.md.
Same WAL + busy-timeout config as open_shared,
but runs run_identity_store_schema — a strict subset of
open_shared’s tables (everything except
db_accounts) — instead of run_shared_store_schema. Crucially,
this store’s PATH is never gated by isolated_auth_enabled() (see
its resolver, registry::resolve_identity_store_path): unlike
open_shared, there is no isolated/per-channel variant of this
store at all.
Sourcepub fn open_source_readonly(path: &Path) -> Result<Self, StoreError>
pub fn open_source_readonly(path: &Path) -> Result<Self, StoreError>
Open a sibling objects.db file for read-only backfill access.
Does NOT run schema migrations or stamp a version — the source DB is
never modified (spec §3.1). WAL mode is not set either: the file is
opened with SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_NO_MUTEX so concurrent
writers on the same DB are unaffected. Tables absent in older schemas
(e.g. db_drone_definitions) return empty results via the normal
StoreError path; callers use .unwrap_or_default().
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 crates/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 mstore’s
exclusive connection lock. Idempotent — gated by a marker file in
data_dir (skip with None for tests).
Sourcepub fn checkpoint(&self) -> Result<(), StoreError>
pub fn checkpoint(&self) -> Result<(), StoreError>
Run PRAGMA wal_checkpoint(TRUNCATE) to fold the WAL back into the
main DB file. Called periodically and on clean shutdown. The 5s
busy_timeout set at open handles transient reader contention — if it
can’t fully truncate it returns partial progress (safe; picked up on
the next pass).
Sourcepub fn agents_consolidate_looks_incomplete(&self) -> Result<bool, StoreError>
pub fn agents_consolidate_looks_incomplete(&self) -> Result<bool, StoreError>
True when db_agent_definitions/db_agent_instances have rows but
db_agents doesn’t — i.e. the consolidation backfill’s marker/stamp
can’t be trusted as proof it actually ran. See
agents_consolidate::consolidate_looks_incomplete.
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.
Sourcepub fn set_global_memory_observer(
&self,
observer: Arc<dyn Fn(GlobalMemoryChange) + Send + Sync>,
)
pub fn set_global_memory_observer( &self, observer: Arc<dyn Fn(GlobalMemoryChange) + Send + Sync>, )
Attach the Global Memory record’s observer (see the field).
Sourcepub(super) fn notify_global_memory(&self, change: GlobalMemoryChange)
pub(super) fn notify_global_memory(&self, change: GlobalMemoryChange)
Tell the observer, if any, about a committed change. Callers invoke it once their connection lock is released.
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_wan_identity(&self, wan_identity: Arc<WanIdentityStore>)
pub fn set_wan_identity(&self, wan_identity: Arc<WanIdentityStore>)
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.
Sourcepub fn wan_identity(&self) -> Option<Arc<WanIdentityStore>>
pub fn wan_identity(&self) -> Option<Arc<WanIdentityStore>>
The channel-wide WAN identity store, if attached. None means WAN
signing is off for this process — never fall back to another store.
pub fn set_def_registry(&self, def_registry: Arc<DefinitionStore>)
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.
pub fn migration_is_applied(&self, id: &str) -> bool
pub fn migration_mark_applied( &self, id: &str, scope: &str, duration_ms: u64, ) -> Result<(), StoreError>
pub fn migrations_list_applied(&self) -> Result<Vec<String>, StoreError>
Source§impl Store
impl Store
Sourcepub fn viewer_device_insert(
&self,
device_id: &str,
token_hash: &str,
device_name: &str,
device_key: &str,
created_ms: i64,
) -> Result<(), StoreError>
pub fn viewer_device_insert( &self, device_id: &str, token_hash: &str, device_name: &str, device_key: &str, created_ms: i64, ) -> Result<(), StoreError>
Record a new paired device.
Sourcepub fn viewer_device_list(&self) -> Result<Vec<ViewerDevice>, StoreError>
pub fn viewer_device_list(&self) -> Result<Vec<ViewerDevice>, StoreError>
Every paired device, oldest first.
Sourcepub fn viewer_device_token_hashes(
&self,
) -> Result<Vec<(String, String)>, StoreError>
pub fn viewer_device_token_hashes( &self, ) -> Result<Vec<(String, String)>, StoreError>
(device_id, token_hash) of every paired device, for the bearer check,
which compares each hash in constant time rather than looking one up.
Sourcepub fn viewer_device_touch(
&self,
device_id: &str,
last_seen_ms: i64,
) -> Result<bool, StoreError>
pub fn viewer_device_touch( &self, device_id: &str, last_seen_ms: i64, ) -> Result<bool, StoreError>
Stamp a device’s last request. False when it was revoked meanwhile.
Sourcepub fn viewer_device_delete(&self, device_id: &str) -> Result<bool, StoreError>
pub fn viewer_device_delete(&self, device_id: &str) -> Result<bool, StoreError>
Revoke a device. False when there was no such device.
Sourcepub fn agent_hide_from_devices_get(&self, id: &str) -> Result<bool, StoreError>
pub fn agent_hide_from_devices_get(&self, id: &str) -> Result<bool, StoreError>
Whether agent id (a definition id) is hidden from paired devices.
False for an unknown agent.
Sourcepub fn agent_hide_from_devices_set(
&self,
id: &str,
hidden: bool,
) -> Result<bool, StoreError>
pub fn agent_hide_from_devices_set( &self, id: &str, hidden: bool, ) -> Result<bool, StoreError>
Hide agent id from paired devices, or show it again. False for an
unknown agent. Leaves updated_at alone, like agent_last_runtime_set:
who may watch the agent is not an edit of how it runs.
Source§impl Store
impl Store
pub fn widget_storage_get( &self, widget_id: &str, key: &str, ) -> Result<Option<String>, StoreError>
Sourcepub fn widget_storage_set(
&self,
widget_id: &str,
key: &str,
value: &str,
max_total: usize,
) -> Result<bool, StoreError>
pub fn widget_storage_set( &self, widget_id: &str, key: &str, value: &str, max_total: usize, ) -> Result<bool, StoreError>
Sets key, unless the package’s keys and values would then take more
than max_total bytes; returns whether it was set.
pub fn widget_storage_delete( &self, widget_id: &str, key: &str, ) -> Result<(), StoreError>
Sourcepub fn widget_storage_keys(
&self,
widget_id: &str,
prefix: &str,
) -> Result<Vec<String>, StoreError>
pub fn widget_storage_keys( &self, widget_id: &str, prefix: &str, ) -> Result<Vec<String>, StoreError>
The package’s keys that start with prefix, sorted.
Sourcepub fn widget_storage_purge(&self, widget_id: &str) -> Result<(), StoreError>
pub fn widget_storage_purge(&self, widget_id: &str) -> Result<(), StoreError>
Everything the package stored (when it’s uninstalled).
Source§impl Store
impl Store
Sourcepub fn work_claim_put(
&self,
claim: &WorkClaim,
now_ms: i64,
) -> Result<WorkClaim, StoreError>
pub fn work_claim_put( &self, claim: &WorkClaim, now_ms: i64, ) -> Result<WorkClaim, StoreError>
Record claim, or renew the holder’s existing claim on the same
target: its id and created_at stay, its note and expiry are
replaced. Expired claims are deleted first. Returns the stored claim.
Sourcepub fn work_claims_live(
&self,
now_ms: i64,
) -> Result<Vec<WorkClaim>, StoreError>
pub fn work_claims_live( &self, now_ms: i64, ) -> Result<Vec<WorkClaim>, StoreError>
Every unexpired claim, oldest first.
Source§impl Store
impl Store
Sourcepub fn work_queue_enqueue(&self, item: &WorkItem) -> Result<(), StoreError>
pub fn work_queue_enqueue(&self, item: &WorkItem) -> Result<(), StoreError>
Insert a new open item. id is caller-supplied so an enqueue can be
made idempotent by the caller (re-enqueueing the same id is a conflict,
not a duplicate).
Sourcepub fn work_queue_claim(
&self,
filter: &ClaimFilter,
now_ms: i64,
lease_ms: i64,
) -> Result<Option<WorkItem>, StoreError>
pub fn work_queue_claim( &self, filter: &ClaimFilter, now_ms: i64, lease_ms: i64, ) -> Result<Option<WorkItem>, StoreError>
Atomically claim the highest-priority eligible item, or None when
nothing is claimable.
The entire claim is ONE conditional UPDATE ... RETURNING. That is what
makes concurrent claims safe: SQLite serializes writers, so exactly one
caller can observe a given row in open and transition it. Do NOT
refactor this into a SELECT-then-UPDATE — that reintroduces the
read-modify-write race this shape exists to avoid, and the race is
invisible in single-threaded tests.
now_ms and lease_ms are injected rather than read from the clock
here so tests can drive expiry deterministically.
Sourcepub fn work_queue_heartbeat(
&self,
id: &str,
agent_id: &str,
caller_uid: &str,
attempt: i64,
now_ms: i64,
lease_ms: i64,
) -> Result<Option<HolderMatch>, StoreError>
pub fn work_queue_heartbeat( &self, id: &str, agent_id: &str, caller_uid: &str, attempt: i64, now_ms: i64, lease_ms: i64, ) -> Result<Option<HolderMatch>, StoreError>
Extend a live lease.
attempt is the fence token — the attempts value returned by the
claim this call belongs to. Holder identity alone is NOT sufficient
(Codex P1 on PR #2898): agent_id is stable across claims, so after
expire → reap → the SAME agent reclaims, a delayed call left over
from the FIRST claim still satisfies a holder-and-state predicate and
would silently operate on the SECOND claim. That is a textbook ABA.
attempts increments on every claim, so comparing it fences each claim
instance apart with no extra column.
Sourcepub fn work_queue_complete(
&self,
id: &str,
agent_id: &str,
caller_uid: &str,
attempt: i64,
result: &str,
now_ms: i64,
) -> Result<Option<HolderMatch>, StoreError>
pub fn work_queue_complete( &self, id: &str, agent_id: &str, caller_uid: &str, attempt: i64, result: &str, now_ms: i64, ) -> Result<Option<HolderMatch>, StoreError>
Mark a claimed item finished. Same holder + fence guard as heartbeat — a stale completion from a previous claim must not close out a newer one with an old result.
Sourcepub fn work_queue_release(
&self,
id: &str,
agent_id: &str,
caller_uid: &str,
attempt: i64,
reason: &str,
now_ms: i64,
) -> Result<Option<HolderMatch>, StoreError>
pub fn work_queue_release( &self, id: &str, agent_id: &str, caller_uid: &str, attempt: i64, reason: &str, now_ms: i64, ) -> Result<Option<HolderMatch>, StoreError>
Give a claim back voluntarily.
attempts is NOT decremented — a release still consumed an attempt.
And once those attempts are exhausted the item goes to failed, not
back to open (Codex P2 on PR #2898): reopening unconditionally let a
hot-potato item cycle claim→release→claim forever, because the reaper
only parks rows that are still claimed and a released row isn’t. That
silently defeated the documented attempt bound for exactly the case the
bound exists for.
Fenced by attempt for the same ABA reason as work_queue_heartbeat.
Sourcepub fn work_queue_reap(&self, now_ms: i64) -> Result<(usize, usize), StoreError>
pub fn work_queue_reap(&self, now_ms: i64) -> Result<(usize, usize), StoreError>
Reclaim every item whose lease expired: back to open, or to failed
once attempts >= max_attempts.
Returns (reopened, failed). Safe to call on any interval; it is
idempotent because an already-reaped row is no longer claimed.
Sourcepub fn work_queue_cancel(
&self,
id: &str,
reason: &str,
now_ms: i64,
) -> Result<bool, StoreError>
pub fn work_queue_cancel( &self, id: &str, reason: &str, now_ms: i64, ) -> Result<bool, StoreError>
Explicitly abandon an item regardless of holder (operator action).
pub fn work_queue_get(&self, id: &str) -> Result<Option<WorkItem>, StoreError>
Sourcepub fn work_queue_claimed_by(
&self,
agent_id: &str,
uid: &str,
) -> Result<Vec<WorkItem>, StoreError>
pub fn work_queue_claimed_by( &self, agent_id: &str, uid: &str, ) -> Result<Vec<WorkItem>, StoreError>
List items, newest-updated first. state empty = all states.
Every item currently claimed by this agent — by name, or by UID when
one is given — with no row cap. A superset of the store’s holder rule
(HOLDER_BY_UID); callers narrow it with the exact rule. Used when an
agent quits, to release its claims at once (SPEC_AGENT_SELF_QUIT §4.3):
a capped fleet-wide list could miss them on a busy queue.
pub fn work_queue_list( &self, state: &str, limit: usize, ) -> Result<Vec<WorkItem>, StoreError>
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 UnsafeUnpin 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<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> 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