π News Translate
July 23, 2026 Β· View on GitHub
This workflow is the sole writer of analysis/daily/$DATE/$SUB/executive-brief_<lang>.md files. It translates the English-master executive brief (executive-brief.md, produced by the per-type news workflows) into 13 non-English sibling files β together with the English source these give "all 14 languages". It never generates original analysis and never modifies executive-brief.md itself.
π Authoritative content contract:
TRANSLATION_GUIDE.md Β§Executive Brief Markdown Translations. Read this in full before producing any translation β it defines verbatim-preserve blocks, always-translate blocks, structural parity, tone register per language, RTL rules, and the validator acceptance checklist.
Pipeline
- Wall-clock checkpoint (mandatory) β as the very first bash call in this run, record
JOB_START=$(date +%s)and export it. After every source completes Pass 2, re-readNOW=$(date +%s)and computeELAPSED_MIN=$(( (NOW - JOB_START) / 60 )); print it. IfELAPSED_MIN >= 35, stop selecting new sources and proceed directly to the validate β stage β commit βsafeoutputs___create_pull_requestflow regardless of remaining$TRANSLATION_WORKLISTentries. IfELAPSED_MIN >= 42, halt all translation work immediately and execute the07-commit-and-pr.md Β§Emergency deadline order of operationsbash block to ship a partial PR before Timer A (60-min jobtimeout-minutes) and Timer B (~60-min Copilot API session) fire. A partial PR is always better than zero output β see run #26633644372, where source #2 failed withCAPIError: 429 Maximum effective tokens exceeded. - The
Build executive-brief translation work listpre-flight step has already exported four env vars into the agent sandbox:$TRANSLATION_WORKLIST(comma-separated repo-relative paths β read this verbatim, or parse the same content line-by-line from the file at$EXEC_BRIEF_WORKLIST_FILEat${GITHUB_WORKSPACE}/.exec-brief-worklist.txt),$TRANSLATION_LANGS(comma-separated language codes β this is the per-run language batch, already capped atinputs.max_langs(default 13) and pre-selected greenfield-first from the languages that still need work; translate exactly and only these codes β do not expand to the full 13-language set),$MAX_BRIEFS/$MAX_BRIEFS_RESOLVED(1β7, clamp-applied), and the audit counters$MISSING_COUNT/$DRIFT_COUNT. Do not re-scan the filesystem to rebuild this list β the pre-flight step has already honouredinputs.article_date,inputs.subfolder,inputs.languages,inputs.max_briefs,inputs.max_langs, andinputs.force_retranslate. Languages omitted from$TRANSLATION_LANGSthis run are intentionally deferred to the next scheduled run (the source stays MISSING/DRIFT until all requested languages are present and current) β this per-run cap is what keeps the session under the Copilot weighted effective-token cap that aborted run #26641603577. The selector is greenfield-first: sources with one or more missing target files (MISSING) always win themax_briefsbatch slots over sources that only need drift-fixes (DRIFT);DRIFTis only consulted whenMISSINGis empty. If$TRANSLATION_WORKLISTis empty (so$MISSING_COUNT=0and$DRIFT_COUNT=0), nothing is pending β proceed to improvement-mode (re-validate every existing translation against the current<!-- source-sha: ... -->trailer and fix any drift the validator flags). If still nothing changes after improvement-mode, follow07-commit-and-pr.md Β§No-op policy. - For each source path in
$TRANSLATION_WORKLIST(or each line of$EXEC_BRIEF_WORKLIST_FILE):- Pass 1 β translate: Read the source
executive-brief.mdin full once. For every language inTRANSLATION_LANGS, produceanalysis/daily/$DATE/$SUB/executive-brief_<lang>.mdfollowing the TRANSLATION_GUIDE rules β write each file with oneedittool call per language (never viapython3,bashheredocs, or shell redirection β see01-bash-and-shell-safety.md Β§File creation & overwrite strategy). Apply the per-language tone register from TRANSLATION_GUIDE at write time (so Pass 2 never needs a full re-read just to adjust register), and forar/hestart the file with<!-- dir: rtl -->. Preserve every verbatim block (YAML, HTML comments exceptsource-sha,dok_idcodes, Mermaid DSL bodies, code fences, URLs, file paths, evidence-anchor canonical column values). Translate every always-translate block (prose, headings, list items, table cell text, image alt-text, BLUF, decisions, link text). Do not read a freshly written translation back into model context to "confirm" it β the validator in Pass 2 is the authoritative gate. - Pass 2 β validate-first & targeted refine: This is the primary token-efficiency control. Run
npx tsx scripts/validate-executive-brief-translations.ts --source <source> --lang "$TRANSLATION_LANGS"in the runtime shell (the--langscope restricts validation to this run's language batch so deferred languages are not reported as missing). That validator already performs every structural-parity check at near-zero model-token cost: heading / table-row / code-fence / Mermaid-block count parity,dok_idand URL set equality, banned-English-phrase detection (Executive Brief,Decisions,Confidence,BLUF, β¦), the<!-- dir: rtl -->marker forar/he, and the<!-- source-sha: -->trailer. Do not read passing translations back into model context β reading every target file back in full is exactly what drove run #26633644372 to7.6Meffective tokens and a429 Maximum effective tokens exceededabort. Only read back (and re-translate / fix) the specificexecutive-brief_<lang>.mdfiles the validator reports as failing, then re-run the validator until it is clean for the requested languages. - Append the source-revision marker: compute
SRC_SHA=$(git log -1 --format=%H -- <source>)in the runtime shell. Use theedittool to add or replace the final<!-- source-sha: $SRC_SHA -->line at the end of every translation file whose language is in$TRANSLATION_LANGS(consistent with the file-write contract β no>>orecho). Do not touch translation files for languages outside the current batch β their marker must remain stale (or absent) so the next run correctly classifies them as DRIFT/MISSING. This is the drift signal future runs use to decide whether to retranslate. - Title post-processing: for each language code
$Lin$TRANSLATION_LANGS, runnpx tsx scripts/postprocess-translated-brief.ts analysis/daily/$DATE/$SUB/executive-brief_$L.mdto re-apply the renderer'scleanArticleTitlepipeline on the translated H1. This strips locale-specific boilerplate prefixes (Exekutiv sammanfattning β,Zusammenfassung β,ζ§θ‘ζθ¦οΌβ¦) and trailing date suffixes that the Pass 1 translation may have left in place. The helper only rewrites when the cleaned title differs from the source; it never falls back to a BLUF synthesis. Verify the script reportsβorβοΈfor every file (noβ). Do not passexecutive-brief_*.mdas a glob β that would rewrite deferred-language files outside$TRANSLATION_LANGSwithout subsequent validation, defeating the per-run language cap. - Final validate: re-run
npx tsx scripts/validate-executive-brief-translations.ts --source <source> --lang "$TRANSLATION_LANGS"(when available) to confirm the title post-processing in sub-step 4 did not break parity, or fall back to the structural sanity checks listed in TRANSLATION_GUIDE Β§Acceptance checklist. Fix any failures by re-translating only the offending language (read back only that file). Do not commit a file that fails validation. - Re-evaluate the wall-clock checkpoint (from step 1) before starting the next source. Never start a new source after
ELAPSED_MIN >= 35.
- Pass 1 β translate: Read the source
- Stage every newly written / refreshed
executive-brief_<lang>.mdfile. Do not touchexecutive-brief.mditself, any HTML undernews/, any file underanalysis/daily/*/article.md, or any non-translation file. - Call
safeoutputs___create_pull_requestexactly once covering the whole batch, by agent minute 42 at the latest (hard deadline 45 per07-commit-and-pr.md Β§Deadline enforcement). The branch prefixnews/translate/briefs/is enforced by the safe-outputs config (07-commit-and-pr.md).
Inputs
article_dateβ optional. Restrict scanning to a single date folder.subfolderβ optional. Restrict scanning to a single document type folder.languagesβ defaultall-extra(=sv,da,no,fi,de,fr,es,nl,ar,he,ja,ko,zh). Aliases:nordic-extra,eu-extra,cjk,rtl,all-extra. Comma list also accepted.max_briefsβ default1, range1β7. Caps the number of source files processed in this run; total file output =max_briefs Γ |TRANSLATION_LANGS|(= at the defaults; hard-cap worst case , safely under the 200-file safe-outputs cap). The default was lowered from 3 to 1 after run #26633644372 hit the Copilot effective-token limit before finishing source #2.max_langsβ default13(= a full source in one run), range1β13. Caps the number of target languages translated this run. The builder pre-selects the still-needed languages greenfield-first (missing files first, then drift) and truncates to this many. Run #26641603577 hit 27.0M weighted effective tokens (> the 25M cap) at 13 languages, but that was while the heavygithub toolsets:[all]+ three data-MCP server schemas were re-sent every turn; that dead-weight surface has been removed (this workflow only uses bash/edit/safe-outputs), collapsing the per-turn footprint so a full 13-language source now fits in one run. Lower this first if an unusually large brief approaches the cap.force_retranslateβ defaultfalse. Whentrue, every requested language is rewritten even if the<!-- source-sha: -->marker matches.analysis_depthβ defaultstandard. Echoed into the validator output for parity with content workflows; does not change translation behaviour.
Batch-size rationale
| Quantity | Value | Source |
|---|---|---|
| Average source size | ~650 words (range 232β2040) | wc -w over analysis/daily/**/executive-brief.md |
| Per-language Pass 1 write + validator gate | ~45β75 s | Sonnet-class translation budget; Pass 2 is bash-validated, not re-read into context |
Per-source wall time (β€max_langs languages, sequential) | ~15β25 min at max_langs=13 | derived |
Default max_briefs / max_langs | 1 source / 13 languages | a slim per-turn MCP/tool surface keeps the weighted effective-token total under Copilot's per-session cap even at a full 13-language source |
| Per-session token driver | per-turn tool-schema surface Γ turn count | before the fix the dominant cost was the fixed per-turn schema (github toolsets:[all] + three data-MCP servers) re-sent every turn β the ~3.5Γ weighting multiplier. The translate agent calls none of those tools, so the surface was stripped to bash/edit/safe-outputs (+ one riksdag health-gate tool); the per-turn footprint now collapses toward the raw token count |
| Run #26641603577 (pre-fix) | 13 langs β 27.0M weighted / 25M cap β 429 before PR | gh-aw raw 7.67M; the weighted metric was ~3.5Γ raw and exceeded the cap β driven by the dead-weight per-turn schema, now removed; no PR shipped |
| Primary token levers | strip unused per-turn MCP/tool schema + validator-first Pass 2 | removing the data-MCP servers + github toolsets:[all] cuts the fixed per-turn cost so the 25M cap stops being the limiter; structural parity stays in the bash validator |
| Per-run file output | max_briefs Γ max_langs = = 13 files (default) | well under safe-outputs max-patch-files: 100 |
| Daily throughput (3 runs) | up to 39 language-files / day at defaults | wall-clock (Timer A/B) and the 100-file cap, not the token budget, are now the governing limits |
| Current backlog drain (β₯159 untranslated sources) | greenfield-first, oldest source + missing languages first | linear |
If a run is behind schedule at agent minute 30 with translations still pending, the agent MUST trim max_briefs downward (drop the last selected source) rather than skip the Pass 2 validator gate β quality over completeness. A partial PR is always better than missing Timer A (job timeout-minutes: 60). The default max_briefs=1 was set after run #26633644372 repeatedly hit CAPIError: 429 Maximum effective tokens exceeded while attempting source #2; the validator-first Pass 2 above removes the read-back token sink, and stripping the unused per-turn MCP/tool schema (github toolsets:[all] + the scb/world-bank data servers β none of which this workflow ever calls) collapses the fixed per-turn cost that drove the ~3.5Γ weighting, so the weighted effective-token total stays under the 25M cap and the PR safe-output is reached even at the default 13-language batch.
Rules specific to this workflow
- Never generate original analysis or write files outside
analysis/daily/**/executive-brief_<lang>.md. - Never modify the English source
executive-brief.mdβ it is owned by per-type news workflows. - Validate every translation with
scripts/validate-executive-brief-translations.tsbefore commit. Re-translate (do not commit) any file that fails. - Keep the PR under the safe-outputs 200-file cap. The default
max_briefs=1Γ 13 languages = 13 files leaves ample headroom; the hard capmax_briefs=7Γ 13 = 91 stays well under 200. The previous default of 3 was reduced after run #26633644372 hit the Copilot effective-token cap before the PR call. - Per-language idempotency, not workflow-level no-op:
- The pre-flight selector is greenfield-first: it classifies every candidate source as
MISSING(β₯1 requested language file absent or empty) orDRIFT(every requested language file present but β₯1 has a stale<!-- source-sha: -->trailer), then fills themax_briefsbatch slots withMISSINGsources oldest-first and only falls back toDRIFTonceMISSINGis empty. Drift-fix work never displaces an untranslated brief while the backlog still has missing files. - A target language is skipped for that language only when all three hold:
executive-brief_<lang>.mdexists and is non-empty,- its
<!-- source-sha: <sha> -->trailer matches the currentgit log -1 --format=%H -- executive-brief.md, and scripts/validate-executive-brief-translations.tspasses for that language.
- When all requested languages for all candidate sources satisfy the three conditions, the run enters translation-improvement mode: re-run the validator across every translation, fix any drift / structural-parity regressions / RTL-marker omissions the validator flags, refresh
<!-- source-sha: -->trailers if the source has been recommitted since, and commit the resulting changes. Append the improvement-mode rerun marker per07-commit-and-pr.md Β§No-op policy. safeoutputs___noopis only allowed under the conditions in07-commit-and-pr.md Β§No-op policy. "All translations already exist and are valid" is never a noop trigger; it is an improvement trigger.
- The pre-flight selector is greenfield-first: it classifies every candidate source as
Time budget
π‘ Plan to call
safeoutputs___create_pull_requestby agent minute 42 (hard deadline 45) to reserve job-level headroom for setup variance and the safe-outputs runner. The operative constraint is Timer A (jobtimeout-minutes: 60) and Timer B (~60-min Copilot API session). See00-base-contract.md Β§Session timingand07-commit-and-pr.md Β§Deadline enforcement. The defaultmax_briefs=1is sized to keep runs below the Copilot effective-token ceiling; exceeding it risks repeating run #26633644372, where the session aborted on source #2 withCAPIError: 429 Maximum effective tokens exceeded.
Single run (target ~36β40 agent minutes in a 60-min job, hard deadline 45 agent minutes for the PR call):
| Minutes | Phase |
|---|---|
| 0β3 | MCP pre-warm; work-list resolved into $TRANSLATION_WORKLIST env var + $EXEC_BRIEF_WORKLIST_FILE (${GITHUB_WORKSPACE}/.exec-brief-worklist.txt); record JOB_START=$(date +%s) |
| 3β5 | Read TRANSLATION_GUIDE Β§Executive Brief Markdown Translations + selected sources |
| 5β32 | Pass 1 write + validator-first Pass 2 Γ max_briefs sources Γ all requested languages; re-evaluate ELAPSED_MIN after every source and stop selecting new sources once ELAPSED_MIN >= 35 |
| 32β38 | Final validation with scripts/validate-executive-brief-translations.ts; append <!-- source-sha: --> trailers; stage scoped files |
| 38β42 | One safeoutputs___create_pull_request call β HARD DEADLINE agent minute 45; if ELAPSED_MIN >= 42 at any point, switch to the 07-commit-and-pr.md Β§Emergency deadline order of operations bash block immediately |
If the batch cannot finish under this budget, commit the translations completed so far and call safeoutputs___create_pull_request with label partial; the next scheduled run picks up the remaining work. A partial PR is always better than losing the whole batch to Timer A.
All non-workflow-specific rules are in the imported modules β do not restate them here.