family-ai-agent 1.0.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 (132) hide show
  1. package/.env.example +49 -0
  2. package/README.md +161 -0
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +336 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/config/index.d.ts +37 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +68 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/config/models.d.ts +17 -0
  12. package/dist/config/models.d.ts.map +1 -0
  13. package/dist/config/models.js +128 -0
  14. package/dist/config/models.js.map +1 -0
  15. package/dist/core/agents/agent-factory.d.ts +31 -0
  16. package/dist/core/agents/agent-factory.d.ts.map +1 -0
  17. package/dist/core/agents/agent-factory.js +151 -0
  18. package/dist/core/agents/agent-factory.js.map +1 -0
  19. package/dist/core/agents/base-agent.d.ts +51 -0
  20. package/dist/core/agents/base-agent.d.ts.map +1 -0
  21. package/dist/core/agents/base-agent.js +245 -0
  22. package/dist/core/agents/base-agent.js.map +1 -0
  23. package/dist/core/agents/index.d.ts +8 -0
  24. package/dist/core/agents/index.d.ts.map +1 -0
  25. package/dist/core/agents/index.js +9 -0
  26. package/dist/core/agents/index.js.map +1 -0
  27. package/dist/core/agents/personalities/automation.d.ts +14 -0
  28. package/dist/core/agents/personalities/automation.d.ts.map +1 -0
  29. package/dist/core/agents/personalities/automation.js +146 -0
  30. package/dist/core/agents/personalities/automation.js.map +1 -0
  31. package/dist/core/agents/personalities/chat.d.ts +10 -0
  32. package/dist/core/agents/personalities/chat.d.ts.map +1 -0
  33. package/dist/core/agents/personalities/chat.js +132 -0
  34. package/dist/core/agents/personalities/chat.js.map +1 -0
  35. package/dist/core/agents/personalities/coding.d.ts +16 -0
  36. package/dist/core/agents/personalities/coding.d.ts.map +1 -0
  37. package/dist/core/agents/personalities/coding.js +166 -0
  38. package/dist/core/agents/personalities/coding.js.map +1 -0
  39. package/dist/core/agents/personalities/research.d.ts +13 -0
  40. package/dist/core/agents/personalities/research.d.ts.map +1 -0
  41. package/dist/core/agents/personalities/research.js +133 -0
  42. package/dist/core/agents/personalities/research.js.map +1 -0
  43. package/dist/core/agents/types.d.ts +102 -0
  44. package/dist/core/agents/types.d.ts.map +1 -0
  45. package/dist/core/agents/types.js +2 -0
  46. package/dist/core/agents/types.js.map +1 -0
  47. package/dist/core/orchestrator/graph.d.ts +118 -0
  48. package/dist/core/orchestrator/graph.d.ts.map +1 -0
  49. package/dist/core/orchestrator/graph.js +233 -0
  50. package/dist/core/orchestrator/graph.js.map +1 -0
  51. package/dist/database/client.d.ts +19 -0
  52. package/dist/database/client.d.ts.map +1 -0
  53. package/dist/database/client.js +95 -0
  54. package/dist/database/client.js.map +1 -0
  55. package/dist/index.d.ts +41 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +67 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/llm/openrouter-client.d.ts +45 -0
  60. package/dist/llm/openrouter-client.d.ts.map +1 -0
  61. package/dist/llm/openrouter-client.js +155 -0
  62. package/dist/llm/openrouter-client.js.map +1 -0
  63. package/dist/memory/conversation/index.d.ts +37 -0
  64. package/dist/memory/conversation/index.d.ts.map +1 -0
  65. package/dist/memory/conversation/index.js +196 -0
  66. package/dist/memory/conversation/index.js.map +1 -0
  67. package/dist/memory/index.d.ts +4 -0
  68. package/dist/memory/index.d.ts.map +1 -0
  69. package/dist/memory/index.js +5 -0
  70. package/dist/memory/index.js.map +1 -0
  71. package/dist/memory/knowledge-base/index.d.ts +51 -0
  72. package/dist/memory/knowledge-base/index.d.ts.map +1 -0
  73. package/dist/memory/knowledge-base/index.js +222 -0
  74. package/dist/memory/knowledge-base/index.js.map +1 -0
  75. package/dist/memory/longterm/vector-store.d.ts +44 -0
  76. package/dist/memory/longterm/vector-store.d.ts.map +1 -0
  77. package/dist/memory/longterm/vector-store.js +229 -0
  78. package/dist/memory/longterm/vector-store.js.map +1 -0
  79. package/dist/safety/audit-logger.d.ts +68 -0
  80. package/dist/safety/audit-logger.d.ts.map +1 -0
  81. package/dist/safety/audit-logger.js +215 -0
  82. package/dist/safety/audit-logger.js.map +1 -0
  83. package/dist/safety/guardrails/input-guardrail.d.ts +21 -0
  84. package/dist/safety/guardrails/input-guardrail.d.ts.map +1 -0
  85. package/dist/safety/guardrails/input-guardrail.js +145 -0
  86. package/dist/safety/guardrails/input-guardrail.js.map +1 -0
  87. package/dist/safety/guardrails/output-guardrail.d.ts +18 -0
  88. package/dist/safety/guardrails/output-guardrail.d.ts.map +1 -0
  89. package/dist/safety/guardrails/output-guardrail.js +125 -0
  90. package/dist/safety/guardrails/output-guardrail.js.map +1 -0
  91. package/dist/safety/index.d.ts +4 -0
  92. package/dist/safety/index.d.ts.map +1 -0
  93. package/dist/safety/index.js +5 -0
  94. package/dist/safety/index.js.map +1 -0
  95. package/dist/utils/errors.d.ts +36 -0
  96. package/dist/utils/errors.d.ts.map +1 -0
  97. package/dist/utils/errors.js +94 -0
  98. package/dist/utils/errors.js.map +1 -0
  99. package/dist/utils/logger.d.ts +8 -0
  100. package/dist/utils/logger.d.ts.map +1 -0
  101. package/dist/utils/logger.js +47 -0
  102. package/dist/utils/logger.js.map +1 -0
  103. package/docker/init-db.sql +149 -0
  104. package/docker/sandbox/Dockerfile.sandbox +29 -0
  105. package/docker-compose.yml +61 -0
  106. package/package.json +80 -0
  107. package/src/cli/index.ts +392 -0
  108. package/src/config/index.ts +85 -0
  109. package/src/config/models.ts +156 -0
  110. package/src/core/agents/agent-factory.ts +192 -0
  111. package/src/core/agents/base-agent.ts +333 -0
  112. package/src/core/agents/index.ts +27 -0
  113. package/src/core/agents/personalities/automation.ts +202 -0
  114. package/src/core/agents/personalities/chat.ts +159 -0
  115. package/src/core/agents/personalities/coding.ts +227 -0
  116. package/src/core/agents/personalities/research.ts +177 -0
  117. package/src/core/agents/types.ts +124 -0
  118. package/src/core/orchestrator/graph.ts +305 -0
  119. package/src/database/client.ts +109 -0
  120. package/src/index.ts +104 -0
  121. package/src/llm/openrouter-client.ts +218 -0
  122. package/src/memory/conversation/index.ts +313 -0
  123. package/src/memory/index.ts +23 -0
  124. package/src/memory/knowledge-base/index.ts +357 -0
  125. package/src/memory/longterm/vector-store.ts +364 -0
  126. package/src/safety/audit-logger.ts +357 -0
  127. package/src/safety/guardrails/input-guardrail.ts +191 -0
  128. package/src/safety/guardrails/output-guardrail.ts +160 -0
  129. package/src/safety/index.ts +21 -0
  130. package/src/utils/errors.ts +120 -0
  131. package/src/utils/logger.ts +74 -0
  132. package/tsconfig.json +37 -0
