You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: multilingual way stubs for ja, de, es, ar (#103)
* feat: add multilingual locale stubs for ja, de, es, ar
332 locale entries across 83 ways (82 original + 1 tuning way),
stored as packed .locales.jsonl files co-located with each way.
Native-language matching dramatically outperforms cross-language:
ja: -0.03 → 0.93, ar: 0.04 → 0.96, de: 0.08 → 0.82, es: 0.44 → 0.84
Per-entry embed_threshold auto-tuned by `ways tune`.
Ref: #102 (4 of 52 supported languages)
* refactor: packed .locales.jsonl format, CLI support, and docs
Storage: replace individual .lang.md stub files with co-located
.locales.jsonl (one per way, all languages in one file). Override
mechanism: external .lang.md files supersede packed entries.
CLI changes:
- corpus.rs: read .locales.jsonl with override detection, inherit
parent way threshold, respect tuned embed_threshold
- language.rs: detect locales from packed files, show uncovered
languages by default, --audit for per-way detail
- frontmatter.rs: LocaleEntry struct with optional embed_threshold
- is_stale: check .jsonl timestamps
Test fix: EN embedding test now uses ways-corpus-en.jsonl (not the
combined corpus that includes multilingual entries). Each test
validates its own model against its own corpus.
Docs: rewrite languages.md for packed format and tuning workflow,
amend ADR-107 with evaluation data and packed storage design,
update authoring/optimization ways, add tuning sub-way.
Also: fix .gitignore to track .locales.jsonl, fix absolute path
permissions for subagent file access.
* feat: `ways tune` — parallel threshold auto-tuner with discrimination audit
Two modes:
- `ways tune [--apply]`: compute optimal embed_threshold per locale
entry by scoring against the full corpus. Parallel (cores - 4),
~13s for 328 entries on 32 cores.
- `ways tune --audit`: flag entries with low discrimination gap,
showing which ways they're confused with (confusers).
Two dimensions to optimize:
- Discrimination (gap): description quality — wide gap = precise
- Sensitivity (threshold): auto-computed from discrimination data
The audit surfaces entries where no threshold can give both correct
matches and zero false positives — the description itself needs work.
* fix: session sim tests expect .marker.main not .marker
Pre-existing bug: way markers changed to .marker.{agent_id} for
per-agent scoping (subagents don't block each other), but the
session simulator tests still checked for the old .marker path.
10/10 scenarios pass.
* fix: review feedback — shared locale validation, tuner error reporting
- Extract extract_locale_from_filename() to util.rs with languages.json
validation (prevents false positives like "foo.setup.md" → "setup")
- Remove duplicate implementations from corpus.rs and language.rs
- Add failure counter to parallel tuner — surfaces error count in summary
instead of silently dropping failed ways
Copy file name to clipboardExpand all lines: docs/architecture/system/ADR-107-way-match-corpus-batch-mode-and-locale-support.md
+44-9Lines changed: 44 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -107,18 +107,52 @@ Way body content (the guidance injected into agent context) is NOT translated. R
107
107
- The guidance is for the agent's reasoning, not displayed to the user
108
108
- Cross-language injection is well-understood: English instructions → non-English output
109
109
110
-
The ADR-107 Draft's Tier 1/Tier 2 file model (`{name}-{lang}.md` with frontmatter-only stubs) is **deferred**. It solved a real problem (matching vocabulary in the user's language) but the embedding engine solves it better — cross-language semantic matching without per-language vocabulary files. If BM25 is the only engine and a non-Romance language is needed, the tiered file model can be revisited.
110
+
### Native language stubs (shipped)
111
111
112
-
### Embedding model upgrade path
112
+
The original ADR-107 Draft proposed a tiered file model (`{name}-{lang}.md`). This was initially deferred in favor of cross-language embedding. However, evaluation data showed that native-language stubs dramatically outperform cross-language matching:
113
113
114
-
The current `all-MiniLM-L6-v2` (21MB, English, 98% accuracy) serves the English-only use case well. For multilingual matching:
114
+
| Language | EN model × EN desc | Multi model × cross-lang | Multi model × native stub |
| all-MiniLM-L6-v2 | 21MB | English | Current, shipping |
119
-
| paraphrase-multilingual-MiniLM-L12-v2 |~120MB | 52 | Same architecture, multilingual training data |
121
+
Native stubs are now the primary multilingual matching strategy. Each stub provides a `description` and `vocabulary` in the target language, scored by the multilingual embedding model.
120
122
121
-
The upgrade is a model swap — same GGUF format, same `way-embed` binary, same embedding dimensions. `make setup` downloads the appropriate model based on configured language. If `output_language` is `en` or unset, the smaller English model is used. If non-English, the multilingual model is downloaded.
123
+
### Packed locale storage (.locales.jsonl)
124
+
125
+
Stubs are stored as **packed JSONL**, one file per way, co-located with the way it belongs to:
-**No `embed_threshold`** in packed format — hardcoded to `0.25` in the corpus generator. Per-way override requires externalizing to a full `.lang.md` file.
140
+
-**No `embed_model`** in packed format — always `"multilingual"` for locale stubs.
141
+
-**Override mechanism**: if `briefing.ja.md` exists as a real file on disk, it supersedes the `ja` entry in `briefing.locales.jsonl`. This allows graduating any stub to a full native-language way with body content.
142
+
-**Co-location over aggregation**: one `.locales.jsonl` per way (not per language, not one global file). Way deletion = directory deletion, translations go with it.
143
+
144
+
This replaces the individual `{name}.{lang}.md` stub files (which would grow to 4,000+ files at full language coverage). The packed format keeps the training corpus version-controlled, diffable, and lintable while eliminating file sprawl.
145
+
146
+
### Dual embedding model (shipped)
147
+
148
+
Both models ship simultaneously. `make setup` downloads both:
149
+
150
+
| Model | Size | Languages | Use case |
151
+
|-------|------|-----------|----------|
152
+
| all-MiniLM-L6-v2 | 21MB | English | Precise EN matching (default) |
`ways corpus` splits entries by `embed_model` field into two corpora (`ways-corpus-en.jsonl`, `ways-corpus-multi.jsonl`). The scanner queries both and merges results. Each way's English entry is scored by the EN model; each locale stub is scored by the multilingual model.
122
156
123
157
`languages.json` defines the supported language set for the multilingual model. Adding a language means verifying it's in the model's training data and adding the entry — no code changes.
124
158
@@ -161,7 +195,8 @@ This makes model selection empirical: run the tests against candidate models, pi
161
195
### Neutral
162
196
163
197
- Way content stays English — no translation infrastructure needed
164
-
- The tiered file model from the original Draft is deferred, not rejected — it becomes relevant if someone needs BM25-only matching in non-Romance languages
Copy file name to clipboardExpand all lines: docs/architecture/system/multilingual-model-evaluation.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,6 +64,6 @@ The multilingual model enables three matching strategies:
64
64
65
65
1.**English ways + English model** — current production. High precision for English prompts.
66
66
2.**English ways + multilingual model (cross-language)** — user types in any language, matches against English descriptions. Works but scores 30-50% lower.
67
-
3.**Native-language stubs + multilingual model (same-language)** — frontmatter-only `.ja.md` stubs with native descriptions. Consistently scores 0.80+ across tested languages.
67
+
3.**Native-language stubs + multilingual model (same-language)** — locale entries in `.locales.jsonl` with native descriptions. Consistently scores 0.80+ across tested languages.
68
68
69
69
**Recommendation:** Ship both models. English ways use the English model (precise, 21MB). Multilingual stubs use the multilingual model (broad, 127MB). Per-way `embed_model` frontmatter field controls routing. This gives per-language threshold tuning without compromising English accuracy.
Copy file name to clipboardExpand all lines: docs/hooks-and-ways/languages.md
+80-16Lines changed: 80 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,37 +49,42 @@ embed_threshold: 0.35
49
49
50
50
Ways with `embed_model: multilingual` are scored by the multilingual model against a separate corpus.
51
51
52
-
## Creating language stubs
52
+
## Locale stubs — packed format
53
53
54
-
A language stub is a frontmatter-only `.{lang}.md` file that provides native-language matching vocabulary for an existing way. The way body stays English — only the matching changes.
54
+
Locale stubs provide native-language matching vocabulary for existing ways. They're stored as **packed JSONL**, one file per way, co-located with the way they belong to:
55
55
56
56
```
57
57
hooks/ways/softwaredev/code/security/
58
-
security.md # English way — full body + frontmatter
59
-
security.ja.md # Japanese stub — frontmatter only, no body
60
-
security.ko.md # Korean stub — frontmatter only, no body
58
+
security.md # English way — full body + frontmatter
59
+
security.locales.jsonl # all language stubs (one line per language)
61
60
```
62
61
63
-
Example stub (`security.ja.md`):
62
+
Each line in the `.locales.jsonl` is a self-contained locale entry:
{"lang":"es","description":"Seguridad general, codificación segura","vocabulary":"seguridad vulnerable defensa OWASP","embed_threshold":0.78}
68
+
{"lang":"ar","description":"نظرة عامة على الأمان والبرمجة الآمنة","vocabulary":"أمان برمجة آمنة حماية ثغرات","embed_threshold":0.84}
72
69
```
73
70
74
71
When a Japanese user types a prompt, the scanner:
75
-
1.Matches `security.ja.md`'s frontmatter using the multilingual model
72
+
1.Scores the Japanese stub's description using the multilingual model
76
73
2. Injects `security.md`'s English body (the guidance text)
77
74
78
-
The agent reads the English guidance and responds in the configured output language.
75
+
### Format rules
76
+
77
+
-**`embed_threshold`** is optional — omit it and the corpus generator defaults to 0.25. Use `ways tune --apply` to compute optimal values automatically.
78
+
-**`embed_model`** is implicit — always `multilingual` for locale stubs (not stored in the file).
79
+
-**No body content** — just the JSONL line. If someone writes a full native-language way, they create `security.ja.md` as a regular file, which overrides the packed entry.
80
+
81
+
### Override mechanism
82
+
83
+
If `security.ja.md` exists as a real file alongside `security.locales.jsonl`, the `.md` file wins for Japanese. This lets authors graduate a stub into a full native-language way with body content, without touching the packed file.
79
84
80
85
### Why same-language stubs matter
81
86
82
-
Cross-language matching (Japanese prompt → English description) scores ~0.69. Same-language matching (Japanese prompt → Japanese description) scores ~0.93. The stub's native-language description dramatically improves matching precision.
87
+
Cross-language matching (Japanese prompt → English description) scores ~0.69. Same-language matching (Japanese prompt → Japanese description) scores ~0.93. The native stub dramatically improves matching precision.
See `docs/architecture/system/multilingual-model-evaluation.md` for full test results.
91
96
97
+
## Tuning and auditing
98
+
99
+
### Auto-tuning thresholds
100
+
101
+
`ways tune` computes the optimal `embed_threshold` for each locale entry by scoring it against the full corpus and finding the discrimination boundary:
102
+
103
+
```bash
104
+
# Preview what would change (dry run)
105
+
ways tune
106
+
107
+
# Tune a specific way
108
+
ways tune --way security
109
+
110
+
# Apply tuned thresholds to .locales.jsonl files
111
+
ways tune --apply
112
+
113
+
# Regenerate corpus with tuned values
114
+
ways corpus
115
+
```
116
+
117
+
The tuner runs in parallel (all cores minus 4). ~13 seconds for 328 entries on a 32-core machine.
118
+
119
+
### Discrimination audit
120
+
121
+
`ways tune --audit` flags entries where the description doesn't clearly separate this way from others — no threshold can fix an ambiguous description:
122
+
123
+
```bash
124
+
# Flag entries with discrimination gap < 0.15
125
+
ways tune --audit
126
+
127
+
# Adjust the gap threshold
128
+
ways tune --audit --audit-threshold 0.20
129
+
```
130
+
131
+
The audit shows **confusers** — which ways the ambiguous entry is being confused with:
132
+
133
+
```
134
+
softwaredev/docs/mermaid
135
+
ar — gap 0.07 (self 1.00, noise 0.93) confused with: softwaredev/visualization/diagrams (0.93)
136
+
```
137
+
138
+
This tells the author: "your Arabic mermaid description looks too similar to the diagrams way — revise the vocabulary to distinguish them."
{"lang":"ar","description":"إعداد الإحاطة الصباحية وملخص الأحداث الليلية","vocabulary":"إحاطة صباحية ملخص ليلي تقرير يومي مستجدات","embed_threshold":0.56}
2
+
{"lang":"de","description":"Morgendliches Briefing, was ist über Nacht passiert, Tagesübersicht über alle Posteingänge und Kalender","vocabulary":"Morgenbriefing Tagesübersicht aufholen was habe ich verpasst Zusammenfassung Prioritäten Posteingang Kalender Überblick","embed_threshold":0.69}
3
+
{"lang":"es","description":"Resumen matutino, ponerse al día con lo que pasó durante la noche","vocabulary":"ponerse al día resumen matutino inicio del día agenda prioridades briefing","embed_threshold":0.62}
{"lang":"ar","description":"إدارة محادثات الفريق ومنصات التراسل","vocabulary":"محادثة فريق رسائل تواصل منصة تراسل","embed_threshold":0.64}
2
+
{"lang":"de","description":"Team-Chat und Messaging-Plattformen, Nachrichten lesen und mit Freigabe senden, Kommunikationskanäle","vocabulary":"Teams Chat Nachricht Slack Kanal ungelesen Konversation Direktnachricht Gruppenchat senden antworten Benachrichtigung Erwähnung","embed_threshold":0.66}
3
+
{"lang":"es","description":"Chat de equipo y plataformas de mensajería, envío de mensajes","vocabulary":"teams chat mensaje slack canal no leído conversación respuesta notificación","embed_threshold":0.62}
{"lang":"de","description":"Persönliche Assistenz für E-Mail, Posteingang, Kalender, Aufgaben und Kommunikation über mehrere Konten hinweg","vocabulary":"Assistenz Triage Briefing aufholen Posteingang Tagesablauf Terminplan Agenda Konten Arbeitsbereich verwalten helfen","embed_threshold":0.69}
3
+
{"lang":"es","description":"Asistente ejecutivo para correo, bandeja de entrada, calendario, tareas y comunicaciones","vocabulary":"asistente ejecutivo triaje briefing bandeja de entrada agenda calendario","embed_threshold":0.68}
{"lang":"ar","description":"صياغة الردود على رسائل البريد الإلكتروني","vocabulary":"صياغة رد بريد إلكتروني كتابة رسالة مسودة","embed_threshold":0.67}
2
+
{"lang":"de","description":"E-Mail-Entwürfe schreiben, Schreibstil kalibrieren, Antworten mit korrektem Threading erstellen","vocabulary":"Entwurf Antwort verfassen E-Mail schreiben Nachricht Tonfall Stil Thread Anhang formulieren","embed_threshold":0.76}
3
+
{"lang":"es","description":"Redactar respuestas de correo, estilo de escritura, borradores de email","vocabulary":"borrador respuesta redactar correo escribir mensaje tono estilo hilo","embed_threshold":0.71}
0 commit comments