* feat(llm): cross-provider reasoning text fallback with opt-in (#223 Phase 2)
Phase 2 of #223 — wires the shared `<thinking>` text fallback into every
adapter's outbound path behind two new opt-in flags.
User-facing API (`AgentConfig` / `LLMChatOptions`):
- `preserveReasoningAsText?: boolean` — default `false`. When `true`,
outbound IR-to-native conversion downgrades each `ReasoningBlock` that
the target adapter cannot natively echo to inline `<thinking>` text and
prepends it to the next text part (rather than dropping silently).
- `compressReasoningText?: boolean | { minChars?: number }` — defaults
default-on when `preserveReasoningAsText` is true (per maintainer's #223
guidance to "tie compress default-on to the preserve flag"). Mirrors the
shape of `compressToolResults`; head+tail truncation defaults to 1200
chars per block.
Adapter wiring:
- openai-common.ts: refactored to drop the never-wired (since #234)
`OpenAIReasoningReplayOptions` type in favour of reading the AgentConfig
flags. Replaced the private `reasoningBlockToThinkingText` /
`truncateReasoningForReplay` helpers with the shared
`reasoningBlockToInlineText` introduced in Phase 1.
- openai.ts / azure-openai.ts / copilot.ts pass the new flags through
`buildOpenAIMessageList`. Subclasses (deepseek, grok, qiniu, minimax)
inherit chat() so the wiring propagates automatically.
- anthropic.ts: foreign-provenance reasoning falls back to plain text when
opt-in is set; own-provenance signed blocks still native-echo (Phase 1
contract unchanged); own-provenance UNSIGNED blocks now also fall back
to text rather than dropping silently (was always wasted otherwise).
- gemini.ts: same own/foreign-provenance pattern as anthropic, with the
same outbound capability gate.
- bedrock.ts: capability is `'never'` (until the deferred signature
follow-up restores native echo); all reasoning falls back to text when
opt-in is set. Emits a standalone text block since Bedrock messages
accept multiple content blocks.
- ai-sdk.ts: capability `'never'` — when opt-in is set, swaps the
AI SDK structured `{type: 'reasoning'}` part for a text part wrapped
in `<thinking>`. Default-off preserves pre-Phase-2 pass-through behaviour
to avoid silently changing what existing users have today.
Tests:
- Refreshed openai-common.test.ts to use the new flag names. Added cases
for `compressReasoningText: false` (no truncation) and the default-on
semantics when preserve is true and compress is undefined.
- Added per-adapter `reasoning text fallback (#223 Phase 2)` describe
blocks to anthropic, gemini, bedrock, and ai-sdk test files. Each
covers the default-off drop, the opt-in text-fallback, the
own-provenance native echo path, and (for anthropic) the redacted
placeholder.
- Updated tests/reasoning-fallback.test.ts so `maxChars: Infinity` now
asserts pass-through (no truncation) rather than clamp-to-1. The
positive-Infinity sentinel is how callers signal
`compressReasoningText: false`.
Docs + example:
- docs/context-management.md gains a "Preserving Reasoning Across
Providers" section with capability table, default behaviour rules,
and the loop-detector caveat for local OpenAI-compatible models.
- examples/patterns/cross-provider-reasoning.ts demonstrates the
Agent.prompt() model-swap scenario and documents the loop-detector
failure mode.
Refs #223
* test(llm): tighten reasoning-flag propagation test scope (#223 Phase 2 self-review)
Address self-review feedback before opening the Phase 2 PR:
- tests/runner-reasoning-flags.test.ts: rewrite header to honestly describe
what is covered. `AgentRunner.stream()` internally drives turns via
`adapter.chat()` (`runner.ts:766`) — `adapter.stream()` is on the
`LLMAdapter` interface but unused by the runner. The previous "stream path"
claim was therefore testing a non-existent code path. Drop the dead
`capturedStream` instrumentation; replace it with a `runner.stream()`
entrypoint test that exercises the same `adapter.chat()` propagation,
pinning the contract for the stream-side flag flow.
- tests/reasoning-fallback.test.ts: add a runtime-invariant test for the
`NO_TRUNCATION` constant. The Phase 2 commit exported the sentinel and
documented it in JSDoc, but had no test that would CI-fail if a future
refactor removed the `Number.POSITIVE_INFINITY` special-case branch in
`resolveMaxChars`. The new assertion ties the constant value, the
resolver branch, and the documented "no truncation" contract together.
Refs #223
* docs(context-management): acknowledge tool-use-only third capability state
After merging upstream/main (which added 'tool-use-only' for DeepSeek V4
via #251), update the capability table to include the new state and its
two sub-rules:
- tool-calling conversation → native reasoning_content echo
- non-tool conversation → drop (DeepSeek spec)
Also clarify that 'tool-use-only' is the only capability where
same-provider native echo happens WITHOUT preserveReasoningAsText opt-in
(the DeepSeek API forces it for protocol compliance).
Refs #223, #251
* fix(llm): add missing nativeReasoningEchoProvider to ReasoningOutboundOptions
Repair an omission from merge commit 9ca9a91: the field was added to my
working tree alongside the openai-common.ts conflict resolution, but
never staged — so the merge commit landed openai-common.ts referencing
`outboundOptions?.nativeReasoningEchoProvider` while the type declaration
on the receiving end (`ReasoningOutboundOptions` in reasoning-fallback.ts)
did not carry the field.
Locally the bug was masked because vitest runs against the working tree
where the field exists; `npm test` reported 869/874 passing. The pushed
branch would compile-fail at the openai-common.ts access site for any
reviewer pulling the PR.
This commit just adds the field declaration + its JSDoc (already used
correctly throughout openai-common.ts and openai.ts). No behaviour change
relative to what the merge intended.
Refs #223, #251
Examples
Runnable scripts demonstrating open-multi-agent. Organized by category — pick one that matches what you're trying to do.
All scripts run with npx tsx examples/<category>/<name>.ts. Scripts that call a model require the corresponding API key in your environment.
basics — start here
The four core execution modes. Read these first.
| Example | What it shows |
|---|---|
basics/single-agent |
One agent with bash + file tools, then streaming via the Agent class. |
basics/team-collaboration |
runTeam() coordinator pattern — goal in, results out. |
basics/task-pipeline |
runTasks() with explicit task DAG and dependencies. |
basics/multi-model-team |
Different models per agent in one team. |
providers — model & adapter examples
One example per supported provider. All follow the same three-agent (architect / developer / reviewer) shape so they're easy to compare.
| Example | Provider | Env var |
|---|---|---|
providers/ollama |
Ollama (local) + Claude | ANTHROPIC_API_KEY |
providers/gemma4-local |
Gemma 4 via Ollama (100% local) | — |
providers/local-quantized |
Quantized MoE on vLLM / llama-server with tuned sampling (topK / minP / frequencyPenalty / parallelToolCalls / extraBody.repetition_penalty) |
— |
providers/copilot |
GitHub Copilot (GPT-4o + Claude) | GITHUB_TOKEN |
providers/azure-openai |
Azure OpenAI | AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT (+ optional AZURE_OPENAI_API_VERSION, AZURE_OPENAI_DEPLOYMENT) |
providers/bedrock |
AWS Bedrock (Claude via Converse API) | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION |
providers/grok |
xAI Grok | XAI_API_KEY |
providers/gemini |
Google Gemini | GEMINI_API_KEY |
providers/minimax |
MiniMax M2.7 | MINIMAX_API_KEY |
providers/deepseek |
DeepSeek Chat | DEEPSEEK_API_KEY |
providers/openrouter |
OpenRouter (OpenAI-compatible) | OPENROUTER_API_KEY |
providers/groq |
Groq (OpenAI-compatible) | GROQ_API_KEY |
providers/mistral |
Mistral (OpenAI-compatible) | MISTRAL_API_KEY |
providers/zhipu |
Zhipu GLM (OpenAI-compatible) | ZHIPU_API_KEY |
providers/doubao |
Doubao / ByteDance (OpenAI-compatible) | ARK_API_KEY |
providers/qiniu |
Qiniu (OpenAI-compatible) | QINIU_API_KEY |
providers/qwen |
Qwen / DashScope (OpenAI-compatible) | DASHSCOPE_API_KEY |
providers/moonshot |
Moonshot AI / Kimi (OpenAI-compatible) | MOONSHOT_API_KEY |
patterns — orchestration patterns
Reusable shapes for common multi-agent problems.
| Example | Pattern |
|---|---|
patterns/fan-out-aggregate |
MapReduce-style fan-out via AgentPool.runParallel(). |
patterns/structured-output |
Zod-validated JSON output from an agent. |
patterns/task-retry |
Per-task retry with exponential backoff. |
patterns/multi-perspective-code-review |
Multiple reviewer agents in parallel, then synthesis. |
patterns/research-aggregation |
Multi-source research collated by a synthesis agent. |
patterns/cost-tiered-pipeline |
Run the same four-stage pipeline twice to compare flagship vs tiered model cost. |
patterns/agent-handoff |
Synchronous sub-agent delegation via delegate_to_agent. |
cookbook — use-case recipes
End-to-end examples framed around a concrete problem (meeting summarization, translation QA, competitive monitoring, etc.) rather than a single orchestration primitive. Lighter bar than production/: no tests or pinned model versions required. Good entry point if you want to see how the patterns compose on a real task.
| Example | Problem solved |
|---|---|
cookbook/meeting-summarizer |
Fan-out post-processing of a transcript into summary, structured action items, and sentiment. |
cookbook/contract-review-dag |
4-task DAG (extract → compliance-check + summary → notify) with step-level retry. Run normally or with FORCE_FAIL=task2 to exercise retry. |
cookbook/incident-postmortem-dag |
5-task DAG with three parallel root tasks (log patterns + deploy correlation + blast radius) feeding root-cause hypothesis and final postmortem synthesis. |
cookbook/competitive-monitoring |
Parallel source monitoring (Twitter/Reddit/News), contradiction detection, and aggregated intelligence reporting. |
cookbook/paper-replication-triage |
Multi-source paper replication triage with artifact discovery, seeded conflicts, and a structured go/no-go plan. |
cookbook/rare-disease-information-triage |
Source-isolated rare disease information triage with mock fixtures, seeded misinformation/conflict detection, and safety-boundary arbitration. |
cookbook/personalized-interview-simulator |
Interactive interviewer loop with observer flags, shared memory, and structured debrief. |
integrations — external systems
Hooking the framework up to outside-the-box tooling.
| Example | Integrates with |
|---|---|
integrations/trace-observability |
onTrace spans for LLM calls, tools, and tasks. |
integrations/mcp-github |
An MCP server's tools exposed to an agent via connectMCPTools(). |
integrations/mcp-bilig-workpaper |
Bilig WorkPaper MCP tools for formula readback, recalculation, and persisted workbook JSON. |
integrations/with-vercel-ai-sdk/ |
Next.js app — OMA runTeam() + AI SDK useChat streaming. |
integrations/express-customer-support/ |
Express REST API — runTasks() behind POST /tickets with per-agent Zod output schemas, mix-and-match provider env vars, and HTTP error mapping (400/502/504). |
production — real-world use cases
End-to-end examples wired to real workflows. Higher bar than the categories above. See production/README.md for the acceptance criteria and how to contribute.
Adding a new example
| You're adding… | Goes in… | Filename |
|---|---|---|
| A new model provider | providers/ |
<provider-name>.ts (lowercase, hyphenated) |
| A reusable orchestration pattern | patterns/ |
<pattern-name>.ts |
| A use-case-driven example (problem-first, uses one or more patterns) | cookbook/ |
<use-case>.ts |
| Integration with an outside system (MCP server, observability backend, framework, app) | integrations/ |
<system>.ts or <system>/ for multi-file |
| A real-world end-to-end use case, production-grade | production/ |
<use-case>/ directory with its own README |
Conventions:
- No numeric prefixes. Folders signal category; reading order is set by this README.
- File header docstring with one-line title,
Run:block, and prerequisites. - Imports should resolve as
from '../../src/index.js'(one level deeper than the old flat layout). - Match the provider template when adding a provider: three-agent team (architect / developer / reviewer) building a small REST API. Keeps comparisons honest.
- Add a row to the table in this file for the corresponding category.