]> git.giacomolli.uk - onechat/blobdiff - prompt.go
Store the subagents and read them with zoom
[onechat] / prompt.go
index b536a36d9658722969d477bd936ad176f1e69b2c..71671c510caa6b0c3ec837de6555f1ecc5807e76 100644 (file)
--- a/prompt.go
+++ b/prompt.go
@@ -9,24 +9,25 @@ const (
        // promptName stands for the name of the agent in promptText.
        promptName = "{name}"
 
        // promptName stands for the name of the agent in promptText.
        promptName = "{name}"
 
-       // taskSentence is the sentence of promptText that gets instructionsClause
-       // when the user has instructions.
-       taskSentence = "Do the user's tasks yourself, with your tools."
+       // promptTaskSentence is the sentence of promptText that gets
+       // promptInstructionsClause when the user has instructions.
+       promptTaskSentence = "Do the user's tasks yourself, with your tools."
 
 
-       // instructionsClause points to the instructions of the user at the end
-       // of the prompt.
-       instructionsClause = ", following the user's instructions\nat the end of this prompt: who they are, how their files are organized and how\nthey want work done"
+       // promptInstructionsClause points to the instructions of the user at
+       // the end of the prompt.
+       promptInstructionsClause = ", following the user's instructions\nat the end of this prompt: who they are, how their files are organized and how\nthey want work done"
 
 
-       // instructionsHeading starts the instructions of the user after
+       // promptInstructionsHeading starts the instructions of the user after
        // promptText.
        // promptText.
-       instructionsHeading = "\n\n# The user's instructions\n\n"
+       promptInstructionsHeading = "\n\n# The user's instructions\n\n"
 )
 
 // promptText is the system prompt of the reference design, nearly word for
 // word. The reference design names its own agent, so promptText has the name of
 // the agent and the kind reply in its place. It describes only what OneChat can
 // do, so it leaves out the spec text on images, subagents, computers, and
 )
 
 // promptText is the system prompt of the reference design, nearly word for
 // word. The reference design names its own agent, so promptText has the name of
 // the agent and the kind reply in its place. It describes only what OneChat can
 // do, so it leaves out the spec text on images, subagents, computers, and
-// background tasks. prompt adds the clause on the instructions of the user only
+// background tasks. Its paragraph on persistent mode tells the model that a
+// final answer ends the turn. prompt adds the clause on the instructions of the user only
 // when there are instructions. It holds no date and no state, so turns and
 // compactions share its prompt cache.
 const promptText = `You are {name}, an AI agent that works for one user in a single chat that never
 // when there are instructions. It holds no date and no state, so turns and
 // compactions share its prompt cache.
 const promptText = `You are {name}, an AI agent that works for one user in a single chat that never
@@ -57,6 +58,7 @@ long for one message is split over several in a row.
 Tools:
 - zoom(id, n) opens line id+n into the two lines it was made from;
 - zoom(id, 1) gives message id whole
 Tools:
 - zoom(id, n) opens line id+n into the two lines it was made from;
 - zoom(id, 1) gives message id whole
+- zoom("Name") gives an agent's whole chat
 - date(id) gives the date and time of message id
 
 # Turns
 - date(id) gives the date and time of message id
 
 # Turns
@@ -72,6 +74,13 @@ say in your reply what you learned that will matter later.
 
 Messages the user sends while you work reach you between tool calls.
 
 
 Messages the user sends while you work reach you between tool calls.
 
+You are in persistent mode: a reply with no tool call is your final answer, and
+it ends your turn. You act again only when the user writes. So keep going until
+the user's task is done, or until only the user can unblock it. Answer a
+question asked in the middle of a task, then go on with the task in the same
+turn. When the user says "continue", resume the latest unfinished task from the
+view.
+
 # Compactions
 
 You write {name}'s memory: one step of the tree, compressing one message into a
 # Compactions
 
 You write {name}'s memory: one step of the tree, compressing one message into a
@@ -118,23 +127,24 @@ func prompt(name, instructions string) string {
                return text
        }
 
                return text
        }
 
-       text = strings.Replace(text, taskSentence, strings.TrimSuffix(taskSentence, ".")+instructionsClause+".", 1)
+       text = strings.Replace(text, promptTaskSentence, strings.TrimSuffix(promptTaskSentence, ".")+promptInstructionsClause+".", 1)
 
 
-       return text + instructionsHeading + instructions
+       return text + promptInstructionsHeading + instructions
 }
 
 }
 
-// agentTools returns the function tools of every call: zoom, date, shell,
+// promptTools returns the function tools of every call: zoom, date, shell,
 // read, write, and edit. A compaction sends them too and never calls them,
 // because a change of the tools ends the shared prompt cache.
 // read, write, and edit. A compaction sends them too and never calls them,
 // because a change of the tools ends the shared prompt cache.
