Skip to content

feat(platform): i18n-first agent data model + truthful agent response language#1605

Merged
larryro merged 5 commits into
mainfrom
feat/agent-i18n-first-model
Apr 23, 2026
Merged

feat(platform): i18n-first agent data model + truthful agent response language#1605
larryro merged 5 commits into
mainfrom
feat/agent-i18n-first-model

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Apr 23, 2026

Summary

Two intertwined fixes to the org-level "What language should your agents use?" dropdown:

  1. Label was misleading — the switch only flagged the authoring locale for UI translation lookups; it did not change what agents actually output.
  2. Data model had duplication — translatable fields coexisted at the top level and under i18n.<locale>.*, so once en / de / fr were all populated the top-level copy was redundant.

Now the dropdown truthfully controls agent output language (renamed to "Agent response language"), and the data model is i18n-first with top-level fields demoted to legacy fallbacks. Display chrome continues to resolve by each user's own UI locale, so agent labels in the sidebar / picker / composer menu follow the reader.

Resolution precedence

i18n[requestedLocale] → i18n[base-language] → i18n.en → top-level (legacy fallback)

BCP-47 narrowing so fr-FR / de-AT / fr-CH still match their base language. Schema superRefine requires displayName (and systemInstructions for chat agents) in at least one locale.

Scope

  • Server: new getOrganizationDefaultLocale internal query, parallelized into resolveAgentConfigInline and resolveAgentConfig (no TTFT regression). toSerializableConfig takes an optional locale and resolves systemInstructions with the same precedence. Delegation threaded — parent + delegates speak the same language; scaffold text ("DELEGATION AGENTS" / "You can delegate...") localized in en / de / fr.
  • Client: useChatAgents resolves against the user's UI locale so the agent picker, composer-mode menu, and chat welcome all render in the reader's language — independent of the org-wide output setting. useEffectiveAgent call simplified.
  • Editor UI: new LocaleTabs component powers tabbed i18n editing with auto-translate on the instructions tab (new, for systemInstructions) and the general tab (displayName / description). The conversation-starters editor writes its default-tab edits into i18n[defaultLocale] and retires the legacy top-level field on first save.
  • Locales: fr added to SUPPORTED_AGENT_LOCALES; getOrganizationDefaultLocale clamps stale values to the supported list.
  • Example agents: all 9 files under examples/agents/ restructured to the i18n-first shape with complete en + de + fr translations for every translatable field. A dev script (scripts/backfill-example-i18n.ts) mechanically moved top-level → i18n.en; translations were generated by parallel sub-agents and committed as-is.
  • Placeholder hygiene: word-filter placeholder no longer uses a Chinese profanity for its CJK example; the three supported UI locales demonstrate multilingual support instead (forbidden / verboten / interdit).

Hybrid semantics (confirmed with the user)

  • Agent OUTPUT (systemInstructions → LLM) resolves via the org's defaultLocale — an editorial decision applied org-wide.
  • Agent DISPLAY (displayName / description / conversationStarters in chat chrome) resolves via the user's UI locale — matches Slack / Notion / Linear chrome behavior and survives multi-org membership cleanly.

Test plan

  • npx tsc --noEmit clean
  • npm run lint --workspace=@tale/platform clean
  • npx vitest run — 5069/5069 pass (new config.test.ts covers server-side resolution precedence; resolve-agent-locale.test.ts rewritten for i18n-first; use-effective-agent.test.ts and the TTFT test updated for the new parallel query)
  • All 9 example agent JSON files pass the updated agentJsonSchema (including the new superRefine)
  • Manual E2E: set org language to DE → chat → agent replies in German. Switch user UI to FR → sidebar and picker render in French while chat output stays German.
  • Manual E2E: Instructions tab → auto-translate EN → DE produces German text; clearing the DE tab reverts to the "untranslated" badge.
  • Manual E2E: delegation chain — parent with orgLocale=de delegates → delegate responds in German; delegation scaffold text also in German.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added multilingual support for agents across English, German, and French locales
    • Introduced agent field auto-translation to quickly populate translations
    • Added locale-switching interface for editing agent names, descriptions, and instructions by language
    • New help text clarifying that agent output language is independent from user interface language settings

larryro added 2 commits April 23, 2026 15:24
… language switch

Previously, the org-level "What language should your agents use?" dropdown
was misleading — it only flagged the authoring locale for UI translation
lookups and had no effect on what agents actually output. The translatable
fields (displayName / description / conversationStarters / systemInstructions)
also coexisted at the top level and under `i18n.<locale>`, creating
duplication once all locales were populated.

- Data model: top-level translatable fields become optional legacy
  fallbacks; canonical values live under `i18n.<locale>.*`. Resolution
  precedence is `i18n[locale] → i18n[base-language] → i18n.en → top-level`.
  Schema superRefine requires displayName (and systemInstructions for chat
  agents) in at least one locale. BCP-47 narrowing so fr-CH / de-AT still
  match their base language.
- Agent response language: new `getOrganizationDefaultLocale` internal
  query is parallelized into `resolveAgentConfigInline` and
  `resolveAgentConfig` (no TTFT regression). `toSerializableConfig` takes
  an optional `locale` and resolves `systemInstructions` through the same
  precedence, so agents actually respond in the org's chosen language.
  Delegation threaded — parent + delegates speak the same language;
  scaffold text ("DELEGATION AGENTS" / "You can delegate...") localized
  in en/de/fr.
- Hybrid display resolution: agent chrome (displayName, description,
  conversationStarters) follows each user's UI locale via `useChatAgents`
  so the agent picker, composer-mode menu and chat welcome all render in
  the reader's language — independent of the org-wide output setting.
