108 lines
6.6 KiB
Clojure
108 lines
6.6 KiB
Clojure
(ns tbd.agent.prompt
|
|
"What an LLM is told about driving this app through the page's tools (tbd.ui.webmcp), for an
|
|
MCP client outside the page (Claude, Codex) connected through WebMCP: it gets this as the
|
|
prompts and resources the page registers (tbd.ui.webmcp, in the browser: `guide`, `task`,
|
|
`tic-tac-toe`). This is a .cljc (compiled into the browser bundle, see ui/shadow-cljs.edn's
|
|
`:source-paths`) because those are genuinely needed client-side.
|
|
|
|
The page's own chatbot's system prompt is a separate, server-only concern: it lives in
|
|
tbd.agent.instructions (`src/`, never on the UI's classpath), which reuses `act`, `work-it-out`
|
|
and `examples` from here rather than duplicating them, so the wording stays shared without the
|
|
chatbot's own instructions being bundled into the browser.
|
|
|
|
It is about acting. A model that is only asked to be helpful answers a request for action with
|
|
an offer of help, so the text says: call the tool in the same turn, chain the calls, say little
|
|
afterwards, and the few cases where not to. It is short and concrete on purpose, with examples:
|
|
the models behind LM Studio follow that better than a long list of rules.")
|
|
|
|
(def act
|
|
"Shared with tbd.agent.instructions/chat, the in-page chatbot's own system prompt."
|
|
(str "Act, don't offer. When the user asks for something a tool can do, call the tool in this same\n"
|
|
"turn. Never say you are about to do something, never ask \"would you like me to\", never ask them\n"
|
|
"to confirm what they just asked for.\n"
|
|
"\n"))
|
|
|
|
(defn work-it-out
|
|
"How to get on without asking: `chain` is the bullet about chaining calls, which is where the
|
|
two agents differ (the chatbot's tools follow the page it is on, an outside agent has them all).
|
|
Shared with tbd.agent.instructions/chat."
|
|
[chain]
|
|
(str "Work it out yourself:\n"
|
|
"- Detail missing: take the obvious default and go. Ask only when a required argument is genuinely\n"
|
|
" unknowable (which of several todos they mean) or when you would delete or overwrite something\n"
|
|
" and cannot tell what.\n"
|
|
chain
|
|
"- The page changes between messages: the user clicks and types too. A tool result is only what the\n"
|
|
" page held when it ran, so an earlier one may no longer hold. Before you answer from the page or\n"
|
|
" act on it (a board, a list, an id), get what it holds now — current state if you have it, else\n"
|
|
" read it again with a tool. When the user says it changed or that you are wrong, check again\n"
|
|
" first: never go by an earlier result or by what you said before.\n"
|
|
"- A call fails: the result still comes back to you, with what went wrong. Read it, fix the\n"
|
|
" arguments or read the state, and try once more. No apologies, no explanation of what went\n"
|
|
" wrong unless it still fails.\n"
|
|
"\n"))
|
|
|
|
(defn examples
|
|
"Shared with tbd.agent.instructions/chat. `reads?` is whether looking something up (a todo's id,
|
|
the board) takes its own call first (an outside agent's list_todos/get_tic_tac_toe) or is
|
|
already in view (the chatbot's state, tbd.chat.schema/RunAgentInput's `state`) — the one place
|
|
the two agents' examples differ."
|
|
[reads?]
|
|
(str "Examples:\n"
|
|
"\"add a todo to buy milk\" -> create_todo{\"todos\":[{\"title\":\"buy milk\"}]} -> \"Added.\"\n"
|
|
"\"tick off the milk one\" -> " (when reads? "list_todos -> ")
|
|
"set_todo_done{\"todos\":[{\"id\":\"3f2b8c1e-5a47-4d0e-9b6a-1c2d3e4f5a6b\",\"done\":true}]} -> \"Ticked off.\"\n"
|
|
"\"untick all my todos\" -> " (when reads? "list_todos -> ") "ONE set_todo_done with every todo in \"todos\" -> "
|
|
"\"Unticked them all.\"\n"
|
|
"\"add milk and eggs\" -> ONE create_todo with both titles in \"todos\" -> \"Added both.\"\n"
|
|
"\"what's 19 and 4?\" -> add{\"x\":19,\"y\":4} -> \"23.\""))
|
|
|
|
;; --- An MCP client outside the page (Claude, Codex) ---
|
|
|
|
(def guide
|
|
"The instructions of an agent that drives the page from outside, through WebMCP: the resource
|
|
the page registers for it to read first, and what a prompt starts from. The WebMCP bridge sends
|
|
no `instructions` of its own, so this is where the agent learns that the tools are the page's
|
|
and what the user expects of them. Markdown, since a client shows it as a document."
|
|
(str "# Driving this web app\n"
|
|
"\n"
|
|
"You are connected to a web app the user has open in their browser. The tools you were given\n"
|
|
"type, click and read on that page, and every call shows on their screen the moment you make it:\n"
|
|
"they are watching, and what you do is done in front of them.\n"
|
|
"\n"
|
|
"The app has several pages, which the `navigate` tool lists. Every tool is on offer whichever page\n"
|
|
"is open, and a tool that changes something opens the page it acts on first; `navigate` is for when\n"
|
|
"the user only wants to look at a page. The `webmcp://app/state` resource says which page is open\n"
|
|
"and whether anyone is logged in.\n"
|
|
"\n"
|
|
act
|
|
(work-it-out (str "- Chain calls without stopping. You need an id: list first, then act. Never invent an id, a\n"
|
|
" title or the state of the page: read it with a tool.\n"))
|
|
"Then report in one short plain sentence, from the tool results: \"Added it.\" \"That's 23.\" The user\n"
|
|
"sees the page change, so don't describe what it now shows and don't paste JSON. Say something\n"
|
|
"was done only if a tool result says it was. Not being logged in is the user's to fix, on the page:\n"
|
|
"tell them so.\n"
|
|
"\n"
|
|
(examples true)))
|
|
|
|
(defn task
|
|
"The prompt that hands an outside agent a job on the page: `guide`, then the user's `request` if
|
|
they gave one (else the agent asks what they want)."
|
|
[request]
|
|
(str guide
|
|
"\n\n"
|
|
(if (and (string? request) (seq request))
|
|
(str "The user's request: " request)
|
|
"Ask the user what they want done in the app, in a sentence, and do that.")))
|
|
|
|
(def tic-tac-toe
|
|
"The prompt of a game of tic-tac-toe with the person at the page, for an outside agent. Names
|
|
only the tools: get_tic_tac_toe, move_tic_tac_toe and their descriptions carry the rest — whose
|
|
turn it is, which side is whose, what a move is refused for — so the agent drives from those and
|
|
the game's state, and works out which side it plays from how the conversation started, rather
|
|
than the prompt assigning one. Starting over is the person's: their page has its own New game
|
|
button, no tool for it."
|
|
(str "You are playing tic-tac-toe with the person on the app's page. The tools are get_tic_tac_toe "
|
|
"and move_tic_tac_toe. Starting a new game is the person's to do, on the page: there is no "
|
|
"tool for it."))
|