remem-ai 0.6.90

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use std::collections::BTreeMap;

use axum::{response::IntoResponse, Json};

use crate::api::types::{CapabilitiesFeatures, CapabilitiesResponse};

pub(in crate::api) async fn handle_capabilities() -> impl IntoResponse {
    let mut endpoints = BTreeMap::from([
        ("health", "/api/v1/health"),
        ("status", "/api/v1/status"),
        ("stats", "/api/v1/stats"),
        ("search", "/api/v1/search"),
        ("search_explain", "/api/v1/search?explain=true"),
        ("memory_list", "/api/v1/memories"),
        ("memory_detail", "/api/v1/memories/{id}"),
        ("save_memory", "/api/v1/memories"),
        ("candidate_rows", "/api/v1/candidates"),
        ("candidate_blocked", "/api/v1/candidates/blocked"),
        ("candidate_review", "/api/v1/candidates/{id}/approve"),
        ("graph", "/api/v1/graph"),
        ("user_recall", "/api/v1/user/recall"),
        ("session_activity", "/api/v1/session-activity"),
        (
            "session_activity_sessions",
            "/api/v1/session-activity/sessions",
        ),
        (
            "session_activity_project",
            "/api/v1/session-activity/project",
        ),
        ("session_activity_stats", "/api/v1/session-stats"),
    ]);
    endpoints.extend(candidate_console_endpoint_bundle(true));
    endpoints.extend(memory_governance_endpoint_bundle(
        MemoryGovernanceFeatureSet {
            archive: true,
            restore: true,
        },
    ));
    endpoints.extend(read_resource_endpoint_bundle(ReadResourceFeatureSet {
        observations: true,
        sessions: true,
        workstreams: true,
        events: true,
        tasks: true,
    }));
    Json(CapabilitiesResponse {
        version: crate::build_info::package_version(),
        schema_version: crate::build_info::binary_schema_version(),
        api_version: 1,
        features: CapabilitiesFeatures {
            health: true,
            status: true,
            stats: true,
            search: true,
            search_explain: true,
            memory_list: true,
            memory_detail: true,
            save_memory: true,
            memory_archive: true,
            memory_restore: true,
            memory_delete: false,
            candidate_rows: true,
            candidate_filters: true,
            candidate_review: true,
            candidate_detail: true,
            candidate_evidence: true,
            candidate_review_safe: true,
            observations: true,
            sessions: true,
            session_activity: true,
            workstreams: true,
            events: true,
            tasks: true,
            graph: true,
            user_recall: true,
            user_recall_usage_policy: true,
        },
        endpoints,
    })
}

#[derive(Clone, Copy, Default)]
struct MemoryGovernanceFeatureSet {
    archive: bool,
    restore: bool,
}

fn memory_governance_endpoint_bundle(
    features: MemoryGovernanceFeatureSet,
) -> BTreeMap<&'static str, &'static str> {
    let mut endpoints = BTreeMap::new();
    if features.archive {
        endpoints.insert("memory_archive", "/api/v1/memories/{id}/archive");
    }
    if features.restore {
        endpoints.insert("memory_restore", "/api/v1/memories/{id}/restore");
    }
    endpoints
}

#[derive(Clone, Copy, Default)]
struct ReadResourceFeatureSet {
    observations: bool,
    sessions: bool,
    workstreams: bool,
    events: bool,
    tasks: bool,
}

fn read_resource_endpoint_bundle(
    features: ReadResourceFeatureSet,
) -> BTreeMap<&'static str, &'static str> {
    let mut endpoints = BTreeMap::new();
    insert_resource_pair(
        &mut endpoints,
        features.observations,
        ("observations_list", "/api/v1/observations"),
        ("observations_detail", "/api/v1/observations/{id}"),
    );
    insert_resource_pair(
        &mut endpoints,
        features.sessions,
        ("sessions_list", "/api/v1/sessions"),
        ("sessions_detail", "/api/v1/sessions/{id}"),
    );
    insert_resource_pair(
        &mut endpoints,
        features.workstreams,
        ("workstreams_list", "/api/v1/workstreams"),
        ("workstreams_detail", "/api/v1/workstreams/{id}"),
    );
    insert_resource_pair(
        &mut endpoints,
        features.events,
        ("events_list", "/api/v1/events"),
        ("events_detail", "/api/v1/events/{id}"),
    );
    insert_resource_pair(
        &mut endpoints,
        features.tasks,
        ("tasks_list", "/api/v1/tasks"),
        ("tasks_detail", "/api/v1/tasks/{id}"),
    );
    endpoints
}

fn insert_resource_pair(
    endpoints: &mut BTreeMap<&'static str, &'static str>,
    enabled: bool,
    list: (&'static str, &'static str),
    detail: (&'static str, &'static str),
) {
    if enabled {
        endpoints.insert(list.0, list.1);
        endpoints.insert(detail.0, detail.1);
    }
}

#[cfg(test)]
fn resource_pair_is_atomic(
    enabled: bool,
    endpoints: &BTreeMap<&'static str, &'static str>,
    list_key: &str,
    detail_key: &str,
) -> bool {
    endpoints.contains_key(list_key) == enabled && endpoints.contains_key(detail_key) == enabled
}