- Editor UI: new `LocaleTabs` component powers tabbed i18n editing with
  auto-translate on the instructions tab (systemInstructions) and the
  general tab (displayName / description). The existing conversation-
  starters editor writes its default-tab edits into `i18n[defaultLocale]`
  and retires the legacy top-level field on first save.
- Label: org-settings dropdown renamed to "Agent response language" (en /
  de / fr) with a one-line helper clarifying it governs agent output and
  not the per-user UI locale.
- fr added to SUPPORTED_AGENT_LOCALES; `getOrganizationDefaultLocale`
  clamps stale values to supported locales.
- Example agents: all 9 files under examples/agents/ backfilled to the
  i18n-first shape with complete en + de + fr translations for every
  translatable field. A dev script (scripts/backfill-example-i18n.ts)
  mechanically restructured top-level → i18n.en.
- Tests: new config.test.ts covers the server-side resolution precedence;
  resolve-agent-locale tests rewritten for i18n-first; use-effective-agent
  and TTFT tests updated for the new parallel query.
…ilter placeholder

The content-safety word-filter placeholder included a Chinese profanity to
demonstrate CJK substring matching. Replaced with the three supported UI
locales (forbidden / verboten / interdit) — same multilingual hint, no
profanity, each locale leads with its own language.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

This pull request implements a multi-locale (i18n) structure for agent configurations. Agent metadata—including displayName, description, systemInstructions, and conversationStarters—are migrated from root-level fields to locale-specific i18n blocks supporting English, German, and French locales. The changes span 9 example agent JSON files being restructured, a new LocaleTabs React component for locale switching, UI route updates for agent editing (general settings, instructions, conversation starters) with auto-translation support, backend Convex functions to fetch and thread organization locale, and updated schema validation and locale-resolution utilities. A backfill script automates migration of existing example files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and concisely describes the main change: shifting to an i18n-first agent data model where agent output language is now controlled by organization settings, addressing the core objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-i18n-first-model

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
services/platform/convex/agents/file_actions.ts (1)

512-618: ⚠️ Potential issue | 🟠 Major

Include docs updates for this user-visible behavior change (en/de/fr) in the same PR.

Agents now respect the organization's default locale when resolving localized instructions. This affects what users see the agent output, so documentation must ship together across all published locales (en, de, fr) per project requirements.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/agents/file_actions.ts` around lines 512 - 618, The
PR changes resolveAgentConfig to pass orgLocale into toSerializableConfig, which
causes agent instructions to be localized using the organization's default
locale; update the user-facing documentation across all published locales (en,
de, fr) in the same PR to reflect this behavior change—update docs that describe
agent output/localization, any examples referencing instruction language, and
release notes for this feature in the project's documentation files so readers
in each locale see the new default-locale behavior; ensure the doc edits
reference resolveAgentConfig/toSerializableConfig behavior and mention that
effectiveConfig.supportedModels validation is unaffected.
services/platform/convex/agents/config.ts (1)

37-57: ⚠️ Potential issue | 🔴 Critical

Update documentation for locale-aware agent instructions in all three locales.

