M0: compilable skeleton — Kigi 0.1.0 fork surgery

Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.

Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
  kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
  ptyctl, ptyctl-cli, third_party/ unchanged; proto package
  xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
  KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
  (templates re-encrypted)

Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
  trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
  module & dc_log, heap-profile uploader, auth-diagnostics uploader,
  session-analytics halves of feedback; local zero-egress observability
  preserved in new kigi-log crate (unified log, --debug firehose,
  subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
  direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
  relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
  ~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
  kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
  session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
  shell util

Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
  https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
  https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
  Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted

Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
  workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
  all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
  exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
  insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean

Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
  (new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
  fast-worktree); RSS measurement tests serialized via serial_test

Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
  notices sustained; kigi-tools ported-code notices extended; README,
  CONTRIBUTING, SECURITY, AGENTS.md rewritten

Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 changed files with 1353757 additions and 0 deletions
@@ -0,0 +1,648 @@
//! Session lifecycle hooks for the memory system.
//!
//! Provides `on_session_end()` which auto-saves a session summary to memory
//! when a session ends. This runs best-effort — failures are logged but
//! don't prevent shutdown.
//!
//! ## What is saved
//!
//! The current implementation writes a **structured metadata summary** with
//! zero latency and no LLM call:
//! - message counts (user / assistant / tool results)
//! - the first few real user topics from the session (never synthetic prefixes)
//! - session date
//!
//! For richer content capture (decisions, patterns, reasoning)
//! use `/flush`, which is user-initiated and produces an LLM-generated summary.
//!
//! ## Reliability
//!
//! - **Minimum conversation gate:** Skip sessions with < 3 *real* user prompts
//! or < 50 total query bytes (synthetic metadata-only prefixes and
//! auto-continue markers are excluded).
//! - **`save_on_end` config gate:** Skipped when `[memory.session].save_on_end = false`.
//! - **SIGTERM:** Triggered via `SessionCommand::Shutdown` handler
use crate::sampling::ConversationItem;
use crate::session::memory::storage::{MemoryStorage, slugify};
/// Minimum number of *real* user prompts required to save a session summary.
///
/// "Real" excludes synthetic metadata prefixes and auto-continue sentinels —
/// see [`extract_real_user_queries`].
const MIN_USER_MESSAGES: usize = 3;
/// Minimum total byte length of all real user queries required to save.
///
/// Prevents trivial sessions (e.g. "hey" / "ok" / "thanks") from being indexed
/// even when they technically exceed [`MIN_USER_MESSAGES`].
///
/// Uses `str::len()` (byte length) rather than `chars().count()` — for the
/// mostly-ASCII inputs this gate targets, the distinction is immaterial.
const MIN_TOTAL_QUERY_BYTES: usize = 50;
/// Result of the session end hook.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SessionEndResult {
/// Session was too short (< [`MIN_USER_MESSAGES`] real user prompts or
/// < [`MIN_TOTAL_QUERY_BYTES`] total bytes), or `save_on_end` was false.
Skipped,
/// Summary was written to the daily log.
Written(String),
/// Hook failed (logged, not fatal).
Failed(String),
}
/// Run the session end hook — save a structured metadata summary to memory.
///
/// This is called from the `SessionCommand::Shutdown` handler and the
/// channel-closed path. It is best-effort: errors are logged but do not
/// prevent shutdown.
///
/// Generates a metadata summary with zero latency — **no LLM call is made**.
/// The summary includes message counts, real user topics, and session date.
///
/// For rich content capture (decisions, patterns, reasoning), use `/flush`.
///
/// Returns the path written (if any) for logging purposes.
pub fn on_session_end(
storage: &MemoryStorage,
conversation: &[ConversationItem],
session_id: &str,
save_on_end: bool,
) -> SessionEndResult {
// Respect the user's config choice. Callers that have `save_on_end = false`
// should still call this function (to keep the call-site simple), trusting
// that the gate is enforced here.
if !save_on_end {
tracing::debug!("session end: save_on_end=false, skipping memory summary");
return SessionEndResult::Skipped;
}
// Extract real user queries — this excludes the synthetic metadata-only
// prefix (`<user_info>/<git_status>`) that appears as
// the first ConversationItem::User in every session, as well as the
// internal `__auto_continue__` sentinel. Using raw user-item counts
// inflates the gate and pollutes slugs/topics with metadata text.
let real_queries =
crate::session::helpers::session_compact::extract_real_user_queries(conversation);
// Gate: skip sessions with too few real user prompts.
if real_queries.len() < MIN_USER_MESSAGES {
tracing::debug!(
real_count = real_queries.len(),
min = MIN_USER_MESSAGES,
"session too short for memory save, skipping"
);
return SessionEndResult::Skipped;
}
// Gate: skip sessions whose real queries are too brief in aggregate.
let total_bytes: usize = real_queries.iter().map(|q| q.len()).sum();
if total_bytes < MIN_TOTAL_QUERY_BYTES {
tracing::debug!(
total_bytes,
min = MIN_TOTAL_QUERY_BYTES,
"session content too brief for memory save, skipping"
);
return SessionEndResult::Skipped;
}
// Derive the slug from the first *real* query, not the raw first User item
// (which is the synthetic prefix and would produce a meaningless slug).
let first_real_query = real_queries.first().map(String::as_str).unwrap_or("");
let slug = slugify(first_real_query, 30);
let slug = if slug.is_empty() { "session" } else { &slug };
// Generate a lightweight summary from conversation metadata (no LLM).
let summary = generate_metadata_summary(conversation, &real_queries);
// Write to daily session log.
let date = chrono::Utc::now().format("%Y-%m-%d").to_string();
match storage.write_daily_log(&date, slug, session_id, &summary, false) {
Ok(path) => {
tracing::info!(
path = %path.display(),
real_user_messages = real_queries.len(),
"session end: wrote memory summary"
);
SessionEndResult::Written(path.display().to_string())
}
Err(e) => {
tracing::warn!(error = %e, "session end: failed to write memory summary");
SessionEndResult::Failed(e.to_string())
}
}
}
/// Generate a structured session summary from conversation metadata.
///
/// Uses `real_queries` (pre-computed via [`extract_real_user_queries`]) for
/// the user-message count and topics so that synthetic bootstrap messages and
/// auto-continue sentinels are never surfaced in the saved summary.
///
/// This does NOT call an LLM — it extracts structured information directly
/// from the conversation items: message counts, session date, and the first
/// few real user topics. For richer content capture use `/flush`.
pub(crate) fn generate_metadata_summary(
conversation: &[ConversationItem],
real_queries: &[String],
) -> String {
let real_count = real_queries.len();
let assistant_count = conversation
.iter()
.filter(|item| matches!(item, ConversationItem::Assistant(_)))
.count();
let tool_count = conversation
.iter()
.filter(|item| matches!(item, ConversationItem::ToolResult(_)))
.count();
// ── Assemble summary ─────────────────────────────────────────────────────
let mut summary = String::new();
summary.push_str("## Session Summary\n\n");
summary.push_str(&format!(
"- **Messages:** {} user, {} assistant, {} tool results\n",
real_count, assistant_count, tool_count
));
summary.push_str(&format!(
"- **Date:** {}\n\n",
chrono::Utc::now().format("%Y-%m-%d %H:%M UTC")
));
// Topics — first few real queries (never the synthetic prefix text).
// chars().take(100) avoids byte-boundary panics on multi-byte Unicode.
let topics: Vec<String> = real_queries
.iter()
.take(5)
.map(|q| q.chars().take(100).collect::<String>())
.collect();
if !topics.is_empty() {
summary.push_str("## Topics Discussed\n\n");
for (i, topic) in topics.iter().enumerate() {
summary.push_str(&format!("{}. {}\n", i + 1, topic));
}
summary.push('\n');
}
summary
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sampling::conversation::{AssistantItem, ContentPart, ToolResultItem, UserItem};
use tempfile::TempDir;
fn make_user(text: &str) -> ConversationItem {
ConversationItem::User(UserItem {
content: vec![ContentPart::Text { text: text.into() }],
synthetic_reason: None,
..Default::default()
})
}
/// Build a realistic first-turn user message: metadata prefix + user query in tags.
///
/// This matches what `construct_user_message` + `user_query()` produce.
fn make_synthetic_prefix_with_query(query: &str) -> ConversationItem {
make_user(&format!(
"<user_info>\nOS Version: macos\nShell: /bin/bash\n</user_info>\n\
<git_status>\n(no changes)\n</git_status>\n\
<user_query>\n{query}\n</user_query>"
))
}
/// Build a metadata-only prefix (no <user_query> tag) — represents the
/// synthetic bootstrap message on sessions that never received a real prompt.
fn make_metadata_only() -> ConversationItem {
make_user("<user_info>\nOS Version: macos\n</user_info>")
}
fn make_assistant(text: &str) -> ConversationItem {
ConversationItem::Assistant(AssistantItem {
content: text.into(),
tool_calls: vec![],
model_id: None,
model_fingerprint: None,
reasoning_effort: None,
})
}
fn test_storage(tmp: &TempDir) -> MemoryStorage {
let global = tmp.path().join("memory");
let workspace = global.join("test_ws");
MemoryStorage::with_paths(global, workspace)
}
// -----------------------------------------------------------------------
// Existing behaviour tests (updated for new signatures / semantics)
// -----------------------------------------------------------------------
#[test]
fn test_on_session_end_skips_short_sessions() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
// Only 1 real user message — should skip.
let conv = vec![make_user("hello"), make_assistant("hi")];
let result = on_session_end(&storage, &conv, "test-session-id", true);
assert_eq!(result, SessionEndResult::Skipped);
}
#[test]
fn test_on_session_end_skips_brief_sessions() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
// 3 real messages but very short — total bytes < MIN_TOTAL_QUERY_BYTES (50).
// "hi" (2) + "ok" (2) + "bye" (3) = 7 bytes
let conv = vec![
make_user("hi"),
make_assistant("hello"),
make_user("ok"),
make_assistant("sure"),
make_user("bye"),
make_assistant("goodbye"),
];
let result = on_session_end(&storage, &conv, "sess-brief", true);
assert_eq!(
result,
SessionEndResult::Skipped,
"sessions with brief content should be skipped even with enough messages"
);
}
#[test]
fn test_on_session_end_writes_summary() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
let conv = vec![
make_user("help me fix the auth bug"),
make_assistant("sure, let me look at auth.rs"),
make_user("also check the tests"),
make_assistant("found the issue"),
make_user("great, can you fix the login page too"),
make_assistant("on it"),
];
let result = on_session_end(&storage, &conv, "sess12345678", true);
assert!(
matches!(result, SessionEndResult::Written(_)),
"should write summary, got {result:?}"
);
// Verify file was created.
let files = storage.list_memory_files().unwrap();
let session_files: Vec<_> = files
.iter()
.filter(|f| {
f.file_name()
.unwrap()
.to_str()
.unwrap()
.contains("sess1234")
})
.collect();
assert!(!session_files.is_empty(), "session log file should exist");
}
#[test]
fn test_on_session_end_summary_has_structure() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
let conv = vec![
make_user("implement feature X"),
make_assistant("working on it"),
make_user("also add tests for edge cases"),
make_assistant("done"),
make_user("make sure everything compiles cleanly"),
make_assistant("verified"),
ConversationItem::ToolResult(ToolResultItem {
tool_call_id: "tc_1".to_string(),
content: "file written".into(),
images: Vec::new(),
}),
];
on_session_end(&storage, &conv, "sess12345678", true);
let files = storage.list_memory_files().unwrap();
let session_file = files
.iter()
.find(|f| {
f.file_name()
.unwrap()
.to_str()
.unwrap()
.contains("sess1234")
})
.unwrap();
let content = std::fs::read_to_string(session_file).unwrap();
assert!(content.contains("## Session Summary"));
assert!(content.contains("3 user"));
assert!(content.contains("## Topics Discussed"));
assert!(content.contains("implement feature X"));
assert!(content.contains("also add tests for edge cases"));
}
#[test]
fn test_on_session_end_empty_conversation() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
let result = on_session_end(&storage, &[], "test-id", true);
assert_eq!(result, SessionEndResult::Skipped);
}
#[test]
fn test_generate_metadata_summary_format() {
let conv = vec![
make_user("first question"),
make_assistant("answer"),
make_user("second question"),
];
let real_queries = vec!["first question".to_string(), "second question".to_string()];
let summary = generate_metadata_summary(&conv, &real_queries);
assert!(summary.contains("## Session Summary"));
assert!(summary.contains("2 user"));
assert!(summary.contains("1 assistant"));
assert!(summary.contains("first question"));
}
// -----------------------------------------------------------------------
// Real-user-query extraction tests
// -----------------------------------------------------------------------
/// `save_on_end = false` always skips — even for a long conversation.
#[test]
fn test_on_session_end_save_on_end_false_skips() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
let conv = vec![
make_user("task one"),
make_assistant("done"),
make_user("task two"),
make_assistant("done"),
];
let result = on_session_end(&storage, &conv, "sess-disabled", false);
assert_eq!(
result,
SessionEndResult::Skipped,
"save_on_end=false must skip even with enough messages"
);
// No session log file should have been written (the MEMORY.md templates
// created by ensure_initialized are expected and are not session logs).
let files = storage.list_memory_files().unwrap();
let session_logs: Vec<_> = files
.iter()
.filter(|f| f.components().any(|c| c.as_os_str() == "sessions"))
.collect();
assert!(
session_logs.is_empty(),
"no session log should be created when save_on_end=false"
);
}
/// Synthetic metadata-only prefix (no `<user_query>`) is excluded from the
/// real-message count so it cannot push the session over the gate threshold.
#[test]
fn test_synthetic_prefix_alone_does_not_count_as_real_message() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
// The conversation has 2 User items, but the first is metadata-only
// and the second is a real query. Only 1 real prompt → still skipped.
let conv = vec![
make_metadata_only(), // synthetic, no <user_query>
make_assistant("hi"),
make_user("help me with something"), // 1 real prompt
make_assistant("sure"),
];
let result = on_session_end(&storage, &conv, "sess-synth", true);
assert_eq!(
result,
SessionEndResult::Skipped,
"metadata-only prefix must not count toward the real-message gate"
);
}
/// With a real synthetic prefix + two real queries the session IS written,
/// and the slug is derived from the first real query (not the prefix text).
#[test]
fn test_slug_derived_from_real_query_not_prefix() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
// First User item: metadata prefix wrapping the real first query.
// Second User item: a follow-up plain-text query.
let conv = vec![
make_synthetic_prefix_with_query("fix the login bug"),
make_assistant("on it"),
make_user("also add a test for it"),
make_assistant("done"),
make_user("and update the error messages"),
make_assistant("updated"),
];
let result = on_session_end(&storage, &conv, "sess-slug-check", true);
assert!(
matches!(result, SessionEndResult::Written(_)),
"should write, got {result:?}"
);
let files = storage.list_memory_files().unwrap();
// The file name slug should come from "fix the login bug", not from the
// raw metadata prefix text.
assert!(
files
.iter()
.any(|f| f.to_str().unwrap().contains("fix-the-login-bug")),
"slug should be derived from the first real query"
);
}
/// Topics in the summary contain real query text, not the metadata prefix.
#[test]
fn test_topics_contain_real_queries_not_metadata() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
let conv = vec![
make_synthetic_prefix_with_query("implement the auth feature"),
make_assistant("working on it"),
make_user("add integration tests too"),
make_assistant("done"),
make_user("check the error handling as well"),
make_assistant("verified"),
];
on_session_end(&storage, &conv, "sess-topics", true);
let files = storage.list_memory_files().unwrap();
let session_file = files
.iter()
.find(|f| f.to_str().unwrap().contains("implement-the-auth"))
.expect("session file should exist with correct slug");
let content = std::fs::read_to_string(session_file).unwrap();
assert!(
content.contains("implement the auth feature"),
"topics must include the real first query"
);
assert!(
content.contains("add integration tests too"),
"topics must include the second real query"
);
// The raw metadata prefix text must NOT appear as a topic.
assert!(
!content.contains("<user_info>"),
"metadata tag text must not appear in topics"
);
assert!(
!content.contains("OS Version"),
"metadata content must not appear in topics"
);
}
/// The *actual* AUTO_CONTINUE_PROMPT text pushed into the conversation after
/// auto-compaction must not be counted as a real user message, and must not
/// appear in session-end topics.
///
/// This is the key regression test for the correctness fix: we use the
/// real stored text, not just the `"__auto_continue__"` request-id sentinel.
#[test]
fn test_actual_auto_continue_prompt_excluded() {
use crate::session::helpers::session_compact::AUTO_CONTINUE_PROMPT;
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
// Old sessions may contain AUTO_CONTINUE_PROMPT as a User item after
// auto-compaction. Verify it's excluded from real user query counts.
let conv = vec![
make_synthetic_prefix_with_query("implement feature Z"),
make_assistant("done"),
// Simulated auto-compaction: AUTO_CONTINUE_PROMPT is pushed as a User item.
make_user(AUTO_CONTINUE_PROMPT),
make_assistant("continuing..."),
];
// Only 1 real query ("implement feature Z") — should skip (< MIN_USER_MESSAGES).
let result = on_session_end(&storage, &conv, "sess-autocompact", true);
assert_eq!(
result,
SessionEndResult::Skipped,
"AUTO_CONTINUE_PROMPT must not count as a real user message"
);
}
/// Long Unicode user queries are truncated at a character boundary, never a byte boundary.
#[test]
fn test_topics_unicode_truncation_no_panic() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
// Build a query that is > 100 chars when byte-counted but the 100th byte
// falls inside a multi-byte sequence (emoji are 4 bytes each).
let emoji_query = "🦀".repeat(30); // 30 × 4 = 120 bytes, but 30 chars
assert!(emoji_query.len() > 100, "precondition: > 100 bytes");
let conv = vec![
make_user(&emoji_query),
make_assistant("done"),
make_user("second question about the codebase"),
make_assistant("ok"),
make_user("third question about testing"),
make_assistant("yes"),
];
// Must not panic; the summary should be produced successfully.
let result = on_session_end(&storage, &conv, "sess-unicode", true);
assert!(
matches!(result, SessionEndResult::Written(_)),
"should write summary without panic on Unicode query, got {result:?}"
);
}
/// `__auto_continue__` sentinels do not count as real user messages.
#[test]
fn test_auto_continue_sentinel_excluded_from_count() {
let tmp = TempDir::new().unwrap();
let storage = test_storage(&tmp);
storage.ensure_initialized().unwrap();
// Session has only 1 real human query; the other two User items are
// auto-continue sentinels.
let conv = vec![
make_user("<user_query>\n__auto_continue__\n</user_query>"),
make_assistant("continuing"),
make_user("real human question"),
make_assistant("answer"),
make_user("<user_query>\n__auto_continue__\n</user_query>"),
make_assistant("done"),
];
let result = on_session_end(&storage, &conv, "sess-autocont", true);
assert_eq!(
result,
SessionEndResult::Skipped,
"auto-continue sentinels must not count toward the real-message gate"
);
}
// -----------------------------------------------------------------------
// Summary format tests
// -----------------------------------------------------------------------
/// Summary only contains Session Summary and Topics Discussed — no
/// Tools Used or Files Touched sections (those are low-value noise).
#[test]
fn test_generate_metadata_summary_only_session_and_topics() {
let conv = vec![
make_user("hello"),
make_assistant("hi there"),
make_user("how are you"),
make_assistant("great"),
];
let queries = vec!["hello".to_string(), "how are you".to_string()];
let summary = generate_metadata_summary(&conv, &queries);
assert!(summary.contains("## Session Summary"));
assert!(summary.contains("## Topics Discussed"));
assert!(
!summary.contains("## Tools Used"),
"tools section must not appear"
);
assert!(
!summary.contains("## Files Touched"),
"files section must not appear"
);
assert!(
!summary.contains("## Shell Commands"),
"commands section must not appear"
);
}
}
@@ -0,0 +1,18 @@
//! Memory system shim.
//!
//! The memory "core engine" now lives in the standalone `kigi-memory`
//! crate. This module re-exports that crate's public surface under the
//! historical `crate::session::memory::*` paths so the ~30 reverse-dependency
//! call sites in this crate keep compiling unchanged.
//!
//! Only `hooks` stays here: it is session glue (depends on
//! `crate::sampling` and `crate::session::helpers::session_compact`) and is
//! not part of the relocatable core engine.
pub mod hooks;
pub use kigi_memory::{
MemoryBackendImpl, MemoryBackendParams, MemoryIndex, MemoryScope, MemoryStorage, archive,
backend, chunker, dream, dream_lock, embed_missing_chunks, embedding, index, init_sqlite_vec,
mmr, query_expansion, schema, search, storage, text_utils, watcher,
};