init
diff --git a/loop/anthropic.go b/loop/anthropic.go
new file mode 100644
index 0000000..8ff5d6b
--- /dev/null
+++ b/loop/anthropic.go
@@ -0,0 +1,103 @@
+package loop
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+
+ "dodo.cloud/neo/tools"
+
+ "github.com/anthropics/anthropic-sdk-go"
+)
+
+type AnthropicAgent struct {
+ reg tools.Registry
+ client anthropic.Client
+}
+
+func (a *AnthropicAgent) Run(todo *ToDo) error {
+ sp := fmt.Sprintf(systemPrompt, ToDoJSONSchema())
+ var toolParams []anthropic.ToolParam
+ for _, t := range a.reg.All() {
+ schema, err := GetToolSchema(t.InputSchema())
+ if err != nil {
+ return err
+ }
+ toolParams = append(toolParams, anthropic.ToolParam{
+ Name: t.Name(),
+ Description: anthropic.String(t.Description()),
+ InputSchema: schema,
+ })
+ }
+ tools := make([]anthropic.ToolUnionParam, len(toolParams))
+ for i, toolParam := range toolParams {
+ tools[i] = anthropic.ToolUnionParam{OfTool: &toolParam}
+ }
+ k, err := json.MarshalIndent(todo, "", "\t")
+ if err != nil {
+ return err
+ }
+ for {
+ items := findActionableItems(todo, "assistant")
+ if len(items) == 0 {
+ time.Sleep(30 * time.Second)
+ continue
+ }
+ var itemIds []string
+ var messages []anthropic.MessageParam
+ for _, i := range items {
+ itemIds = append(itemIds, i.ID)
+ }
+ messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock(string(k))))
+ messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock(
+ fmt.Sprintf("Work on TODO item: %s", strings.Join(itemIds, ", ")))))
+ for {
+ resp, err := a.client.Messages.New(context.TODO(), anthropic.MessageNewParams{
+ MaxTokens: 10240,
+ System: []anthropic.TextBlockParam{
+ {Text: sp},
+ },
+ Messages: messages,
+ Model: anthropic.ModelClaudeOpus4_6,
+ Tools: tools,
+ })
+ if err != nil {
+ return err
+ }
+ fmt.Printf("--- STOP_REASON: %s\n", resp.StopReason)
+ messages = append(messages, resp.ToParam())
+
+ var toolResults []anthropic.ContentBlockParamUnion
+ for _, block := range resp.Content {
+ switch v := block.AsAny().(type) {
+ case anthropic.TextBlock:
+ fmt.Printf("AI: %s\n", v.Text)
+ case anthropic.ToolUseBlock:
+ t := a.reg.Get(v.Name)
+ if t == nil {
+ toolResults = append(toolResults, anthropic.NewToolResultBlock(v.ID, fmt.Sprintf("unknown tool %q", v.Name), true))
+ continue
+ }
+ args := v.JSON.Input.Raw()
+ fmt.Printf("CALLING TOOL: %s %s\n", v.Name, args)
+ out, err := t.Call(string(args))
+ if err != nil {
+ fmt.Printf("ERR: %s\n", err.Error())
+ toolResults = append(toolResults, anthropic.NewToolResultBlock(v.ID, err.Error(), true))
+ } else {
+ toolResults = append(toolResults, anthropic.NewToolResultBlock(v.ID, out, false))
+ }
+ }
+ }
+ if len(toolResults) == 0 {
+ break
+ }
+ messages = append(messages, anthropic.NewUserMessage(toolResults...))
+ }
+ for _, i := range items {
+ i.AssignedTo = "user"
+ }
+ }
+}