This PR adds locale-based resolution for agent systemInstructions — agents now return different instructions based on the user's organization locale — but ships with no docs. Documentation explaining the i18n field structure and how to set locale-specific instructions must be added to docs/AGENTS.md, docs/de/platform/agents/, and docs/fr/platform/agents/ in this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/agents/config.ts` around lines 37 - 57, Add
documentation describing the new locale-aware agent instructions introduced by
resolveInstructions used in toSerializableConfig: update docs/AGENTS.md and the
localized folders docs/de/platform/agents/ and docs/fr/platform/agents/ to
explain the i18n field structure on AgentJsonConfig (how to provide
systemInstructions per locale, fallback behavior when locale is missing,
examples for en/de/fr, and how resolveInstructions picks the locale); include a
small JSON/YAML example showing locale keys and the default key, note how to
supply locale to toSerializableConfig, and document any validation rules or
supported locale codes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/platform/app/routes/dashboard/`$id/agents/$agentId/index.tsx:
- Around line 288-295: The UI change adds locale-aware agent authoring via
LocaleTabs (props: defaultLocale, editingLocale, onEditingLocaleChange,
hasTranslation, onAutoTranslate using handleAutoTranslate when hasSource, and
isTranslating driven by translateMutation.isPending), but the PR must also
update user docs; add corresponding documentation updates under docs/ for en,
de, and fr that describe the new editing flow (how to switch editingLocale, what
hasTranslation and onAutoTranslate do) and the org-level language behavior
(defaultLocale semantics and when auto-translate is available), ensuring the
same PR includes these localized markdown pages and links from the relevant
settings/agents documentation so the UI and docs remain in sync.
- Around line 82-109: The current writeFields (function writeFields) can drop an
untouched legacy top-level field when migrating to i18n; to fix, seed the
per-locale `next` object with the default-locale legacy values before applying
patch and overrides so untouched fields are preserved (e.g., compute a base =
editingLocale === defaultLocale ? { displayName: legacyDisplayName, description:
legacyDescription } : {} and then const next = { ...base, ...existingOverrides
}; apply the patch changes as before), and then call updateConfig(i18n:
nextI18n, ...) as now; this preserves untouched legacy fields during the first
edit and allows the existing shouldClearLegacy logic to safely retire top-level
values.
- Around line 50-51: The editingLocale state is initialized from
getOrganizationDefaultLocale before useOrganization finishes, causing stale tab
selection; add a useEffect that watches the resolved defaultLocale (computed via
getOrganizationDefaultLocale(organization?.metadata)) and calls
setEditingLocale(defaultLocale) whenever defaultLocale changes so editingLocale
always stays in sync with the org's resolved default locale; reference the
defaultLocale variable, editingLocale state, setEditingLocale setter, and
getOrganizationDefaultLocale/useOrganization to locate where to add the effect.

In `@services/platform/app/routes/dashboard/`$id/agents/$agentId/instructions.tsx:
- Around line 59-60: The editingLocale state is initialized from
getOrganizationDefaultLocale but never updates when useOrganization resolves;
add a useEffect that watches defaultLocale (derived via
getOrganizationDefaultLocale(organization?.metadata)) and calls
setEditingLocale(defaultLocale) when defaultLocale is defined and different from
the current editingLocale to sync the active locale; reference defaultLocale,
editingLocale, setEditingLocale, and getOrganizationDefaultLocale to locate
where to add the effect.

In `@services/platform/convex/agent_tools/delegation/create_delegation_tool.ts`:
- Around line 180-182: The scaffold lookup is using the full locale string which
fails for regional tags like "de-CH"; normalize the locale to its primary
language subtag before indexing DELEGATION_SCAFFOLD by deriving a baseLocale
(e.g., take locale?.split('-')[0] or equivalent) and then use (baseLocale ?
DELEGATION_SCAFFOLD[baseLocale] : undefined) ?? DELEGATION_SCAFFOLD.en so
scaffold resolves "de-CH"/"fr-CH" to "de"/"fr" correctly while still falling
back to English.

In `@services/platform/convex/agents/__tests__/config.test.ts`:
- Around line 10-84: Add a test that verifies BCP-47 narrowing: create an
AgentJsonConfig with i18n.de.systemInstructions (and optionally i18n.en) and
call toSerializableConfig('test', config, undefined, 'de-AT'), then assert
result.instructions === 'German i18n' to confirm that toSerializableConfig falls
back from 'de-AT' to 'de'; reference the existing test style (use baseConfig,
AgentJsonConfig, and systemInstructions keys) and mirror the structure of the
other cases in the describe block.

In `@services/platform/convex/agents/file_utils.ts`:
- Around line 29-34: The comment describing resolution precedence is stale:
update the docstring in file_utils.ts to match runtime behavior in
services/platform/convex/agents/config.ts by adding the base-language fallback
into the chain so it reads something like "i18n[locale] → i18n[base-language] →
i18n['en'] → top-level"; ensure you reference the same terminology used in
config.ts (e.g., base-language or baseLanguage) so readers can map the comment
to the runtime lookup implemented there.
- Line 35: The change made displayName optional which breaks downstream code
that builds a copy name using `${source.config.displayName} (Copy)`; either
revert displayName to a required property in the type definition in
file_utils.ts (remove the `?` on displayName) or add a safe fallback in
file_actions.ts when formatting the copy name (e.g., use
`source.config.displayName ?? <fallback>` or derive from another stable field)
so `${source.config.displayName} (Copy)` never becomes `undefined (Copy)`.
Ensure you update the declaration of displayName or the formatting in
file_actions.ts accordingly.

In `@services/platform/convex/agents/unified_chat.ts`:
- Line 301: The helper resolveAgentConfigInline currently returns an extra
orgLocale field that isn’t used by any caller in this file; remove orgLocale
from the return contract (the Promise generic and any object construction/return
that includes orgLocale) in resolveAgentConfigInline and in the other similar
helper mentioned (same file, the other return at 349) so the function returns
only InlineConfigResult; update any internal code that sets orgLocale to stop
including it or keep it local-only.

In `@services/platform/convex/lib/agent_chat/internal_actions.ts`:
- Around line 693-696: The comment falsely claims the org's `defaultLocale` is
fetched in parallel with delegate loading; update the comment near the
`orgLocale` fetch and the delegate-loading block in internal_actions.ts to
accurately state that the code performs these operations sequentially (the code
`await`s `orgLocale` before starting delegate loading), or if you prefer true
parallelism, start both promises without awaiting (e.g., kick off
delegate-loading promise and orgLocale promise and `await` them both later).
Ensure the comment references `orgLocale` and the delegate loading
function/variable so readers can locate the relevant lines.

In `@services/platform/convex/organizations/internal_queries.ts`:
- Around line 30-34: The parse block for metadata currently swallows errors and
returns appDefaultLocale; update the catch in the try { metadata =
JSON.parse(metadata) } catch { ... } to log a warning before returning: call
console.warn with a clear message that includes the malformed metadata and the
caught error (e.g., "Failed to parse org metadata" plus metadata and error) so
the failure is visible while still returning appDefaultLocale. Ensure you
reference the same variables (metadata, appDefaultLocale) and keep behavior
otherwise unchanged.

In `@services/platform/lib/shared/schemas/agents.ts`:
- Around line 41-45: Add documentation describing the new i18n-first agent
model: explain that translatable fields live under the i18n object keyed by
locale
(i18n.<locale>.(displayName|description|conversationStarters|systemInstructions)),
show example structure for one or more locales, and state that i18n entries take
precedence over legacy top-level fields (displayName, description,
conversationStarters, systemInstructions) while the superRefine still requires
those fields to exist in at least one locale or top-level fallback; apply this
content to the English, German and French create.md docs in the platform agents
documentation so authors know how to author per-locale overrides and which
values win.

In `@services/platform/lib/shared/utils/__tests__/resolve-agent-locale.test.ts`:
- Around line 34-100: Add a new unit test in the resolveAgentLocale suite that
asserts BCP-47 base-language fallback: call resolveAgentLocale(i18nFirstAgent,
'de-AT') and verify it selects fields from i18n.de (e.g., displayName ===
'Chat-Assistent', conversationStarters === ['Hallo','Hilf mir'],
systemInstructions === 'Du bist ein hilfreicher deutscher Assistent.') to ensure
region-variant narrowing uses the base language; reference the
resolveAgentLocale function and the existing i18nFirstAgent fixture so the test
follows the same patterns as the other cases.

In `@services/platform/lib/shared/utils/resolve-agent-locale.ts`:
- Around line 20-24: The locale-field precedence logic (requested locale → base
language → app default 'en' → top-level legacy field) is duplicated in
resolveAgentLocale() and the systemInstructions resolution in
services/platform/convex/agents/config.ts; extract that logic into a single
helper (e.g., resolveLocaleField or resolveAgentLocaleField) that accepts
(i18nMap, requestedLocale, fieldName, appDefault='en', topLevelFallback?) and
returns the resolved string following the four-step precedence, then replace the
inlined logic in resolveAgentLocale() and the systemInstructions resolution to
call this helper; update imports/exports accordingly so both modules use the
shared helper.

In `@services/platform/scripts/backfill-example-i18n.ts`:
- Around line 15-17: The import specifiers are not consistently sorted; please
reorder them to follow the repo import-order rules by alphabetizing members
within each import and ordering import statements per project convention: for
these lines, alphabetize the node:fs named imports (readFileSync, readdirSync,
writeFileSync) and the node:path named imports (dirname, join, resolvePath), and
keep the node:url import (fileURLToPath) grouped appropriately with the other
node imports so all import specifiers are consistently sorted.
- Line 12: Update the usage comment string to reference the actual filename;
change the commented usage "tsx
services/platform/scripts/backfill_example_i18n.ts" to "tsx
services/platform/scripts/backfill-example-i18n.ts" so the usage example matches
the real file name (look for the top-of-file usage comment that contains
backfill_example_i18n.ts).
- Around line 26-29: The AgentFile interface uses unknown and the script uses an
`as` cast around the i18n data; replace `unknown` with a concrete type (e.g.,
make i18n?: Record<string, Record<string, string>> or another precise shape that
matches your i18n values) and remove the `as` assertion at the location where
the parsed/loaded data is cast (the code around the `as` on line 78). Instead of
casting, parse/assign the value into a variable annotated with the concrete type
and perform a simple runtime validation/type guard before using it (or
map/transform the raw object into the strongly typed shape). Update the
AgentFile interface and the variable handling the parsed i18n so no `unknown`,
`any`, or `as` remain.

---

Outside diff comments:
In `@services/platform/convex/agents/config.ts`:
- Around line 37-57: Add documentation describing the new locale-aware agent
instructions introduced by resolveInstructions used in toSerializableConfig:
update docs/AGENTS.md and the localized folders docs/de/platform/agents/ and
docs/fr/platform/agents/ to explain the i18n field structure on AgentJsonConfig
(how to provide systemInstructions per locale, fallback behavior when locale is
missing, examples for en/de/fr, and how resolveInstructions picks the locale);
include a small JSON/YAML example showing locale keys and the default key, note
how to supply locale to toSerializableConfig, and document any validation rules
or supported locale codes.

In `@services/platform/convex/agents/file_actions.ts`:
- Around line 512-618: The PR changes resolveAgentConfig to pass orgLocale into
toSerializableConfig, which causes agent instructions to be localized using the
organization's default locale; update the user-facing documentation across all
published locales (en, de, fr) in the same PR to reflect this behavior
change—update docs that describe agent output/localization, any examples
referencing instruction language, and release notes for this feature in the
project's documentation files so readers in each locale see the new
default-locale behavior; ensure the doc edits reference
resolveAgentConfig/toSerializableConfig behavior and mention that
effectiveConfig.supportedModels validation is unaffected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8bdbe5fd-e72d-4748-9b78-10604784369a

📥 Commits

Reviewing files that changed from the base of the PR and between 300e36c and fd0e6cc.

⛔ Files ignored due to path filters (1)
  • services/platform/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (37)
  • examples/agents/chat-agent.json
  • examples/agents/crm-assistant.json
  • examples/agents/file-assistant.json
  • examples/agents/image-creator.json
  • examples/agents/integration-assistant.json
  • examples/agents/researcher.json
  • examples/agents/translator.json
  • examples/agents/web-assistant.json
  • examples/agents/workflow-assistant.json
  • services/platform/app/features/agents/components/locale-tabs.tsx
  • services/platform/app/features/chat/hooks/__tests__/use-effective-agent.test.ts
  • services/platform/app/features/chat/hooks/queries.ts
  • services/platform/app/features/chat/hooks/use-effective-agent.ts
  • services/platform/app/features/settings/organization/components/organization-settings.tsx
  • services/platform/app/routes/dashboard/$id/agents/$agentId/conversation-starters.tsx
  • services/platform/app/routes/dashboard/$id/agents/$agentId/index.tsx
  • services/platform/app/routes/dashboard/$id/agents/$agentId/instructions.tsx
  • services/platform/convex/agent_tools/delegation/create_delegation_tool.ts
  • services/platform/convex/agent_tools/delegation/load_delegation_agents.ts
  • services/platform/convex/agents/__tests__/config.test.ts
  • services/platform/convex/agents/__tests__/unified_chat_ttft.test.ts
  • services/platform/convex/agents/config.ts
  • services/platform/convex/agents/file_actions.ts
  • services/platform/convex/agents/file_utils.ts
  • services/platform/convex/agents/unified_chat.ts
  • services/platform/convex/lib/agent_chat/__tests__/tool_building_parallelization.test.ts
  • services/platform/convex/lib/agent_chat/internal_actions.ts
  • services/platform/convex/organizations/internal_queries.ts
  • services/platform/lib/shared/constants/agents.ts
  • services/platform/lib/shared/schemas/agents.ts
  • services/platform/lib/shared/utils/__tests__/resolve-agent-locale.test.ts
  • services/platform/lib/shared/utils/get-organization-default-locale.ts
  • services/platform/lib/shared/utils/resolve-agent-locale.ts
  • services/platform/messages/de.json
  • services/platform/messages/en.json
  • services/platform/messages/fr.json
  • services/platform/scripts/backfill-example-i18n.ts

Comment on lines +50 to +51
const defaultLocale = getOrganizationDefaultLocale(organization?.metadata);
const [editingLocale, setEditingLocale] = useState(defaultLocale);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep editingLocale in sync with the resolved org default.

This state is captured before useOrganization() finishes, so orgs whose default locale is not the initial fallback keep editing the wrong tab. That means the first edits can be written into i18n.en even when the org-level response language is actually de or fr.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/routes/dashboard/`$id/agents/$agentId/index.tsx around
lines 50 - 51, The editingLocale state is initialized from
getOrganizationDefaultLocale before useOrganization finishes, causing stale tab
selection; add a useEffect that watches the resolved defaultLocale (computed via
getOrganizationDefaultLocale(organization?.metadata)) and calls
setEditingLocale(defaultLocale) whenever defaultLocale changes so editingLocale
always stays in sync with the org's resolved default locale; reference the
defaultLocale variable, editingLocale state, setEditingLocale setter, and
getOrganizationDefaultLocale/useOrganization to locate where to add the effect.

