lean-ctx 3.1.3

Context Runtime for AI Agents with CCP. 42 MCP tools, 10 read modes, 90+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing + diaries, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24 AI tools. Reduces LLM token consumption by up to 99%.
Documentation
use axum::extract::State;
use axum::http::HeaderMap;
use axum::http::StatusCode;
use axum::Json;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::auth::{auth_user, AppState};
use super::helpers::internal_error;

#[derive(Deserialize)]
pub struct KnowledgeEnvelope {
    pub entries: Vec<IncomingEntry>,
}

#[derive(Deserialize)]
pub struct IncomingEntry {
    pub category: String,
    pub key: String,
    pub value: String,
    #[allow(dead_code)]
    pub updated_by: Option<String>,
    #[allow(dead_code)]
    pub updated_at: Option<String>,
}

#[derive(Serialize)]
pub struct OutEntry {
    pub category: String,
    pub key: String,
    pub value: String,
    pub updated_by: String,
    pub updated_at: String,
}

pub async fn post_knowledge(
    State(state): State<AppState>,
    headers: HeaderMap,
    Json(env): Json<KnowledgeEnvelope>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
    let (user_id, email) = auth_user(&state, &headers).await?;
    let mut synced = 0i64;
    for e in env.entries {
        if e.key.trim().is_empty() {
            continue;
        }
        upsert(&state, user_id, &e.category, &e.key, &e.value).await?;
        synced += 1;
    }
    Ok(Json(
        serde_json::json!({ "synced": synced, "updated_by": email }),
    ))
}

pub async fn get_knowledge(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Json<Vec<OutEntry>>, (StatusCode, String)> {
    let (user_id, email) = auth_user(&state, &headers).await?;
    let client = state.pool.get().await.map_err(internal_error)?;
    let rows = client
        .query(
            "SELECT category, key, value, updated_at FROM knowledge_entries WHERE user_id=$1 ORDER BY updated_at DESC",
            &[&user_id],
        )
        .await
        .map_err(internal_error)?;

    let mut out = Vec::with_capacity(rows.len());
    for r in rows {
        let updated_at: DateTime<Utc> = r.get(3);
        out.push(OutEntry {
            category: r.get(0),
            key: r.get(1),
            value: r.get(2),
            updated_by: email.clone(),
            updated_at: updated_at.to_rfc3339(),
        });
    }
    Ok(Json(out))
}

async fn upsert(
    state: &AppState,
    user_id: Uuid,
    category: &str,
    key: &str,
    value: &str,
) -> Result<(), (StatusCode, String)> {
    let client = state.pool.get().await.map_err(internal_error)?;
    client
        .execute(
            r#"
INSERT INTO knowledge_entries (user_id, category, key, value, updated_at)
VALUES ($1,$2,$3,$4, NOW())
ON CONFLICT (user_id, category, key)
DO UPDATE SET value=EXCLUDED.value, updated_at=NOW()
"#,
            &[&user_id, &category, &key, &value],
        )
        .await
        .map_err(internal_error)?;
    Ok(())
}