framework-ify. proper agent runtime + adopt ag-ui
CI / Test + coverage (pull_request) Successful in 1m41s

This commit is contained in:
2026-09-21 18:34:19 -09:00
parent 187252177c
commit a3ef6ec077
26 changed files with 1514 additions and 1200 deletions
+20 -41
View File
@@ -1,31 +1,32 @@
(ns tbd.agent.prompt
"What an LLM is told about driving this app through the page's tools (tbd.ui.webmcp): one piece
of prompt engineering, worn by two agents. The page's own chatbot gets it as its system message
(tbd.chat.core, on the server: `chat`); an MCP client outside the page, Claude or Codex,
connected through WebMCP, gets it as the prompts and resources the page registers (tbd.ui.webmcp,
in the browser: `guide`, `task`, `tic-tac-toe`). It is a .cljc so that both read the same text:
tune it here, and both agents follow.
"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`, `tic-tac-toe-rules` 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.
What the two agents share (`act`, `tic-tac-toe-rules`, `examples`) is written once. What differs is
where the agent stands: the chatbot is in the page, and its calls and their results are
displayed under its message; the outside agent is in a terminal or an editor, is offered every
tool at once, and has its own conversation, so it needs a report of what happened."
the models behind LM Studio follow that better than a long list of rules."
(:require [clojure.string :as str]))
(def ^:private act
(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
(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)."
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"
@@ -41,12 +42,14 @@
" apologies, no explanation of what went wrong unless it still fails.\n"
"\n"))
(def ^:private tic-tac-toe-rules
(def tic-tac-toe-rules
"Shared with tbd.agent.instructions/chat."
(str "Tic-tac-toe: check whose turn it is, move only for your own side, one move, then stop and let the\n"
"person play theirs.\n"
"\n"))
(def ^:private examples
(def examples
"Shared with tbd.agent.instructions/chat."
(str "Examples:\n"
"\"add a todo to buy milk\" -> create_todo{\"todos\":[{\"title\":\"buy milk\"}]} -> \"Added.\"\n"
"\"tick off the milk one\" -> list_todos -> set_todo_done{\"todos\":[{\"id\":\"3f2b8c1e-5a47-4d0e-9b6a-1c2d3e4f5a6b\",\"done\":true}]} -> \"Ticked off.\"\n"
@@ -55,30 +58,6 @@
"\"what's 19 and 4?\" -> add{\"x\":19,\"y\":4} -> \"23.\"\n"
"\"play tic tac toe, you're O\" -> get_tic_tac_toe -> it is X's turn -> \"You're up, X starts.\""))
;; --- The page's own chatbot ---
(def chat
"The chatbot's instructions, the start of its system message: tbd.chat.core adds where the user
is and the tools it has. Its calls show under its message, so it answers in a sentence and
never repeats them."
(str "You are the assistant built into this web app. Your tools type, click and read on the page the\n"
"user is looking at, and every call shows on their screen the moment you make it.\n"
"\n"
act
(work-it-out (str "- Chain calls without stopping. The tool you need belongs to another page: navigate there, then\n"
" use it. You need an id: list first, then act. Never invent an id, a title or the state of the\n"
" page: read it with a tool.\n"))
"Then say one short plain sentence: \"Added it.\" \"That's 23.\" The call and its result are already\n"
"on screen under your message, so never repeat them, never paste JSON, never use markdown,\n"
"headings or bullet lists. Say something was done only if a tool result says it was.\n"
"\n"
"Some things are not tool work: greetings, questions about you, questions you can simply answer.\n"
"Answer those in a sentence or two, with no tool call. Asked what you can do, say it plainly from\n"
"the tools you have. Think briefly before you act.\n"
"\n"
tic-tac-toe-rules
examples))
;; --- An MCP client outside the page (Claude, Codex) ---
(def guide