areev 0.1.2

Rust SDK for the Areev knowledge database — gRPC and HTTP transports
Documentation
use serde::{Deserialize, Serialize};

/// Grain types supported by Areev.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum GrainType {
    Belief,
    Event,
    State,
    Workflow,
    Action,
    Observation,
    Goal,
    Reasoning,
    Consensus,
    Consent,
}

impl std::fmt::Display for GrainType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            Self::Belief => "belief",
            Self::Event => "event",
            Self::State => "state",
            Self::Workflow => "workflow",
            Self::Action => "action",
            Self::Observation => "observation",
            Self::Goal => "goal",
            Self::Reasoning => "reasoning",
            Self::Consensus => "consensus",
            Self::Consent => "consent",
        };
        f.write_str(s)
    }
}

/// Options for the add operation (import intelligence).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AddOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extract_event_date: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub auto_relate: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extract_memories: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sync: Option<bool>,
}

/// Request to add a grain.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddRequest {
    pub grain_type: GrainType,
    pub fields: serde_json::Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub options: Option<AddOptions>,
}

/// Response from adding a grain.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddResponse {
    pub hash: String,
}

/// Request to recall (query) grains.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RecallRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub query: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub subject: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub relation: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub object: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub namespace: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub grain_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub temporal_expr: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tags: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub deduplicate: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rerank: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub query_expansion: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub explanation: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_score: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub diversity: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub recency_weight: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub entity: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub multi_hop: Option<u32>,
    /// Additional fields not covered above (passed through as-is).
    #[serde(flatten)]
    pub extra: serde_json::Map<String, serde_json::Value>,
}

/// A single search result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchHit {
    pub hash: String,
    pub grain_type: String,
    pub score: f64,
    pub fields: serde_json::Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_namespace: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub explanation: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub relative_time: Option<String>,
}

/// Response from a recall query.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecallResponse {
    pub count: u32,
    pub results: Vec<SearchHit>,
}

/// Request to forget (delete) a grain.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForgetRequest {
    pub hash: String,
}

/// Request to supersede a grain.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupersedeRequest {
    pub old_hash: String,
    pub grain_type: GrainType,
    pub fields: serde_json::Value,
}

/// Response from superseding a grain.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupersedeResponse {
    pub new_hash: String,
}

/// Request to remember natural language text.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RememberRequest {
    /// Natural language text to remember (1-32768 bytes).
    pub text: String,
    /// Sync mode: extract beliefs inline via LLM (default: false = async via Axtion).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sync: Option<bool>,
    /// Keep the source Observation after extraction (default: false = forget source).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keep_source: Option<bool>,
    /// Namespace for the observation and extracted beliefs.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub namespace: Option<String>,
    /// User ID (mandatory per compliance).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_id: Option<String>,
    /// Tags for the observation and extracted beliefs.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tags: Option<Vec<String>>,
    /// Source attribution (e.g., "conversation", "note", "document").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_type: Option<String>,
    /// Explicit timestamp in milliseconds since epoch.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<i64>,
    /// Confidence for LLM-extracted beliefs (default 0.9).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub confidence: Option<f64>,
    /// Extract temporal references from content and auto-populate valid_from.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extract_event_date: Option<bool>,
    /// Auto-detect updates/extends relationships with existing grains.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub auto_relate: Option<bool>,
}

/// Response from remembering natural language text.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RememberResponse {
    /// Content-address hash of the source Observation grain.
    pub source_hash: String,
    /// Whether sync or async extraction was used.
    pub mode: String,
    /// Number of beliefs extracted.
    pub extracted_count: u32,
    /// Whether the source Observation was forgotten after extraction.
    pub source_forgotten: bool,
    /// Hashes of beliefs extracted in sync mode. Empty for async.
    #[serde(default)]
    pub extracted_hashes: Vec<String>,
    /// Warnings from the extraction pipeline.
    #[serde(default)]
    pub warnings: Vec<String>,
    /// Extraction marker status (for async mode).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub marker_status: Option<String>,
}

/// Response from getting a grain by hash.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetResponse {
    pub hash: String,
    pub grain_type: String,
    pub fields: serde_json::Value,
}

/// Health check response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthResponse {
    pub status: String,
    pub version: String,
}

/// Database statistics.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatsResponse {
    pub total_grains: u64,
    pub disk_space_bytes: u64,
    pub store_size: String,
    #[serde(default)]
    pub type_counts: std::collections::HashMap<String, u64>,
}