-func agentTools() []Tool {
-       return []Tool{
+func promptTools() []ModelTool {
+       return []ModelTool{
                {
                        Type:        "function",
                        Name:        "zoom",
                {
                        Type:        "function",
                        Name:        "zoom",
-                       Description: "Open the line id+n of the view into the two lines of n/2 under it; n = 1 gives the message whole.",
-                       Parameters: objectSchema(map[string]any{
-                               "id": integerSchema("The first message of the line: a multiple of n."),
-                               "n":  integerSchema("The number of messages of the line: a power of 2."),
+                       Description: "Open the line id+n of the view into the two lines of n/2 under it; n = 1 gives the message whole. With an agent's name, give that agent's whole chat.",
+                       Parameters: promptObjectSchema(map[string]any{
+                               "id":   promptIntegerSchema("The first message of the line: a multiple of n."),
+                               "n":    promptIntegerSchema("The number of messages of the line: a power of 2."),
+                               "name": promptNullableStringSchema("The name of an agent whose whole chat to give, or null for the line id+n."),
                        }),
                        Strict: true,
                },
                        }),
                        Strict: true,
                },
@@ -142,8 +152,8 @@ func agentTools() []Tool {
                        Type:        "function",
                        Name:        "date",
                        Description: "The date and time of message id.",
                        Type:        "function",
                        Name:        "date",
                        Description: "The date and time of message id.",
-                       Parameters: objectSchema(map[string]any{
-                               "id": integerSchema("The message."),
+                       Parameters: promptObjectSchema(map[string]any{
+                               "id": promptIntegerSchema("The message."),
                        }),
                        Strict: true,
                },
                        }),
                        Strict: true,
                },
@@ -151,8 +161,8 @@ func agentTools() []Tool {
                        Type:        "function",
                        Name:        "shell",
                        Description: "Run a command with bash, as the user, in the working directory, with no input. Gives its output and error output, and its exit status when it fails.",
                        Type:        "function",
                        Name:        "shell",
                        Description: "Run a command with bash, as the user, in the working directory, with no input. Gives its output and error output, and its exit status when it fails.",
-                       Parameters: objectSchema(map[string]any{
-                               "command": stringSchema("The bash command."),
+                       Parameters: promptObjectSchema(map[string]any{
+                               "command": promptStringSchema("The bash command."),
                        }),
                        Strict: true,
                },
                        }),
                        Strict: true,
                },
@@ -160,8 +170,8 @@ func agentTools() []Tool {
                        Type:        "function",
                        Name:        "read",
                        Description: "Read a text file.",
                        Type:        "function",
                        Name:        "read",
                        Description: "Read a text file.",
-                       Parameters: objectSchema(map[string]any{
-                               "path": stringSchema("The file, absolute or relative to the working directory."),
+                       Parameters: promptObjectSchema(map[string]any{
+                               "path": promptStringSchema("The file, absolute or relative to the working directory."),
                        }),
                        Strict: true,
                },
                        }),
                        Strict: true,
                },
@@ -169,9 +179,9 @@ func agentTools() []Tool {
                        Type:        "function",
                        Name:        "write",
                        Description: "Write a text file whole, replacing it when it exists. Makes the missing directories.",
                        Type:        "function",
                        Name:        "write",
                        Description: "Write a text file whole, replacing it when it exists. Makes the missing directories.",
-                       Parameters: objectSchema(map[string]any{
-                               "path": stringSchema("The file, absolute or relative to the working directory."),
-                               "text": stringSchema("The whole new content."),
+                       Parameters: promptObjectSchema(map[string]any{
+                               "path": promptStringSchema("The file, absolute or relative to the working directory."),
+                               "text": promptStringSchema("The whole new content."),
                        }),
                        Strict: true,
                },
                        }),
                        Strict: true,
                },
@@ -179,19 +189,19 @@ func agentTools() []Tool {
                        Type:        "function",
                        Name:        "edit",
                        Description: "Replace the one occurrence of old in a text file with new.",
                        Type:        "function",
                        Name:        "edit",
                        Description: "Replace the one occurrence of old in a text file with new.",
-                       Parameters: objectSchema(map[string]any{
-                               "path": stringSchema("The file, absolute or relative to the working directory."),
-                               "old":  stringSchema("The exact text to replace. It must occur once in the file."),
-                               "new":  stringSchema("The text that replaces old."),
+                       Parameters: promptObjectSchema(map[string]any{
+                               "path": promptStringSchema("The file, absolute or relative to the working directory."),
+                               "old":  promptStringSchema("The exact text to replace. It must occur once in the file."),
+                               "new":  promptStringSchema("The text that replaces old."),
                        }),
                        Strict: true,
                },
        }
 }
 
                        }),
                        Strict: true,
                },
        }
 }
 
-// objectSchema returns a strict JSON schema of an object whose properties are
-// all required.
-func objectSchema(properties map[string]any) map[string]any {
+// promptObjectSchema returns a strict JSON schema of an object whose properties
+// are all required.
+func promptObjectSchema(properties map[string]any) map[string]any {
        required := make([]string, 0, len(properties))
        for name := range properties {
                required = append(required, name)
        required := make([]string, 0, len(properties))
        for name := range properties {
                required = append(required, name)
@@ -207,16 +217,24 @@ func objectSchema(properties map[string]any) map[string]any {
        }
 }
 
        }
 }
 
-// integerSchema returns the JSON schema of an integer.
-func integerSchema(description string) map[string]any {
+// promptIntegerSchema returns the JSON schema of an integer.
+func promptIntegerSchema(description string) map[string]any {
        return map[string]any{
                "type":        "integer",
                "description": description,
        }
 }
 
        return map[string]any{
                "type":        "integer",
                "description": description,
        }
 }
 
-// stringSchema returns the JSON schema of a string.
-func stringSchema(description string) map[string]any {
+// promptNullableStringSchema returns the JSON schema of a string or null.
+func promptNullableStringSchema(description string) map[string]any {
+       return map[string]any{
+               "type":        []string{"string", "null"},
+               "description": description,
+       }
+}
+
+// promptStringSchema returns the JSON schema of a string.
+func promptStringSchema(description string) map[string]any {
        return map[string]any{
                "type":        "string",
                "description": description,
        return map[string]any{
                "type":        "string",
                "description": description,