Comment on lines +82 to +109
const writeFields = useCallback(
(patch: { displayName?: string; description?: string }) => {
const existingI18n = config.i18n ?? {};
const existingOverrides = existingI18n[editingLocale] ?? {};
const next = { ...existingOverrides };
if ('displayName' in patch) {
const v = patch.displayName?.trim();
next.displayName = v ? patch.displayName : undefined;
}
if ('description' in patch) {
const v = patch.description?.trim();
next.description = v ? patch.description : undefined;
}
const nextI18n = {
...existingI18n,
[editingLocale]: next,
};
// On first edit of the default-locale tab on a legacy agent, retire the
// top-level fields so the i18n entries become canonical.
const shouldClearLegacy =
editingLocale === defaultLocale &&
(!!legacyDisplayName || !!legacyDescription);
updateConfig({
i18n: nextI18n,
...(shouldClearLegacy
? { displayName: undefined, description: undefined }
: {}),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don't clear the untouched legacy field during migration.

On a legacy agent, editing only description seeds next with the new description, then shouldClearLegacy removes both top-level fields. Because displayName was never copied into next, that edit silently drops the existing name entirely; the inverse happens when only displayName is edited. Seed next with the default-locale legacy values before clearing them, or only clear the specific field(s) migrated in this patch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/routes/dashboard/`$id/agents/$agentId/index.tsx around
lines 82 - 109, The current writeFields (function writeFields) can drop an
untouched legacy top-level field when migrating to i18n; to fix, seed the
per-locale `next` object with the default-locale legacy values before applying
patch and overrides so untouched fields are preserved (e.g., compute a base =
editingLocale === defaultLocale ? { displayName: legacyDisplayName, description:
legacyDescription } : {} and then const next = { ...base, ...existingOverrides
}; apply the patch changes as before), and then call updateConfig(i18n:
nextI18n, ...) as now; this preserves untouched legacy fields during the first
edit and allows the existing shouldClearLegacy logic to safely retire top-level
values.

Comment on lines +288 to +295
<LocaleTabs
defaultLocale={defaultLocale}
editingLocale={editingLocale}
onEditingLocaleChange={setEditingLocale}
hasTranslation={hasTranslation}
onAutoTranslate={hasSource ? handleAutoTranslate : undefined}
isTranslating={translateMutation.isPending}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Confirm the matching docs shipped with this UI change.

This introduces locale-aware agent authoring in a user-facing settings screen, but I don't see corresponding docs in the provided diff. Please verify that the same PR updates docs/ in en, de, and fr for the new editing flow and org-level language behavior.

I can help draft the docs delta if needed. Based on learnings: "Documentation must be updated in the same PR as any code change that alters what users see, configure, or interact with" and "Docs ship with the code... update docs/ in all three locales (en, de, fr)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/routes/dashboard/`$id/agents/$agentId/index.tsx around
lines 288 - 295, The UI change adds locale-aware agent authoring via LocaleTabs
(props: defaultLocale, editingLocale, onEditingLocaleChange, hasTranslation,
onAutoTranslate using handleAutoTranslate when hasSource, and isTranslating
driven by translateMutation.isPending), but the PR must also update user docs;
add corresponding documentation updates under docs/ for en, de, and fr that
describe the new editing flow (how to switch editingLocale, what hasTranslation
and onAutoTranslate do) and the org-level language behavior (defaultLocale
semantics and when auto-translate is available), ensuring the same PR includes
these localized markdown pages and links from the relevant settings/agents
documentation so the UI and docs remain in sync.

Comment on lines +59 to +60
const defaultLocale = getOrganizationDefaultLocale(organization?.metadata);
const [editingLocale, setEditingLocale] = useState(defaultLocale);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Sync the active locale when the org default loads.

useOrganization() resolves after the first render, so this state is initialized with the fallback locale and never updates for orgs whose default is de/fr. The page can therefore open on the wrong tab and persist instructions into the wrong i18n bucket until the user notices and switches manually.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/routes/dashboard/`$id/agents/$agentId/instructions.tsx
around lines 59 - 60, The editingLocale state is initialized from
getOrganizationDefaultLocale but never updates when useOrganization resolves;
add a useEffect that watches defaultLocale (derived via
getOrganizationDefaultLocale(organization?.metadata)) and calls
setEditingLocale(defaultLocale) when defaultLocale is defined and different from
the current editingLocale to sync the active locale; reference defaultLocale,
editingLocale, setEditingLocale, and getOrganizationDefaultLocale to locate
where to add the effect.

Comment on lines +180 to +182
const scaffold =
(locale ? DELEGATION_SCAFFOLD[locale] : undefined) ??
DELEGATION_SCAFFOLD.en;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize regional locale tags before scaffold lookup.

Line 181 does an exact key lookup only. Regional tags like de-CH/fr-CH will incorrectly fall back to English instead of de/fr.

💡 Proposed fix
-  const scaffold =
-    (locale ? DELEGATION_SCAFFOLD[locale] : undefined) ??
-    DELEGATION_SCAFFOLD.en;
+  const normalizedLocale = locale?.toLowerCase();
+  const baseLocale = normalizedLocale?.split('-')[0];
+  const scaffold =
+    (normalizedLocale ? DELEGATION_SCAFFOLD[normalizedLocale] : undefined) ??
+    (baseLocale ? DELEGATION_SCAFFOLD[baseLocale] : undefined) ??
+    DELEGATION_SCAFFOLD.en;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const scaffold =
(locale ? DELEGATION_SCAFFOLD[locale] : undefined) ??
DELEGATION_SCAFFOLD.en;
const normalizedLocale = locale?.toLowerCase();
const baseLocale = normalizedLocale?.split('-')[0];
const scaffold =
(normalizedLocale ? DELEGATION_SCAFFOLD[normalizedLocale] : undefined) ??
(baseLocale ? DELEGATION_SCAFFOLD[baseLocale] : undefined) ??
DELEGATION_SCAFFOLD.en;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/agent_tools/delegation/create_delegation_tool.ts`
around lines 180 - 182, The scaffold lookup is using the full locale string
which fails for regional tags like "de-CH"; normalize the locale to its primary
language subtag before indexing DELEGATION_SCAFFOLD by deriving a baseLocale
(e.g., take locale?.split('-')[0] or equivalent) and then use (baseLocale ?
DELEGATION_SCAFFOLD[baseLocale] : undefined) ?? DELEGATION_SCAFFOLD.en so
scaffold resolves "de-CH"/"fr-CH" to "de"/"fr" correctly while still falling
back to English.

Comment on lines +34 to 100
describe('resolveAgentLocale', () => {
it('returns i18n[locale] values when fully present', () => {
const result = resolveAgentLocale(i18nFirstAgent, 'en');
expect(result.displayName).toBe('Chat Agent');
expect(result.description).toBe('English description');
expect(result.conversationStarters).toEqual(['Hello', 'Help me']);
expect(result.systemInstructions).toBe(
'You are a helpful English assistant.',
);
});

it('returns full i18n overrides for a translated locale', () => {
const result = resolveAgentLocale(agent, 'de', 'en');

expect(result).toEqual({
displayName: 'Chat-Assistent',
description: 'English description',
conversationStarters: ['Hallo', 'Hilf mir'],
});
it('falls back field-by-field to i18n.en when requested locale has partial overrides', () => {
const result = resolveAgentLocale(i18nFirstAgent, 'de');
expect(result.displayName).toBe('Chat-Assistent');
expect(result.description).toBe('English description'); // from en
expect(result.conversationStarters).toEqual(['Hallo', 'Hilf mir']);
expect(result.systemInstructions).toBe(
'Du bist ein hilfreicher deutscher Assistent.',
);
});

it('falls back field-by-field for partial overrides', () => {
const result = resolveAgentLocale(agent, 'fr', 'en');

expect(result).toEqual({
displayName: 'Agent de chat',
description: 'English description',
conversationStarters: ['Hello', 'Help me'],
});
it('falls back to i18n.en for unknown locale, never uses i18n.de', () => {
const result = resolveAgentLocale(i18nFirstAgent, 'es');
expect(result.displayName).toBe('Chat Agent'); // en, not de
expect(result.description).toBe('English description');
expect(result.conversationStarters).toEqual(['Hello', 'Help me']);
expect(result.systemInstructions).toBe(
'You are a helpful English assistant.',
);
});

it('falls back to top-level for unknown locale', () => {
const result = resolveAgentLocale(agent, 'es', 'en');
it('falls back to i18n.en when requested locale has no entry', () => {
const result = resolveAgentLocale(i18nFirstAgent, 'fr');
expect(result.displayName).toBe('Agent de chat'); // fr override
expect(result.description).toBe('English description'); // en fallback
expect(result.conversationStarters).toEqual(['Hello', 'Help me']); // en
expect(result.systemInstructions).toBe(
'You are a helpful English assistant.',
);
});

expect(result).toEqual({
displayName: 'Chat Agent',
description: 'English description',
conversationStarters: ['Hello', 'Help me'],
});
it('resolves legacy agent (no i18n) to top-level fields', () => {
const result = resolveAgentLocale(legacyAgent, 'de');
expect(result.displayName).toBe('Legacy');
expect(result.description).toBe('Old format');
expect(result.conversationStarters).toEqual(['Hi']);
expect(result.systemInstructions).toBe('You are legacy.');
});

it('handles agent with no i18n field', () => {
const simpleAgent = {
displayName: 'Simple',
description: 'No translations',
it('falls through to top-level when neither i18n[locale] nor i18n.en have the field', () => {
const agent = {
displayName: 'Top-level Name',
systemInstructions: 'Top-level instructions',
i18n: {
de: { displayName: 'DE Name' },
},
};
const result = resolveAgentLocale(simpleAgent, 'de', 'en');
const result = resolveAgentLocale(agent, 'fr');
expect(result.displayName).toBe('Top-level Name');
expect(result.systemInstructions).toBe('Top-level instructions');
});

expect(result).toEqual({
displayName: 'Simple',
description: 'No translations',
conversationStarters: undefined,
});
it('returns empty displayName when no source has one', () => {
const result = resolveAgentLocale({}, 'en');
expect(result.displayName).toBe('');
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add an explicit BCP-47 base-language fallback test.

This suite should directly assert region-variant narrowing (e.g., de-AT resolving via i18n.de) to protect the intended precedence behavior.

🧪 Suggested test case
 describe('resolveAgentLocale', () => {
+  it('uses base-language locale when region variant is requested', () => {
+    const result = resolveAgentLocale(i18nFirstAgent, 'de-AT');
+    expect(result.displayName).toBe('Chat-Assistent');
+    expect(result.description).toBe('English description'); // en fallback
+    expect(result.conversationStarters).toEqual(['Hallo', 'Hilf mir']);
+    expect(result.systemInstructions).toBe(
+      'Du bist ein hilfreicher deutscher Assistent.',
+    );
+  });
+
   it('returns i18n[locale] values when fully present', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/lib/shared/utils/__tests__/resolve-agent-locale.test.ts`
around lines 34 - 100, Add a new unit test in the resolveAgentLocale suite that
asserts BCP-47 base-language fallback: call resolveAgentLocale(i18nFirstAgent,
'de-AT') and verify it selects fields from i18n.de (e.g., displayName ===
'Chat-Assistent', conversationStarters === ['Hallo','Hilf mir'],
systemInstructions === 'Du bist ein hilfreicher deutscher Assistent.') to ensure
region-variant narrowing uses the base language; reference the
resolveAgentLocale function and the existing i18nFirstAgent fixture so the test
follows the same patterns as the other cases.

Comment on lines +20 to +24
* Resolves locale-specific agent fields with i18n-first precedence:
* 1. `i18n[requestedLocale].<field>`
* 2. `i18n[baseLanguage].<field>` — e.g. `fr-CH` narrows to `fr`
* 3. `i18n[appDefault='en'].<field>`
* 4. top-level `<field>` (legacy fallback for pre-i18n agents)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract this precedence chain into one shared helper.

resolveAgentLocale() now duplicates the same requested → base-language → en → legacy fallback logic that services/platform/convex/agents/config.ts:16-35 already carries for systemInstructions. If those drift, the UI can resolve one value while the server serializes another. Please centralize the field-resolution logic and have both paths call it.

Also applies to: 33-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/lib/shared/utils/resolve-agent-locale.ts` around lines 20 -
24, The locale-field precedence logic (requested locale → base language → app
default 'en' → top-level legacy field) is duplicated in resolveAgentLocale() and
the systemInstructions resolution in services/platform/convex/agents/config.ts;
extract that logic into a single helper (e.g., resolveLocaleField or
resolveAgentLocaleField) that accepts (i18nMap, requestedLocale, fieldName,
appDefault='en', topLevelFallback?) and returns the resolved string following
the four-step precedence, then replace the inlined logic in resolveAgentLocale()
and the systemInstructions resolution to call this helper; update
imports/exports accordingly so both modules use the shared helper.

* Does NOT call the LLM translator — de/fr gaps remain for the author (or a
* follow-up script) to fill via the shipped auto-translate UI.
*
* Usage: tsx services/platform/scripts/backfill_example_i18n.ts
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the usage command path typo.

Line 12 references backfill_example_i18n.ts, but the file is backfill-example-i18n.ts.

Proposed fix
- * Usage: tsx services/platform/scripts/backfill_example_i18n.ts
+ * Usage: tsx services/platform/scripts/backfill-example-i18n.ts
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* Usage: tsx services/platform/scripts/backfill_example_i18n.ts
* Usage: tsx services/platform/scripts/backfill-example-i18n.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/scripts/backfill-example-i18n.ts` at line 12, Update the
usage comment string to reference the actual filename; change the commented
usage "tsx services/platform/scripts/backfill_example_i18n.ts" to "tsx
services/platform/scripts/backfill-example-i18n.ts" so the usage example matches
the real file name (look for the top-of-file usage comment that contains
backfill_example_i18n.ts).

Comment on lines +15 to +17
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { join, dirname, resolve as resolvePath } from 'node:path';
import { fileURLToPath } from 'node:url';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Sort import specifiers to match repo import-order rules.

Please sort imported members consistently.

Proposed fix
-import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
-import { join, dirname, resolve as resolvePath } from 'node:path';
+import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { dirname, join, resolve as resolvePath } from 'node:path';
As per coding guidelines: "`**/*.{ts,tsx,js,jsx}`: Imports at the top, exports at the bottom, both sorted."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { join, dirname, resolve as resolvePath } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve as resolvePath } from 'node:path';
import { fileURLToPath } from 'node:url';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/scripts/backfill-example-i18n.ts` around lines 15 - 17, The
import specifiers are not consistently sorted; please reorder them to follow the
repo import-order rules by alphabetizing members within each import and ordering
import statements per project convention: for these lines, alphabetize the
node:fs named imports (readFileSync, readdirSync, writeFileSync) and the
node:path named imports (dirname, join, resolvePath), and keep the node:url
import (fileURLToPath) grouped appropriately with the other node imports so all
import specifiers are consistently sorted.

Comment on lines +26 to +29
interface AgentFile {
[key: string]: unknown;
i18n?: Record<string, Record<string, unknown>>;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove unknown and as from this script’s types.

This script currently uses both unknown and an as assertion (Line 78), which violates the repo TypeScript rule.

Proposed fix
+type JsonValue =
+  | string
+  | number
+  | boolean
+  | null
+  | { [key: string]: JsonValue }
+  | JsonValue[];
+
 interface AgentFile {
-  [key: string]: unknown;
-  i18n?: Record<string, Record<string, unknown>>;
+  [key: string]: JsonValue;
+  i18n?: Record<string, Record<string, JsonValue>>;
 }
@@
-    const agent = JSON.parse(raw) as AgentFile;
+    const agent: AgentFile = JSON.parse(raw);
As per coding guidelines: "`**/*.{ts,tsx}`: Never `as`, never `any`, never `unknown` in TypeScript."

Also applies to: 78-78

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/scripts/backfill-example-i18n.ts` around lines 26 - 29, The
AgentFile interface uses unknown and the script uses an `as` cast around the
i18n data; replace `unknown` with a concrete type (e.g., make i18n?:
Record<string, Record<string, string>> or another precise shape that matches
your i18n values) and remove the `as` assertion at the location where the
parsed/loaded data is cast (the code around the `as` on line 78). Instead of
casting, parse/assign the value into a variable annotated with the concrete type
and perform a simple runtime validation/type guard before using it (or
map/transform the raw object into the strongly typed shape). Update the
AgentFile interface and the variable handling the parsed i18n so no `unknown`,
`any`, or `as` remain.

larryro added 3 commits April 23, 2026 16:48
Server-side `normalizeAgentConfig` is the single chokepoint for canonical
agent state, keyed to the org's configured `defaultLocale`. The UI writes
naive payloads into `i18n[locale]` and the server strips empty placeholders
(I-2) and retires the legacy top-level fields per-field when the default
locale carries content (I-1). Legacy-only agents (I-4) are untouched so the
resolver's fallback chain keeps working for pre-migration data.

- `normalizeAgentConfig(config, defaultLocale)` wired into `saveAgent`,
  `duplicateAgent`, and — as a softened validator — `restoreFromHistory`.
  Snapshot/restore stay byte-faithful; old snapshots that no longer pass
  the current schema log a warning instead of failing the restore. Prod
  warn-log when normalize changes anything for observability.
- `duplicateAgent` no longer yields "undefined (Copy)" for i18n-only
  sources. Each populated i18n locale gets its own suffixed displayName;
  legacy top-level falls back to the agent slug via `resolveAgentLocale`.
- `buildDelegationTools` passes a real org-scoped `orgSlug` (was hardcoded
  `'default'`, a multi-tenant bug). `orgSlug` + `orgLocale` are hoisted
  into the outer `Promise.all` and shared with `buildWorkflowTools` and
  model lookup — one query instead of three.
- Resolver and normalize share `isEffectivelyEmpty` so `""`, `[]`, and
  whitespace-only values fall through the i18n chain instead of
  masquerading as present. `pickField` preserves `displayName: string`
  via an explicit `?? ''` guard.
- `narrowBcp47` extracted so `resolve-agent-locale` and the delegation
  scaffold lookup agree: `fr-CH` narrows to `fr` before falling to `en`.
- Agent editor UI drops all client-side legacy-retirement logic; server
  normalization takes over. Fixes the `||` fallthrough in `instructions.tsx`
  that leaked legacy content into non-default locale tabs.
- Page title resolves `displayName` through the locale chain and refetches
  on save so it stays in sync with server-normalized state.
- `agent-navigation` normalizes client-side inside `markSaved` so the
  dirty-state baseline matches disk, and surfaces schema errors in the
  save-failed toast.
- Org-settings helper text moves to `Select.description` (proper
  `aria-describedby`) and invalidates the auth-session query so in-flight
  tabs see locale changes without reload.
- CI test asserts every `examples/agents/*.json` is already normalized,
  since `scaffoldNewOrganization` copies examples into new orgs outside
  the write boundary.
- Delete the one-shot `backfill-example-i18n.ts` script (migration done;
  a future one will be written fresh).
… lists

`agents-table` (admin), `top-agents-table` (analytics), and the agent
`delegation` picker filtered `listAgents` output by `typeof displayName ===
'string'` (or `!== undefined`). After the write-boundary normalization
clears top-level translatable fields, i18n-first agents have
`displayName: undefined` at the raw level — so every migrated agent
silently disappeared from those three lists.

Each consumer now calls `resolveAgentLocale(agent, userLocale)` (matching
what `useChatAgents` already does for the chat picker) and falls back to
the slug only when no locale has a displayName. Error entries are
recognised by their `status` field instead.
…undary

- Drop unused re-exports of TRANSLATABLE_FIELDS / TranslatableField;
  the "exposed for the delegation-scaffold lookup" consumer doesn't
  exist, and Knip was failing on the dead exports.
- AgentNavigation now reads org metadata via useOrganization; mock it
  in agent-navigation.test.tsx so the axe audit renders without hitting
  api.organizations.queries on the mocked api object.
@larryro larryro merged commit e63c3c8 into main Apr 23, 2026
15 checks passed
@larryro larryro deleted the feat/agent-i18n-first-model branch April 23, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant