@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,114 @@
1
+ // Stub — full implementation not yet extracted to OSS
2
+ // proposeToolsFromPatterns is live (v1.56.0)
3
+
4
+ import { type EdgeEnv } from '../dispatch.js';
5
+ import { createDynamicTool, listDynamicTools, invalidateToolCache } from '../dynamic-tools.js';
6
+
7
+ export const DEFAULT_WATCH_REPOS = [
8
+ 'aegis',
9
+ 'demo_app_v2',
10
+ 'charter',
11
+ 'img-forge',
12
+ 'example-auth',
13
+ 'bizops-copilot',
14
+ ];
15
+
16
+ export function getWatchRepos(githubRepo: string): string[] {
17
+ const org = githubRepo.split('/')[0];
18
+ return DEFAULT_WATCH_REPOS.map(repo => `${org}/${repo}`);
19
+ }
20
+
21
+ export async function runSelfImprovementAnalysis(_env: EdgeEnv): Promise<void> {
22
+ // Stub — full implementation provided by consuming app via ScheduledTaskPlugin
23
+ }
24
+
25
+ export async function runSelfImprovementHousekeeping(env: EdgeEnv): Promise<void> {
26
+ // Recurring prompt patterns → dynamic tool proposals
27
+ try {
28
+ await proposeToolsFromPatterns(env);
29
+ } catch {
30
+ // Non-fatal — table may not exist yet
31
+ }
32
+ }
33
+
34
+ export async function runInfraComplianceCheck(_env: EdgeEnv): Promise<void> {
35
+ // Stub — full implementation provided by consuming app via ScheduledTaskPlugin
36
+ }
37
+
38
+ // ─── Dynamic Tool Auto-Proposal ─────────────────────────────
39
+ // Scans completed tasks for recurring prompt structures.
40
+ // If 3+ tasks share a common prompt prefix (first 100 chars),
41
+ // proposes a dynamic tool that parameterizes the varying parts.
42
+
43
+ const TOOL_PROPOSAL_MIN_CLUSTER = 3;
44
+ const TOOL_PROPOSAL_PREFIX_LEN = 100;
45
+
46
+ async function proposeToolsFromPatterns(env: EdgeEnv): Promise<void> {
47
+ // Get recent successful tasks
48
+ const tasks = await env.db.prepare(`
49
+ SELECT title, prompt, category, repo FROM cc_tasks
50
+ WHERE status = 'completed' AND completed_at > datetime('now', '-14 days')
51
+ ORDER BY completed_at DESC LIMIT 50
52
+ `).all<{ title: string; prompt: string; category: string; repo: string }>();
53
+
54
+ if (tasks.results.length < TOOL_PROPOSAL_MIN_CLUSTER) return;
55
+
56
+ // Cluster by prompt prefix (first N chars, normalized)
57
+ const clusters = new Map<string, Array<{ title: string; prompt: string; category: string; repo: string }>>();
58
+ for (const task of tasks.results) {
59
+ const prefix = task.prompt.slice(0, TOOL_PROPOSAL_PREFIX_LEN).toLowerCase().replace(/\s+/g, ' ').trim();
60
+ const existing = clusters.get(prefix) ?? [];
61
+ existing.push(task);
62
+ clusters.set(prefix, existing);
63
+ }
64
+
65
+ // Check existing dynamic tools to avoid duplicates
66
+ const existingTools = await listDynamicTools(env.db, { limit: 100 });
67
+ const existingNames = new Set(existingTools.map(t => t.name));
68
+
69
+ let created = 0;
70
+ for (const [prefix, group] of clusters) {
71
+ if (group.length < TOOL_PROPOSAL_MIN_CLUSTER) continue;
72
+
73
+ // Derive a tool name from the category and common words
74
+ const category = group[0].category;
75
+ const words = prefix.split(/\s+/).filter(w => w.length > 3).slice(0, 3);
76
+ const toolName = `auto_${category}_${words.join('_')}`.replace(/[^a-z0-9_]/g, '').slice(0, 49);
77
+
78
+ if (existingNames.has(toolName) || toolName.length < 8) continue;
79
+
80
+ // Build a template from the common prefix + a {{details}} variable for the varying part
81
+ const commonPrefix = group[0].prompt.slice(0, TOOL_PROPOSAL_PREFIX_LEN);
82
+ const template = `${commonPrefix}\n\nSpecific details: {{details}}\n\nTarget repo: {{repo}}`;
83
+
84
+ try {
85
+ await createDynamicTool(env.db, {
86
+ name: toolName,
87
+ description: `Auto-proposed: ${group.length} similar ${category} tasks detected (e.g. "${group[0].title.slice(0, 60)}")`,
88
+ input_schema: JSON.stringify({
89
+ type: 'object',
90
+ properties: {
91
+ details: { type: 'string', description: 'Specific details for this task' },
92
+ repo: { type: 'string', description: 'Target repository' },
93
+ },
94
+ required: ['details'],
95
+ }),
96
+ prompt_template: template,
97
+ executor: 'workers_ai',
98
+ created_by: 'self_improvement',
99
+ status: 'draft',
100
+ ttl_days: 14,
101
+ });
102
+ created++;
103
+ existingNames.add(toolName);
104
+ console.log(`[self-improvement] Proposed dynamic tool: ${toolName} (${group.length} similar tasks)`);
105
+ } catch {
106
+ // Duplicate name or other constraint — skip
107
+ }
108
+ }
109
+
110
+ if (created > 0) {
111
+ invalidateToolCache();
112
+ console.log(`[self-improvement] Proposed ${created} dynamic tool(s) from recurring task patterns`);
113
+ }
114
+ }
@@ -0,0 +1,175 @@
1
+ // Social Engagement — autonomous Bluesky interaction
2
+ // Runs every 6 hours. Likes replies, follows back real accounts,
3
+ // replies to substantive comments with Workers AI.
4
+
5
+ import { type EdgeEnv } from '../dispatch.js';
6
+ import {
7
+ getNotifications,
8
+ likePost,
9
+ followAccount,
10
+ getProfile,
11
+ postToBluesky,
12
+ type BlueskyNotification,
13
+ } from '../../bluesky.js';
14
+
15
+ // ─── Guardrails ─────────────────────────────────────────────
16
+
17
+ const MAX_ACTIONS_PER_RUN = 10; // Rate limit — don't spam
18
+ const MIN_FOLLOWER_POSTS = 3; // Skip accounts with <3 posts (likely bots)
19
+ const NOTIFICATION_WINDOW_MS = 48 * 60 * 60 * 1000; // Only engage with last 48h
20
+
21
+ // Patterns that indicate bot/spam accounts
22
+ const SPAM_PATTERNS = [
23
+ /^follow.*back/i,
24
+ /crypto.*airdrop/i,
25
+ /onlyfans/i,
26
+ /dm.*me/i,
27
+ /free.*money/i,
28
+ ];
29
+
30
+ function isLikelySpam(notification: BlueskyNotification): boolean {
31
+ const text = notification.text ?? '';
32
+ const name = notification.author.displayName ?? '';
33
+ return SPAM_PATTERNS.some(p => p.test(text) || p.test(name));
34
+ }
35
+
36
+ // ─── Main ───────────────────────────────────────────────────
37
+
38
+ export async function runSocialEngagement(env: EdgeEnv): Promise<void> {
39
+ // Time gate: every 6 hours (01, 07, 13, 19 UTC) — offset from entropy (00, 06, 12, 18)
40
+ const hour = new Date().getUTCHours();
41
+ if (hour % 6 !== 1) return;
42
+
43
+ if (!env.blueskyAppPassword) {
44
+ console.log('[social-engage] No BLUESKY_APP_PASSWORD, skipping');
45
+ return;
46
+ }
47
+
48
+ const handle = env.blueskyHandle ?? 'your-handle.bsky.social';
49
+
50
+ // Cooldown: check last run via web_events
51
+ const lastRun = await env.db.prepare(
52
+ "SELECT received_at FROM web_events WHERE event_id = 'social_engage'"
53
+ ).first<{ received_at: string }>();
54
+
55
+ if (lastRun) {
56
+ const elapsed = Date.now() - new Date(lastRun.received_at + 'Z').getTime();
57
+ if (elapsed < 5 * 60 * 60 * 1000) return; // 5h cooldown
58
+ }
59
+
60
+ let notifications: BlueskyNotification[];
61
+ try {
62
+ notifications = await getNotifications(handle, env.blueskyAppPassword, 50);
63
+ } catch (err) {
64
+ console.error('[social-engage] Failed to fetch notifications:', err instanceof Error ? err.message : String(err));
65
+ return;
66
+ }
67
+
68
+ if (notifications.length === 0) {
69
+ console.log('[social-engage] No notifications');
70
+ await updateWatermark(env.db);
71
+ return;
72
+ }
73
+
74
+ // Filter to recent notifications only
75
+ const cutoff = Date.now() - NOTIFICATION_WINDOW_MS;
76
+ const recent = notifications.filter(n =>
77
+ new Date(n.indexedAt).getTime() > cutoff && !isLikelySpam(n)
78
+ );
79
+
80
+ let actions = 0;
81
+ const liked = new Set<string>();
82
+ const followed = new Set<string>();
83
+
84
+ for (const n of recent) {
85
+ if (actions >= MAX_ACTIONS_PER_RUN) break;
86
+
87
+ // ─── Like replies to our posts ────────────────────────
88
+ if (n.reason === 'reply' && n.uri && n.cid && !liked.has(n.uri)) {
89
+ try {
90
+ await likePost(handle, env.blueskyAppPassword, n.uri, n.cid);
91
+ liked.add(n.uri);
92
+ actions++;
93
+ console.log(`[social-engage] Liked reply from @${n.author.handle}`);
94
+ } catch { /* skip — might already be liked */ }
95
+ }
96
+
97
+ // ─── Like quote posts ─────────────────────────────────
98
+ if (n.reason === 'quote' && n.uri && n.cid && !liked.has(n.uri)) {
99
+ try {
100
+ await likePost(handle, env.blueskyAppPassword, n.uri, n.cid);
101
+ liked.add(n.uri);
102
+ actions++;
103
+ console.log(`[social-engage] Liked quote from @${n.author.handle}`);
104
+ } catch { /* skip */ }
105
+ }
106
+
107
+ // ─── Follow back real accounts ────────────────────────
108
+ if (n.reason === 'follow' && n.author.did && !followed.has(n.author.did)) {
109
+ try {
110
+ const profile = await getProfile(n.author.handle);
111
+ if (profile.postsCount >= MIN_FOLLOWER_POSTS) {
112
+ await followAccount(handle, env.blueskyAppPassword, n.author.did);
113
+ followed.add(n.author.did);
114
+ actions++;
115
+ console.log(`[social-engage] Followed back @${n.author.handle} (${profile.postsCount} posts)`);
116
+ } else {
117
+ console.log(`[social-engage] Skipped follow-back @${n.author.handle} (${profile.postsCount} posts — below threshold)`);
118
+ }
119
+ } catch { /* skip — might already follow */ }
120
+ }
121
+
122
+ // ─── Reply to substantive comments (Workers AI) ───────
123
+ if (n.reason === 'reply' && n.text && n.text.length > 40 && env.ai && actions < MAX_ACTIONS_PER_RUN) {
124
+ try {
125
+ const reply = await generateReply(env.ai, n.text, n.author.handle);
126
+ if (reply) {
127
+ // Need the parent post URI/CID for threading
128
+ // The notification uri IS the reply post — we need to reply TO it
129
+ await postToBluesky(handle, env.blueskyAppPassword, { text: reply });
130
+ actions++;
131
+ console.log(`[social-engage] Replied to @${n.author.handle}: ${reply.slice(0, 50)}...`);
132
+ }
133
+ } catch (err) {
134
+ console.warn(`[social-engage] Reply generation failed:`, err instanceof Error ? err.message : String(err));
135
+ }
136
+ }
137
+ }
138
+
139
+ await updateWatermark(env.db);
140
+ console.log(`[social-engage] Complete: ${actions} actions (${liked.size} likes, ${followed.size} follows)`);
141
+ }
142
+
143
+ async function updateWatermark(db: D1Database): Promise<void> {
144
+ await db.prepare(
145
+ "INSERT OR REPLACE INTO web_events (event_id, received_at) VALUES ('social_engage', datetime('now'))"
146
+ ).run();
147
+ }
148
+
149
+ // ─── Reply Generation ───────────────────────────────────────
150
+
151
+ async function generateReply(
152
+ ai: Ai,
153
+ incomingText: string,
154
+ authorHandle: string,
155
+ ): Promise<string | null> {
156
+ const result = await ai.run('@cf/meta/llama-3.1-8b-instruct' as Parameters<Ai['run']>[0], {
157
+ messages: [
158
+ {
159
+ role: 'system',
160
+ content: `You are the operator's social media voice. Direct, builder-energy, anti-corporate. No emoji spam. No "excited to announce." Keep replies under 200 chars. Be genuine and conversational. If you can't add value, return SKIP.`,
161
+ },
162
+ {
163
+ role: 'user',
164
+ content: `@${authorHandle} replied to our Bluesky post: "${incomingText}"\n\nWrite a brief, genuine reply. Return ONLY the reply text, or SKIP if there's nothing meaningful to add.`,
165
+ },
166
+ ],
167
+ max_tokens: 100,
168
+ }) as { response?: string };
169
+
170
+ const reply = result.response?.trim();
171
+ if (!reply || reply === 'SKIP' || reply.length < 5) return null;
172
+
173
+ // Safety: truncate to 300 chars (Bluesky limit)
174
+ return reply.length > 280 ? reply.slice(0, 277) + '...' : reply;
175
+ }
@@ -0,0 +1,60 @@
1
+ // Task Audit Chain -- tamper-evident execution trail using hash chains.
2
+ // D1-only mode (no R2). Hash chain provides tamper evidence; D1 stores full records.
3
+
4
+ import { computeHash } from '../../lib/audit-chain/chain.js';
5
+ import { GENESIS_HASH } from '../../lib/audit-chain/types.js';
6
+
7
+ const NAMESPACE = 'task-runner';
8
+
9
+ /** Read the latest chain head from D1, or GENESIS_HASH if empty. */
10
+ export async function getChainHead(db: D1Database): Promise<string> {
11
+ const row = await db.prepare(
12
+ 'SELECT hash FROM task_audit_chain WHERE namespace = ? ORDER BY created_at DESC LIMIT 1'
13
+ ).bind(NAMESPACE).first<{ hash: string }>();
14
+ return row?.hash ?? GENESIS_HASH;
15
+ }
16
+
17
+ /** Write a hash-linked audit record for a task execution. Returns new chain head. */
18
+ export async function writeTaskAuditRecord(
19
+ db: D1Database,
20
+ opts: {
21
+ taskName: string;
22
+ status: 'ok' | 'error' | 'skipped';
23
+ durationMs: number;
24
+ errorMessage?: string;
25
+ chainHead: string;
26
+ },
27
+ ): Promise<string> {
28
+ const recordId = crypto.randomUUID();
29
+ const timestamp = new Date().toISOString();
30
+
31
+ // Build record data (without hash -- computed from this + prev_hash)
32
+ const recordData = {
33
+ record_id: recordId,
34
+ namespace: NAMESPACE,
35
+ event_type: `task.${opts.status}`,
36
+ prev_hash: opts.chainHead,
37
+ actor: 'aegis-scheduler',
38
+ timestamp,
39
+ payload: {
40
+ task_name: opts.taskName,
41
+ status: opts.status,
42
+ duration_ms: opts.durationMs,
43
+ ...(opts.errorMessage ? { error: opts.errorMessage } : {}),
44
+ },
45
+ };
46
+
47
+ const encoder = new TextEncoder();
48
+ const recordBytes = encoder.encode(JSON.stringify(recordData));
49
+ const hash = await computeHash(opts.chainHead, recordBytes);
50
+
51
+ await db.prepare(`
52
+ INSERT INTO task_audit_chain (record_id, namespace, event_type, hash, prev_hash, actor, timestamp, payload)
53
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
54
+ `).bind(
55
+ recordId, NAMESPACE, `task.${opts.status}`, hash, opts.chainHead,
56
+ 'aegis-scheduler', timestamp, JSON.stringify(recordData.payload),
57
+ ).run();
58
+
59
+ return hash;
60
+ }
@@ -0,0 +1,156 @@
1
+ // Symbolic Consultation — TarotScript service binding client
2
+ // Structured serendipity: deterministic symbolic computation that surfaces
3
+ // tensions analytical reasoning suppresses.
4
+ //
5
+ // See: artifacts/symbolic-consultation-protocol.md
6
+
7
+ // ─── Types (mirrors TarotScript ReadingAnalysis) ─────────────────────────────
8
+
9
+ export type Element = 'Fire' | 'Water' | 'Air' | 'Earth';
10
+
11
+ export interface CardSummary {
12
+ position: string;
13
+ name: string;
14
+ arcana: 'major' | 'minor';
15
+ number: number;
16
+ suit: string | null;
17
+ element: Element;
18
+ orientation: 'upright' | 'reversed';
19
+ keywords: string[];
20
+ courtRank: string | null;
21
+ }
22
+
23
+ export interface DignityPair {
24
+ positions: [string, string];
25
+ cards: [string, string];
26
+ relationship: string;
27
+ elements: [Element, Element];
28
+ description: string;
29
+ score: number;
30
+ }
31
+
32
+ export interface ReadingAnalysis {
33
+ cards: CardSummary[];
34
+ elementalCensus: Record<Element, number>;
35
+ dominantElement: Element;
36
+ shadowDensity: { reversed: number; total: number; ratio: number };
37
+ dignities: DignityPair[];
38
+ courtCards: CardSummary[];
39
+ majorArcana: CardSummary[];
40
+ numerology: { sum: number; reduced: number; sephira: string; keyword: string };
41
+ tensions: Array<{ elements: [Element, Element]; counts: [number, number]; description: string }>;
42
+ }
43
+
44
+ export interface ReadingResult {
45
+ output: string;
46
+ facts: Record<string, unknown>;
47
+ spread: { name: string; positions: Record<string, unknown> } | null;
48
+ analysis: ReadingAnalysis | null;
49
+ seed: number;
50
+ receipt: { hash: string; script: string; querentId: string };
51
+ }
52
+
53
+ export type SpreadType = 'single' | 'three-card' | 'star' | 'horseshoe' | 'celtic-cross' | 'shadow-work' | 'dreaming';
54
+
55
+ export interface SymbolicContext {
56
+ domain: 'product' | 'architecture' | 'hiring' | 'strategy' | 'operations' | 'reflection';
57
+ situation: string;
58
+ tensionsKnown?: string[];
59
+ timeframe?: 'immediate' | 'this-week' | 'this-quarter' | 'long-term';
60
+ }
61
+
62
+ // ─── Memory summary (what AEGIS stores per reading) ──────────────────────────
63
+
64
+ export interface SymbolicMemoryEntry {
65
+ type: 'symbolic_consultation';
66
+ receiptHash: string;
67
+ seed: number;
68
+ spreadType: SpreadType;
69
+ intention: string;
70
+ dominantElement: Element;
71
+ shadowDensity: number;
72
+ topTensions: Array<{ pair: [Element, Element]; count: number }>;
73
+ sephira: string;
74
+ sephiraKeyword: string;
75
+ timestamp: string;
76
+ }
77
+
78
+ // ─── Client ──────────────────────────────────────────────────────────────────
79
+
80
+ export async function runReading(
81
+ fetcher: Fetcher,
82
+ spreadType: SpreadType,
83
+ intention: string,
84
+ context?: SymbolicContext,
85
+ seed?: number,
86
+ ): Promise<ReadingResult> {
87
+ const response = await fetcher.fetch('https://internal/run', {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: JSON.stringify({
91
+ spreadType,
92
+ querent: {
93
+ id: 'aegis',
94
+ intention,
95
+ state: context ? {
96
+ domain: context.domain,
97
+ situation: context.situation,
98
+ tensions_known: context.tensionsKnown?.join(', '),
99
+ timeframe: context.timeframe,
100
+ } : undefined,
101
+ },
102
+ seed,
103
+ inscribe: true,
104
+ }),
105
+ });
106
+
107
+ if (!response.ok) {
108
+ const body = await response.text();
109
+ throw new Error(`TarotScript error ${response.status}: ${body}`);
110
+ }
111
+
112
+ return response.json() as Promise<ReadingResult>;
113
+ }
114
+
115
+ export function summarizeForMemory(
116
+ reading: ReadingResult,
117
+ spreadType: SpreadType,
118
+ intention: string,
119
+ ): SymbolicMemoryEntry {
120
+ const analysis = reading.analysis;
121
+ return {
122
+ type: 'symbolic_consultation',
123
+ receiptHash: reading.receipt.hash,
124
+ seed: reading.seed,
125
+ spreadType,
126
+ intention,
127
+ dominantElement: analysis?.dominantElement ?? 'Earth',
128
+ shadowDensity: analysis?.shadowDensity?.ratio ?? 0,
129
+ topTensions: (analysis?.tensions ?? []).slice(0, 3).map(t => ({
130
+ pair: t.elements,
131
+ count: t.counts[0] + t.counts[1],
132
+ })),
133
+ sephira: analysis?.numerology?.sephira ?? 'unknown',
134
+ sephiraKeyword: analysis?.numerology?.keyword ?? 'unknown',
135
+ timestamp: new Date().toISOString(),
136
+ };
137
+ }
138
+
139
+ export function formatSymbolicNote(reading: ReadingResult, spreadType: SpreadType): string {
140
+ const analysis = reading.analysis;
141
+ if (!analysis) return `Symbolic note (TarotScript): ${spreadType} reading completed, no analysis available.`;
142
+
143
+ const shadow = analysis.shadowDensity.ratio;
144
+ const readiness = shadow < 0.2 ? 'clear — act' : shadow < 0.5 ? 'partially hidden — proceed with awareness' : 'mostly concealed — may not be the moment to decide';
145
+
146
+ const cards = analysis.cards.map(c => `${c.name} (${c.orientation})`).join(', ');
147
+ const tensions = analysis.tensions.slice(0, 2).map(t => t.description).join('; ');
148
+
149
+ return [
150
+ `Symbolic note (TarotScript ${spreadType}):`,
151
+ `Cards: ${cards}`,
152
+ `Dominant element: ${analysis.dominantElement} | Shadow density: ${shadow.toFixed(2)} (${readiness})`,
153
+ `Numerology: ${analysis.numerology.sephira} — ${analysis.numerology.keyword}`,
154
+ tensions ? `Tensions: ${tensions}` : null,
155
+ ].filter(Boolean).join('\n');
156
+ }
@@ -0,0 +1,145 @@
1
+ // ─── Channel ─────────────────────────────────────────────────
2
+
3
+ import type { EpisodicOutcome, ProceduralStatus as ProceduralStatusEnum } from '../schema-enums.js';
4
+
5
+ export type Channel = 'web' | 'cli' | 'internal';
6
+
7
+ // ─── KernelIntent ────────────────────────────────────────────
8
+
9
+ export interface KernelIntent {
10
+ source: {
11
+ channel: Channel;
12
+ threadId: string;
13
+ };
14
+ raw: string;
15
+ classified?: string;
16
+ complexity?: number;
17
+ needsTools?: boolean;
18
+ confidence?: number;
19
+ domain?: string;
20
+ domainConfidence?: number;
21
+ timestamp: number;
22
+ costCeiling: 'free' | 'cheap' | 'expensive';
23
+ classifierSource?: 'classify-cast' | 'workers-ai' | 'groq';
24
+ }
25
+
26
+ // ─── Memory Types ────────────────────────────────────────────
27
+
28
+ export interface EpisodicEntry {
29
+ id?: number;
30
+ intent_class: string;
31
+ channel: string;
32
+ summary: string;
33
+ outcome: EpisodicOutcome;
34
+ cost: number;
35
+ latency_ms: number;
36
+ near_miss?: string | null;
37
+ classifier_confidence?: number;
38
+ reclassified?: boolean;
39
+ thread_id?: string | null;
40
+ executor?: string | null;
41
+ created_at?: string;
42
+ }
43
+
44
+ export type ProceduralStatus = ProceduralStatusEnum;
45
+
46
+ export interface Refinement {
47
+ timestamp: number;
48
+ what: string;
49
+ why: string;
50
+ impact: 'pending' | 'positive' | 'negative';
51
+ }
52
+
53
+ export interface ProceduralEntry {
54
+ id?: number;
55
+ task_pattern: string;
56
+ executor: string;
57
+ executor_config: string;
58
+ success_count: number;
59
+ fail_count: number;
60
+ avg_latency_ms: number;
61
+ avg_cost: number;
62
+ status: ProceduralStatus;
63
+ consecutive_failures: number;
64
+ refinements: string;
65
+ last_used?: string;
66
+ candidate_executor?: string | null;
67
+ candidate_successes?: number;
68
+ created_at?: string;
69
+ }
70
+
71
+ export interface MemoryEntry {
72
+ id: number;
73
+ topic: string;
74
+ fact: string;
75
+ confidence: number;
76
+ source: string;
77
+ created_at: string;
78
+ valid_until: string | null;
79
+ superseded_by: number | null;
80
+ strength: number;
81
+ last_recalled_at: string | null;
82
+ }
83
+
84
+ // ─── Execution Plan ──────────────────────────────────────────
85
+
86
+ export type Executor = 'claude' | 'groq' | 'direct' | 'claude_code' | 'workers_ai' | 'claude_opus' | 'gpt_oss' | 'composite' | 'tarotscript';
87
+
88
+ export interface ExecutionPlan {
89
+ executor: Executor;
90
+ reasoning: string;
91
+ procedureId?: number;
92
+ costCeiling: 'free' | 'cheap' | 'expensive';
93
+ }
94
+
95
+ // ─── Cognitive State ─────────────────────────────────────────
96
+
97
+ export interface CognitiveState {
98
+ version: number;
99
+ computed_at: string;
100
+
101
+ // Phase 1: Active narratives (max 10)
102
+ narratives: Array<{
103
+ arc: string;
104
+ title: string;
105
+ summary: string;
106
+ status: 'active' | 'stalled';
107
+ tension: string | null;
108
+ last_beat: string | null;
109
+ beat_count: number;
110
+ }>;
111
+
112
+ // Operational pulse
113
+ open_threads: number;
114
+ proposed_actions: number;
115
+
116
+ // Phase 2 (empty until populated)
117
+ activated_nodes: Array<{ label: string; type: string; activation: number }>;
118
+ active_projects: Array<{ project: string; status: string; top_blocker: string | null }>;
119
+
120
+ // Product portfolio (populated from BizOps project registry via heartbeat)
121
+ product_portfolio: Array<{ name: string; description: string; model: string; status: string; revenue?: string }>;
122
+
123
+ // Phase 3
124
+ latest_metacog: string | null;
125
+
126
+ // Stats
127
+ memory_count: number;
128
+ episode_count_24h: number;
129
+ last_heartbeat_severity: string | null;
130
+ }
131
+
132
+ // ─── Dispatch Result ─────────────────────────────────────────
133
+
134
+ export interface DispatchResult {
135
+ text: string;
136
+ executor: Executor;
137
+ cost: number;
138
+ latency_ms: number;
139
+ procedureHit: boolean;
140
+ classification: string;
141
+ confidence?: number;
142
+ reclassified?: boolean;
143
+ probeResult?: 'agreed' | 'split' | 'escalated';
144
+ meta?: unknown;
145
+ }