Skip to main content

WriteCommand

Enum WriteCommand 

Source
pub enum WriteCommand {
Show 14 variants Remember { episode: Episode, embedding: Embedding, audit_principal: Option<String>, reply: Sender<Result<MemoryId>>, }, RememberBatch { items: Vec<(Episode, Embedding)>, audit_principal: Option<String>, reply: Sender<Result<Vec<MemoryId>>>, }, Forget { memory_id: MemoryId, reason: String, audit_principal: Option<String>, reply: Sender<Result<()>>, }, Update { memory_id: MemoryId, content: String, embedding: Embedding, audit_principal: Option<String>, reply: Sender<Result<MemoryUpdateReport>>, }, IngestDocument { path: PathBuf, chunk_config: ChunkConfig, audit_principal: Option<String>, reply: Sender<Result<IngestReport>>, }, ForgetDocument { doc_id: DocumentId, audit_principal: Option<String>, reply: Sender<Result<ForgetDocumentReport>>, }, Consolidate { scope: ConsolidationScope, audit_principal: Option<String>, reply: Sender<Result<ConsolidationReport>>, }, Reembed { scope: ReembedScope, audit_principal: Option<String>, reply: Sender<Result<ReembedReport>>, }, SaveSnapshot { reply: Sender<Result<()>>, }, Backup { dest_path: PathBuf, reply: Sender<Result<()>>, }, NormalizeSubjects { aliases: Vec<(String, String)>, dry_run: bool, audit_principal: Option<String>, reply: Sender<Result<NormalizeReport>>, }, EmitLlmSamplingAudit { event: AuditEvent, reply: Sender<Result<()>>, }, AttachAbstractionBatch { items: Vec<(MemoryId, SemanticAbstraction)>, episode_count: usize, duration_ms: u64, clusters_deferred: usize, audit_principal: Option<String>, reply: Sender<Result<AttachAbstractionBatchReport>>, }, ResolveContradiction { a_id: String, b_id: String, kind: String, status: String, resolution_note: Option<String>, winning_triple_id: Option<String>, audit_principal: Option<String>, reply: Sender<Result<ResolveContradictionReport>>, },
}
Expand description

All write operations go through this enum. Each variant carries a oneshot reply channel.

v0.8.0 P4: every mutating variant also carries audit_principal: Option<String> — the authenticated principal’s subject. Threaded from the auth middleware (HTTP / MCP) through to the writer-actor, where the synchronous audit emit records “who did this”. None covers CLI / no-auth / system-initiated paths.

Variants§

§

Remember

Fields

§episode: Episode
§embedding: Embedding
§audit_principal: Option<String>
§

RememberBatch

v0.9.2: atomically insert N episodes in one BEGIN IMMEDIATE tx. Used by agentic clients (solo-jarvis) that write back a full turn — user message + assistant response + tool outputs — as one transactional unit so a session crash can never leave a half- persisted turn.

Same outbox-via-pending_index discipline as single Remember: BEGIN IMMEDIATE → INSERTs (episodes + embeddings + pending_index per item) → ONE batch-level audit row inside the tx → COMMIT → hnsw.add per item → DELETE pending_index rows. If an hnsw.add crashes mid-batch the SQL state is already committed and the un-drained outbox rows replay on next startup.

Item count capped at MAX_REMEMBER_BATCH_SIZE; over-cap requests are rejected before BEGIN with Error::InvalidInput.

Reply is Vec<MemoryId> in input order — caller pairs them with their input items by position.

Fields

§audit_principal: Option<String>
§

Forget

Fields

§memory_id: MemoryId
§reason: String
§audit_principal: Option<String>
§reply: Sender<Result<()>>
§

Update

Fields

§memory_id: MemoryId
§content: String
§embedding: Embedding
§audit_principal: Option<String>
§

IngestDocument

Ingest a document from path into the documents / document_chunks tables, embedding each chunk via the writer’s configured Embedder. Same outbox-via-pending_index discipline as Remember: BEGIN IMMEDIATE → INSERT documents → INSERT document_chunks → INSERT pending_index (kind=‘chunk’) → COMMIT → hnsw.add per chunk → DELETE pending_index rows. Content-hash dedup short-circuits re-ingest of the same normalized text.

Available only when the writer was spawned with an active embedder (the spawn_full_with_embedder* variants). Other spawn paths get a clear “not configured” error — same pattern as Reembed.

Fields

§path: PathBuf
§chunk_config: ChunkConfig
§audit_principal: Option<String>
§

ForgetDocument

Soft-delete a document: set documents.status='forgotten' and tombstone every chunk’s HNSW rowid. Chunks remain in SQL for forensic value; queries that JOIN through documents filter status='active'. Forgotten docs survive content-hash dedup — re-ingesting the same content returns the forgotten doc_id.

Fields

§doc_id: DocumentId
§audit_principal: Option<String>
§

Consolidate

§

Reembed

Fields

§audit_principal: Option<String>
§

SaveSnapshot

Fields

§reply: Sender<Result<()>>
§

Backup

Online encrypted backup of the writer’s source database to dest_path. The destination is created with PRAGMA key bound to the same raw key the writer holds, so the backup file restores under the same passphrase + salt as the source.