// -- Harness chat (HPL — Harness Pause Loop) ----------------------------------

/// Terminal or paused state of a Flow-A harness chat turn.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessChatStatus {
    /// Turn finished normally (EndTurn from LLM or iteration cap hit).
    Completed,
    /// Turn paused on one or more `client://` tool calls. The caller must
    /// execute them and POST back to `/chat/resume` with their outputs.
    RequiresAction,
}

/// Request body for `POST /api/memories/{id}/harnesses/{slug}/chat`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HarnessChatRequest {
    /// Stable id for a multi-turn conversation.
    pub conversation_id: String,
    /// The user's message for this turn.
    pub user_message: String,
    /// Optional LLM model override.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,
    /// Optional LLM provider override.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub provider: Option<String>,
}

/// One client-executed tool call the caller must run during a Flow-A pause.
///
/// `tool_call_id` is the LLM's identifier and must be echoed back in the
/// matching [`ChatToolOutput`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingToolCall {
    pub tool_call_id: String,
    pub tool_name: String,
    /// Raw JSON string of arguments as emitted by the LLM.
    pub arguments: String,
}

/// Caller-supplied result for a single `pending_tool_calls` entry.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatToolOutput {
    pub tool_call_id: String,
    /// Structured output (object / array / scalar), validated server-side
    /// against the binding's `output_schema`.
    pub output: serde_json::Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_error: Option<bool>,
}

/// An Axtion tool result that ran inline in the same iteration as pending
/// `client://` calls. Surfaced for observability; the session's internal
/// messages already reflect it.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InlineToolResult {
    pub tool_call_id: String,
    pub tool_name: String,
    pub content: String,
    pub is_error: bool,
}

/// Request body for `POST /api/memories/{id}/harnesses/{slug}/chat/resume`.
///
/// The OpenAI-style "all outputs in one call" rule applies — every
/// `pending_tool_calls` entry from the prior `requires_action` response must
/// be represented exactly once.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResumeRequest {
    /// Opaque session id from the prior `requires_action` response.
    pub session_id: String,
    pub tool_outputs: Vec<ChatToolOutput>,
    /// Forensic-only: caller's claimed tool-exec wallclock (ms).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tenant_timestamp_ms: Option<i64>,
}

/// One tool invocation trace recorded during a harness turn (Flow-A).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallTrace {
    pub iteration: u32,
    pub tool_id: String,
    /// Raw arguments JSON string as sent by the LLM.
    pub arguments: String,
    /// Tool output text, or error message when `is_error` is true.
    pub result: String,
    pub is_error: bool,
}

/// Response from `POST .../chat` or `POST .../chat/resume`.
///
/// Terminal when `status == Completed`, paused when `status == RequiresAction`
/// — in which case `session_id` + `pending_tool_calls` carry the continuation
/// state and must be posted back to `/chat/resume`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HarnessChatResponse {
    /// Terminal vs paused.
    #[serde(default = "default_status_completed")]
    pub status: HarnessChatStatus,
    /// Assistant text (may be empty while the LLM is still calling tools).
    pub text: String,
    pub harness_slug: String,
    pub conversation_id: String,
    pub system_prompt: String,
    pub tool_names: Vec<String>,
    pub blocked_tools: Vec<String>,
    pub tool_calls: Vec<ToolCallTrace>,
    pub iterations: u32,
    pub assemble_params: std::collections::HashMap<String, String>,
    pub input_tokens: u32,
    pub output_tokens: u32,
    pub provider: String,
    pub model: String,
    pub retrieval_ms: u64,
    pub llm_ms: u64,
    pub tools_ms: u64,

    /// Opaque session id — present when `status == RequiresAction`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub session_id: Option<String>,
    /// Client-executed tool calls the caller must run before resuming.
    /// Empty when `status == Completed`.
    #[serde(default)]
    pub pending_tool_calls: Vec<PendingToolCall>,
    /// Axtion tool results executed inline in the same iteration as pending
    /// `client://` calls. Surfaced for debugging; callers typically ignore.
    #[serde(default)]
    pub inline_tool_results: Vec<InlineToolResult>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub user_event_hash: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub assistant_event_hash: Option<String>,
    #[serde(default)]
    pub action_hashes: Vec<String>,
}

fn default_status_completed() -> HarnessChatStatus {
    HarnessChatStatus::Completed
}