@sesamespace/hivemind 0.8.13 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +2 -1
  2. package/dist/{chunk-MLY4VFOO.js → chunk-BHCDOHSK.js} +3 -3
  3. package/dist/{chunk-PFZO67E2.js → chunk-DPLCEMEC.js} +2 -2
  4. package/dist/{chunk-HTLHMXAL.js → chunk-FBQBBAPZ.js} +2 -2
  5. package/dist/{chunk-NSTTILSN.js → chunk-FK6WYXRM.js} +79 -2
  6. package/dist/chunk-FK6WYXRM.js.map +1 -0
  7. package/dist/{chunk-LJHJGDKY.js → chunk-ICSJNKI6.js} +62 -2
  8. package/dist/chunk-ICSJNKI6.js.map +1 -0
  9. package/dist/{chunk-4Y7A25UG.js → chunk-IXBIAX76.js} +2 -2
  10. package/dist/{chunk-ZM7RK5YV.js → chunk-M3A2WRXM.js} +560 -37
  11. package/dist/chunk-M3A2WRXM.js.map +1 -0
  12. package/dist/commands/fleet.js +3 -3
  13. package/dist/commands/init.js +3 -3
  14. package/dist/commands/start.js +3 -3
  15. package/dist/commands/upgrade.js +1 -1
  16. package/dist/commands/watchdog.js +3 -3
  17. package/dist/dashboard.html +873 -131
  18. package/dist/index.js +2 -2
  19. package/dist/main.js +375 -7
  20. package/dist/main.js.map +1 -1
  21. package/dist/start.js +1 -1
  22. package/install.sh +162 -0
  23. package/package.json +24 -23
  24. package/packages/memory/Cargo.lock +6480 -0
  25. package/packages/memory/Cargo.toml +21 -0
  26. package/packages/memory/src/src/context.rs +179 -0
  27. package/packages/memory/src/src/embeddings.rs +51 -0
  28. package/packages/memory/src/src/main.rs +887 -0
  29. package/packages/memory/src/src/promotion.rs +808 -0
  30. package/packages/memory/src/src/scoring.rs +142 -0
  31. package/packages/memory/src/src/store.rs +460 -0
  32. package/packages/memory/src/src/tasks.rs +321 -0
  33. package/.pnpmrc.json +0 -1
  34. package/AUTO-DEBUG-DESIGN.md +0 -267
  35. package/DASHBOARD-PLAN.md +0 -206
  36. package/MEMORY-ENHANCEMENT-PLAN.md +0 -211
  37. package/TOOL-USE-DESIGN.md +0 -173
  38. package/dist/chunk-LJHJGDKY.js.map +0 -1
  39. package/dist/chunk-NSTTILSN.js.map +0 -1
  40. package/dist/chunk-ZM7RK5YV.js.map +0 -1
  41. package/docs/TOOL-PARITY-PLAN.md +0 -191
  42. package/src/memory/dashboard-integration.ts +0 -295
  43. package/src/memory/index.ts +0 -187
  44. package/src/memory/performance-test.ts +0 -208
  45. package/src/memory/processors/agent-sync.ts +0 -312
  46. package/src/memory/processors/command-learner.ts +0 -298
  47. package/src/memory/processors/memory-api-client.ts +0 -105
  48. package/src/memory/processors/message-flow-integration.ts +0 -168
  49. package/src/memory/processors/research-digester.ts +0 -204
  50. package/test-caitlin-access.md +0 -11
  51. /package/dist/{chunk-MLY4VFOO.js.map → chunk-BHCDOHSK.js.map} +0 -0
  52. /package/dist/{chunk-PFZO67E2.js.map → chunk-DPLCEMEC.js.map} +0 -0
  53. /package/dist/{chunk-HTLHMXAL.js.map → chunk-FBQBBAPZ.js.map} +0 -0
  54. /package/dist/{chunk-4Y7A25UG.js.map → chunk-IXBIAX76.js.map} +0 -0