@@ -0,0 +1,177 @@
1
+ import { nanoid } from 'nanoid';
2
+ import { BaseAgent } from '../base-agent.js';
3
+ import type {
4
+ AgentConfig,
5
+ AgentState,
6
+ TaskResult,
7
+ RetrievedMemory,
8
+ HandoffDecision,
9
+ } from '../types.js';
10
+ import { getRecommendedModel } from '../../../config/models.js';
11
+
12
+ const RESEARCH_SYSTEM_PROMPT = `You are an expert Research Agent in the Family AI Agent system, specializing in information gathering and analysis.
13
+
14
+ Your identity: Research Specialist
15
+ Your personality:
16
+ - Thorough and methodical
17
+ - Fact-focused and objective
18
+ - Clear in presenting findings
19
+ - Good at synthesizing information from multiple sources
20
+
21
+ Your capabilities:
22
+ - Web search and information retrieval
23
+ - Fact-checking and verification
24
+ - Summarizing complex topics
25
+ - Comparing and analyzing data
26
+ - Creating research reports
27
+
28
+ Research methodology:
29
+ 1. Understand the research question clearly
30
+ 2. Identify key search terms and concepts
31
+ 3. Gather information from multiple sources
32
+ 4. Verify facts and cross-reference
33
+ 5. Synthesize findings into coherent summaries
34
+ 6. Cite sources when possible
35
+
36
+ Guidelines:
37
+ - Always prioritize accuracy over speed
38
+ - Clearly distinguish between facts and opinions
39
+ - Note any uncertainties or conflicting information
40
+ - Provide sources for claims when available
41
+ - Ask for clarification if the research question is ambiguous
42
+
43
+ When presenting findings:
44
+ - Start with a brief summary/answer
45
+ - Provide supporting details and evidence
46
+ - List sources and references
47
+ - Note any limitations or areas needing further research`;
48
+
49
+ export class ResearchAgent extends BaseAgent {
50
+ constructor(tools: AgentConfig['tools'] = []) {
51
+ super({
52
+ identity: {
53
+ id: `research-${nanoid(8)}`,
54
+ name: 'Researcher',
55
+ role: 'research',
56
+ description: 'Expert research and information gathering agent',
57
+ capabilities: [
58
+ 'web_search',
59
+ 'fact_checking',
60
+ 'summarization',
61
+ 'analysis',
62
+ 'report_writing',
63
+ 'data_comparison',
64
+ 'source_verification',
65
+ ],
66
+ },
67
+ model: getRecommendedModel('research'),
68
+ temperature: 0.3, // Lower temperature for factual accuracy
69
+ systemPrompt: RESEARCH_SYSTEM_PROMPT,
70
+ tools,
71
+ maxIterations: 15, // More iterations for thorough research
72
+ timeout: 180000, // 3 minutes for research tasks
73
+ });
74
+ }
75
+
76
+ protected buildSystemPrompt(
77
+ context: Record<string, unknown>,
78
+ memories: RetrievedMemory[]
79
+ ): string {
80
+ let prompt = this.systemPrompt;
81
+
82
+ // Add previous research findings from memory
83
+ const researchMemories = memories.filter(
84
+ (m) => m.type === 'episodic' || m.type === 'semantic'
85
+ );
86
+
87
+ if (researchMemories.length > 0) {
88
+ prompt += '\n\n## Previous Research Findings\n';
89
+ for (const memory of researchMemories) {
90
+ prompt += `- ${memory.content} (relevance: ${(memory.relevanceScore * 100).toFixed(0)}%)\n`;
91
+ }
92
+ }
93
+
94
+ // Add research context
95
+ if (context.researchTopic) {
96
+ prompt += `\n\n## Current Research Topic\n${context.researchTopic}`;
97
+ }
98
+
99
+ if (context.researchConstraints) {
100
+ prompt += `\n\n## Research Constraints\n`;
101
+ prompt += JSON.stringify(context.researchConstraints, null, 2);
102
+ }
103
+
104
+ return prompt;
105
+ }
106
+
107
+ protected async shouldHandoff(
108
+ state: AgentState,
109
+ result: TaskResult
110
+ ): Promise<HandoffDecision> {
111
+ const output = String(result.output).toLowerCase();
112
+
113
+ // Check if implementation is needed
114
+ if (
115
+ output.includes('to implement this') ||
116
+ output.includes('code needed') ||
117
+ output.includes('write the code')
118
+ ) {
119
+ return {
120
+ shouldHandoff: true,
121
+ targetAgent: 'coding',
122
+ reason: 'Research complete, implementation needed',
123
+ context: {
124
+ researchFindings: result.output,
125
+ },
126
+ };
127
+ }
128
+
129
+ // Check if automation is suggested
130
+ if (
131
+ output.includes('automate this process') ||
132
+ output.includes('schedule regular')
133
+ ) {
134
+ return {
135
+ shouldHandoff: true,
136
+ targetAgent: 'automation',
137
+ reason: 'Research suggests automation solution',
138
+ context: {
139
+ researchFindings: result.output,
140
+ },
141
+ };
142
+ }
143
+
144
+ return {
145
+ shouldHandoff: false,
146
+ reason: 'Research task complete',
147
+ };
148
+ }
149
+
150
+ // Additional research-specific methods
151
+ async summarize(content: string, maxLength: number = 500): Promise<TaskResult> {
152
+ return this.execute(
153
+ `Summarize the following content in ${maxLength} words or less:\n\n${content}`,
154
+ { task: 'summarization' }
155
+ );
156
+ }
157
+
158
+ async factCheck(claim: string): Promise<TaskResult> {
159
+ return this.execute(
160
+ `Fact-check the following claim and provide evidence:\n\n"${claim}"`,
161
+ { task: 'fact_checking' }
162
+ );
163
+ }
164
+
165
+ async compare(items: string[]): Promise<TaskResult> {
166
+ return this.execute(
167
+ `Compare and analyze the following items:\n\n${items.map((i, idx) => `${idx + 1}. ${i}`).join('\n')}`,
168
+ { task: 'comparison' }
169
+ );
170
+ }
171
+ }
172
+
173
+ export function createResearchAgent(tools: AgentConfig['tools'] = []): ResearchAgent {
174
+ return new ResearchAgent(tools);
175
+ }
176
+
177
+ export default ResearchAgent;
@@ -0,0 +1,124 @@
1
+ import type { BaseMessage } from '@langchain/core/messages';
2
+ import type { StructuredTool } from '@langchain/core/tools';
3
+
4
+ export type AgentRole = 'supervisor' | 'research' | 'coding' | 'automation' | 'chat';
5
+
6
+ export type AgentStatus = 'idle' | 'thinking' | 'executing' | 'waiting' | 'completed' | 'error';
7
+
8
+ export interface AgentIdentity {
9
+ id: string;
10
+ name: string;
11
+ role: AgentRole;
12
+ description: string;
13
+ capabilities: string[];
14
+ }
15
+
16
+ export interface AgentConfig {
17
+ identity: AgentIdentity;
18
+ model: string;
19
+ temperature?: number;
20
+ maxTokens?: number;
21
+ systemPrompt: string;
22
+ tools: StructuredTool[];
23
+ maxIterations?: number;
24
+ timeout?: number;
25
+ }
26
+
27
+ export interface AgentState {
28
+ messages: BaseMessage[];
29
+ currentTask?: TaskDefinition;
30
+ activeAgents: AgentIdentity[];
31
+ sharedContext: Record<string, unknown>;
32
+ taskResults: TaskResult[];
33
+ nextAgent: string | null;
34
+ memories: RetrievedMemory[];
35
+ iterationCount: number;
36
+ status: AgentStatus;
37
+ }
38
+
39
+ export interface TaskDefinition {
40
+ id: string;
41
+ type: 'research' | 'coding' | 'automation' | 'chat' | 'complex';
42
+ description: string;
43
+ input: string;
44
+ priority: 'low' | 'normal' | 'high' | 'critical';
45
+ requiredCapabilities: string[];
46
+ context?: Record<string, unknown>;
47
+ parentTaskId?: string;
48
+ subtasks?: TaskDefinition[];
49
+ deadline?: Date;
50
+ }
51
+
52
+ export interface TaskResult {
53
+ taskId: string;
54
+ agentId: string;
55
+ status: 'success' | 'partial' | 'failed';
56
+ output: unknown;
57
+ error?: string;
58
+ executionTimeMs: number;
59
+ metadata?: Record<string, unknown>;
60
+ }
61
+
62
+ export interface RetrievedMemory {
63
+ id: string;
64
+ type: 'semantic' | 'episodic' | 'procedural';
65
+ content: string;
66
+ relevanceScore: number;
67
+ metadata?: Record<string, unknown>;
68
+ }
69
+
70
+ export interface AgentMessage {
71
+ id: string;
72
+ timestamp: Date;
73
+ sender: AgentIdentity;
74
+ recipient: AgentIdentity | 'supervisor' | 'broadcast';
75
+ type: 'request' | 'response' | 'handoff' | 'status' | 'error';
76
+ payload: {
77
+ task?: TaskDefinition;
78
+ result?: TaskResult;
79
+ context?: Record<string, unknown>;
80
+ reason?: string;
81
+ };
82
+ metadata: {
83
+ priority: 'low' | 'normal' | 'high' | 'critical';
84
+ threadId: string;
85
+ parentMessageId?: string;
86
+ };
87
+ }
88
+
89
+ export interface HandoffDecision {
90
+ shouldHandoff: boolean;
91
+ targetAgent?: AgentRole;
92
+ reason: string;
93
+ context?: Record<string, unknown>;
94
+ }
95
+
96
+ export interface RoutingDecision {
97
+ selectedAgents: AgentRole[];
98
+ executionMode: 'single' | 'sequential' | 'parallel';
99
+ reasoning: string;
100
+ }
101
+
102
+ // Tool result types
103
+ export interface ToolResult {
104
+ success: boolean;
105
+ output: unknown;
106
+ error?: string;
107
+ }
108
+
109
+ // Agent events for logging/monitoring
110
+ export type AgentEventType =
111
+ | 'started'
112
+ | 'thinking'
113
+ | 'tool_call'
114
+ | 'tool_result'
115
+ | 'handoff'
116
+ | 'completed'
117
+ | 'error';
118
+
119
+ export interface AgentEvent {
120
+ type: AgentEventType;
121
+ agentId: string;
122
+ timestamp: Date;
123
+ data: Record<string, unknown>;
124
+ }
@@ -0,0 +1,305 @@
1
+ import { StateGraph, Annotation, END, START } from '@langchain/langgraph';
2
+ import { BaseMessage, HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
3
+ import { nanoid } from 'nanoid';
4
+
5
+ import { getAgentFactory } from '../agents/agent-factory.js';
6
+ import type {
7
+ AgentRole,
8
+ AgentState,
9
+ TaskDefinition,
10
+ TaskResult,
11
+ RetrievedMemory,
12
+ AgentIdentity,
13
+ } from '../agents/types.js';
14
+ import { createLogger } from '../../utils/logger.js';
15
+
16
+ const logger = createLogger('OrchestratorGraph');
17
+
18
+ // Define the state annotation for LangGraph
19
+ export const AgentStateAnnotation = Annotation.Root({
20
+ // Messages in the conversation
21
+ messages: Annotation<BaseMessage[]>({
22
+ reducer: (prev, next) => [...prev, ...next],
23
+ default: () => [],
24
+ }),
25
+
26
+ // Current user input
27
+ input: Annotation<string>({
28
+ reducer: (_, next) => next,
29
+ default: () => '',
30
+ }),
31
+
32
+ // Current task being processed
33
+ currentTask: Annotation<TaskDefinition | null>({
34
+ reducer: (_, next) => next,
35
+ default: () => null,
36
+ }),
37
+
38
+ // Active agents in this workflow
39
+ activeAgents: Annotation<AgentIdentity[]>({
40
+ reducer: (prev, next) => {
41
+ // Merge unique agents
42
+ const existing = new Set(prev.map((a) => a.id));
43
+ return [...prev, ...next.filter((a) => !existing.has(a.id))];
44
+ },
45
+ default: () => [],
46
+ }),
47
+
48
+ // Shared context for multi-agent coordination
49
+ sharedContext: Annotation<Record<string, unknown>>({
50
+ reducer: (prev, next) => ({ ...prev, ...next }),
51
+ default: () => ({}),
52
+ }),
53
+
54
+ // Results from completed tasks
55
+ taskResults: Annotation<TaskResult[]>({
56
+ reducer: (prev, next) => [...prev, ...next],
57
+ default: () => [],
58
+ }),
59
+
60
+ // Retrieved memories for context
61
+ memories: Annotation<RetrievedMemory[]>({
62
+ reducer: (_, next) => next,
63
+ default: () => [],
64
+ }),
65
+
66
+ // Next agent to route to
67
+ nextAgent: Annotation<AgentRole | 'end' | null>({
68
+ reducer: (_, next) => next,
69
+ default: () => null,
70
+ }),
71
+
72
+ // Final response to user
73
+ response: Annotation<string>({
74
+ reducer: (_, next) => next,
75
+ default: () => '',
76
+ }),
77
+
78
+ // Error state
79
+ error: Annotation<string | null>({
80
+ reducer: (_, next) => next,
81
+ default: () => null,
82
+ }),
83
+ });
84
+
85
+ export type GraphState = typeof AgentStateAnnotation.State;
86
+
87
+ // Supervisor node - analyzes and routes tasks
88
+ async function supervisorNode(state: GraphState): Promise<Partial<GraphState>> {
89
+ logger.debug('Supervisor analyzing task', { input: state.input?.slice(0, 100) });
90
+
91
+ const input = state.input.toLowerCase();
92
+
93
+ // Simple routing logic based on keywords
94
+ let nextAgent: AgentRole = 'chat';
95
+
96
+ // Research patterns
97
+ if (
98
+ input.includes('search') ||
99
+ input.includes('find') ||
100
+ input.includes('research') ||
101
+ input.includes('look up') ||
102
+ input.includes('what is') ||
103
+ input.includes('who is') ||
104
+ input.includes('investigate')
105
+ ) {
106
+ nextAgent = 'research';
107
+ }
108
+ // Coding patterns
109
+ else if (
110
+ input.includes('code') ||
111
+ input.includes('program') ||
112
+ input.includes('function') ||
113
+ input.includes('debug') ||
114
+ input.includes('fix bug') ||
115
+ input.includes('implement') ||
116
+ input.includes('write a script') ||
117
+ input.includes('refactor')
118
+ ) {
119
+ nextAgent = 'coding';
120
+ }
121
+ // Automation patterns
122
+ else if (
123
+ input.includes('automate') ||
124
+ input.includes('schedule') ||
125
+ input.includes('every day') ||
126
+ input.includes('every hour') ||
127
+ input.includes('batch') ||
128
+ input.includes('workflow') ||
129
+ input.includes('recurring')
130
+ ) {
131
+ nextAgent = 'automation';
132
+ }
133
+
134
+ const task: TaskDefinition = {
135
+ id: nanoid(),
136
+ type: nextAgent === 'chat' ? 'chat' : nextAgent === 'coding' ? 'coding' : nextAgent === 'research' ? 'research' : 'automation',
137
+ description: state.input,
138
+ input: state.input,
139
+ priority: 'normal',
140
+ requiredCapabilities: [],
141
+ };
142
+
143
+ logger.info('Task routed', { taskId: task.id, targetAgent: nextAgent });
144
+
145
+ return {
146
+ currentTask: task,
147
+ nextAgent,
148
+ sharedContext: {
149
+ routedAt: new Date().toISOString(),
150
+ routingReason: `Routed to ${nextAgent} based on input analysis`,
151
+ },
152
+ };
153
+ }
154
+
155
+ // Generic agent execution node
156
+ async function agentNode(
157
+ role: AgentRole
158
+ ): Promise<(state: GraphState) => Promise<Partial<GraphState>>> {
159
+ return async (state: GraphState): Promise<Partial<GraphState>> => {
160
+ logger.debug(`${role} agent executing`, { taskId: state.currentTask?.id });
161
+
162
+ const factory = getAgentFactory();
163
+ const agent = factory.getAgent(role);
164
+
165
+ try {
166
+ const result = await agent.execute(
167
+ state.input,
168
+ state.sharedContext,
169
+ state.memories
170
+ );
171
+
172
+ const aiMessage = new AIMessage({
173
+ content: String(result.output),
174
+ name: agent.name,
175
+ });
176
+
177
+ return {
178
+ messages: [aiMessage],
179
+ taskResults: [result],
180
+ response: String(result.output),
181
+ activeAgents: [agent.getInfo()],
182
+ nextAgent: 'end',
183
+ };
184
+ } catch (error) {
185
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
186
+ logger.error(`${role} agent failed`, { error: errorMessage });
187
+
188
+ return {
189
+ error: errorMessage,
190
+ response: `Sorry, I encountered an error: ${errorMessage}`,
191
+ nextAgent: 'end',
192
+ };
193
+ } finally {
194
+ factory.releaseAgent(agent.id);
195
+ }
196
+ };
197
+ }
198
+
199
+ // Create agent nodes
200
+ const chatAgentNode = await agentNode('chat');
201
+ const researchAgentNode = await agentNode('research');
202
+ const codingAgentNode = await agentNode('coding');
203
+ const automationAgentNode = await agentNode('automation');
204
+
205
+ // Routing function
206
+ function routeToAgent(state: GraphState): AgentRole | 'end' {
207
+ if (state.error) {
208
+ return 'end';
209
+ }
210
+ return state.nextAgent ?? 'chat';
211
+ }
212
+
213
+ // Build the graph
214
+ export function createOrchestratorGraph() {
215
+ const workflow = new StateGraph(AgentStateAnnotation)
216
+ // Add nodes
217
+ .addNode('supervisor', supervisorNode)
218
+ .addNode('chat', chatAgentNode)
219
+ .addNode('research', researchAgentNode)
220
+ .addNode('coding', codingAgentNode)
221
+ .addNode('automation', automationAgentNode)
222
+
223
+ // Entry point
224
+ .addEdge(START, 'supervisor')
225
+
226
+ // Conditional routing from supervisor
227
+ .addConditionalEdges('supervisor', routeToAgent, {
228
+ chat: 'chat',
229
+ research: 'research',
230
+ coding: 'coding',
231
+ automation: 'automation',
232
+ end: END,
233
+ })
234
+
235
+ // All agents go to END after execution
236
+ .addEdge('chat', END)
237
+ .addEdge('research', END)
238
+ .addEdge('coding', END)
239
+ .addEdge('automation', END);
240
+
241
+ return workflow.compile();
242
+ }
243
+
244
+ // Singleton graph instance
245
+ let graphInstance: ReturnType<typeof createOrchestratorGraph> | null = null;
246
+
247
+ export function getOrchestratorGraph() {
248
+ if (!graphInstance) {
249
+ graphInstance = createOrchestratorGraph();
250
+ }
251
+ return graphInstance;
252
+ }
253
+
254
+ // Helper function to run the orchestrator
255
+ export async function runOrchestrator(
256
+ input: string,
257
+ context: Record<string, unknown> = {},
258
+ memories: RetrievedMemory[] = []
259
+ ): Promise<GraphState> {
260
+ const graph = getOrchestratorGraph();
261
+
262
+ const initialState: Partial<GraphState> = {
263
+ input,
264
+ messages: [new HumanMessage(input)],
265
+ sharedContext: context,
266
+ memories,
267
+ };
268
+
269
+ logger.info('Orchestrator started', { input: input.slice(0, 100) });
270
+
271
+ const result = await graph.invoke(initialState);
272
+
273
+ logger.info('Orchestrator completed', {
274
+ response: result.response?.slice(0, 100),
275
+ agentsUsed: result.activeAgents?.map((a) => a.name),
276
+ });
277
+
278
+ return result as GraphState;
279
+ }
280
+
281
+ // Stream execution
282
+ export async function* streamOrchestrator(
283
+ input: string,
284
+ context: Record<string, unknown> = {},
285
+ memories: RetrievedMemory[] = []
286
+ ): AsyncGenerator<{ node: string; state: Partial<GraphState> }, void, unknown> {
287
+ const graph = getOrchestratorGraph();
288
+
289
+ const initialState: Partial<GraphState> = {
290
+ input,
291
+ messages: [new HumanMessage(input)],
292
+ sharedContext: context,
293
+ memories,
294
+ };
295
+
296
+ const stream = await graph.stream(initialState);
297
+
298
+ for await (const chunk of stream) {
299
+ for (const [node, state] of Object.entries(chunk)) {
300
+ yield { node, state: state as Partial<GraphState> };
301
+ }
302
+ }
303
+ }
304
+
305
+ export default { createOrchestratorGraph, getOrchestratorGraph, runOrchestrator, streamOrchestrator };
@@ -0,0 +1,109 @@
1
+ import pg from 'pg';
2
+ import { getDatabaseUrl } from '../config/index.js';
3
+ import { createLogger } from '../utils/logger.js';
4
+ import { MemoryError } from '../utils/errors.js';
5
+
6
+ const { Pool } = pg;
7
+ const logger = createLogger('Database');
8
+
9
+ let pool: pg.Pool | null = null;
10
+
11
+ export function getPool(): pg.Pool {
12
+ if (!pool) {
13
+ pool = new Pool({
14
+ connectionString: getDatabaseUrl(),
15
+ max: 20,
16
+ idleTimeoutMillis: 30000,
17
+ connectionTimeoutMillis: 5000,
18
+ });
19
+
20
+ pool.on('error', (err) => {
21
+ logger.error('Unexpected pool error', { error: err.message });
22
+ });
23
+
24
+ pool.on('connect', () => {
25
+ logger.debug('New client connected to pool');
26
+ });
27
+ }
28
+
29
+ return pool;
30
+ }
31
+
32
+ export async function query<T extends pg.QueryResultRow = pg.QueryResultRow>(
33
+ text: string,
34
+ params?: unknown[]
35
+ ): Promise<pg.QueryResult<T>> {
36
+ const pool = getPool();
37
+ const start = Date.now();
38
+
39
+ try {
40
+ const result = await pool.query<T>(text, params);
41
+ const duration = Date.now() - start;
42
+ logger.debug('Query executed', { duration, rows: result.rowCount });
43
+ return result;
44
+ } catch (error) {
45
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
46
+ logger.error('Query failed', { error: errorMessage, query: text.slice(0, 100) });
47
+ throw new MemoryError(`Database query failed: ${errorMessage}`);
48
+ }
49
+ }
50
+
51
+ export async function getClient(): Promise<pg.PoolClient> {
52
+ const pool = getPool();
53
+ return pool.connect();
54
+ }
55
+
56
+ export async function transaction<T>(
57
+ callback: (client: pg.PoolClient) => Promise<T>
58
+ ): Promise<T> {
59
+ const client = await getClient();
60
+
61
+ try {
62
+ await client.query('BEGIN');
63
+ const result = await callback(client);
64
+ await client.query('COMMIT');
65
+ return result;
66
+ } catch (error) {
67
+ await client.query('ROLLBACK');
68
+ throw error;
69
+ } finally {
70
+ client.release();
71
+ }
72
+ }
73
+
74
+ export async function healthCheck(): Promise<boolean> {
75
+ try {
76
+ const result = await query('SELECT 1 as health');
77
+ return result.rows.length > 0;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ export async function closePool(): Promise<void> {
84
+ if (pool) {
85
+ await pool.end();
86
+ pool = null;
87
+ logger.info('Database pool closed');
88
+ }
89
+ }
90
+
91
+ // Initialize the database connection
92
+ export async function initDatabase(): Promise<void> {
93
+ const healthy = await healthCheck();
94
+ if (healthy) {
95
+ logger.info('Database connection established');
96
+ } else {
97
+ throw new MemoryError('Failed to connect to database');
98
+ }
99
+ }
100
+
101
+ export default {
102
+ getPool,
103
+ query,
104
+ getClient,
105
+ transaction,
106
+ healthCheck,
107
+ closePool,
108
+ initDatabase,
109
+ };