]> git.giacomolli.uk - onechat/blobdiff - prompt.go
Name and order the terminal client code
[onechat] / prompt.go
index dc0fb06dcf8138cd3161574b1a92e2757db31d9c..546a86b4c2eec9aa7abf408614f14e351afbca32 100644 (file)
--- a/prompt.go
+++ b/prompt.go
@@ -5,15 +5,31 @@ import (
        "strings"
 )
 
        "strings"
 )
 
-// promptName stands for the name of the agent in promptText.
-const promptName = "{name}"
+const (
+       // 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."
+
+       // 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"
+
+       // instructionsHeading starts the instructions of the user after
+       // promptText.
+       instructionsHeading = "\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
 
 // 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,
-// background tasks, and the instructions of the user. It holds no date and no
-// state, so turns and compactions share its prompt cache.
+// do, so it leaves out the spec text on images, subagents, computers, and
+// 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
 ends. Each call to you is a turn or a compaction: the view below is followed by
 the user's new message, or by a task starting "Compaction:".
 const promptText = `You are {name}, an AI agent that works for one user in a single chat that never
 ends. Each call to you is a turn or a compaction: the view below is followed by
 the user's new message, or by a task starting "Compaction:".
@@ -57,6 +73,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
@@ -92,18 +115,27 @@ exactly. Tag each item with its kind ("user: ...; echo: ..."), and credit quoted
 text to its real author. Never make anything look further along than it was. If
 told the line is too long, shorten it. Non-ASCII characters cost 2-4 bytes.`
 
 text to its real author. Never make anything look further along than it was. If
 told the line is too long, shorten it. Non-ASCII characters cost 2-4 bytes.`
 
-// prompt returns the system prompt of the agent name. Turns and compactions
-// send the same prompt and tools, so compactions read the prompt cache of the
-// turns.
-func prompt(name string) string {
-       return strings.ReplaceAll(promptText, promptName, name)
+// prompt returns the system prompt of the agent name with the instructions of
+// the user at its end. Without instructions it has neither them nor the clause
+// that points to them. Turns and compactions send the same prompt and tools, so
+// compactions read the prompt cache of the turns.
+func prompt(name, instructions string) string {
+       text := strings.ReplaceAll(promptText, promptName, name)
+       instructions = strings.TrimSpace(instructions)
+       if instructions == "" {
+               return text
+       }
+
+       text = strings.Replace(text, taskSentence, strings.TrimSuffix(taskSentence, ".")+instructionsClause+".", 1)
+
+       return text + instructionsHeading + instructions
 }
 
 }
 
-// agentTools returns the function tools of every call: zoom and date. 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{
+// agentTools 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.
+func agentTools() []ModelTool {
+       return []ModelTool{
                {
                        Type:        "function",
                        Name:        "zoom",
                {
                        Type:        "function",
                        Name:        "zoom",
@@ -123,6 +155,45 @@ func agentTools() []Tool {
                        }),
                        Strict: true,
                },
                        }),
                        Strict: true,
                },
+               {
+                       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."),
+                       }),
+                       Strict: true,
+               },
+               {
+                       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."),
+                       }),
+                       Strict: true,
+               },
+               {
+                       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."),
+                       }),
+                       Strict: true,
+               },
+               {
+                       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."),
+                       }),
+                       Strict: true,
+               },
        }
 }
 
        }
 }
 
@@ -151,3 +222,11 @@ func integerSchema(description string) map[string]any {
                "description": description,
        }
 }
                "description": description,
        }
 }
+
+// stringSchema returns the JSON schema of a string.
+func stringSchema(description string) map[string]any {
+       return map[string]any{
+               "type":        "string",
+               "description": description,
+       }
+}