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}