OMA Multi-Agent Orchestrator
first-fluke/oh-my-agent
Splits a complex feature into prioritized tasks, spawns specialist CLI subagents in parallel, tracks them through shared memory and verifies each result.
Fans a list of independent items out to subagents in parallel, merges the results back into a table and supports retrying only the rows that failed.
$ npx skills add langchain-ai/langchain-skills --skill swarm -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install langchain-ai/langchain-skills swarm --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/langchain-ai/langchain-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/config/skills/swarm .claude/skills/swarm && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "swarm" agent skill from https://github.com/langchain-ai/langchain-skills/tree/main/config/skills/swarm into .claude/skills/swarm/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swarm", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/langchain-ai/langchain-skills/tree/main/config/skills/swarmType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add langchain-ai/langchain-skills --skill swarm -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install langchain-ai/langchain-skills swarm --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/langchain-ai/langchain-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/config/skills/swarm .agents/skills/swarm && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "swarm" agent skill from https://github.com/langchain-ai/langchain-skills/tree/main/config/skills/swarm into .agents/skills/swarm/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swarm", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add langchain-ai/langchain-skills --skill swarm -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install langchain-ai/langchain-skills swarm --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/langchain-ai/langchain-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/config/skills/swarm .cursor/skills/swarm && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "swarm" agent skill from https://github.com/langchain-ai/langchain-skills/tree/main/config/skills/swarm into .cursor/skills/swarm/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swarm", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/langchain-ai/langchain-skills.git --path config/skills/swarm--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add langchain-ai/langchain-skills --skill swarm -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install langchain-ai/langchain-skills swarm --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/langchain-ai/langchain-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/config/skills/swarm .gemini/skills/swarm && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "swarm" agent skill from https://github.com/langchain-ai/langchain-skills/tree/main/config/skills/swarm into .gemini/skills/swarm/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swarm", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install langchain-ai/langchain-skills swarmInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add langchain-ai/langchain-skills --skill swarm -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/langchain-ai/langchain-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/config/skills/swarm .github/skills/swarm && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "swarm" agent skill from https://github.com/langchain-ai/langchain-skills/tree/main/config/skills/swarm into .github/skills/swarm/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swarm", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add langchain-ai/langchain-skills --skill swarm -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install langchain-ai/langchain-skills swarm --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/langchain-ai/langchain-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/config/skills/swarm .opencode/skills/swarm && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "swarm" agent skill from https://github.com/langchain-ai/langchain-skills/tree/main/config/skills/swarm into .opencode/skills/swarm/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swarm", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
swarmFans a list of independent items out to subagents in parallel, merges the results back into a table and supports retrying only the rows that failed.
Swarm treats each row of a table as one unit of work. `create` builds the table from files matched by a glob, a list of file paths or pre-parsed records, and `run` applies an instruction template across the rows, batches the work and merges results back, returning counts of completed, failed and skipped rows plus the failures. Aggregation afterward uses `rows()` and plain JavaScript, and the skill says not to spawn more subagents for it.
Choosing the source matters: with a glob or file paths, each file becomes a row and the subagent reads it through a file placeholder, while data inside a JSONL, CSV or JSON file must be parsed first and passed as tasks, one record per row. Large files are read in chunks of 500 lines. Failed rows are reprocessed by rerunning with a filter on the missing result column. The subagent type is left unset for classification, extraction and labeling. It requires the QuickJS code interpreter with the swarm_task tool.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 16a992f. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 8 files in scripts/ (TypeScript), which the agent can run.
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Requires @langchain/quickjs code interpreter with swarm_task PTC tool
From compatibility in the SKILL.md frontmatter.
Swarm Parallel Dispatch loads about 3k tokens when it runs. Until then it costs about 34 tokens; SKILL.md has 909 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from langchain-ai/langchain-skills at commit 16a992f, republished under its MIT licence (© langchain-ai). 909 words, ~2,999 tokens.
.claude/skills/swarm/SKILL.md (or your agent's skills folder). This skill also uses 8 other files; get the full folder from GitHub.Process many independent items in parallel. create builds a table handle;
run fans work out across rows and merges results back. One row = one unit
of work — swarm handles batching automatically.
instruction template across rows. Results are merged
back into the table. Returns { completed, failed, skipped, failures }.rows() and plain JS to count, filter, or summarize.
Do not spawn additional subagents for aggregation.filter: { column: "<col>", exists: false } to
reprocess only failed rows.glob / filePaths — one file = one row. Use when each file is an
independent unit of work. Each row gets { id, file }; the subagent reads
the file itself via the {file} placeholder.
tasks — pass pre-built records directly. Use when the data lives inside
a file (JSONL, CSV, JSON array). Read and parse the file first inside
eval, then pass the records. One record = one row — do not group
multiple items into a single row.
For small files (under ~500 lines), parse and create in one block:
const { create } = await import("@/skills/swarm");
const raw = await tools.readFile({ file_path: "/data.jsonl" });
const records = raw.trim().split("\n").map(l => JSON.parse(l));
const table = await create({ tasks: records });
console.log(table);For large files, read in chunks of 500 lines to avoid truncation:
const { create } = await import("@/skills/swarm");
let records = [];
let offset = 0;
while (true) {
const chunk = await tools.readFile({ file_path: "/data.txt", offset, limit: 500 });
const lines = chunk.split("\n").filter(l => l.trim());
for (const l of lines) { records.push({ id: `r${records.length}`, text: l }); }
if (lines.length < 500) break;
offset += 500;
}
const table = await create({ tasks: records });
console.log(table);When the file is too large to parse and dispatch in one eval call, split
across two blocks. Only the block that calls swarm functions needs the import:
// eval 1: parse only — no swarm import needed
const raw = await tools.readFile({ file_path: "/data.jsonl" });
globalThis.records = raw.trim().split("\n").map(l => JSON.parse(l));
console.log(`Parsed ${globalThis.records.length} records`);// eval 2: create and dispatch
const { create, run } = await import("@/skills/swarm");
const table = await create({ tasks: globalThis.records });
const result = await run(table.id, {
instruction: "Classify {text}",
responseSchema: {
type: "object",
properties: { label: { type: "string" } },
required: ["label"],
},
});
console.log(result);Passing filePaths: ["/data.jsonl"] would produce a table with one row
pointing at the file — not one row per record inside it.
subagentTypeOmit subagentType for classification, extraction, labeling, and any task
where a single model call with structured output is sufficient. This is the
default and is significantly cheaper and faster — each dispatch is a direct
model call, no tools, no iteration.
Set subagentType when the task requires tools, file access, or multi-step
reasoning. Each dispatch runs a full agentic loop with the named subagent.
// Direct model call — classification, no tools needed
await run(table.id, {
instruction: "Classify {text}",
responseSchema: { type: "object", properties: { label: { type: "string" } }, required: ["label"] },
});
// Subagent — needs to read files and reason over multiple steps
await run(table.id, {
subagentType: "reviewer",
instruction: "Review {file} for security issues.",
responseSchema: { type: "object", properties: { finding: { type: "string" } }, required: ["finding"] },
});instruction is a per-item template with {column} placeholders.
Placeholders are resolved by the framework — your column names appear in
prompts as references to the values listed alongside, never as raw
template syntax. Subagents do the work — do not process items yourself in
JS and write the results into rows.
context is free-form prose prepended to every subagent prompt. Use it for
shared background: domain terms, classification rules, examples, etc.
const { create, run } = await import("@/skills/swarm");
const table = await create({ glob: "src/**/*.ts" });
const r = await run(table.id, {
subagentType: "reviewer",
instruction: "Review {file} for security issues. List findings or write 'no issues'.",
context: "TypeScript Express backend using Prisma ORM. Focus on injection, auth bypass, path traversal.",
responseSchema: {
type: "object",
properties: { review: { type: "string" } },
required: ["review"],
},
});
console.log(r);
// → { completed: 45, failed: 2, skipped: 0, failures: [...] }responseSchema is required. Schema properties become top-level columns on
each row and constrain what subagents can return.
const { run } = await import("@/skills/swarm");
await run(table.id, {
instruction: "Classify: {text}",
responseSchema: {
type: "object",
properties: {
sentiment: { type: "string", enum: ["positive", "negative", "neutral"] },
},
required: ["sentiment"],
},
});
// Row after: { id: "r1", text: "...", sentiment: "positive" }By default, swarm auto-batches to keep total dispatches under 10. For small tables (≤10 rows) each row gets its own subagent call. For larger tables, rows are grouped automatically.
Set batchSize to control grouping:
batchSize: 1 forces per-row
dispatch; batchSize: 20 groups in twenties.(row, rowCount) => number. Returns the desired batch size
for each row. Rows with the same batch size are grouped together, then
chunked. Allows mixed dispatch where some rows go solo and others batch.const { create, run } = await import("@/skills/swarm");
const table = await create({ tasks: items });
// Complex items get individual attention; simple ones batch together
await run(table.id, {
instruction: "Analyze {text}",
responseSchema: {
type: "object",
properties: { analysis: { type: "string" } },
required: ["analysis"],
},
batchSize: (row) => (row.token_count > 1000 ? 1 : 10),
});Batch sizes are clamped to [1, 50] after evaluation.
After run(), use rows() and plain JS — no additional subagents needed.
const { rows } = await import("@/skills/swarm");
const data = await rows(table.id, { columns: ["sentiment"] });
const counts = {};
data.forEach(r => { counts[r.sentiment] = (counts[r.sentiment] || 0) + 1 });
console.log(counts);
// → { positive: 120, negative: 45, neutral: 35 }run updates the table in place — chain calls to accumulate columns.
const { create, run } = await import("@/skills/swarm");
const table = await create({ tasks: interviews });
await run(table.id, {
instruction: "Classify sentiment of {text}",
responseSchema: {
type: "object",
properties: { sentiment: { type: "string", enum: ["positive", "negative", "neutral"] } },
required: ["sentiment"],
},
});
await run(table.id, {
filter: { column: "sentiment", equals: "negative" },
instruction: "Summarize why {text} had negative sentiment.",
responseSchema: {
type: "object",
properties: { summary: { type: "string" } },
required: ["summary"],
},
});When subagents perform actions (write a file, apply a fix) rather than return
data, use a simple schema with a status or marker field. The exists: false
filter still works for retries.
const { create, run } = await import("@/skills/swarm");
const fixedSchema = {
type: "object",
properties: { fixed: { type: "string" } },
required: ["fixed"],
};
const table = await create({ glob: "src/**/*.ts" });
await run(table.id, {
subagentType: "fixer",
instruction: "Add missing JSDoc to all exported functions in {file}.",
responseSchema: fixedSchema,
});
// retry any that failed
await run(table.id, {
subagentType: "fixer",
instruction: "Add missing JSDoc to all exported functions in {file}.",
responseSchema: fixedSchema,
filter: { column: "fixed", exists: false },
});{ column: "status", equals: "done" }
{ column: "status", notEquals: "done" }
{ column: "category", in: ["A", "B"] }
{ column: "result", exists: false } // not yet processed
{ and: [filter1, filter2] }
{ or: [filter1, filter2] }@/skills/swarm in blocks where you call swarm functions.
Data preparation (reading files, parsing, storing in globalThis) does not
need the import. Destructure only what you use: { create }, { run },
{ create, run }, etc.readFile inside eval returns raw content — no line-number
prefixes. Request at most 500 lines per call. For files with more
than 500 lines, loop with incrementing offset.eval. Data read
inside the sandbox stays there; it never enters the agent's context window..swarm/ directly. Always use create().instruction + context.
Subagents can't see the agent's context.create() rejects sources that produce
duplicate ids. For tasks, that's a caller-side responsibility; for
glob / filePaths, ids are auto-disambiguated by parent directory.instruction references {foo} and
no matched row provides foo, run() throws before any subagent is
dispatched.create(source)Create a table. Returns a handle { id, count, columns }.
| Source | Description |
|---|---|
{ glob: "src/**/*.ts" } or { glob: ["src/**/*.ts", "lib/**/*.ts"] } | Match files by one or more patterns. Columns: id, file |
{ filePaths: ["a.ts", "b.ts"] } | Explicit file list. Columns: id, file |
{ tasks: [{ id: "t1", text: "..." }] } | Custom rows. Each must have id |
run(tableId, options)Dispatch work across rows. Returns { completed, failed, skipped, failures }.
| Option | Default | Description |
|---|---|---|
instruction | (required) | Template with {column} placeholders |
responseSchema | (required) | JSON Schema (type: "object") — properties become row columns |
context | — | Prose prepended to every subagent prompt |
filter | — | Only dispatch matching rows |
subagentType | — | Name of subagent to dispatch to. When set, runs a full agentic loop. When omitted, runs a direct model call |
batchSize | auto | Number or (row, rowCount) => number. Auto caps dispatches at 10; 1 = per-row; function = per-row sizing |
concurrency | 10 | Max concurrent subagent dispatches (clamped to 1–10) |
rows(tableId, options?)Retrieve rows. Use for inspection and JS-based aggregation.
| Option | Description |
|---|---|
filter | Only return matching rows |
columns | Project to specific columns |
limit | Max rows returned |
© langchain-ai, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 8 other files (scripts) in config/skills/swarm of langchain-ai/langchain-skills.
Open the folder on GitHubat commit 16a992f
Swarm Parallel Dispatch next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Swarm Parallel Dispatch this skilllangchain-ai/langchain-skills | 1.3k | — | ~3k | Automated safety check: Pass | MIT | |
| OMA Multi-Agent Orchestratorfirst-fluke/oh-my-agent | 1.3k | — | ~3.1k | Automated safety check: Pass | MIT | |
| Cursor Orchestratecursor/plugins | 11k | — | ~1.1k | Automated safety check: Pass | None | |
| Launching Agent Teamslexler/skill-factory | 239 | — | ~1.3k | Automated safety check: Pass | Apache-2.0 | |
| Agents Project Coordinatorasgeirtj/system_prompts_leaks | 69k | — | ~2.9k | Automated safety check: Pass | CC0-1.0 | |
| Team Agent Pipelinezereight/gitlab-mcp | 2k | 1 repos | ~1.5k | Automated safety check: Pass | MIT |
first-fluke/oh-my-agent
Splits a complex feature into prioritized tasks, spawns specialist CLI subagents in parallel, tracks them through shared memory and verifies each result.
cursor/plugins
Splits a large goal into a tree of parallel Cursor cloud agents, with planners, workers and verifiers coordinated by a script and reporting through structured handoffs.
lexler/skill-factory
Plans and launches Claude Code agent teams with distinct roles, right-sized tasks and detailed spawn prompts, and says when subagents or worktrees fit better.
asgeirtj/system_prompts_leaks
Runs a goal as a project in which the agent coordinates separate agent threads, judging when to split the work, and interviews you first when nothing can be verified.
zereight/gitlab-mcp
Spawns a chosen number of coordinated agents on a shared task list and runs them through plan, requirements, execution, verification and fix stages.
NeverSight/learn-skills.dev
Meta-agent skill for orchestrating complex tasks through autonomous sub-agents.
langchain-ai/langchain-skills
Builds agent evaluations in stages: inspect the repository and traces, agree a Task Spec with you, then build, audit and run a Harbor task with an independent verifier.
langchain-ai/langchain-skills
INVOKE THIS SKILL when implementing human-in-the-loop patterns, pausing for approval, or handling errors in LangGraph.
langchain-ai/langchain-skills
Routes LangGraph agents with typed decision models that return probabilities, and finds LLM calls that only exist to produce a routing decision.
langchain-ai/langchain-skills
INVOKE THIS SKILL when your LangGraph needs to persist state, remember conversations, travel through history, or configure subgraph checkpointer scoping.
langchain-ai/langchain-skills
Explains how to build agents with the Deep Agents framework: create_deep_agent, the built-in middleware, the harness, SKILL.md format and configuration options.
langchain-ai/langchain-skills
INVOKE THIS SKILL when using subagents, task planning, or human approval in Deep Agents.
Works with
Categories
Fans a list of independent items out to subagents in parallel, merges the results back into a table and supports retrying only the rows that failed. Swarm treats each row of a table as one unit of work. `create` builds the table from files matched by a glob, a list of file paths or pre-parsed records, and `run` applies an instruction template across the rows, batches the work and merges results back, returning counts of completed, failed and skipped rows plus the failures.
Swarm Parallel Dispatch fits situations like: running the same instruction over hundreds of files or records; classifying, labeling or extracting data from many independent items; re-running only the rows that failed in an earlier batch.
Run `npx skills add langchain-ai/langchain-skills --skill swarm -a claude-code`. Or copy the skill folder (config/skills/swarm in langchain-ai/langchain-skills) into .claude/skills/swarm in your project. Claude Code loads it when a task matches its description.
Run `npx skills add langchain-ai/langchain-skills --skill swarm -a codex`. Or copy the skill folder (config/skills/swarm in langchain-ai/langchain-skills) into .agents/skills/swarm in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add langchain-ai/langchain-skills --skill swarm -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/swarm, .gemini/skills/swarm, .github/skills/swarm and .opencode/skills/swarm in your project.
Going by SKILL.md and its folder, Swarm Parallel Dispatch needs TypeScript for the scripts in its folder. Our summary lists: The @langchain/quickjs code interpreter with the swarm_task tool. Compatibility (from SKILL.md): Requires @langchain/quickjs code interpreter with swarm_task PTC tool.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Swarm Parallel Dispatch is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 3k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Swarm Parallel Dispatch: OMA Multi-Agent Orchestrator (first-fluke/oh-my-agent, 1.3k stars), Cursor Orchestrate (cursor/plugins, 11k stars), Launching Agent Teams (lexler/skill-factory, 239 stars) and Agents Project Coordinator (asgeirtj/system_prompts_leaks, 69k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
langchain-ai (a GitHub organization, an official publisher) maintains it in langchain-ai/langchain-skills, which has 1,276 GitHub stars. The repository holds 22 skills in this directory. The repository was last updated on October 8, 2026.
Source: langchain-ai/langchain-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.