tuna-agent 0.1.24 → 0.1.26

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.
@@ -5,6 +5,8 @@ export declare class ClaudeCodeAdapter implements AgentAdapter {
5
5
  readonly type: AgentType;
6
6
  readonly displayName = "Claude Code";
7
7
  private readonly agentConfig;
8
+ private taskCount;
9
+ private static readonly PATTERN_CHECK_INTERVAL;
8
10
  constructor(config: AgentConfig);
9
11
  checkHealth(): Promise<{
10
12
  ok: boolean;
@@ -26,5 +28,10 @@ export declare class ClaudeCodeAdapter implements AgentAdapter {
26
28
  comment?: string;
27
29
  cwd: string;
28
30
  }): Promise<void>;
31
+ /**
32
+ * Self-Improvement: detect patterns from Mem0 and update CLAUDE.md.
33
+ * Runs every N tasks to evolve the agent's permanent knowledge.
34
+ */
35
+ runSelfImprovement(cwd: string): Promise<void>;
29
36
  dispose(): Promise<void>;
30
37
  }
@@ -12,6 +12,8 @@ export class ClaudeCodeAdapter {
12
12
  type = 'claude-code';
13
13
  displayName = 'Claude Code';
14
14
  agentConfig;
15
+ taskCount = 0;
16
+ static PATTERN_CHECK_INTERVAL = 5; // Check patterns every N tasks
15
17
  constructor(config) {
16
18
  this.agentConfig = config;
17
19
  }
@@ -206,6 +208,9 @@ export class ClaudeCodeAdapter {
206
208
  }
207
209
  // Track last output for reflection
208
210
  lastTaskOutput = turnAccumulatedText.trim();
211
+ if (lastTaskOutput) {
212
+ console.log(`[Reflection] Captured ${lastTaskOutput.length} chars of task output for reflection`);
213
+ }
209
214
  // Send finalized message for the last turn's remaining text
210
215
  if (turnAccumulatedText.trim()) {
211
216
  ws.sendPMMessage(task.id, {
@@ -252,7 +257,9 @@ export class ClaudeCodeAdapter {
252
257
  console.log(`[ClaudeCode] No follow-up after ${FOLLOW_UP_TIMEOUT_MS / 1000}s — closing task`);
253
258
  pendingInputResolvers.delete(task.id);
254
259
  const timeoutOutput = lastTaskOutput || 'Task completed (no follow-up)';
255
- this.runReflection(task, timeoutOutput, 'done', task.repoPath).catch(() => { });
260
+ this.runReflection(task, timeoutOutput, 'done', task.repoPath)
261
+ .then(() => this.runSelfImprovement(task.repoPath))
262
+ .catch(() => { });
256
263
  return;
257
264
  }
258
265
  throw err;
@@ -284,7 +291,9 @@ export class ClaudeCodeAdapter {
284
291
  ws.sendPMMessage(task.id, { sender: 'pm', content: 'Task completed.' });
285
292
  console.log(`[ClaudeCode] Agent Team task ${task.id} completed (${(totalDurationMs / 1000).toFixed(1)}s)`);
286
293
  // Post-task reflection with actual output (non-blocking)
287
- this.runReflection(task, lastTaskOutput || 'Task completed without text output', 'done', task.repoPath).catch(() => { });
294
+ this.runReflection(task, lastTaskOutput || 'Task completed without text output', 'done', task.repoPath)
295
+ .then(() => this.runSelfImprovement(task.repoPath))
296
+ .catch(() => { });
288
297
  }
289
298
  finally {
290
299
  cleanupAttachments(task.id);
@@ -677,7 +686,7 @@ export class ClaudeCodeAdapter {
677
686
  return;
678
687
  try {
679
688
  // Step 1: Generate AI-powered reflection via Ollama
680
- console.log(`[Reflection] Generating AI reflection for task ${task.id} (${status})`);
689
+ console.log(`[Reflection] Generating AI reflection for task ${task.id} (${status}), input: ${resultSummary.substring(0, 150)}...`);
681
690
  const { callMem0Reflect, callMem0AddMemory } = await import('../mcp/setup.js');
682
691
  const aiReflection = await callMem0Reflect(task.description, resultSummary, status);
683
692
  if (!aiReflection) {
@@ -727,6 +736,60 @@ export class ClaudeCodeAdapter {
727
736
  console.warn(`[Rating→Mem0] Failed:`, err instanceof Error ? err.message : err);
728
737
  }
729
738
  }
739
+ /**
740
+ * Self-Improvement: detect patterns from Mem0 and update CLAUDE.md.
741
+ * Runs every N tasks to evolve the agent's permanent knowledge.
742
+ */
743
+ async runSelfImprovement(cwd) {
744
+ if (!process.env.MEM0_SSH_HOST)
745
+ return;
746
+ this.taskCount++;
747
+ if (this.taskCount % ClaudeCodeAdapter.PATTERN_CHECK_INTERVAL !== 0)
748
+ return;
749
+ try {
750
+ console.log(`[Self-Improve] Running pattern detection (every ${ClaudeCodeAdapter.PATTERN_CHECK_INTERVAL} tasks, count=${this.taskCount})`);
751
+ const { callMem0Patterns } = await import('../mcp/setup.js');
752
+ const patterns = await callMem0Patterns(this.agentConfig.name, 3);
753
+ if (patterns.length === 0) {
754
+ console.log(`[Self-Improve] No patterns detected yet`);
755
+ return;
756
+ }
757
+ // Read current CLAUDE.md to check for existing rules
758
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
759
+ let existingContent = '';
760
+ if (fs.existsSync(claudeMdPath)) {
761
+ existingContent = fs.readFileSync(claudeMdPath, 'utf-8');
762
+ }
763
+ // Filter out patterns that are already in CLAUDE.md
764
+ const newPatterns = patterns.filter(p => {
765
+ // Simple dedup: check if rule text (or close match) already exists
766
+ const ruleNormalized = p.rule.toLowerCase().replace(/[^a-z0-9\s]/g, '');
767
+ return !existingContent.toLowerCase().includes(ruleNormalized.substring(0, 50));
768
+ });
769
+ if (newPatterns.length === 0) {
770
+ console.log(`[Self-Improve] ${patterns.length} patterns found but all already in CLAUDE.md`);
771
+ return;
772
+ }
773
+ // Append new rules to CLAUDE.md under "## Learned Rules"
774
+ const SECTION_HEADER = '## Learned Rules';
775
+ const rulesBlock = newPatterns.map(p => `- ${p.rule} (confidence: ${p.confidence})`).join('\n');
776
+ if (existingContent.includes(SECTION_HEADER)) {
777
+ // Append to existing section
778
+ const updatedContent = existingContent.replace(SECTION_HEADER, `${SECTION_HEADER}\n${rulesBlock}`);
779
+ fs.writeFileSync(claudeMdPath, updatedContent);
780
+ }
781
+ else {
782
+ // Add new section at the end
783
+ const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
784
+ fs.writeFileSync(claudeMdPath, existingContent + separator + `${SECTION_HEADER}\n${rulesBlock}\n`);
785
+ }
786
+ console.log(`[Self-Improve] Added ${newPatterns.length} new rules to CLAUDE.md:`);
787
+ newPatterns.forEach(p => console.log(`[Self-Improve] - ${p.rule}`));
788
+ }
789
+ catch (err) {
790
+ console.warn(`[Self-Improve] Failed:`, err instanceof Error ? err.message : err);
791
+ }
792
+ }
730
793
  async dispose() {
731
794
  // No persistent resources to clean up
732
795
  }
@@ -9,6 +9,15 @@ export declare function callMem0AddMemory(text: string, agentName: string): Prom
9
9
  * Returns array of memory texts sorted by relevance.
10
10
  */
11
11
  export declare function callMem0SearchMemory(query: string, agentName: string, limit?: number): Promise<string[]>;
12
+ /**
13
+ * Detect patterns from accumulated memories via mem0-patterns script.
14
+ * Returns synthesized rules from clusters of similar memories.
15
+ */
16
+ export declare function callMem0Patterns(agentName: string, minCluster?: number): Promise<Array<{
17
+ rule: string;
18
+ confidence: number;
19
+ sources: string[];
20
+ }>>;
12
21
  /**
13
22
  * Generate AI-powered reflection from task results using Ollama.
14
23
  * Calls mem0-reflect script on relabs01 via SSH.
package/dist/mcp/setup.js CHANGED
@@ -124,6 +124,51 @@ export async function callMem0SearchMemory(query, agentName, limit = 5) {
124
124
  });
125
125
  });
126
126
  }
127
+ /**
128
+ * Detect patterns from accumulated memories via mem0-patterns script.
129
+ * Returns synthesized rules from clusters of similar memories.
130
+ */
131
+ export async function callMem0Patterns(agentName, minCluster = 3) {
132
+ if (!MEM0_SSH_HOST)
133
+ return [];
134
+ const { execFile } = await import('child_process');
135
+ const safeAgentName = agentName.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'agent';
136
+ const input = JSON.stringify({ user_id: safeAgentName, min_cluster: minCluster });
137
+ return new Promise((resolve) => {
138
+ let cmd;
139
+ let args;
140
+ let options = {};
141
+ if (MEM0_SSH_HOST === 'local') {
142
+ cmd = 'mem0-patterns';
143
+ args = [];
144
+ options.env = { ...process.env };
145
+ }
146
+ else {
147
+ cmd = 'ssh';
148
+ args = ['-p', MEM0_SSH_PORT, '-o', 'StrictHostKeyChecking=no'];
149
+ if (MEM0_SSH_KEY)
150
+ args.push('-i', MEM0_SSH_KEY);
151
+ args.push(MEM0_SSH_HOST, 'mem0-patterns');
152
+ }
153
+ const child = execFile(cmd, args, { ...options, timeout: 120000 }, (err, stdout) => {
154
+ if (err) {
155
+ console.warn(`[Mem0 Patterns] Failed: ${err.message}`);
156
+ resolve([]);
157
+ return;
158
+ }
159
+ try {
160
+ const data = JSON.parse(stdout.trim());
161
+ console.log(`[Mem0 Patterns] ${data.total_memories} memories, ${data.clusters} clusters, ${(data.patterns || []).length} patterns found`);
162
+ resolve(data.patterns || []);
163
+ }
164
+ catch {
165
+ resolve([]);
166
+ }
167
+ });
168
+ child.stdin?.write(input);
169
+ child.stdin?.end();
170
+ });
171
+ }
127
172
  /**
128
173
  * Generate AI-powered reflection from task results using Ollama.
129
174
  * Calls mem0-reflect script on relabs01 via SSH.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"