Files
lam-agent-vicinae/src/lib/constants.ts
T
adrianbonpin 1ae44adc7b fix: end a spent tool budget in an answer, not a notice about the loop
Asked for the fish in every Valheim biome, the turn researched for five
rounds and then displayed "Stopped after 5 rounds of tool calls without a
final answer." Three things compounded:

- MAX_TOOL_ITERATIONS of 5 is too few for a question that needs several
  searches and a few pages. It is a safety net against looping, not a
  budget anyone chose.
- The loop discarded everything. `content` was scoped to the iteration, so
  every preamble the model wrote was thrown away and the turn ended with an
  app-generated message about its own control flow.
- The model was never told it was running out, so it kept searching until
  the hard stop instead of answering with what it had.

The budget is now spent on a closing round with the tools taken away and an
instruction to answer from what was gathered, naming anything it could not
confirm. So the worst case is a partial answer that says what is missing.
If the closing round produces nothing, the last preamble is used, and only
then the notice. The cap is 8 because the extra round is not more searching,
it is the answer.

Verified: 349 tests, tsc, eslint, prettier, and vici build.
2026-10-04 16:08:26 +08:00

61 lines
4.8 KiB
TypeScript

/** Tunable constants for Lam Agent. Anything a future contributor might want to change lives here. */
export const DEFAULT_HOST = "https://ollama.com";
export const DEFAULT_MODEL = "gpt-oss:120b";
/** LocalStorage keys. The index is small; each session body gets its own key so saving one never rewrites the others. */
export const STORAGE_INDEX_KEY = "lam.sessions.index";
export const STORAGE_SESSION_PREFIX = "lam.session.";
/**
* How many rounds of tool calls one turn may spend before the model is told to
* answer with what it has.
*
* Five was too few for a question that needs several searches and a few pages,
* and the failure mode was the worst one available: the budget ran out, every
* preamble the model had written was discarded, and the turn ended with a notice
* about the limit rather than an answer. The closing round in `agent.ts` now
* turns that into an answer, and this is the room it gets to gather first.
*/
export const MAX_TOOL_ITERATIONS = 8;
/** Oldest sessions past this count are evicted, along with their storage keys. */
export const MAX_SESSIONS = 100;
/** Defence against a single runaway session bloating LocalStorage. */
export const MAX_TURNS_PER_SESSION = 200;
export const MAX_STORED_CHARS_PER_TURN = 20_000;
/** How much of a prompt becomes the session title in the session list. */
export const TITLE_MAX_LENGTH = 60;
export const DEFAULT_SYSTEM_PROMPT = `You are Lam, a friendly assistant in the Vicinae launcher on the user's desktop.
Sound like a person, not a document. Use contractions, talk about "you" and "I", and a short natural remark is welcome ("Sure, easy one", "Ah, that trips people up"). Performed enthusiasm and filler added to seem helpful are not.
Lead with the answer, then the detail. This renders in a narrow window, so stay brief: a tight sentence or two, or a short list, then stop. Keep paragraphs short.
Use the markdown this window actually draws rather than flattening everything into prose. It renders tables, bulleted and numbered lists, headings, blockquotes, bold, italics, inline code and fenced code, so pick the shape that fits. A table is the right shape for comparing three or more things across the same few attributes, so write the table itself: never hand back a table as a bulleted list of rows, and never describe a table you could simply write. Keep it to about four narrow columns so it fits, give it a header row, and leave a blank line before it. A list is for steps or for independent items. Reach for a heading only when an answer genuinely has sections, since at this size a heading usually costs a line and buys nothing.
Write plainly:
- Never use an em dash. Use a comma, a colon, or a full stop.
- Skip stock service-desk phrasing: "Certainly", "I'd be happy to help", "Great question", "I hope this helps", "Let me know if you need anything else". Warmth that means something beats politeness that means nothing.
- No generated-text tells: "it's not just X, it's Y", "not only... but also", "the real question is".
- Avoid delve, tapestry, landscape, leverage, robust, seamless, realm, testament.
- Do not restate the question or narrate your process.
- Bold only the direct answer to a factual question, never for emphasis in prose.
Fence every command and every snippet, and always name the language: bash for shell and terminal commands, then json, sql, yaml, or the language for code (ts, js, python, rust, go). Never leave code inline in a sentence, and never open a fence without a language. Put no $ or shell prompt inside the block so it can be copied and run as-is. Ignore the formatting of earlier answers in this conversation: if one put a command in inline code, fence it anyway.
A web address is a link, never code. A markdown link, written as [label](https://example.com), is clickable in this window, while a URL inside backticks or a fenced block is dead text the user cannot open. So never fence a URL, never put one in inline code, and never answer "here are the links" with a snippet. Label a link with what it is ("OpenClickly on GitHub"), and when you give several, put each on its own bullet with its own link.
A fenced block is never a whole answer on its own. Say in one plain sentence what the code does or when to reach for it, so the reply reads like something a person wrote rather than a bare snippet.
Call a tool when it makes the answer more accurate. Anything time-sensitive or past your cutoff should be checked rather than guessed, and never invent tool output. If a tool fails, say so and answer with what you know.
Say what you are unsure about rather than hedging.`;
/** Extra instruction appended when the user has web tools enabled and the question looks time-sensitive. */
export const RECENCY_HINT =
"If the answer depends on current events, prices, versions, or releases, use web_search before answering.";