Available only when the writer was spawned with a KeyMaterial (the spawn_full_with_key_and_optional_steward variant). Other spawn paths get a clear “not configured” error.

Fields

§dest_path: PathBuf
§reply: Sender<Result<()>>
§

NormalizeSubjects

Backfill: rewrite historical triples.subject_id and triples.object_id values per a caller-supplied alias map. Each (from, to) pair is applied to both the subject and object columns (a name appearing in either position should normalize identically).

Opt-in: read-path alias resolution (v0.5.0 P1) already covers query-time bridging without touching stored rows. This command is for users who want the underlying data to match the canonical identity (e.g., when exporting to a system that won’t honor IdentityConfig.user_aliases). See docs/dev-log/0071-v0.5.x-roadmap.md Priority 10.

Fields

§aliases: Vec<(String, String)>

(from_id, to_id) pairs — e.g. [("alex", "user"), ("bob", "user")]. Each pair is applied as UPDATE triples SET subject_id = to WHERE subject_id = from and the symmetric object update.

§dry_run: bool

When true, run the UPDATEs inside a transaction, count the affected rows, then ROLLBACK instead of committing. The returned report’s row counts reflect what would have been rewritten.

§audit_principal: Option<String>
§

EmitLlmSamplingAudit

v0.9.0 P2: emit a single AuditOperation::LlmSamplingCall row into the per-tenant audit_events table. Used by SamplingLlmClient (in solo-api) on every peer.create_message completion — success or failure.

Routed through the writer-actor so the INSERT lands inside a dedicated sync transaction on the writer’s connection (lesson #30: ACID for the sampling call’s only persisted trace). The reply channel surfaces insert failures to the caller so a missed audit row can NOT be silently swallowed — the caller of SamplingLlmClient::complete() must see the failure because this row is the ONLY record of the call.

Privacy invariant: event.details must NOT contain the raw prompt content. Enforcement lives at the construction site (SamplingLlmClient::audit_event); we surface only metadata (model hint, message count, max_tokens, duration_ms, total prompt char count). The audit test sampling_audit_row_omits_raw_prompt_text pins this.

Fields

§reply: Sender<Result<()>>
§

AttachAbstractionBatch

v0.9.0 P4c: persist a batch of (cluster_id, abstraction) pairs in a single transaction + emit ONE AuditOperation::MemoryTriplesExtract audit row carrying the batch’s aggregate counts.

Sent by the daemon-side consolidate-timer’s triples batch path (see crates/solo-cli/src/commands/daemon.rs:: triples_batch_tick). For each (cluster_id, abstraction):

  • INSERT one semantic_abstractions row.
  • INSERT N triples rows (where N = abstraction.triples.len()).

Then emit ONE audit row per batch, with details_json = {episode_count, cluster_count, abstractions_built, triples_extracted, duration_ms}.

Atomicity (plan §4 P4c / lesson #30): the entire batch runs inside ONE BEGIN IMMEDIATE tx; the audit emit is SYNC inside that same tx. If the audit emit fails, the whole batch aborts — preserving the “audit row IS the only persisted record of the batch” invariant.

Partial-batch tolerance: per-cluster INSERT failures (e.g. FK violation if the cluster_id was dropped between snapshot and persist) are LOGGED and skipped, but the tx stays open. The audit row’s details_json.abstractions_built counter reflects the SUCCESSFUL inserts only. Test: [tests::p4c_attach_abstraction_batch_tests].

Fields

§items: Vec<(MemoryId, SemanticAbstraction)>

(cluster_id, abstraction) pairs to persist. The abstraction’s cluster_id field is REQUIRED to equal the tuple’s MemoryId; the handler asserts this and rejects the whole batch on mismatch.

§episode_count: usize

Number of episodes that flowed into this batch (for the audit row’s details_json.episode_count).

§duration_ms: u64

Wall-time the upstream collection+LLM round-trip took (for the audit row’s details_json.duration_ms). The writer-actor’s own tx duration is small and not included.

§clusters_deferred: usize

v0.10.1 (P4 audit m5): number of clusters that timed out during their per-cluster abstract_cluster call. Surfaces in the audit row’s details_json.clusters_deferred and in the returned AttachAbstractionBatchReport.clusters_deferred. Distinct from clusters_failed (per-cluster INSERT SAVEPOINT rollbacks): a deferred cluster never made it INTO the batch’s items because the LLM call timed out upstream.

§audit_principal: Option<String>

Cached audit principal_subject for the daemon path — usually None since the consolidate timer runs without an explicit principal.

§

ResolveContradiction

Mark a contradiction resolved / unresolved / reopened. Routes through the writer actor (dev-log 0152 finding H1 — restores ADR-0003: the previous code path used the reader pool to UPDATE contradictions, racing with the writer-actor on multiple connections and writing the audit row outside the tx).

Atomicity: UPDATE + audit emit inside one BEGIN IMMEDIATE transaction. If the audit row insert fails, the UPDATE rolls back.

Fields

§a_id: String
§b_id: String
§kind: String
§status: String
§resolution_note: Option<String>
§winning_triple_id: Option<String>
§audit_principal: Option<String>

Trait Implementations§

Source§

impl Debug for WriteCommand

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more