Skip to main content

heartbit_core/config/
memory.rs

1#![allow(missing_docs)]
2use serde::Deserialize;
3
4/// Memory configuration for the orchestrator.
5#[derive(Debug, Deserialize)]
6#[serde(tag = "type", rename_all = "snake_case")]
7pub enum MemoryConfig {
8    /// In-memory store (for development/testing).
9    InMemory,
10    /// PostgreSQL-backed store.
11    Postgres {
12        database_url: String,
13        /// Optional embedding configuration for hybrid retrieval.
14        #[serde(default)]
15        embedding: Option<EmbeddingConfig>,
16    },
17}
18
19/// Configuration for embedding generation (optional).
20///
21/// When configured under `[memory.embedding]`, enables hybrid retrieval
22/// (BM25 + vector cosine) for improved recall quality.
23#[derive(Debug, Clone, Deserialize)]
24pub struct EmbeddingConfig {
25    /// Provider name: "openai", "local", or "none" (default).
26    #[serde(default = "default_embedding_provider")]
27    pub provider: String,
28    /// Model name for the embedding API.
29    #[serde(default = "default_embedding_model")]
30    pub model: String,
31    /// Environment variable name containing the API key.
32    #[serde(default = "default_embedding_api_key_env")]
33    pub api_key_env: String,
34    /// Base URL for the embedding API (optional, defaults to OpenAI).
35    pub base_url: Option<String>,
36    /// Embedding vector dimension (auto-detected from model if omitted).
37    pub dimension: Option<usize>,
38    /// Model cache directory for local embedding provider (optional).
39    pub cache_dir: Option<String>,
40}
41
42fn default_embedding_provider() -> String {
43    "none".into()
44}
45
46fn default_embedding_model() -> String {
47    "text-embedding-3-small".into()
48}
49
50fn default_embedding_api_key_env() -> String {
51    "OPENAI_API_KEY".into()
52}
53
54/// Knowledge base configuration for document retrieval.
55#[derive(Debug, Deserialize)]
56pub struct KnowledgeConfig {
57    /// Maximum byte length per chunk.
58    #[serde(default = "default_chunk_size")]
59    pub chunk_size: usize,
60    /// Number of overlapping bytes between consecutive chunks.
61    #[serde(default = "default_chunk_overlap")]
62    pub chunk_overlap: usize,
63    /// Document sources to index.
64    #[serde(default)]
65    pub sources: Vec<KnowledgeSourceConfig>,
66}
67
68fn default_chunk_size() -> usize {
69    1000
70}
71
72fn default_chunk_overlap() -> usize {
73    200
74}
75
76/// A single knowledge source to index.
77#[derive(Debug, Deserialize)]
78#[serde(tag = "type", rename_all = "snake_case")]
79pub enum KnowledgeSourceConfig {
80    /// A single file path.
81    File { path: String },
82    /// A glob pattern matching multiple files.
83    Glob { pattern: String },
84    /// A URL to fetch and index.
85    Url { url: String },
86}
87
88/// Workspace configuration for agent home directories.
89///
90/// When configured, each agent gets a persistent home directory where it
91/// can freely create and organize files. File tools resolve relative paths
92/// against this directory.
93#[derive(Debug, Deserialize)]
94pub struct WorkspaceConfig {
95    /// Workspace root directory. All agents share this single directory.
96    /// Defaults to `~/.heartbit/workspaces` if not specified.
97    #[serde(default = "default_workspace_root")]
98    pub root: String,
99    /// Environment variable allowlist for bash subprocesses.
100    /// Empty = use `DAEMON_ENV_ALLOWLIST` defaults.
101    #[serde(default)]
102    pub env_allowlist: Vec<String>,
103    /// Additional file path patterns to deny (e.g., `*.pem`, `secrets/`).
104    #[serde(default)]
105    pub protected_paths: Vec<String>,
106    /// Enable Landlock filesystem sandbox (Linux only, requires `sandbox` feature).
107    #[serde(default)]
108    pub sandbox: bool,
109    /// Additional paths the sandbox should allow reading (beyond system defaults).
110    #[serde(default)]
111    pub sandbox_read_paths: Vec<String>,
112}
113
114fn default_workspace_root() -> String {
115    let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".into());
116    format!("{home}/.heartbit/workspaces")
117}
118
119/// Restate server connection settings.
120#[derive(Debug, Deserialize)]
121pub struct RestateConfig {
122    pub endpoint: String,
123}
124
125/// OpenTelemetry configuration.
126#[derive(Debug, Deserialize)]
127pub struct TelemetryConfig {
128    /// OTLP endpoint (e.g., "http://localhost:4317")
129    pub otlp_endpoint: String,
130    /// Service name reported to the collector
131    #[serde(default = "default_service_name")]
132    pub service_name: String,
133    /// Observability verbosity mode: "production", "analysis", or "debug".
134    /// Overridden by `HEARTBIT_OBSERVABILITY` env var.
135    #[serde(default)]
136    pub observability_mode: Option<String>,
137}
138
139fn default_service_name() -> String {
140    "heartbit".into()
141}
142
143/// LSP integration configuration.
144///
145/// When present, language servers are spawned lazily after file-modifying tools
146/// and diagnostics are appended to tool output.
147#[derive(Debug, Deserialize)]
148pub struct LspConfig {
149    /// Whether LSP integration is enabled. Default: `true` when the section is present.
150    #[serde(default = "default_lsp_enabled")]
151    pub enabled: bool,
152}
153
154fn default_lsp_enabled() -> bool {
155    true
156}