/ internal / tools / memory_append.go
memory_append.go
 1  package tools
 2  
 3  import (
 4  	"context"
 5  	"encoding/json"
 6  	"fmt"
 7  	"strings"
 8  
 9  	"github.com/Kocoro-lab/ShanClaw/internal/agent"
10  	ctxwin "github.com/Kocoro-lab/ShanClaw/internal/context"
11  )
12  
13  // MemoryAppendTool appends entries to the agent's MEMORY.md via BoundedAppend,
14  // preventing concurrent sessions from clobbering each other's writes and
15  // overflowing to detail files when the line limit is reached.
16  // The memory directory is read from context (set by AgentLoop.Run).
17  type MemoryAppendTool struct{}
18  
19  type memoryAppendArgs struct {
20  	Content string `json:"content"`
21  }
22  
23  func (t *MemoryAppendTool) Info() agent.ToolInfo {
24  	return agent.ToolInfo{
25  		Name:        "memory_append",
26  		Description: "Append new entries to MEMORY.md. Use this instead of file_write or file_edit for memory updates. Writes are atomic, flock-protected, and auto-overflow to detail files when MEMORY.md exceeds the line limit.",
27  		Parameters: map[string]any{
28  			"type": "object",
29  			"properties": map[string]any{
30  				"content": map[string]any{
31  					"type":        "string",
32  					"description": "New entries to append (markdown bullet points)",
33  				},
34  			},
35  		},
36  		Required: []string{"content"},
37  	}
38  }
39  
40  func (t *MemoryAppendTool) Run(ctx context.Context, argsJSON string) (agent.ToolResult, error) {
41  	var args memoryAppendArgs
42  	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
43  		return agent.ToolResult{Content: fmt.Sprintf("invalid arguments: %v", err), IsError: true}, nil
44  	}
45  
46  	if strings.TrimSpace(args.Content) == "" {
47  		return agent.ToolResult{Content: "content must not be empty", IsError: true}, nil
48  	}
49  
50  	memDir := agent.MemoryDirFromContext(ctx)
51  	if memDir == "" {
52  		return agent.ToolResult{Content: "memory not configured for this agent", IsError: true}, nil
53  	}
54  
55  	if err := ctxwin.BoundedAppend(memDir, args.Content); err != nil {
56  		return agent.ToolResult{Content: fmt.Sprintf("error appending: %v", err), IsError: true}, nil
57  	}
58  
59  	return agent.ToolResult{Content: fmt.Sprintf("appended to %s/MEMORY.md", memDir)}, nil
60  }
61  
62  func (t *MemoryAppendTool) RequiresApproval() bool { return false }
63  
64  func (t *MemoryAppendTool) IsReadOnlyCall(string) bool { return false }