feat(assistant): recommendations check the catalogue first and tag knowledge picks

'recommend me 10 scifi films' answered prose-only and OFFERED to check the
catalogue — the preamble invited knowledge recommendations (paragraph 2) but
only ordered tool calls for existence questions (paragraph 3), so the model
never ran the tool and emitted none of the tags the iframe renders as rich
preview cards.

- discovery of a kind the node could hold (films/music/books…) now gets a
  catalogue-and-peers check FIRST, knowledge picks on top
- the 'would you like me to look?' stall is banned outright — looking is
  one tool call, do it then answer
- the preamble teaches the exact [[film_ext:Title|Year|Director]] /
  tv_ext / song_ext / book_ext / podcast_ext formats, same-line with the
  one-line reason, real titles only, never for items the tool returned

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-07 09:13:47 -04:00
co-authored by Claude
parent 3f22e4375d
commit 1ac08a3ed3
+51 -9
View File
@@ -771,18 +771,31 @@ instead of fabricating a tool call.\n\n\
That refusal rule is about ACTIONS ON THIS NODE, not about conversation. You are also the \
owner's general assistant: answer ordinary questions, explain things, and give recommendations \
(films, music, podcasts, books, reading) from your own knowledge, exactly as a capable \
assistant would. Those answers are what the content surfaces in this app render as cards, so \
declining to answer them leaves the owner staring at an empty panel. Only a request to CHANGE \
or READ something on the node needs a tool — and if no tool covers it, say so plainly rather \
than pretending. Never let a general question be refused merely because no tool matches it.\n\n\
assistant would. Only a request to CHANGE or READ something on the node needs a tool — and if \
no tool covers it, say so plainly rather than pretending. Never let a general question be \
refused merely because no tool matches it.\n\n\
When the question is about what actually EXISTS here — this node's own shared files, a peer's \
catalogue, purchased items, the film catalogue — call the content tool rather than answering \
from memory or from earlier in this conversation, and call it once per place the question spans \
(asking about \"films\" covers both the catalogue and the peers). The app renders whatever that \
tool returns as cards beside your reply, so your text should introduce and summarise the result \
— how many, anything notable — rather than re-listing every title, size and price in prose. A \
long enumeration duplicates the cards the operator is already looking at. If the tool comes \
back empty, say so plainly; do not fill the gap with remembered or invented items.";
(asking about \"films\" covers both the catalogue and the peers). The same applies to \
RECOMMENDATIONS of a kind this node could hold: \"recommend me ten sci-fi films\" gets a \
catalogue-and-peers check FIRST, then your own picks on top. And never end a turn by offering \
to check (\"would you like me to look?\") — looking costs one tool call; do it, then answer \
with what you found. The app renders whatever the tool returns as cards beside your reply, so \
your text should introduce and summarise the result — how many, anything notable — rather than \
re-listing every title, size and price in prose. If the tool comes back empty, say so plainly; \
do not present remembered items as if they were found here.\n\n\
Recommendations from your own knowledge are still wanted, and the app renders each one as a \
rich preview card when you tag it. Put the tag on the SAME line as the title's one-line \
reason, in EXACTLY these formats:\n\
- [[film_ext:Title|Year|Director]] for a film, e.g. [[film_ext:Arrival|2016|Denis Villeneuve]]\n\
- [[tv_ext:Title|Year|Creator]] for a TV series\n\
- [[song_ext:Title|Artist|Year]] for a song\n\
- [[book_ext:Title|Author|Year]] for a book\n\
- [[podcast_ext:Title|Host|Year]] for a podcast\n\
Tag only specific, real titles you are confident about — never invent a title to fill a tag, \
and never tag a title the tool already returned (its card is already on screen). An untagged \
recommendation renders as plain text and is easily missed.";
pub fn build_system_prompt(visible_tools: &[tools::ToolDef]) -> String {
let mut prompt = String::from(SYSTEM_PROMPT_PREAMBLE);
@@ -1228,6 +1241,35 @@ mod tests {
);
}
/// Operator report 2026-08-07: "recommend me 10 scifi films" answered
/// with a prose-only knowledge list and an OFFER to check the
/// catalogue — the model classified a recommendation as pure
/// conversation, never called the content tool, and the reply carried
/// none of the preview tags the iframe renders as cards. The preamble
/// must pin both behaviors: check-first on node-holdable
/// recommendations, and the exact tag formats for knowledge picks.
#[test]
fn system_prompt_teaches_discovery_first_and_preview_tags() {
let prompt = build_system_prompt(&[]);
assert!(
prompt.contains("catalogue-and-peers check FIRST"),
"prompt must order a catalogue/peers check before freestyle recommendations"
);
assert!(
prompt.contains("never end a turn by offering to check"),
"prompt must ban the 'would you like me to look?' stall"
);
for tag in [
"[[film_ext:Title|Year|Director]]",
"[[tv_ext:Title|Year|Creator]]",
"[[song_ext:Title|Artist|Year]]",
"[[book_ext:Title|Author|Year]]",
"[[podcast_ext:Title|Host|Year]]",
] {
assert!(prompt.contains(tag), "prompt must teach {tag}");
}
}
/// D-16: revoking a category takes effect on the very next
/// `granted_categories` resolution — not only on the next session /
/// process restart.