Skip to main content

heartbit_core/config/
memory.rs

1use serde::Deserialize;
2
3/// Memory configuration for the orchestrator.
4#[derive(Debug, Deserialize)]
5#[serde(tag = "type", rename_all = "snake_case")]
6pub enum MemoryConfig {
7    /// In-memory store (for development/testing).
8    InMemory,
9    /// PostgreSQL-backed store.
10    Postgres {
11        /// `postgres://…` connection string.
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 {
82        /// Filesystem path to ingest.
83        path: String,
84    },
85    /// A glob pattern matching multiple files.
86    Glob {
87        /// Glob pattern (e.g. `docs/**/*.md`).
88        pattern: String,
89    },
90    /// A URL to fetch and index.
91    Url {
92        /// HTTP(S) URL whose body is fetched and indexed.
93        url: String,
94    },
95}
96
97/// Workspace configuration for agent home directories.
98///
99/// When configured, each agent gets a persistent home directory where it
100/// can freely create and organize files. File tools resolve relative paths
101/// against this directory.
102#[derive(Debug, Deserialize)]
103pub struct WorkspaceConfig {
104    /// Workspace root directory. All agents share this single directory.
105    /// Defaults to `~/.heartbit/workspaces` if not specified.
106    #[serde(default = "default_workspace_root")]
107    pub root: String,
108    /// Environment variable allowlist for bash subprocesses.
109    /// Empty = use `DAEMON_ENV_ALLOWLIST` defaults.
110    #[serde(default)]
111    pub env_allowlist: Vec<String>,
112    /// Additional file path patterns to deny (e.g., `*.pem`, `secrets/`).
113    #[serde(default)]
114    pub protected_paths: Vec<String>,
115    /// Enable Landlock filesystem sandbox (Linux only, requires `sandbox` feature).
116    #[serde(default)]
117    pub sandbox: bool,
118    /// Additional paths the sandbox should allow reading (beyond system defaults).
119    #[serde(default)]
120    pub sandbox_read_paths: Vec<String>,
121}
122
123fn default_workspace_root() -> String {
124    let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".into());
125    format!("{home}/.heartbit/workspaces")
126}
127
128/// Restate server connection settings.
129#[derive(Debug, Deserialize)]
130pub struct RestateConfig {
131    /// Restate ingress endpoint (e.g. `http://localhost:8080`).
132    pub endpoint: String,
133}
134
135/// OpenTelemetry configuration.
136#[derive(Debug, Deserialize)]
137pub struct TelemetryConfig {
138    /// OTLP endpoint (e.g., "http://localhost:4317")
139    pub otlp_endpoint: String,
140    /// Service name reported to the collector
141    #[serde(default = "default_service_name")]
142    pub service_name: String,
143    /// Observability verbosity mode: "production", "analysis", or "debug".
144    /// Overridden by `HEARTBIT_OBSERVABILITY` env var.
145    #[serde(default)]
146    pub observability_mode: Option<String>,
147}
148
149fn default_service_name() -> String {
150    "heartbit".into()
151}
152
153/// LSP integration configuration.
154///
155/// When present, language servers are spawned lazily after file-modifying tools
156/// and diagnostics are appended to tool output.
157#[derive(Debug, Deserialize)]
158pub struct LspConfig {
159    /// Whether LSP integration is enabled. Default: `true` when the section is present.
160    #[serde(default = "default_lsp_enabled")]
161    pub enabled: bool,
162}
163
164fn default_lsp_enabled() -> bool {
165    true
166}