@@ -0,0 +1,321 @@
1
+ use anyhow::Result;
2
+ use arrow_array::{RecordBatch, RecordBatchIterator, StringArray};
3
+ use arrow_schema::{DataType, Field, Schema};
4
+ use chrono::Utc;
5
+ use futures::stream::TryStreamExt;
6
+ use lancedb::{connection::Connection, query::ExecutableQuery, query::QueryBase, Table};
7
+ use serde::{Deserialize, Serialize};
8
+ use std::sync::Arc;
9
+
10
+ const TASKS_TABLE: &str = "tasks";
11
+
12
+ #[derive(Debug, Serialize, Deserialize, Clone)]
13
+ pub struct TaskRecord {
14
+ pub id: String,
15
+ pub context_name: String,
16
+ pub title: String,
17
+ pub description: String,
18
+ pub status: String,
19
+ pub blocked_by: String, // JSON array stored as string
20
+ pub created_at: String,
21
+ pub updated_at: String,
22
+ }
23
+
24
+ #[derive(Debug, Deserialize)]
25
+ pub struct TaskInput {
26
+ pub context_name: String,
27
+ pub title: String,
28
+ pub description: String,
29
+ #[serde(default = "default_status")]
30
+ pub status: String,
31
+ #[serde(default)]
32
+ pub blocked_by: Vec<String>,
33
+ }
34
+
35
+ fn default_status() -> String {
36
+ "planned".to_string()
37
+ }
38
+
39
+ #[derive(Debug, Deserialize)]
40
+ pub struct TaskUpdate {
41
+ pub status: Option<String>,
42
+ pub title: Option<String>,
43
+ pub description: Option<String>,
44
+ pub blocked_by: Option<Vec<String>>,
45
+ }
46
+
47
+ pub struct TaskStore {
48
+ db: Connection,
49
+ }
50
+
51
+ impl TaskStore {
52
+ pub async fn new(db: Connection) -> Result<Self> {
53
+ let store = Self { db };
54
+ store.ensure_table().await?;
55
+ Ok(store)
56
+ }
57
+
58
+ fn schema() -> Arc<Schema> {
59
+ Arc::new(Schema::new(vec![
60
+ Field::new("id", DataType::Utf8, false),
61
+ Field::new("context_name", DataType::Utf8, false),
62
+ Field::new("title", DataType::Utf8, false),
63
+ Field::new("description", DataType::Utf8, false),
64
+ Field::new("status", DataType::Utf8, false),
65
+ Field::new("blocked_by", DataType::Utf8, false),
66
+ Field::new("created_at", DataType::Utf8, false),
67
+ Field::new("updated_at", DataType::Utf8, false),
68
+ ]))
69
+ }
70
+
71
+ async fn ensure_table(&self) -> Result<()> {
72
+ let names = self.db.table_names().execute().await?;
73
+ if !names.contains(&TASKS_TABLE.to_string()) {
74
+ let schema = Self::schema();
75
+ let batch = RecordBatch::new_empty(schema.clone());
76
+ let batches = RecordBatchIterator::new(vec![Ok(batch)], schema);
77
+ self.db
78
+ .create_table(TASKS_TABLE, Box::new(batches))
79
+ .execute()
80
+ .await?;
81
+ tracing::info!("Created tasks table");
82
+ }
83
+ Ok(())
84
+ }
85
+
86
+ pub async fn create_task(&self, input: TaskInput) -> Result<TaskRecord> {
87
+ let id = uuid::Uuid::new_v4().to_string();
88
+ let now = Utc::now().to_rfc3339();
89
+ let blocked_by_json = serde_json::to_string(&input.blocked_by)?;
90
+
91
+ let task = TaskRecord {
92
+ id: id.clone(),
93
+ context_name: input.context_name.clone(),
94
+ title: input.title.clone(),
95
+ description: input.description.clone(),
96
+ status: input.status,
97
+ blocked_by: blocked_by_json.clone(),
98
+ created_at: now.clone(),
99
+ updated_at: now.clone(),
100
+ };
101
+
102
+ let schema = Self::schema();
103
+ let batch = RecordBatch::try_new(
104
+ schema.clone(),
105
+ vec![
106
+ Arc::new(StringArray::from(vec![task.id.as_str()])),
107
+ Arc::new(StringArray::from(vec![task.context_name.as_str()])),
108
+ Arc::new(StringArray::from(vec![task.title.as_str()])),
109
+ Arc::new(StringArray::from(vec![task.description.as_str()])),
110
+ Arc::new(StringArray::from(vec![task.status.as_str()])),
111
+ Arc::new(StringArray::from(vec![blocked_by_json.as_str()])),
112
+ Arc::new(StringArray::from(vec![task.created_at.as_str()])),
113
+ Arc::new(StringArray::from(vec![task.updated_at.as_str()])),
114
+ ],
115
+ )?;
116
+
117
+ let table = self.db.open_table(TASKS_TABLE).execute().await?;
118
+ let batches = RecordBatchIterator::new(vec![Ok(batch)], schema);
119
+ table.add(Box::new(batches)).execute().await?;
120
+
121
+ tracing::debug!("Created task {} in context {}", task.id, task.context_name);
122
+ Ok(task)
123
+ }
124
+
125
+ pub async fn list_tasks(
126
+ &self,
127
+ context: &str,
128
+ status_filter: Option<&str>,
129
+ ) -> Result<Vec<TaskRecord>> {
130
+ let table = self.db.open_table(TASKS_TABLE).execute().await?;
131
+
132
+ let filter = match status_filter {
133
+ Some(status) => format!("context_name = '{}' AND status = '{}'", context, status),
134
+ None => format!("context_name = '{}'", context),
135
+ };
136
+
137
+ let results = table.query().only_if(filter).execute().await?;
138
+
139
+ let mut tasks = Vec::new();
140
+ let batches: Vec<RecordBatch> = results.try_collect().await?;
141
+
142
+ for batch in &batches {
143
+ let ids = batch
144
+ .column_by_name("id")
145
+ .unwrap()
146
+ .as_any()
147
+ .downcast_ref::<StringArray>()
148
+ .unwrap();
149
+ let ctx_names = batch
150
+ .column_by_name("context_name")
151
+ .unwrap()
152
+ .as_any()
153
+ .downcast_ref::<StringArray>()
154
+ .unwrap();
155
+ let titles = batch
156
+ .column_by_name("title")
157
+ .unwrap()
158
+ .as_any()
159
+ .downcast_ref::<StringArray>()
160
+ .unwrap();
161
+ let descriptions = batch
162
+ .column_by_name("description")
163
+ .unwrap()
164
+ .as_any()
165
+ .downcast_ref::<StringArray>()
166
+ .unwrap();
167
+ let statuses = batch
168
+ .column_by_name("status")
169
+ .unwrap()
170
+ .as_any()
171
+ .downcast_ref::<StringArray>()
172
+ .unwrap();
173
+ let blocked_bys = batch
174
+ .column_by_name("blocked_by")
175
+ .unwrap()
176
+ .as_any()
177
+ .downcast_ref::<StringArray>()
178
+ .unwrap();
179
+ let created_ats = batch
180
+ .column_by_name("created_at")
181
+ .unwrap()
182
+ .as_any()
183
+ .downcast_ref::<StringArray>()
184
+ .unwrap();
185
+ let updated_ats = batch
186
+ .column_by_name("updated_at")
187
+ .unwrap()
188
+ .as_any()
189
+ .downcast_ref::<StringArray>()
190
+ .unwrap();
191
+
192
+ for i in 0..batch.num_rows() {
193
+ tasks.push(TaskRecord {
194
+ id: ids.value(i).to_string(),
195
+ context_name: ctx_names.value(i).to_string(),
196
+ title: titles.value(i).to_string(),
197
+ description: descriptions.value(i).to_string(),
198
+ status: statuses.value(i).to_string(),
199
+ blocked_by: blocked_bys.value(i).to_string(),
200
+ created_at: created_ats.value(i).to_string(),
201
+ updated_at: updated_ats.value(i).to_string(),
202
+ });
203
+ }
204
+ }
205
+
206
+ // Sort by created_at
207
+ tasks.sort_by(|a, b| a.created_at.cmp(&b.created_at));
208
+
209
+ Ok(tasks)
210
+ }
211
+
212
+ pub async fn get_task(&self, id: &str) -> Result<Option<TaskRecord>> {
213
+ let table = self.db.open_table(TASKS_TABLE).execute().await?;
214
+ let results = table
215
+ .query()
216
+ .only_if(format!("id = '{}'", id))
217
+ .execute()
218
+ .await?;
219
+
220
+ let batches: Vec<RecordBatch> = results.try_collect().await?;
221
+ for batch in &batches {
222
+ if batch.num_rows() > 0 {
223
+ let ids = batch.column_by_name("id").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
224
+ let ctx_names = batch.column_by_name("context_name").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
225
+ let titles = batch.column_by_name("title").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
226
+ let descriptions = batch.column_by_name("description").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
227
+ let statuses = batch.column_by_name("status").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
228
+ let blocked_bys = batch.column_by_name("blocked_by").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
229
+ let created_ats = batch.column_by_name("created_at").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
230
+ let updated_ats = batch.column_by_name("updated_at").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
231
+
232
+ return Ok(Some(TaskRecord {
233
+ id: ids.value(0).to_string(),
234
+ context_name: ctx_names.value(0).to_string(),
235
+ title: titles.value(0).to_string(),
236
+ description: descriptions.value(0).to_string(),
237
+ status: statuses.value(0).to_string(),
238
+ blocked_by: blocked_bys.value(0).to_string(),
239
+ created_at: created_ats.value(0).to_string(),
240
+ updated_at: updated_ats.value(0).to_string(),
241
+ }));
242
+ }
243
+ }
244
+
245
+ Ok(None)
246
+ }
247
+
248
+ pub async fn update_task(&self, id: &str, update: TaskUpdate) -> Result<Option<TaskRecord>> {
249
+ let existing = self.get_task(id).await?;
250
+ let Some(mut task) = existing else {
251
+ return Ok(None);
252
+ };
253
+
254
+ // Apply updates
255
+ if let Some(status) = update.status {
256
+ task.status = status;
257
+ }
258
+ if let Some(title) = update.title {
259
+ task.title = title;
260
+ }
261
+ if let Some(description) = update.description {
262
+ task.description = description;
263
+ }
264
+ if let Some(blocked_by) = update.blocked_by {
265
+ task.blocked_by = serde_json::to_string(&blocked_by)?;
266
+ }
267
+ task.updated_at = Utc::now().to_rfc3339();
268
+
269
+ // Delete and reinsert
270
+ let table = self.db.open_table(TASKS_TABLE).execute().await?;
271
+ table.delete(&format!("id = '{}'", id)).await?;
272
+
273
+ let schema = Self::schema();
274
+ let batch = RecordBatch::try_new(
275
+ schema.clone(),
276
+ vec![
277
+ Arc::new(StringArray::from(vec![task.id.as_str()])),
278
+ Arc::new(StringArray::from(vec![task.context_name.as_str()])),
279
+ Arc::new(StringArray::from(vec![task.title.as_str()])),
280
+ Arc::new(StringArray::from(vec![task.description.as_str()])),
281
+ Arc::new(StringArray::from(vec![task.status.as_str()])),
282
+ Arc::new(StringArray::from(vec![task.blocked_by.as_str()])),
283
+ Arc::new(StringArray::from(vec![task.created_at.as_str()])),
284
+ Arc::new(StringArray::from(vec![task.updated_at.as_str()])),
285
+ ],
286
+ )?;
287
+
288
+ let batches = RecordBatchIterator::new(vec![Ok(batch)], schema);
289
+ table.add(Box::new(batches)).execute().await?;
290
+
291
+ Ok(Some(task))
292
+ }
293
+
294
+ /// Get the next available task for a context:
295
+ /// - Status is "planned" (not active/complete/archived)
296
+ /// - Not blocked by any incomplete tasks
297
+ pub async fn get_next_task(&self, context: &str) -> Result<Option<TaskRecord>> {
298
+ let planned = self.list_tasks(context, Some("planned")).await?;
299
+ let all_tasks = self.list_tasks(context, None).await?;
300
+
301
+ // Build a set of complete task IDs
302
+ let complete_ids: std::collections::HashSet<String> = all_tasks
303
+ .iter()
304
+ .filter(|t| t.status == "complete" || t.status == "archived")
305
+ .map(|t| t.id.clone())
306
+ .collect();
307
+
308
+ for task in planned {
309
+ let blocked_by: Vec<String> =
310
+ serde_json::from_str(&task.blocked_by).unwrap_or_default();
311
+
312
+ // Task is available if all blockers are complete
313
+ let is_blocked = blocked_by.iter().any(|b| !complete_ids.contains(b));
314
+ if !is_blocked {
315
+ return Ok(Some(task));
316
+ }
317
+ }
318
+
319
+ Ok(None)
320
+ }
321
+ }
package/.pnpmrc.json DELETED
@@ -1 +0,0 @@
1
- {"onlyBuiltDependencies":["better-sqlite3"]}
@@ -1,267 +0,0 @@
1
- # Auto-Debug Agent — Design Doc
2
-
3
- ## Vision
4
-
5
- A Hivemind background processor that watches agent logs, detects errors, diagnoses root causes, and submits fixes as PRs — creating a self-healing codebase. Any fleet agent (e.g. Caitlin) can run this processor to continuously improve the system while using it.
6
-
7
- ## How It Works
8
-
9
- ```
10
- Logs → Detect → Deduplicate → Diagnose → Fix → PR → Verify
11
- ```
12
-
13
- ### 1. Log Watcher (`log-watcher` processor)
14
-
15
- Tails all Hivemind log files:
16
- - `/tmp/hivemind-agent.log`
17
- - `/tmp/hivemind-error.log`
18
- - `/tmp/hivemind-watchdog.log`
19
- - `/tmp/hivemind-memory.log`
20
- - `/tmp/hivemind-memory-error.log`
21
-
22
- **Detection rules:**
23
- - Stack traces (multiline, starts with `Error:` or `at ...`)
24
- - Uncaught exceptions / unhandled rejections
25
- - Repeated warnings (same message 3+ times in 5 min)
26
- - Process crashes (exit codes ≠ 0)
27
- - Health check failures logged by watchdog
28
- - Memory daemon errors
29
- - Sesame connection failures
30
-
31
- **Output:** `ErrorEvent` objects with:
32
- ```typescript
33
- interface ErrorEvent {
34
- id: string; // hash of normalized stack trace
35
- timestamp: Date;
36
- source: "agent" | "watchdog" | "memory";
37
- level: "error" | "crash" | "repeated-warning";
38
- message: string;
39
- stackTrace?: string;
40
- logFile: string;
41
- lineNumber: number;
42
- occurrences: number; // count within dedup window
43
- context: string[]; // surrounding log lines (±10)
44
- }
45
- ```
46
-
47
- ### 2. Deduplication & Prioritization
48
-
49
- Not every error deserves a PR. The processor maintains a local error registry:
50
-
51
- ```typescript
52
- interface ErrorRegistry {
53
- // key: error id (normalized stack hash)
54
- errors: Map<string, {
55
- firstSeen: Date;
56
- lastSeen: Date;
57
- totalOccurrences: number;
58
- status: "new" | "investigating" | "fix-submitted" | "resolved" | "wont-fix";
59
- prUrl?: string;
60
- severity: number; // 0-10, auto-calculated
61
- }>;
62
- }
63
- ```
64
-
65
- **Severity scoring:**
66
- - Crash/uncaught exception: +5
67
- - Occurs on every startup: +3
68
- - Frequency (>10/hour): +2
69
- - Affects Sesame connectivity: +2
70
- - Memory daemon related: +1
71
- - Already has a PR open: -10 (skip)
72
- - Marked wont-fix: -10 (skip)
73
-
74
- **Threshold:** Only investigate errors with severity ≥ 3.
75
-
76
- ### 3. Diagnosis Engine
77
-
78
- When an error crosses the threshold, the processor:
79
-
80
- 1. **Locate source:** Parse stack trace → map to source files in the repo
81
- 2. **Gather context:** Use `code-indexer` data to understand the file, function, and dependencies
82
- 3. **Check git blame:** Was this recently changed? By whom?
83
- 4. **Search for related:** Query `task-tracker` for related tasks, check if a fix is already in progress
84
- 5. **Build diagnosis prompt:** Assemble all context into a structured prompt for the agent's LLM
85
-
86
- The diagnosis is done by the agent itself (not a separate LLM call) — it feeds into the agent's normal message processing as an internal task.
87
-
88
- ### 4. Fix Generation
89
-
90
- Once diagnosed, the agent:
91
-
92
- 1. Creates a feature branch: `auto-fix/{error-id-short}`
93
- 2. Makes the code change
94
- 3. Runs `tsup` to verify build
95
- 4. Runs tests if available
96
- 5. Commits with a structured message:
97
- ```
98
- fix(auto-debug): {short description}
99
-
100
- Error: {original error message}
101
- Occurrences: {count} over {timespan}
102
- Source: {log file}
103
-
104
- Root cause: {diagnosis}
105
-
106
- Auto-generated by hivemind auto-debug processor
107
- ```
108
- 6. Pushes branch and opens PR
109
-
110
- ### 5. PR Template
111
-
112
- ```markdown
113
- ## 🤖 Auto-Debug Fix
114
-
115
- **Error:** `{error message}`
116
- **Source:** `{file}:{line}`
117
- **Frequency:** {count} occurrences over {timespan}
118
- **Severity:** {score}/10
119
-
120
- ### Diagnosis
121
- {LLM-generated root cause analysis}
122
-
123
- ### Fix
124
- {Description of what changed and why}
125
-
126
- ### Log Sample
127
- ```
128
- {relevant log lines}
129
- ```
130
-
131
- ### Verification
132
- - [ ] Build passes (`tsup`)
133
- - [ ] Error no longer reproduces (monitored for 30 min post-deploy)
134
-
135
- ---
136
- *Auto-generated by hivemind auto-debug processor. Review before merging.*
137
- ```
138
-
139
- ### 6. Post-Fix Monitoring
140
-
141
- After a PR is merged and deployed:
142
- - Watch for the same error ID in logs
143
- - If it doesn't recur within 1 hour → mark as `resolved`
144
- - If it recurs → reopen with additional context, bump severity
145
-
146
- ## Architecture
147
-
148
- ### Processor Class
149
-
150
- ```typescript
151
- class LogWatcher extends BackgroundProcess {
152
- name = "log-watcher";
153
- interval = 10_000; // check every 10s
154
-
155
- private tailPositions: Map<string, number>; // track file offsets
156
- private errorRegistry: ErrorRegistry;
157
- private activeBranches: Set<string>;
158
-
159
- async run(context: ProcessContext): Promise<ProcessResult> {
160
- // 1. Read new log lines since last position
161
- const newLines = this.readNewLines();
162
-
163
- // 2. Detect errors
164
- const errors = this.detectErrors(newLines);
165
-
166
- // 3. Deduplicate and score
167
- const actionable = this.processErrors(errors);
168
-
169
- // 4. For high-severity new errors, queue investigation
170
- for (const error of actionable) {
171
- await this.queueInvestigation(error, context);
172
- }
173
-
174
- return { itemsProcessed: newLines.length, errors: [] };
175
- }
176
- }
177
- ```
178
-
179
- ### Integration Points
180
-
181
- - **code-indexer:** Provides file/function context for diagnosis
182
- - **task-tracker:** Tracks fix progress, prevents duplicate work
183
- - **agent-sync:** In fleet mode, coordinates so multiple agents don't fix the same bug
184
- - **Sesame:** Posts status updates to a designated channel (e.g. `#hivemind-debug`)
185
- - **GitHub:** Opens PRs via `gh` CLI
186
-
187
- ### Fleet Coordination
188
-
189
- When multiple agents run this processor:
190
- 1. Error registry is shared via Sesame vault or a shared memory namespace
191
- 2. Lock mechanism: first agent to claim an error ID owns the investigation
192
- 3. Other agents provide additional log samples if they see the same error
193
- 4. Any agent can review/approve another agent's PR
194
-
195
- ## Configuration
196
-
197
- ```toml
198
- [processors.log-watcher]
199
- enabled = true
200
- interval_ms = 10_000
201
- severity_threshold = 3
202
- log_files = [
203
- "/tmp/hivemind-agent.log",
204
- "/tmp/hivemind-error.log",
205
- "/tmp/hivemind-watchdog.log",
206
- "/tmp/hivemind-memory.log",
207
- ]
208
- auto_pr = true # false = diagnose only, don't submit PRs
209
- repo = "baileydavis2026/hivemind"
210
- branch_prefix = "auto-fix"
211
- notify_channel = "hivemind-debug" # Sesame channel for status updates
212
- max_concurrent_fixes = 2 # don't overwhelm with PRs
213
- cooldown_minutes = 30 # min time between PRs for same error class
214
- ```
215
-
216
- ## Safety & Guardrails
217
-
218
- 1. **Human review required:** PRs are never auto-merged. A human must approve.
219
- 2. **Scope limits:** Auto-debug only touches files in the Hivemind repo, never system files or other projects.
220
- 3. **Rate limiting:** Max 2 concurrent fix branches, 30-min cooldown per error class.
221
- 4. **Severity gating:** Only acts on errors above threshold — ignores transient/cosmetic issues.
222
- 5. **Rollback awareness:** If a fix introduces new errors, the processor detects this and comments on the PR.
223
- 6. **No secrets in PRs:** Log context is sanitized (API keys, tokens stripped) before inclusion in PR descriptions.
224
-
225
- ## Implementation Plan
226
-
227
- ### Phase 1: Log Watcher (detection only)
228
- - Implement `LogWatcher` processor
229
- - Error detection, deduplication, registry
230
- - Sesame channel notifications for new errors
231
- - Dashboard integration (error list view)
232
-
233
- ### Phase 2: Diagnosis
234
- - Stack trace → source mapping
235
- - Integration with code-indexer for context
236
- - LLM-powered root cause analysis
237
- - Diagnosis reports posted to Sesame
238
-
239
- ### Phase 3: Auto-Fix PRs
240
- - Branch creation, code changes, PR submission
241
- - Build verification
242
- - Post-merge monitoring
243
- - Fleet coordination (error locking)
244
-
245
- ### Phase 4: Learning
246
- - Track fix success rate per error pattern
247
- - Build pattern library (common fixes for common errors)
248
- - Skip LLM for known patterns → direct fix
249
- - Feed insights back to MEMORY-ENHANCEMENT-PLAN
250
-
251
- ## Success Metrics
252
-
253
- - **Detection rate:** % of real errors caught vs. total errors in logs
254
- - **False positive rate:** % of investigations that led to wont-fix
255
- - **Fix success rate:** % of merged PRs that resolved the error
256
- - **Time to fix:** From first error occurrence to PR merged
257
- - **Regression rate:** % of fixes that introduced new errors
258
-
259
- ## Why This Works
260
-
261
- The fastest path to a bug-free system is having the system's own users (agents) fix bugs as they encounter them. Every agent running Hivemind becomes a contributor to Hivemind's stability. The more agents deployed, the faster bugs are found and fixed. It's a flywheel:
262
-
263
- ```
264
- More agents → More log coverage → More bugs found → More fixes → More stable → More agents
265
- ```
266
-
267
- This is the software equivalent of an immune system — the codebase develops antibodies to its own failure modes.