From 4564d6eb60830af75a74fd086170bc9fbba2dcd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 25 Aug 2026 07:42:50 +0000 Subject: [PATCH] tell the commander the language, do not send it to fetch one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the rule was in CLAUDE.md and it was ignored: the transcript shows two Bash calls — ls the folder, curl /api/tasks — then the question, in English. GET /api/user/settings was never called. which is reasonable of it. its brief says the first message is what it found plus one question and NOTHING else, so an errand before speaking is exactly what it drops. an instruction that competes with another instruction loses. so the platform says it outright: the brief now names the language, from the locale the client is already rendering in. no call, nothing to weigh. CLAUDE.md keeps the rule for later sessions and points at the endpoint only as somewhere to check. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- src/servers/api/command-hq/directory.ts | 7 +++-- .../src/apps/CommandHQ/CommandHqDetail.tsx | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/servers/api/command-hq/directory.ts b/src/servers/api/command-hq/directory.ts index d59b6648..8df9067a 100644 --- a/src/servers/api/command-hq/directory.ts +++ b/src/servers/api/command-hq/directory.ts @@ -335,10 +335,9 @@ export function commanderClaudeMd(params: CommanderClaudeMdParams): string { '', '## Talking to the owner', '', - "**In the owner's language.** `GET /api/user/settings` returns `languages.default` — that is the", - 'language they chose for this platform, and everything you say to them goes in it: your messages, your', - 'questions, the options on them, and what you write in `JOB.md`. Read it on your first turn and do not', - 'assume it is English.', + "**In the owner's language**, which the first message of your first turn names. Everything you say to", + 'them goes in it: your messages, your questions, the options on them, and what you write in `JOB.md`.', + 'If you ever need to check it, `GET /api/user/settings` has it as `languages.default`.' '', 'Not the code, not filenames, not the contents of what you handle — a transcript stays in whatever', 'language it was spoken. Only the words you address to them.', diff --git a/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx b/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx index 7ac23bfb..555675ac 100644 --- a/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx +++ b/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx @@ -93,7 +93,24 @@ const NewCommanderGuide = () => { * before asking anything at all, all live in the Commanders skill — which is re-read from disk every run, so * it can be reworded without a deploy. Duplicating any of it here would create a second copy to drift. */ -const startBrief = (name: string, referencePath: string): string => +/** + * The locale as a name the model will act on. + * + * Told, not fetched. The first version pointed the commander at `GET /api/user/settings` and it never + * called it — its brief says the first message is what it found plus one question and nothing else, so an + * errand before speaking is exactly what it skips. The platform already knows the answer; making the agent + * go and get it is a request it is right to weigh against everything else it was told. + */ +const LANGUAGE_NAMES: Record = { + 'en-GB': 'English', + 'pt-PT': 'European Portuguese', + 'es-ES': 'Spanish', + 'de-DE': 'German', + 'fr-FR': 'French', + 'sv-SE': 'Swedish', +}; + +const startBrief = (name: string, referencePath: string, locale: string): string => [ // i18n-exempt: agent brief — the MODEL reads this; the transcript shows displayText to the person `You have been named "${name}". Look in \`${referencePath}\` and see whether there is anything there`, @@ -104,7 +121,8 @@ const startBrief = (name: string, referencePath: string): string => '', 'Ask it with AskUserQuestion, so I can answer by picking rather than by typing.', '', - 'And talk to me in MY language — `GET /api/user/settings`, `languages.default`. Do not assume English.', + `Talk to me in ${LANGUAGE_NAMES[locale] ?? locale} — every message, every question, every option on`, + 'one. Not the code, not filenames.', '', 'That is the whole of your first message: what you found, and that question. Nothing else.', ].join('\n'); @@ -285,7 +303,7 @@ function ActiveNewCommander({ category: number | 'none' | undefined; onStarted: (id: string) => void; }) { - const { t, tUi } = useTranslation(); + const { t, tUi, locale } = useTranslation(); const navigate = useNavigate(); // Sonnet, not the account's default. A commander is long-running and mostly repeats one settled job, so @@ -312,7 +330,7 @@ function ActiveNewCommander({ if (sentRef.current) return; sentRef.current = true; chat.sendPrompt( - startBrief(commander.name ?? '', commander.referencePaths[0] ?? ''), + startBrief(commander.name ?? '', commander.referencePaths[0] ?? '', locale), undefined, undefined, { path: commander.cwd },