@stackbilt/aegis-core 0.1.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 (148) hide show
  1. package/package.json +96 -0
  2. package/schema.sql +586 -0
  3. package/src/adapters/voice/cloudflare-agent.ts +34 -0
  4. package/src/auth.ts +124 -0
  5. package/src/bluesky.ts +464 -0
  6. package/src/claude-tools/content.ts +188 -0
  7. package/src/claude-tools/email.ts +69 -0
  8. package/src/claude-tools/github.ts +440 -0
  9. package/src/claude-tools/goals.ts +116 -0
  10. package/src/claude-tools/index.ts +353 -0
  11. package/src/claude-tools/web.ts +59 -0
  12. package/src/claude.ts +406 -0
  13. package/src/codebeast.ts +200 -0
  14. package/src/composite.ts +715 -0
  15. package/src/content/column.ts +80 -0
  16. package/src/content/hero-image.ts +47 -0
  17. package/src/content/index.ts +27 -0
  18. package/src/content/journal.ts +91 -0
  19. package/src/content/roundtable.ts +163 -0
  20. package/src/core.ts +309 -0
  21. package/src/dashboard.ts +620 -0
  22. package/src/decision-docs.ts +284 -0
  23. package/src/dispatch.ts +13 -0
  24. package/src/edge-env.ts +58 -0
  25. package/src/email.ts +850 -0
  26. package/src/exports.ts +156 -0
  27. package/src/github-projects.ts +312 -0
  28. package/src/github.ts +670 -0
  29. package/src/groq.ts +247 -0
  30. package/src/health-page.ts +578 -0
  31. package/src/index.ts +89 -0
  32. package/src/kernel/argus-actions.ts +397 -0
  33. package/src/kernel/argus-correlation.ts +639 -0
  34. package/src/kernel/board.ts +91 -0
  35. package/src/kernel/briefing.ts +177 -0
  36. package/src/kernel/classify-memory-topic.ts +166 -0
  37. package/src/kernel/cognition.ts +377 -0
  38. package/src/kernel/court-cards.ts +163 -0
  39. package/src/kernel/dispatch.ts +587 -0
  40. package/src/kernel/domain.ts +50 -0
  41. package/src/kernel/dynamic-tools.ts +322 -0
  42. package/src/kernel/executor-port.ts +45 -0
  43. package/src/kernel/executors/claude.ts +73 -0
  44. package/src/kernel/executors/direct.ts +237 -0
  45. package/src/kernel/executors/groq.ts +18 -0
  46. package/src/kernel/executors/index.ts +87 -0
  47. package/src/kernel/executors/tarotscript.ts +104 -0
  48. package/src/kernel/executors/workers-ai.ts +54 -0
  49. package/src/kernel/insight-cache.ts +76 -0
  50. package/src/kernel/memory/agenda.ts +200 -0
  51. package/src/kernel/memory/blocks.ts +188 -0
  52. package/src/kernel/memory/consolidation.ts +194 -0
  53. package/src/kernel/memory/episodic.ts +241 -0
  54. package/src/kernel/memory/goals.ts +156 -0
  55. package/src/kernel/memory/graph.ts +290 -0
  56. package/src/kernel/memory/index.ts +11 -0
  57. package/src/kernel/memory/insights.ts +316 -0
  58. package/src/kernel/memory/procedural.ts +467 -0
  59. package/src/kernel/memory/pruning.ts +67 -0
  60. package/src/kernel/memory/recall.ts +367 -0
  61. package/src/kernel/memory/semantic.ts +315 -0
  62. package/src/kernel/memory/synthesis.ts +161 -0
  63. package/src/kernel/memory-adapter.ts +369 -0
  64. package/src/kernel/memory-guardrails.ts +76 -0
  65. package/src/kernel/port.ts +23 -0
  66. package/src/kernel/resilience.ts +322 -0
  67. package/src/kernel/router.ts +471 -0
  68. package/src/kernel/scheduled/agent-dispatch.ts +252 -0
  69. package/src/kernel/scheduled/argus-analytics.ts +247 -0
  70. package/src/kernel/scheduled/argus-heartbeat.ts +320 -0
  71. package/src/kernel/scheduled/argus-notify.ts +348 -0
  72. package/src/kernel/scheduled/board-sync.ts +110 -0
  73. package/src/kernel/scheduled/ci-watcher.ts +125 -0
  74. package/src/kernel/scheduled/cognitive-metrics.ts +377 -0
  75. package/src/kernel/scheduled/consolidation.ts +229 -0
  76. package/src/kernel/scheduled/content-drip.ts +47 -0
  77. package/src/kernel/scheduled/content.ts +6 -0
  78. package/src/kernel/scheduled/conversation-facts.ts +204 -0
  79. package/src/kernel/scheduled/cost-report.ts +84 -0
  80. package/src/kernel/scheduled/curiosity.ts +219 -0
  81. package/src/kernel/scheduled/dev-activity.ts +44 -0
  82. package/src/kernel/scheduled/digest.ts +317 -0
  83. package/src/kernel/scheduled/dreaming/agenda-triage.ts +115 -0
  84. package/src/kernel/scheduled/dreaming/facts.ts +239 -0
  85. package/src/kernel/scheduled/dreaming/index.ts +8 -0
  86. package/src/kernel/scheduled/dreaming/llm.ts +33 -0
  87. package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +124 -0
  88. package/src/kernel/scheduled/dreaming/persona.ts +75 -0
  89. package/src/kernel/scheduled/dreaming/symbolic.ts +31 -0
  90. package/src/kernel/scheduled/dreaming/task-proposals.ts +80 -0
  91. package/src/kernel/scheduled/dreaming.ts +66 -0
  92. package/src/kernel/scheduled/entropy.ts +149 -0
  93. package/src/kernel/scheduled/escalation.ts +192 -0
  94. package/src/kernel/scheduled/feed-watcher.ts +206 -0
  95. package/src/kernel/scheduled/goals.ts +214 -0
  96. package/src/kernel/scheduled/governance.ts +41 -0
  97. package/src/kernel/scheduled/heartbeat.ts +220 -0
  98. package/src/kernel/scheduled/inbox-processor.ts +174 -0
  99. package/src/kernel/scheduled/index.ts +245 -0
  100. package/src/kernel/scheduled/issue-proposer.ts +478 -0
  101. package/src/kernel/scheduled/issue-watcher.ts +128 -0
  102. package/src/kernel/scheduled/pr-automerge.ts +213 -0
  103. package/src/kernel/scheduled/product-health.ts +107 -0
  104. package/src/kernel/scheduled/reflection.ts +373 -0
  105. package/src/kernel/scheduled/self-improvement.ts +114 -0
  106. package/src/kernel/scheduled/social-engage.ts +175 -0
  107. package/src/kernel/scheduled/task-audit.ts +60 -0
  108. package/src/kernel/symbolic.ts +156 -0
  109. package/src/kernel/types.ts +145 -0
  110. package/src/landing.ts +1190 -0
  111. package/src/lib/audit-chain/chain.ts +28 -0
  112. package/src/lib/audit-chain/types.ts +12 -0
  113. package/src/lib/observability/errors.ts +55 -0
  114. package/src/markdown.ts +164 -0
  115. package/src/mcp/handlers.ts +647 -0
  116. package/src/mcp/server.ts +184 -0
  117. package/src/mcp/tools.ts +316 -0
  118. package/src/mcp-client.ts +275 -0
  119. package/src/mcp-server.ts +2 -0
  120. package/src/operator/config.example.ts +60 -0
  121. package/src/operator/config.ts +60 -0
  122. package/src/operator/index.ts +46 -0
  123. package/src/operator/persona.example.ts +34 -0
  124. package/src/operator/persona.ts +34 -0
  125. package/src/operator/prompt-builder.ts +190 -0
  126. package/src/operator/types.ts +43 -0
  127. package/src/pulse.ts +1179 -0
  128. package/src/routes/bluesky.ts +116 -0
  129. package/src/routes/cc-tasks.ts +328 -0
  130. package/src/routes/codebeast.ts +1 -0
  131. package/src/routes/content.ts +194 -0
  132. package/src/routes/conversations.ts +25 -0
  133. package/src/routes/dynamic-tools.ts +111 -0
  134. package/src/routes/feedback.ts +192 -0
  135. package/src/routes/health.ts +147 -0
  136. package/src/routes/messages.ts +228 -0
  137. package/src/routes/observability.ts +82 -0
  138. package/src/routes/operator-logs.ts +42 -0
  139. package/src/routes/pages.ts +96 -0
  140. package/src/routes/sessions.ts +54 -0
  141. package/src/sanitize.ts +73 -0
  142. package/src/schema-enums.ts +155 -0
  143. package/src/search.ts +112 -0
  144. package/src/task-intelligence.ts +497 -0
  145. package/src/types.ts +194 -0
  146. package/src/ui.ts +5 -0
  147. package/src/version.ts +3 -0
  148. package/src/workers-ai-chat.ts +333 -0