fn candidate_console_endpoint_bundle(enabled: bool) -> BTreeMap<&'static str, &'static str> {
    if !enabled {
        return BTreeMap::new();
    }
    BTreeMap::from([
        ("candidate_detail", "/api/v1/candidates/{id}"),
        ("candidate_evidence", "/api/v1/candidates/{id}"),
        (
            "candidate_review_safe_approve",
            "/api/v1/candidates/{id}/review/approve",
        ),
        (
            "candidate_review_safe_reject",
            "/api/v1/candidates/{id}/review/reject",
        ),
        (
            "candidate_review_safe_edit",
            "/api/v1/candidates/{id}/review/edit",
        ),
    ])
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn staged_candidate_endpoint_bundle_is_atomic() {
        assert!(candidate_console_endpoint_bundle(false).is_empty());
        assert_eq!(
            candidate_console_endpoint_bundle(true),
            BTreeMap::from([
                ("candidate_detail", "/api/v1/candidates/{id}"),
                ("candidate_evidence", "/api/v1/candidates/{id}"),
                (
                    "candidate_review_safe_approve",
                    "/api/v1/candidates/{id}/review/approve",
                ),
                (
                    "candidate_review_safe_reject",
                    "/api/v1/candidates/{id}/review/reject",
                ),
                (
                    "candidate_review_safe_edit",
                    "/api/v1/candidates/{id}/review/edit",
                ),
            ])
        );
    }

    #[test]
    fn staged_memory_governance_endpoints_are_independent_and_delete_is_absent() {
        assert!(
            memory_governance_endpoint_bundle(MemoryGovernanceFeatureSet::default()).is_empty()
        );
        assert_eq!(
            memory_governance_endpoint_bundle(MemoryGovernanceFeatureSet {
                archive: true,
                restore: false,
            }),
            BTreeMap::from([("memory_archive", "/api/v1/memories/{id}/archive")])
        );
        assert_eq!(
            memory_governance_endpoint_bundle(MemoryGovernanceFeatureSet {
                archive: false,
                restore: true,
            }),
            BTreeMap::from([("memory_restore", "/api/v1/memories/{id}/restore")])
        );
        let all = memory_governance_endpoint_bundle(MemoryGovernanceFeatureSet {
            archive: true,
            restore: true,
        });
        assert_eq!(
            all,
            BTreeMap::from([
                ("memory_archive", "/api/v1/memories/{id}/archive"),
                ("memory_restore", "/api/v1/memories/{id}/restore"),
            ])
        );
        assert!(!all.contains_key("memory_delete"));
    }

    #[test]
    fn staged_read_resource_endpoint_bundles_have_exact_atomic_pairs() {
        assert!(read_resource_endpoint_bundle(ReadResourceFeatureSet::default()).is_empty());
        let all_enabled = ReadResourceFeatureSet {
            observations: true,
            sessions: true,
            workstreams: true,
            events: true,
            tasks: true,
        };
        let endpoints = read_resource_endpoint_bundle(all_enabled);
        assert_eq!(
            endpoints,
            BTreeMap::from([
                ("observations_list", "/api/v1/observations"),
                ("observations_detail", "/api/v1/observations/{id}"),
                ("sessions_list", "/api/v1/sessions"),
                ("sessions_detail", "/api/v1/sessions/{id}"),
                ("workstreams_list", "/api/v1/workstreams"),
                ("workstreams_detail", "/api/v1/workstreams/{id}"),
                ("events_list", "/api/v1/events"),
                ("events_detail", "/api/v1/events/{id}"),
                ("tasks_list", "/api/v1/tasks"),
                ("tasks_detail", "/api/v1/tasks/{id}"),
            ])
        );
        for (list, detail) in [
            ("observations_list", "observations_detail"),
            ("sessions_list", "sessions_detail"),
            ("workstreams_list", "workstreams_detail"),
            ("events_list", "events_detail"),
            ("tasks_list", "tasks_detail"),
        ] {
            assert!(resource_pair_is_atomic(true, &endpoints, list, detail));
            let mut partial = endpoints.clone();
            partial.remove(detail);
            assert!(!resource_pair_is_atomic(true, &partial, list, detail));
        }

        for (features, list, detail) in [
            (
                ReadResourceFeatureSet {
                    observations: true,
                    ..Default::default()
                },
                "observations_list",
                "observations_detail",
            ),
            (
                ReadResourceFeatureSet {
                    sessions: true,
                    ..Default::default()
                },
                "sessions_list",
                "sessions_detail",
            ),
            (
                ReadResourceFeatureSet {
                    workstreams: true,
                    ..Default::default()
                },
                "workstreams_list",
                "workstreams_detail",
            ),
            (
                ReadResourceFeatureSet {
                    events: true,
                    ..Default::default()
                },
                "events_list",
                "events_detail",
            ),
            (
                ReadResourceFeatureSet {
                    tasks: true,
                    ..Default::default()
                },
                "tasks_list",
                "tasks_detail",
            ),
        ] {
            let pair = read_resource_endpoint_bundle(features);
            assert_eq!(pair.len(), 2);
            assert!(resource_pair_is_atomic(true, &pair, list, detail));
        }
    }
}