weavatrix-memory 0.3.1

Event-sourced, bitemporal context compiler for coding agents
Documentation
use super::io::{Reader, Writer};
use crate::{
    AgentId, Confidence, EntityId, Evidence, FactId, MemoryFact, MemoryNode, Result, SessionId,
    Timestamp,
};
use std::collections::BTreeMap;

pub(super) fn write_node(writer: &mut Writer, node: &MemoryNode) -> Result<()> {
    writer.string(node.id.as_str())?;
    writer.string(&node.kind)?;
    writer.string(&node.label)?;
    writer.optional_string(node.repository.as_deref())?;
    writer.optional_string(node.branch.as_deref())?;
    writer.usize(node.attributes.len())?;
    for (key, value) in &node.attributes {
        writer.string(key)?;
        writer.string(value)?;
    }
    Ok(())
}

pub(super) fn read_node(reader: &mut Reader<'_>) -> Result<MemoryNode> {
    let id = EntityId::new(reader.string()?)?;
    let kind = reader.string()?;
    let label = reader.string()?;
    let repository = reader.optional_string()?;
    let branch = reader.optional_string()?;
    let count = reader.count()?;
    let mut attributes = BTreeMap::new();
    for _ in 0..count {
        let key = reader.string()?;
        let value = reader.string()?;
        if attributes.insert(key, value).is_some() {
            return Err(super::io::codec("duplicate node attribute"));
        }
    }
    Ok(MemoryNode {
        id,
        kind,
        label,
        repository,
        branch,
        attributes,
    })
}

pub(super) fn write_fact(writer: &mut Writer, fact: &MemoryFact) -> Result<()> {
    writer.string(fact.id.as_str())?;
    writer.string(fact.source.as_str())?;
    writer.string(&fact.relation)?;
    writer.string(fact.target.as_str())?;
    writer.signed(fact.valid_from.as_unix_micros());
    writer.optional_signed(fact.valid_until.map(Timestamp::as_unix_micros));
    writer.signed(fact.observed_at.as_unix_micros());
    writer.signed(fact.recorded_at.as_unix_micros());
    writer.string(fact.agent_id.as_str())?;
    writer.string(fact.session_id.as_str())?;
    writer.varint(u64::from(fact.confidence.basis_points()));
    writer.usize(fact.evidence.len())?;
    for evidence in &fact.evidence {
        write_evidence(writer, evidence)?;
    }
    writer.bool(fact.supersedes.is_some());
    if let Some(id) = &fact.supersedes {
        writer.string(id.as_str())?;
    }
    Ok(())
}

pub(super) fn read_fact(reader: &mut Reader<'_>) -> Result<MemoryFact> {
    let id = FactId::new(reader.string()?)?;
    let source = EntityId::new(reader.string()?)?;
    let relation = reader.string()?;
    let target = EntityId::new(reader.string()?)?;
    let valid_from = Timestamp::from_unix_micros(reader.signed()?);
    let valid_until = reader.optional_signed()?.map(Timestamp::from_unix_micros);
    let observed_at = Timestamp::from_unix_micros(reader.signed()?);
    let recorded_at = Timestamp::from_unix_micros(reader.signed()?);
    let agent_id = AgentId::new(reader.string()?)?;
    let session_id = SessionId::new(reader.string()?)?;
    let confidence =
        u16::try_from(reader.varint()?).map_err(|_| super::io::codec("confidence exceeds u16"))?;
    let confidence = Confidence::from_basis_points(confidence)?;
    let count = reader.count()?;
    let mut evidence = Vec::with_capacity(count);
    for _ in 0..count {
        evidence.push(read_evidence(reader)?);
    }
    let supersedes = reader
        .bool()?
        .then(|| reader.string().and_then(FactId::new))
        .transpose()?;
    Ok(MemoryFact {
        id,
        source,
        relation,
        target,
        valid_from,
        valid_until,
        observed_at,
        recorded_at,
        agent_id,
        session_id,
        confidence,
        evidence,
        supersedes,
    })
}

fn write_evidence(writer: &mut Writer, evidence: &Evidence) -> Result<()> {
    writer.string(&evidence.kind)?;
    writer.string(&evidence.source)?;
    writer.optional_string(evidence.locator.as_deref())?;
    writer.optional_string(evidence.digest.as_deref())
}

fn read_evidence(reader: &mut Reader<'_>) -> Result<Evidence> {
    Ok(Evidence {
        kind: reader.string()?,
        source: reader.string()?,
        locator: reader.optional_string()?,
        digest: reader.optional_string()?,
    })
}