tuna-agent 0.1.25 → 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
  }
@@ -255,7 +257,9 @@ export class ClaudeCodeAdapter {
255
257
  console.log(`[ClaudeCode] No follow-up after ${FOLLOW_UP_TIMEOUT_MS / 1000}s — closing task`);
256
258
  pendingInputResolvers.delete(task.id);
257
259
  const timeoutOutput = lastTaskOutput || 'Task completed (no follow-up)';
258
- this.runReflection(task, timeoutOutput, 'done', task.repoPath).catch(() => { });
260
+ this.runReflection(task, timeoutOutput, 'done', task.repoPath)
261
+ .then(() => this.runSelfImprovement(task.repoPath))
262
+ .catch(() => { });
259
263
  return;
260
264
  }
261
265
  throw err;
@@ -287,7 +291,9 @@ export class ClaudeCodeAdapter {
287
291
  ws.sendPMMessage(task.id, { sender: 'pm', content: 'Task completed.' });
288
292
  console.log(`[ClaudeCode] Agent Team task ${task.id} completed (${(totalDurationMs / 1000).toFixed(1)}s)`);
289
293
  // Post-task reflection with actual output (non-blocking)
290
- 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(() => { });
291
297
  }
292
298
  finally {
293
299
  cleanupAttachments(task.id);
@@ -730,6 +736,60 @@ export class ClaudeCodeAdapter {
730
736
  console.warn(`[Rating→Mem0] Failed:`, err instanceof Error ? err.message : err);
731
737
  }
732
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
+ }
733
793
  async dispose() {
734
794
  // No persistent resources to clean up
735
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.25",
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"