@@ -0,0 +1,115 @@
1
+ // Phase 2: Agenda Triage — classify agenda items as WORK (→ GitHub issue)
2
+ // or KEEP (stays on operator scratchpad).
3
+
4
+ import type { EdgeEnv } from '../../dispatch.js';
5
+ import { getActiveAgendaItems, resolveAgendaItem } from '../../memory/agenda.js';
6
+ import { createIssue, resolveRepoName } from '../../../github.js';
7
+ import { ensureOnBoard } from '../../board.js';
8
+ import { askWorkersAiOrGroq, parseJsonResponse } from './llm.js';
9
+
10
+ const AGENDA_TRIAGE_SYSTEM = `You classify agenda items as either WORK (should be a GitHub issue) or KEEP (belongs on the operator scratchpad).
11
+
12
+ WORK items are: features to build, bugs to fix, research to do, refactors, documentation to write, code changes, design tasks, prototypes, integrations.
13
+
14
+ KEEP items are: time-sensitive deadlines, waiting-on-external blockers, proposed actions needing human approval, compliance alerts, quick operator reminders, items that need the operator's hands (not code).
15
+
16
+ For each item, return:
17
+ - "verdict": "work" or "keep"
18
+ - "repo": target GitHub repo (only if work) — use "aegis" as default, or infer from context
19
+ - "labels": array of GitHub labels (only if work) — pick from: bug, enhancement, documentation, test, research, refactor
20
+ - "title": cleaned issue title (only if work) — strip prefixes like [PROPOSED ACTION], make concise
21
+ - "body": issue body with context (only if work)
22
+
23
+ Return ONLY valid JSON (no markdown):
24
+ {
25
+ "triage": [
26
+ { "id": 123, "verdict": "keep" },
27
+ { "id": 456, "verdict": "work", "repo": "aegis", "labels": ["enhancement"], "title": "Add foo feature", "body": "Description..." }
28
+ ]
29
+ }`;
30
+
31
+ interface TriageResult {
32
+ triage?: Array<{
33
+ id: number;
34
+ verdict: 'work' | 'keep';
35
+ repo?: string;
36
+ labels?: string[];
37
+ title?: string;
38
+ body?: string;
39
+ }>;
40
+ }
41
+
42
+ export async function triageAgendaToIssues(env: EdgeEnv): Promise<number> {
43
+ if (!env.githubToken || !env.groqApiKey) return 0;
44
+
45
+ const items = await getActiveAgendaItems(env.db);
46
+ if (items.length <= 5) return 0;
47
+
48
+ const now = Date.now();
49
+ const SETTLING_MS = 48 * 60 * 60 * 1000;
50
+ const candidates = items.filter(i => {
51
+ if (i.item.startsWith('[PROPOSED ACTION]') || i.item.startsWith('[PROPOSED TASK]')) return false;
52
+ if (i.context?.includes('Auto-detected by heartbeat')) return false;
53
+ const createdMs = new Date(i.created_at.endsWith('Z') ? i.created_at : i.created_at + 'Z').getTime();
54
+ if (now - createdMs < SETTLING_MS) return false;
55
+ return true;
56
+ });
57
+
58
+ if (candidates.length === 0) return 0;
59
+
60
+ const itemList = candidates.map(i =>
61
+ `ID ${i.id} [${i.priority}]: ${i.item}${i.context ? ` — Context: ${i.context}` : ''}`,
62
+ ).join('\n');
63
+
64
+ let rawResponse: string;
65
+ try {
66
+ rawResponse = await askWorkersAiOrGroq(env, AGENDA_TRIAGE_SYSTEM, itemList, true);
67
+ } catch (err) {
68
+ console.warn('[dreaming:triage] LLM call failed:', err instanceof Error ? err.message : String(err));
69
+ return 0;
70
+ }
71
+
72
+ if (!rawResponse) return 0;
73
+
74
+ const result = parseJsonResponse<TriageResult>(rawResponse);
75
+ if (!result) {
76
+ console.warn('[dreaming:triage] Failed to parse response');
77
+ return 0;
78
+ }
79
+
80
+ let promoted = 0;
81
+ for (const item of result.triage ?? []) {
82
+ if (item.verdict !== 'work' || !item.title || !item.repo) continue;
83
+ if (promoted >= 3) break;
84
+
85
+ const resolvedRepo = resolveRepoName(item.repo);
86
+ const labels = [...(item.labels ?? ['enhancement']), 'aegis'];
87
+
88
+ try {
89
+ const { number, url } = await createIssue(
90
+ env.githubToken, resolvedRepo,
91
+ item.title,
92
+ `${item.body ?? ''}\n\n---\n_Promoted from AEGIS agenda item #${item.id} by dreaming triage._`,
93
+ labels,
94
+ );
95
+ await resolveAgendaItem(env.db, item.id, 'done');
96
+
97
+ const projectIdRow = await env.db.prepare(
98
+ "SELECT received_at FROM web_events WHERE event_id = 'board_project_id'"
99
+ ).first<{ received_at: string }>();
100
+ if (projectIdRow?.received_at) {
101
+ await ensureOnBoard(env.db, env.githubToken, projectIdRow.received_at, resolvedRepo, number, item.title, 'backlog').catch(() => {});
102
+ }
103
+
104
+ promoted++;
105
+ console.log(`[dreaming:triage] Promoted agenda #${item.id} → ${resolvedRepo}#${number}: ${url}`);
106
+ } catch (err) {
107
+ console.warn(`[dreaming:triage] Failed to create issue for agenda #${item.id}:`, err instanceof Error ? err.message : String(err));
108
+ }
109
+ }
110
+
111
+ if (promoted > 0) {
112
+ console.log(`[dreaming:triage] Promoted ${promoted} agenda item(s) to GitHub issues`);
113
+ }
114
+ return promoted;
115
+ }
@@ -0,0 +1,239 @@
1
+ // Phase 1: Fact Extraction — analyze conversation threads + CC sessions,
2
+ // extract durable facts, routing failures, BizOps drift, preferences.
3
+
4
+ import type { EdgeEnv } from '../../dispatch.js';
5
+ import { recordMemory as recordMemoryAdapter } from '../../memory-adapter.js';
6
+ import { validateMemoryWrite } from '../../memory-guardrails.js';
7
+ import { McpClient } from '../../../mcp-client.js';
8
+ import { operatorConfig } from '../../../operator/index.js';
9
+ import { askWorkersAiOrGroq, parseJsonResponse } from './llm.js';
10
+
11
+ // ─── Types ───────────────────────────────────────────────────
12
+
13
+ export interface DreamingResult {
14
+ facts?: Array<{ topic: string; fact: string; confidence: number }>;
15
+ routing_failures?: Array<{ thread_id: string; description: string }>;
16
+ bizops_drift?: Array<{ entity: string; field: string; actual_value: string }>;
17
+ preferences?: Array<{ preference: string; evidence: string }>;
18
+ proposed_tasks?: Array<{ title: string; repo: string; prompt: string; category: string; rationale: string }>;
19
+ proposed_tools?: Array<{ name: string; description: string; prompt_template: string; rationale: string }>;
20
+ }
21
+
22
+ // ─── Prompts ─────────────────────────────────────────────────
23
+
24
+ const DREAMING_SYSTEM = `You are AEGIS's memory consolidation system. You review full conversation threads from the past day and extract durable knowledge.
25
+
26
+ Your job:
27
+ 1. Identify NEW FACTS that should be remembered (business decisions, user preferences, architectural changes, pricing changes, entity updates)
28
+ 2. Identify ROUTING FAILURES where the agent misunderstood the user or gave an irrelevant response
29
+ 3. Identify STALE FACTS that contradict what's in the current BizOps state
30
+ 4. Identify USER PREFERENCES revealed through the conversation (communication style, priorities, workflow patterns)
31
+
32
+ Return ONLY valid JSON (no markdown):
33
+ {
34
+ "facts": [
35
+ { "topic": "category", "fact": "specific durable fact with concrete details", "confidence": 0.8 }
36
+ ],
37
+ "routing_failures": [
38
+ { "thread_id": "id", "description": "what went wrong", "user_intent": "what the user actually wanted", "agent_response": "what the agent did instead" }
39
+ ],
40
+ "bizops_drift": [
41
+ { "entity": "name", "field": "what's stale", "current_value": "what BizOps says", "actual_value": "what the conversation revealed" }
42
+ ],
43
+ "preferences": [
44
+ { "preference": "description of user preference", "evidence": "quote or paraphrase from conversation" }
45
+ ],
46
+ "proposed_tasks": [
47
+ { "title": "short task title", "repo": "aegis", "prompt": "detailed instructions for a Claude Code session to execute this task", "category": "docs|tests|research|bugfix|feature|refactor", "rationale": "why this task is needed based on the conversations" }
48
+ ],
49
+ "proposed_tools": [
50
+ { "name": "snake_case_tool_name", "description": "what this reusable tool does", "prompt_template": "prompt with {{variable}} placeholders", "rationale": "why this recurring pattern deserves a tool" }
51
+ ]
52
+ }
53
+
54
+ Rules:
55
+ - Only extract facts with concrete details (names, numbers, dates, IDs). Skip vague observations.
56
+ - A routing failure is when the user had to redirect or correct the agent.
57
+ - BizOps drift is when conversation reveals information newer than BizOps records.
58
+ - Return empty arrays if nothing notable happened.
59
+ - TOPIC RULES: Use ONLY established topics relevant to your deployment. Do NOT invent new topics. Do NOT use "synthesis_*" or "cross_repo_insights" prefixes. If a fact spans projects, file it under the primary project topic.
60
+ - Do NOT record vague "synthesis" observations. Only record concrete, actionable facts with specific details.
61
+ - proposed_tasks: up to 3 concrete, well-scoped tasks that would improve the system based on what the conversations reveal. Each must have a clear prompt with specific files/changes. Categories: docs (documentation gaps), tests (missing test coverage), research (investigation needed), bugfix (concrete bugs), feature (new functionality), refactor (code quality). Do NOT propose deploy tasks. Only propose tasks with clear evidence from the conversations.
62
+ - proposed_tasks repo should match repos configured in your taskrunner aliases. Use the LOCAL DIRECTORY name, not the GitHub repo name.
63
+ - proposed_tasks MUST reference specific files or modules to change. Do NOT propose vague tasks like "add tests for X project" — name the exact source files to test. BizOps project records are metadata, not codebases — never generate tasks about BizOps project entries themselves.
64
+ - proposed_tools: up to 2 reusable dynamic tools if you notice the operator repeatedly asking for the same kind of analysis/query/summary. Each tool is a prompt template with {{variable}} placeholders that gets executed via LLM. Only propose if there's clear evidence of a recurring pattern. Name must be snake_case, 2-49 chars.`;
65
+
66
+ // ─── Thread Loading ──────────────────────────────────────────
67
+
68
+ export async function fetchConversationThreads(env: EdgeEnv): Promise<string[]> {
69
+ const threads = await env.db.prepare(`
70
+ SELECT DISTINCT conversation_id FROM messages
71
+ WHERE created_at > datetime('now', '-24 hours')
72
+ AND conversation_id IN (
73
+ SELECT conversation_id FROM messages WHERE role = 'user'
74
+ GROUP BY conversation_id HAVING COUNT(*) >= 2
75
+ )
76
+ ORDER BY created_at DESC
77
+ LIMIT 10
78
+ `).all<{ conversation_id: string }>();
79
+
80
+ const threadContents: string[] = [];
81
+ for (const { conversation_id } of threads.results) {
82
+ const messages = await env.db.prepare(
83
+ 'SELECT role, content FROM messages WHERE conversation_id = ? ORDER BY created_at ASC LIMIT 40'
84
+ ).bind(conversation_id).all<{ role: string; content: string }>();
85
+
86
+ if (messages.results.length < 2) continue;
87
+
88
+ const threadText = messages.results.map(m =>
89
+ `${m.role === 'user' ? 'User' : 'AEGIS'}: ${m.content.slice(0, 1500)}`
90
+ ).join('\n');
91
+
92
+ threadContents.push(`--- Thread ${conversation_id} ---\n${threadText}`);
93
+ }
94
+
95
+ // Load Claude Code session digests
96
+ const ccSessions = await env.db.prepare(
97
+ "SELECT id, summary, commits, decisions, repos FROM cc_sessions WHERE created_at > datetime('now', '-24 hours') ORDER BY created_at DESC LIMIT 5"
98
+ ).all<{ id: string; summary: string; commits: string | null; decisions: string | null; repos: string | null }>();
99
+
100
+ for (const session of ccSessions.results) {
101
+ const parts = [`Summary: ${session.summary}`];
102
+ if (session.commits) {
103
+ try {
104
+ const commits = JSON.parse(session.commits) as Array<{ hash?: string; message?: string }>;
105
+ if (commits.length > 0) parts.push(`Commits: ${commits.map(c => c.message ?? c.hash).join('; ')}`);
106
+ } catch { /* skip malformed */ }
107
+ }
108
+ if (session.decisions) {
109
+ try {
110
+ const decisions = JSON.parse(session.decisions) as string[];
111
+ if (decisions.length > 0) parts.push(`Decisions: ${decisions.join('; ')}`);
112
+ } catch { /* skip malformed */ }
113
+ }
114
+ threadContents.push(`--- CC Session ${session.id} ---\n${parts.join('\n')}`);
115
+ }
116
+
117
+ return threadContents;
118
+ }
119
+
120
+ // ─── Context Gathering ───────────────────────────────────────
121
+
122
+ async function fetchBizopsContext(env: EdgeEnv): Promise<string> {
123
+ try {
124
+ const client = new McpClient({
125
+ url: operatorConfig.integrations.bizops.fallbackUrl,
126
+ token: env.bizopsToken,
127
+ prefix: 'bizops',
128
+ fetcher: env.bizopsFetcher,
129
+ rpcPath: '/rpc',
130
+ });
131
+
132
+ const orgs = await client.callTool('list_organizations', {});
133
+ const projects = await client.callTool('list_projects', {});
134
+ return `\n\nCurrent BizOps state (compare against conversations):\nOrganizations: ${typeof orgs === 'string' ? orgs.slice(0, 2000) : JSON.stringify(orgs).slice(0, 2000)}\nProjects: ${typeof projects === 'string' ? projects.slice(0, 2000) : JSON.stringify(projects).slice(0, 2000)}`;
135
+ } catch {
136
+ return '';
137
+ }
138
+ }
139
+
140
+ async function fetchTaskHealthContext(db: D1Database): Promise<string> {
141
+ try {
142
+ const taskStats = await db.prepare(
143
+ `SELECT category, status, COUNT(*) as c FROM cc_tasks t1
144
+ WHERE t1.completed_at > datetime('now', '-7 days')
145
+ AND NOT (
146
+ t1.status = 'failed'
147
+ AND EXISTS (
148
+ SELECT 1 FROM cc_tasks t2
149
+ WHERE t2.status = 'completed'
150
+ AND t2.repo = t1.repo
151
+ AND t2.title LIKE substr(t1.title, 1, 40) || '%'
152
+ AND t2.completed_at > t1.completed_at
153
+ )
154
+ )
155
+ GROUP BY category, status`
156
+ ).all<{ category: string; status: string; c: number }>();
157
+
158
+ if (taskStats.results.length > 0) {
159
+ const byCat: Record<string, { ok: number; total: number }> = {};
160
+ for (const row of taskStats.results) {
161
+ if (!byCat[row.category]) byCat[row.category] = { ok: 0, total: 0 };
162
+ byCat[row.category].total += row.c;
163
+ if (row.status === 'completed') byCat[row.category].ok += row.c;
164
+ }
165
+ const parts = Object.entries(byCat).map(([cat, { ok, total }]) => `${cat} ${ok}/${total} ok`);
166
+ return `\n\nRecent task results (7d): ${parts.join(', ')}`;
167
+ }
168
+ } catch { /* non-fatal */ }
169
+ return '';
170
+ }
171
+
172
+ // ─── Main Fact Extraction ────────────────────────────────────
173
+
174
+ export async function extractFacts(env: EdgeEnv, threadContents: string[]): Promise<DreamingResult | null> {
175
+ const bizopsContext = await fetchBizopsContext(env);
176
+ const taskHealthContext = await fetchTaskHealthContext(env.db);
177
+ const userPrompt = `${threadContents.join('\n\n').slice(0, 25000)}${bizopsContext}${taskHealthContext}`;
178
+
179
+ let rawResponse: string;
180
+ try {
181
+ rawResponse = await askWorkersAiOrGroq(env, DREAMING_SYSTEM, userPrompt);
182
+ } catch (err) {
183
+ console.warn('[dreaming] LLM call failed:', err instanceof Error ? err.message : String(err));
184
+ return null;
185
+ }
186
+
187
+ if (!rawResponse) {
188
+ console.warn('[dreaming] LLM returned empty response');
189
+ return null;
190
+ }
191
+
192
+ return parseJsonResponse<DreamingResult>(rawResponse);
193
+ }
194
+
195
+ // ─── Fact Processing ─────────────────────────────────────────
196
+
197
+ export async function processFacts(env: EdgeEnv, result: DreamingResult): Promise<number> {
198
+ let factsRecorded = 0;
199
+
200
+ for (const fact of (result.facts ?? []).slice(0, 5)) {
201
+ const guard = validateMemoryWrite(fact.topic, fact.fact, { enforceAllowlist: true });
202
+ if (!guard.allowed) {
203
+ console.log(`[dreaming] Blocked: ${guard.reason}`);
204
+ continue;
205
+ }
206
+
207
+ try {
208
+ if (!env.memoryBinding) continue;
209
+ await recordMemoryAdapter(env.memoryBinding, fact.topic, fact.fact, fact.confidence ?? 0.8, 'dreaming_cycle');
210
+ factsRecorded++;
211
+ console.log(`[dreaming] Fact: [${fact.topic}] ${fact.fact.slice(0, 80)}`);
212
+ } catch (err) {
213
+ console.warn('[dreaming] Failed to record fact:', err instanceof Error ? err.message : String(err));
214
+ }
215
+ }
216
+
217
+ // Log routing failures
218
+ for (const failure of (result.routing_failures ?? []).slice(0, 3)) {
219
+ console.warn(`[dreaming] Routing failure detected: ${failure.description}`);
220
+ }
221
+
222
+ // Log BizOps drift
223
+ for (const drift of (result.bizops_drift ?? []).slice(0, 3)) {
224
+ console.warn(`[dreaming] BizOps drift: ${drift.entity}.${drift.field} should be "${drift.actual_value}"`);
225
+ }
226
+
227
+ // Record preferences
228
+ for (const pref of (result.preferences ?? []).slice(0, 3)) {
229
+ if (!pref.preference || pref.preference.length < 15) continue;
230
+ try {
231
+ if (!env.memoryBinding) continue;
232
+ await recordMemoryAdapter(env.memoryBinding, 'operator_preferences', pref.preference, 0.85, 'dreaming_cycle');
233
+ console.log(`[dreaming] Preference: ${pref.preference.slice(0, 80)}`);
234
+ } catch { /* non-fatal */ }
235
+ }
236
+
237
+ console.log(`[dreaming] Extraction complete: ${factsRecorded} facts, ${(result.routing_failures ?? []).length} failures, ${(result.bizops_drift ?? []).length} drift items`);
238
+ return factsRecorded;
239
+ }
@@ -0,0 +1,8 @@
1
+ // Dreaming phase module barrel export
2
+ export { fetchConversationThreads, extractFacts, processFacts, type DreamingResult } from './facts.js';
3
+ export { processTaskProposals } from './task-proposals.js';
4
+ export { triageAgendaToIssues } from './agenda-triage.js';
5
+ export { extractPersonaDimensions } from './persona.js';
6
+ export { runPatternSynthesis } from './pattern-synthesis.js';
7
+ export { runSymbolicReflection } from './symbolic.js';
8
+ export { askWorkersAiOrGroq, parseJsonResponse } from './llm.js';
@@ -0,0 +1,33 @@
1
+ // Shared LLM helper — Workers AI with Groq fallback (zero-cost primary, paid fallback)
2
+
3
+ import type { EdgeEnv } from '../../dispatch.js';
4
+ import { askGroq } from '../../../groq.js';
5
+
6
+ export async function askWorkersAiOrGroq(
7
+ env: EdgeEnv,
8
+ system: string,
9
+ user: string,
10
+ useResponseModel = false,
11
+ ): Promise<string> {
12
+ if (env.ai) {
13
+ const model = useResponseModel
14
+ ? '@cf/meta/llama-3.3-70b-instruct-fp8-fast'
15
+ : (env.gptOssModel || '@cf/meta/llama-3.3-70b-instruct-fp8-fast');
16
+ const result = await env.ai.run(
17
+ model as Parameters<Ai['run']>[0],
18
+ { messages: [{ role: 'system', content: system }, { role: 'user', content: user }] },
19
+ ) as { response?: string; choices?: Array<{ message?: { content?: string } }> };
20
+ return result.choices?.[0]?.message?.content ?? result.response ?? '';
21
+ }
22
+ const groqModel = useResponseModel ? env.groqResponseModel : env.groqModel;
23
+ return askGroq(env.groqApiKey, groqModel, system, user, env.groqBaseUrl);
24
+ }
25
+
26
+ export function parseJsonResponse<T>(raw: string): T | null {
27
+ const cleaned = raw.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();
28
+ try {
29
+ return JSON.parse(cleaned) as T;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
@@ -0,0 +1,124 @@
1
+ // Phase 4: Pattern Synthesis (PRISM) — cross-topic connection discovery.
2
+ // Pulls recent facts from diverse memory topics, finds structural
3
+ // connections, validates utility before committing to meta_insight tier.
4
+
5
+ import type { EdgeEnv } from '../../dispatch.js';
6
+ import { recordMemory as recordMemoryAdapter } from '../../memory-adapter.js';
7
+ import { askWorkersAiOrGroq, parseJsonResponse } from './llm.js';
8
+
9
+ const SYNTHESIS_SYSTEM = `You are a pattern synthesis engine. You receive facts from different domains stored in an AI agent's memory. Your job is to find NON-OBVIOUS structural connections between facts from DIFFERENT topics.
10
+
11
+ Rules:
12
+ - Only propose connections between facts from DIFFERENT topics (cross-domain synthesis).
13
+ - Each connection must be ACTIONABLE — it must change a decision, reveal a risk, or unlock an optimization. "These two things are related" is not enough.
14
+ - Apply adversarial self-check: could this connection be a coincidence or superficial keyword overlap? If yes, discard it.
15
+ - Maximum 2 connections per run. Quality over quantity.
16
+
17
+ Return ONLY valid JSON (no markdown):
18
+ {
19
+ "connections": [
20
+ {
21
+ "fact_a": "first fact (quote or paraphrase)",
22
+ "topic_a": "topic of first fact",
23
+ "fact_b": "second fact (quote or paraphrase)",
24
+ "topic_b": "topic of second fact",
25
+ "insight": "the non-obvious connection and WHY it matters",
26
+ "action": "specific action this insight enables or decision it changes",
27
+ "confidence": 0.8
28
+ }
29
+ ]
30
+ }
31
+
32
+ If no genuine cross-domain connections exist, return {"connections": []}.
33
+ Do NOT force connections. Empty is better than noise.`;
34
+
35
+ interface SynthesisResult {
36
+ connections?: Array<{
37
+ fact_a: string;
38
+ topic_a: string;
39
+ fact_b: string;
40
+ topic_b: string;
41
+ insight: string;
42
+ action: string;
43
+ confidence: number;
44
+ }>;
45
+ }
46
+
47
+ export async function runPatternSynthesis(env: EdgeEnv): Promise<number> {
48
+ if (!env.memoryBinding) return 0;
49
+
50
+ const topicSamples: Array<{ topic: string; content: string }> = [];
51
+ const sampleTopics = [
52
+ 'aegis', 'infrastructure', 'feed_intel',
53
+ 'compliance', 'finance', 'content',
54
+ ];
55
+
56
+ for (const topic of sampleTopics) {
57
+ try {
58
+ const facts = await env.memoryBinding.recall('aegis', {
59
+ topic,
60
+ limit: 3,
61
+ min_confidence: 0.7,
62
+ });
63
+ for (const f of facts) {
64
+ topicSamples.push({ topic: f.topic, content: f.content });
65
+ }
66
+ } catch { /* topic may not exist yet */ }
67
+ }
68
+
69
+ if (topicSamples.length < 6) {
70
+ console.log(`[dreaming:prism] Too few facts for synthesis (${topicSamples.length})`);
71
+ return 0;
72
+ }
73
+
74
+ // Deduplicate — max 3 per topic
75
+ const byTopic: Record<string, typeof topicSamples> = {};
76
+ for (const s of topicSamples) {
77
+ if (!byTopic[s.topic]) byTopic[s.topic] = [];
78
+ if (byTopic[s.topic].length < 3) byTopic[s.topic].push(s);
79
+ }
80
+
81
+ if (Object.keys(byTopic).length < 3) {
82
+ console.log(`[dreaming:prism] Need 3+ topics for cross-domain synthesis (have ${Object.keys(byTopic).length})`);
83
+ return 0;
84
+ }
85
+
86
+ const factsBlock = Object.entries(byTopic)
87
+ .map(([topic, facts]) =>
88
+ `[${topic}]\n${facts.map(f => `- ${f.content}`).join('\n')}`
89
+ ).join('\n\n');
90
+
91
+ try {
92
+ const raw = await askWorkersAiOrGroq(env, SYNTHESIS_SYSTEM, factsBlock.slice(0, 8000));
93
+ if (!raw) return 0;
94
+
95
+ const result = parseJsonResponse<SynthesisResult>(raw);
96
+ if (!result) return 0;
97
+
98
+ let recorded = 0;
99
+ for (const conn of (result.connections ?? []).slice(0, 2)) {
100
+ if (!conn.insight || !conn.action || conn.insight.length < 30) continue;
101
+ if (conn.topic_a === conn.topic_b) continue;
102
+ if (conn.action.length < 20) continue;
103
+
104
+ const metaFact = `[PRISM] Connection: ${conn.topic_a} ↔ ${conn.topic_b} — ${conn.insight}. Action: ${conn.action}`;
105
+ await recordMemoryAdapter(
106
+ env.memoryBinding,
107
+ 'meta_insight',
108
+ metaFact,
109
+ Math.min(conn.confidence ?? 0.8, 0.85),
110
+ 'pattern_synthesis',
111
+ );
112
+ recorded++;
113
+ console.log(`[dreaming:prism] Meta-insight: ${conn.topic_a} ↔ ${conn.topic_b} — ${conn.insight.slice(0, 100)}`);
114
+ }
115
+
116
+ if (recorded === 0) {
117
+ console.log('[dreaming:prism] No actionable cross-domain connections found (this is fine)');
118
+ }
119
+ return recorded;
120
+ } catch (err) {
121
+ console.warn('[dreaming:prism] Pattern synthesis failed:', err instanceof Error ? err.message : String(err));
122
+ return 0;
123
+ }
124
+ }
@@ -0,0 +1,75 @@
1
+ // Phase 3: Persona Extraction — analyze conversation threads for
2
+ // behavioral patterns and personality dimensions.
3
+
4
+ import type { EdgeEnv } from '../../dispatch.js';
5
+ import { recordMemory as recordMemoryAdapter } from '../../memory-adapter.js';
6
+ import { askWorkersAiOrGroq, parseJsonResponse } from './llm.js';
7
+
8
+ const PERSONA_SYSTEM = `You analyze conversations to extract personality dimensions — how someone thinks, communicates, decides, and what they value. You are NOT extracting facts or preferences. You are observing behavioral patterns.
9
+
10
+ Analyze the conversations and return ONLY valid JSON (no markdown):
11
+ {
12
+ "observations": [
13
+ {
14
+ "dimension": "cognitive_style|communication|values|delegation|decision_making|emotional",
15
+ "observation": "specific behavioral pattern observed with evidence",
16
+ "confidence": 0.7
17
+ }
18
+ ]
19
+ }
20
+
21
+ Dimensions:
22
+ - cognitive_style: pattern-matching vs sequential, systems-thinking, attention patterns, how they process information
23
+ - communication: directness, brevity, humor style, when they elaborate vs stay terse, energy signals
24
+ - values: what they protect, what they trade off, aesthetic sensibility, what "good" means to them
25
+ - delegation: how they assign work, trust calibration, review depth, feedback style
26
+ - decision_making: speed, what they weigh, what they dismiss, how they handle uncertainty, reversibility awareness
27
+ - emotional: what energizes them, what drains them, confidence vs insecurity signals, momentum patterns
28
+
29
+ Rules:
30
+ - Only record patterns with clear behavioral evidence from the conversation
31
+ - Be specific: "commits to architectural decisions within one exchange" not "makes fast decisions"
32
+ - Look for HOW they engage, not WHAT they discuss
33
+ - 3-5 observations max. Skip if the conversations are too transactional to reveal personality
34
+ - Return empty observations array if nothing personality-relevant is visible`;
35
+
36
+ interface PersonaResult {
37
+ observations?: Array<{ dimension: string; observation: string; confidence: number }>;
38
+ }
39
+
40
+ export async function extractPersonaDimensions(env: EdgeEnv, threadContents: string[]): Promise<number> {
41
+ if (threadContents.length === 0) return 0;
42
+
43
+ let rawResponse: string;
44
+ try {
45
+ rawResponse = await askWorkersAiOrGroq(env, PERSONA_SYSTEM, threadContents.join('\n\n').slice(0, 15000), true);
46
+ } catch (err) {
47
+ console.warn('[dreaming:persona] LLM call failed:', err instanceof Error ? err.message : String(err));
48
+ return 0;
49
+ }
50
+
51
+ if (!rawResponse) return 0;
52
+
53
+ const result = parseJsonResponse<PersonaResult>(rawResponse);
54
+ if (!result) {
55
+ console.warn('[dreaming:persona] Failed to parse response');
56
+ return 0;
57
+ }
58
+
59
+ let recorded = 0;
60
+ for (const obs of (result.observations ?? []).slice(0, 5)) {
61
+ if (!obs.observation || obs.observation.length < 20 || !obs.dimension) continue;
62
+ const fact = `[${obs.dimension}] ${obs.observation}`;
63
+ try {
64
+ if (!env.memoryBinding) continue;
65
+ await recordMemoryAdapter(env.memoryBinding, 'operator_persona', fact, obs.confidence ?? 0.7, 'persona_extraction');
66
+ recorded++;
67
+ console.log(`[dreaming:persona] ${fact.slice(0, 80)}`);
68
+ } catch { /* non-fatal */ }
69
+ }
70
+
71
+ if (recorded > 0) {
72
+ console.log(`[dreaming:persona] Extracted ${recorded} persona observations`);
73
+ }
74
+ return recorded;
75
+ }
@@ -0,0 +1,31 @@
1
+ // Phase 5: Symbolic Reflection — nightly TarotScript SingleDraw
2
+ // mirrors the day's operational patterns via structured serendipity.
3
+
4
+ import type { EdgeEnv } from '../../dispatch.js';
5
+ import { recordMemory as recordMemoryAdapter } from '../../memory-adapter.js';
6
+ import { runReading, summarizeForMemory, formatSymbolicNote } from '../../symbolic.js';
7
+
8
+ export async function runSymbolicReflection(env: EdgeEnv): Promise<void> {
9
+ if (!env.tarotscriptFetcher) return;
10
+
11
+ try {
12
+ const reading = await runReading(
13
+ env.tarotscriptFetcher,
14
+ 'dreaming',
15
+ "Reflect on today's operational patterns",
16
+ { domain: 'reflection', situation: 'Nightly dreaming cycle mirror', timeframe: 'immediate' },
17
+ );
18
+
19
+ const summary = summarizeForMemory(reading, 'dreaming', "Reflect on today's operational patterns");
20
+ const note = formatSymbolicNote(reading, 'dreaming');
21
+
22
+ const memoryContent = `${note}\nReceipt: ${summary.receiptHash} | Seed: ${summary.seed}`;
23
+ if (env.memoryBinding) {
24
+ await recordMemoryAdapter(env.memoryBinding, 'symbolic_reflection', memoryContent, 0.7, 'dreaming_cycle');
25
+ }
26
+
27
+ console.log(`[dreaming:symbolic] SingleDraw complete — ${summary.dominantElement}, shadow ${summary.shadowDensity.toFixed(2)}, sephira ${summary.sephira}`);
28
+ } catch (err) {
29
+ console.warn('[dreaming:symbolic] TarotScript reading failed:', err instanceof Error ? err.message : String(err));
30
+ }
31
+ }