]> git.giacomolli.uk - onechat/blobdiff - tools.go
Give the root tools to start and message children
[onechat] / tools.go
index 1cb098a564a0d968d6b447bba55f8c1b11defab7..b4fb075b40a5cc7b6a6086417e1bce375a4bfc79 100644 (file)
--- a/tools.go
+++ b/tools.go
@@ -49,11 +49,12 @@ type ToolChat interface {
 // the log as it happens: the call as a tool message, the result as an echo
 // message. The call is durable before its tool runs, so the log never misses an
 // action outside the agent. Commands and files run as the user in the working
-// directory, with no sandbox. Zoom with a name reads the log of a subagent. The
-// toolbox of the agent has the runs of its subagents for spawn and tell, the
-// WhatsApp link for the WhatsApp tools, and the schedules for the schedule
-// tools. The toolbox of a subagent has none of them, so these are unknown tools
-// there.
+// directory, with no sandbox. Zoom with a name reads a child's view or a
+// subagent's log. The toolbox of the agent has the runs of its subagents for
+// spawn and tell, the WhatsApp link for the WhatsApp tools, and the schedules
+// for the schedule tools. Each permanent agent has the parent-child queue. Only
+// the root has child creation. A child has no WhatsApp link or child creation.
+// The toolbox of a subagent has none of them, so these are unknown tools there.
 type Toolbox struct {
        log       ToolLog
        chat      ToolChat
@@ -62,6 +63,9 @@ type Toolbox struct {
        whatsapp  *WhatsApp
        schedules *Schedules
        workdir   string
+       agentName string
+       messages  *DaemonMessages
+       children  *DaemonChildren
 }
 
 // Call logs the call of the tool name with its JSON arguments, makes the log
@@ -94,11 +98,15 @@ func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, err
 
 // run runs one tool and returns its output, or its error as text.
 func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
-       var output string
-       var err error
+       var (
+               output string
+               err    error
+       )
+
        switch name {
        case "zoom":
                var args ToolZoomArguments
+
                err = toolDecodeArguments(arguments, &args)
                switch {
                case err != nil:
@@ -109,36 +117,42 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
                }
        case "date":
                var args ToolDateArguments
+
                err = toolDecodeArguments(arguments, &args)
                if err == nil {
                        output, err = b.date(args.ID)
                }
        case "shell":
                var args ToolShellArguments
+
                err = toolDecodeArguments(arguments, &args)
                if err == nil {
                        output = b.shell(ctx, args.Command)
                }
        case "read":
                var args ToolPathArguments
+
                err = toolDecodeArguments(arguments, &args)
                if err == nil {
                        output, err = b.read(args.Path)
                }
        case "write":
                var args ToolWriteArguments
+
                err = toolDecodeArguments(arguments, &args)
                if err == nil {
                        output, err = b.write(args.Path, args.Text)
                }
        case "edit":
                var args ToolEditArguments
+
                err = toolDecodeArguments(arguments, &args)
                if err == nil {
                        output, err = b.edit(args.Path, args.Old, args.New)
                }
        case "spawn":
                var args ToolSpawnArguments
+
                err = toolDecodeArguments(arguments, &args)
                switch {
                case b.runs == nil:
@@ -146,17 +160,32 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
                case err == nil:
                        output, err = b.runs.Spawn(ctx, args.Tasks, args.Effort)
                }
+       case "agent_create":
+               var args ToolAgentCreateArguments
+
+               err = toolDecodeArguments(arguments, &args)
+               switch {
+               case b.children == nil:
+                       err = fmt.Errorf("unknown tool %q", name)
+               case err == nil:
+                       output, err = b.children.Create(args.Name, args.Responsibility)
+               }
        case "tell":
                var args ToolTellArguments
+
                err = toolDecodeArguments(arguments, &args)
                switch {
                case b.runs == nil:
                        err = fmt.Errorf("unknown tool %q", name)
+               case err == nil && b.messages != nil && b.messages.Child(b.agentName, args.Name) != nil:
+                       err = b.messages.Tell(b.agentName, args.Name, args.Message)
+                       output = "Sent. " + args.Name + "'s reply will reach you as a message."
                case err == nil:
                        output, err = b.runs.Tell(ctx, args.Name, args.Message)
                }
        case "whatsapp_find":
                var args ToolWhatsAppFindArguments
+
                err = toolDecodeArguments(arguments, &args)
                switch {
                case b.whatsapp == nil:
@@ -166,6 +195,7 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
                }
        case "whatsapp_send":
                var args ToolWhatsAppSendArguments
+
                err = toolDecodeArguments(arguments, &args)
                switch {
                case b.whatsapp == nil:
@@ -175,6 +205,7 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
                }
        case "schedule_set":
                var args ToolScheduleSetArguments
+
                err = toolDecodeArguments(arguments, &args)
                switch {
                case b.schedules == nil:
@@ -192,6 +223,7 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
                }
        case "schedule_remove":
                var args ToolScheduleRemoveArguments
+
                err = toolDecodeArguments(arguments, &args)
                switch {
                case b.schedules == nil:
@@ -210,8 +242,15 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
        return output
 }
 
-// zoomAgent returns the whole log of the subagent name.
+// zoomAgent returns a child's view or the whole log of a subagent.
 func (b *Toolbox) zoomAgent(ctx context.Context, name string) (string, error) {
+       if b.messages != nil {
+               child := b.messages.Child(b.agentName, name)
+               if child != nil {
+                       return strings.Join(child.memory.ViewLines(nil), ""), nil
+               }
+       }
+
        subagent, err := b.subagents.Get(ctx, name)
        if err != nil {
                return "", err
@@ -241,7 +280,9 @@ func (b *Toolbox) zoom(id, n int) (string, error) {
        }
 
        level := bits.TrailingZeros(uint(n)) - 1
+
        var lines []string
+
        for _, index := range []int{2 * id / n, 2*id/n + 1} {
                node, built, err := b.chat.Node(level, index)
                if err != nil {
@@ -298,7 +339,9 @@ func (b *Toolbox) shell(ctx context.Context, command string) string {
        err := cmd.Run()
 
        text := output.String()
+
        var exit *exec.ExitError
+
        switch {
        case ctx.Err() != nil:
                text += "\n[interrupted]"
@@ -391,8 +434,9 @@ func (b *Toolbox) path(path string) string {
        return filepath.Join(b.workdir, path)
 }
 
-// ToolZoomArguments are the arguments of zoom. A Name that is not nil names
-// the subagent whose whole log zoom gives, and then ID and N are not used.
+// ToolZoomArguments are the arguments of zoom. A Name that is not nil names the
+// child whose view or the subagent whose whole log zoom gives. ID and N are
+// then not used.
 type ToolZoomArguments struct {
        ID   int     `json:"id"`
        N    int     `json:"n"`
@@ -434,6 +478,12 @@ type ToolSpawnArguments struct {
        Effort *string  `json:"effort"`
 }
 
+// ToolAgentCreateArguments are the arguments of agent_create.
+type ToolAgentCreateArguments struct {
+       Name           string `json:"name"`
+       Responsibility string `json:"responsibility"`
+}
+
 // ToolTellArguments are the arguments of tell.
 type ToolTellArguments struct {
        Name    string `json:"name"`
@@ -530,6 +580,7 @@ func toolClip(text string) string {
 func toolDecodeArguments(arguments string, args any) error {
        decoder := json.NewDecoder(strings.NewReader(arguments))
        decoder.DisallowUnknownFields()
+
        err := decoder.Decode(args)
        if err != nil {
                return fmt.Errorf("arguments: %w", err)