anda_brain 0.13.6

🧠 Anda Brain (大脑) — Autonomous Graph Memory for AI Agents
//! The model's bounded host operations for the Cognitive Memory Profile.
//!
//! No identity, evaluator, observer policy or external-tool capability comes
//! from these arguments. Those remain trusted host configuration.
use anda_cognitive_nexus::nexus::DEFAULT_SPACE;
use anda_core::{BoxError, FunctionDefinition, Json, Resource, Tool, ToolOutput};
use anda_engine::{context::BaseCtx, memory::MemoryManagement, unix_ms};
use serde::Deserialize;
use serde_json::json;
use std::sync::{Arc, LazyLock};

pub(crate) const CAPABILITIES: &str = "Anda Brain uses KIP 2.0 / cognitive-memory@2.0.0. \
This connection exposes the existing Brain API, raw KIP and the KIP Memory Interface at the \
memory_basic level only; memory_experience, memory_learning and full CognitiveMemory conformance are not claimed. \
Procedural candidates remain unproven until independently qualified; trial/evaluation scheduling requires explicit host configuration. \
External dispatch requires installed host bindings and the four-way action gate; memory_runtime status reports this connection's configuration. Model plans cannot write learning records, \
Skill learning pointers, computed lineage, LeaseState or WatchState. Nexus computes dependency validity; a stored review never overrides it. \
Text and mixed-selector Watches require a host-installed semantic evaluator; read memory_runtime status.semantic_attention for this connection's configuration and recovery state. Without that binding, semantic evaluation is unavailable. \
Read constraints, task scope, uncertainty and invalid dependencies must remain visible.";

/// The `strength_policy` pin this deployment writes on every `MnemonicState`
/// base: the standard `kip:strength-half-life-30d` artifact bundled with
/// Nexus, named by its policy id and content digest (Profile §6.1, Spec
/// §59.1). The engine computes `effective_strength` only for a pin it can
/// resolve, and reads `null` for any other.
pub(crate) static STRENGTH_POLICY: LazyLock<Json> = LazyLock::new(|| {
    let artifact: Json =
        serde_json::from_str(anda_cognitive_nexus::profiles::STRENGTH_HALF_LIFE_30D)
            .expect("the bundled strength policy parses");
    json!({
        "artifact_ref": artifact["policy_id"],
        "content_digest": artifact["integrity"]["content_digest"],
    })
});

#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct RuntimeArgs {
    pub operation: String,
    pub target_ref: Option<String>,
    pub expected_version: Option<u64>,
    pub content: Option<Json>,
}

#[derive(Clone)]
pub(crate) struct MemoryRuntimeTool {
    memory: Arc<MemoryManagement>,
    attention: Arc<crate::attention::AttentionRuntime>,
    product_control: Option<Arc<crate::product::control::Control>>,
}

impl MemoryRuntimeTool {
    pub const NAME: &'static str = "memory_runtime";

    pub fn new(
        memory: Arc<MemoryManagement>,
        attention: Arc<crate::attention::AttentionRuntime>,
    ) -> Self {
        Self {
            memory,
            attention,
            product_control: None,
        }
    }

    pub(crate) fn with_product_control(
        mut self,
        control: Arc<crate::product::control::Control>,
    ) -> Self {
        self.product_control = Some(control);
        self
    }

    pub async fn execute(&self, args: RuntimeArgs, maintenance: bool) -> Result<Json, BoxError> {
        if args.operation == "syntax" {
            return Ok(json!({"syntax": anda_kip::KIP_SYNTAX}));
        }
        if args.operation == "status" {
            return tokio::time::timeout(
                crate::agents::READONLY_KIP_TIMEOUT,
                self.attention.runtime_status(),
            )
            .await?;
        }
        if args.operation == "content_digest" {
            let content = args.content.ok_or("content is required")?;
            if serde_json::to_vec(&content)?.len() > 65_536 {
                return Err("content exceeds 64 KiB".into());
            }
            return Ok(json!({"content_digest": anda_cognitive_nexus::content_digest(&content)?}));
        }
        if !maintenance {
            return Err("only Maintenance may manage Watch arming and task leases".into());
        }
        let target = args.target_ref.ok_or("target_ref is required")?;
        let expected = args
            .expected_version
            .filter(|v| *v > 0)
            .ok_or("expected_version must be positive")?;
        let nexus = self.memory.nexus();
        let session = nexus.system_session();
        let operation = async {
            match args.operation.as_str() {
                "arm_watch" => self
                    .attention
                    .arm_watch(target.clone(), expected)
                    .await
                    .map_err(|e| match e.downcast::<anda_kip::KipError>() {
                        Ok(e) => *e,
                        Err(e) => anda_kip::KipError::internal_error(e.to_string()),
                    }),
                "lease_task" => {
                    session
                        .lease_task(
                            DEFAULT_SPACE,
                            &target,
                            expected,
                            &crate::kip::timestamp(unix_ms() + 300_000),
                        )
                        .await
                }
                _ => Err(anda_kip::KipError::unsupported_capability(
                    "unknown memory runtime operation",
                )),
            }
        };
        Ok(
            tokio::time::timeout(std::time::Duration::from_secs(30), operation)
                .await
                .map_err(|_| {
                    anda_kip::KipError::outcome_unknown(
                        "runtime operation timed out; re-read current state before retrying",
                    )
                })??,
        )
    }
}

static DEFINITION: LazyLock<FunctionDefinition> = LazyLock::new(|| {
    serde_json::from_value(json!({
    "name": MemoryRuntimeTool::NAME,
    "description": "Read runtime configuration/status (the full KIP syntax is already in your system context), compute a kip-jcs-safe-v1 SHA-256 content digest, or (Maintenance only) arm a Watch / acquire or renew a five-minute SleepTask lease. Create Watch as disarmed and SleepTask as pending first. Mutations require the exact target and its current _system.version. Re-read after every mutation; never infer the new version. Arming starts a new observation generation, so review gaps before re-arming. External work requires separately installed host callbacks and the fenced action gate. This tool cannot install or invoke an executor or grant authority.",
    "parameters": {"type":"object", "additionalProperties":false,
        "properties": {
            "operation":{"type":"string","enum":["syntax","status","content_digest","arm_watch","lease_task"]},
            "target_ref":{"type":["string","null"]},
            "expected_version":{"type":["integer","null"]},
            "content":{"description":"Canonical JSON to digest; for a SkillRevision, all attributes except behavior_digest. Null for other operations."}
        },
        "required":["operation","target_ref","expected_version","content"]
    }
})).unwrap()
});

impl Tool<BaseCtx> for MemoryRuntimeTool {
    type Args = RuntimeArgs;
    type Output = Json;
    fn name(&self) -> String {
        Self::NAME.into()
    }
    fn description(&self) -> String {
        DEFINITION.description.clone()
    }
    fn definition(&self) -> FunctionDefinition {
        DEFINITION.clone()
    }
    async fn call(
        &self,
        ctx: BaseCtx,
        args: RuntimeArgs,
        _resources: Vec<Resource>,
    ) -> Result<ToolOutput<Json>, BoxError> {
        let _guard = if matches!(args.operation.as_str(), "arm_watch" | "lease_task") {
            if let Some(control) = &self.product_control {
                let guard = control.gate.read().await;
                control.check(&ctx)?;
                Some(guard)
            } else {
                None
            }
        } else {
            None
        };
        let result = self
            .execute(args, ctx.agent == crate::agents::MaintenanceAgent::NAME)
            .await?;
        Ok(ToolOutput::new(result))
    }
}