* feat(core): add Black Forest Labs image adapter
Black Forest Labs (FLUX) generates asynchronously: a submit returns a polling_url, the task is polled until ready, and the image is downloaded from a result URL. BlackForestLabsImageAdapter runs that flow with fetch, sends input images as input_image, input_image_2, and so on, and maps a WIDTHxHEIGHT size to width and height.
A moderated task shows up only in the poll status. The adapter reports Request Moderated and Content Moderated as a non-retryable content_policy failure as soon as it sees them, so runImage moves to the next model instead of polling until the attempt deadline and retrying. Failed and lost tasks are retryable.
The API key goes only to the API origin and HTTPS hosts under bfl.ai, keyed requests refuse redirects, and the pre-signed image download carries no key. providerOptions follows the same rule as the other adapters: fields that carry the prompt or input images are rejected at construction, and width and height are defaults that request.size overrides.
Follow-up to #613.
* fix(core): report detected image media type and the BFL dimensions sent
The built-in image adapters declared a fixed media type, which was wrong whenever providerOptions chose another output format, for example output_format png on Black Forest Labs or jpeg on OpenAI. All four now report the type read from the returned bytes and fall back to the old default only when the bytes are not a recognized image.
The Black Forest Labs attempt parameters kept default width and height from providerOptions even when request.size replaced them in the request body. They now record the same width and height the request sent.
* fix(core): keep a submitted BFL task through transient poll failures
A transient failure while polling or downloading, such as a 429, a 5xx, or a network error, escaped the adapter as retryable, so runImage retried generate() and submitted another billable task while the first kept running. The Black Forest Labs adapter now retries those failures in place until the attempt deadline, honoring Retry-After. Non-retryable errors still throw at once, and so does a 4xx on the download, since an expired delivery link cannot recover without a new task.
The adapter contract now says what it meant: an adapter must not resubmit a generation internally, while retrying a status poll or download of an already-submitted task is allowed.
* fix(core): type rate-limited and timed-out image downloads
downloadImage turned every non-2xx status into api_error, so a 429 or 408 on a pre-signed result URL did not count as transient for the Black Forest Labs in-place retry, escaped to runImage, and resubmitted a billable task. A download 429 is now rate_limit with Retry-After and a 408 is timeout, so both are retried in place against the existing task.
* fix(core): scope BFL credentials and keep a submitted task final at the deadline
The Black Forest Labs adapter sent its key to any HTTPS host under bfl.ai. Behind a proxy that key belongs to the proxy, and a forwarded BFL polling URL would receive it. Sibling BFL hosts now qualify only when baseURL is itself a BFL host; otherwise the key goes to the configured origin alone.
A transient outage that lasted past the attempt deadline still led runImage to retry generate() and submit another billable task. Once a task is submitted, the adapter now reports a deadline as a non-retryable timeout, and runImage keeps a non-retryable adapter verdict at the deadline instead of replacing it with a retryable timeout, so the chain moves to the next model. A deadline before the submit completes stays retryable.
* fix(core): tell deadlines from cancellation and end BFL turns on permanent download failures
runImage now aborts an attempt's signal with a DOMException named TimeoutError when the deadline fires, the same convention as AbortSignal.timeout(), and with the caller's own reason on cancellation. The Black Forest Labs adapter turns only a deadline after the submit into a final timeout; a caller that cancels a direct generate() call gets its own abort reason back.
A permanent failure downloading a submitted task, such as an expired or missing result link, was still retryable, so runImage submitted and paid for another task. Once the task exists, a download failure that is not transient now ends that model's turn as non-retryable.
* fix(core): end a submitted BFL task's turn on any failure and keep its cost
Several failures after a Black Forest Labs task was submitted were still retryable: a Ready status without result.sample, an Error or Failed task, and a lost task. runImage retried them by submitting and paying for another task. Every failure after the submit now ends that model's turn as non-retryable.
The adapter's in-place retry of transient poll and download failures slept through any Retry-After, so a header above runImage's maxRetryAfterMs stalled the attempt until its deadline. runImage now passes its cap to adapters as ImageCallOptions.maxRetryAfterMs, and a longer wait ends the turn at once.
A failure after the submit also dropped the task ID and the cost BFL reported, because adapter params were only returned on success. ImageModelError now carries optional params, runImage copies them into the failed attempt record, and the BFL adapter attaches the task and its cost to every post-submit failure.
* fix(core): close the last BFL paths that resubmit a created task
Two failures after a successful Black Forest Labs submit still reached runImage as retryable and led it to submit and pay for another task: a submit response without polling_url, and a result download that answers 200 with bytes that are not an image. Both now end that model's turn as non-retryable invalid_output and carry the task ID and cost into the failed attempt record. The adapter checks the downloaded bytes itself instead of leaving it to runImage, whose retry of an invalid output would resubmit.
* feat(core): add OpenRouter image adapter
OpenRouter serves image generation and editing through its own /images endpoint, with input images in input_references, so OpenAIImageAdapter pointed at an OpenRouter baseURL cannot reach it. OpenRouterImageAdapter calls that endpoint with fetch, reads base64 output only, rejects a mask instead of dropping it, and honors egressPolicy.
Safety rejections arrive in two shapes. OpenRouter's own moderation answers 403 with reasons or flagged_input in error.metadata; a rejection by the upstream provider carries the provider's code in error.metadata.provider_code and its original body in error.metadata.raw. Both are classified as content_policy, reusing the OpenAI and Seedream rules for upstream codes. The shared content-policy rule now also receives the HTTP status and the full error object.
Follow-up to #610.
* fix(core): classify forwarded safety rejections before retryable 5xx
A gateway such as OpenRouter can forward an upstream safety rejection with a 5xx status. The shared image error classifier checked the 5xx branch first, so the adapter's content-policy rule never ran and the rejection was retried on the same model. The content-policy rule now runs before the timeout and 5xx branches; a 429 is still classified as a rate limit.
* fix(core): reject image providerOptions that replace adapter-set fields
providerOptions was spread last into the request body, so a key such as input_references, image, prompt, or model silently replaced what the adapter built from the request, and an edit could run on different images or none. The OpenAI, OpenRouter, and Seedream adapters now reject those keys with a TypeError at construction and spread the remaining options before their own fields.
* fix(core): keep size and watermark as image providerOptions defaults
The reserved-key check rejected size in every image adapter and watermark in Seedream, which removed a reasonable way to set a default output size. Only keys that carry the request itself (model, prompt, n, the input image fields, mask, and response_format) are rejected now. A size in providerOptions is a default that request.size overrides, and Seedream watermark in providerOptions again overrides the adapter default; the OpenAI multipart body sends a single size field.
* feat(core): add runImage and native image model adapters
Applications that generate or edit images had to call image APIs outside the framework and rebuild the same reliability code each time: retries, fallback across providers, error classification, and a record of every call. runImage() does this once, on an ImageModelAdapter interface that core owns, so no third-party SDK sits between OMA and the provider response it has to classify.
runImage() tries an ordered chain of models, retries retryable failures with jittered backoff that honors Retry-After, moves to the next model on anything else, reads the output format and size from the bytes, runs an optional validate check, and reports every provider call through result.attempts and onAttempt. It resolves with status failed instead of throwing when the chain runs out.
OpenAIImageAdapter (OpenAI Images API and compatible endpoints) and SeedreamImageAdapter (Seedream on Volcengine Ark) call the HTTP APIs with fetch, classify safety rejections as content_policy, fail rather than drop input they cannot use, request base64 output only, and honor egressPolicy. ImageModelError carries the normalized type and retryable flag, and isRetryableError() returns that flag.
Refs #469
* fix(core): keep runImage independent of attempt sinks and caller signals
onAttempt is no longer awaited, so a stalled or rejecting sink cannot hold up retries, cancellation, or the result. A caller that aborts while validate is pending now gets its abort instead of a successful image. Each attempt removes its abort listener from the caller's signal when it ends, so a signal reused across many calls no longer accumulates listeners.
The Anthropic adapter now sends adaptive thinking when thinking is enabled without budgetTokens, except on models from before adaptive thinking, which keep the 1024-token default budget; Claude Opus 4.7, Sonnet 5, and later reject budget_tokens with HTTP 400. The task profiler omits temperature only for Claude models that reject non-default sampling parameters, and the cost-tiered pipeline example drops the temperatures that broke its Opus 4.7 path. Built-in tool descriptions now state the sandbox boundary and match glob's path output, the coordinator prompt reused for synthesis no longer carries an unscoped JSON-only instruction, and the structured-output instruction states its requirement once.
The baseline section of docs/evaluation-ci.md enumerates every condition
that makes a regression comparison warn and skip, and it was missing the
one a threshold introduces: when it sets `minSamples` and either the
current or the baseline aggregate is short, the comparison does not run.
Record that condition and the consequence a gate author has to plan for,
namely that a short baseline now yields a warning where it previously
yielded a `regression` failure, so the guard loosens one check while it
tightens another.
Record the guard and `passSampleCount` in the changelog, set `minSamples`
on the runnable offline-regression example so the option appears where a
gate is actually configured, and credit the contributor.
Follow-up to #602.
Add an optional positive-integer `minSamples` guard to `GateThreshold` so a threshold only counts when the selected aggregate carries enough evidence. Score metrics compare it against `scoredCount`; `passRate` compares it against the new `ScorerAggregate.passSampleCount`, which records the pass denominator whenever `passRate` is emitted. Tag-scoped thresholds use the tag aggregate's own counts. A count below the minimum reports a new `insufficient_samples` failure carrying the observed count and the configured limit, and a report written before `passSampleCount` existed fails closed at zero rather than skipping the guard.
Baseline regression checks honor the same minimum. When either the current or the baseline aggregate is below it, the comparison is skipped with a warning instead of producing a regression number from a sample set the gate already called too small to judge.
Omitting `minSamples` preserves the previous behavior, so the report schema version is unchanged and no migration is required.
Fixes#573
DeepSeek retired the V4-Flash model and named the current generation
`deepseek-flash`, with no version in the name. `deepseek-v4-flash` is still
accepted, but DeepSeek documents it as a temporary compatibility name and
already serves those requests from V4.1-Flash.
The repository handed that name to users in the places most likely to be
copied: the `oma` CLI default for `provider: 'deepseek'`, the adapter JSDoc
that ships in the type declarations, the provider table in docs, the
scaffolder env template, and every DeepSeek example. A copied name that the
vendor calls temporary has an expiry date the reader cannot see.
The provider table and the adapter JSDoc also stated that `deepseek-v4-flash`
resolves to DeepSeek-V4-Flash-0731. That model is retired, so the sentence
described a routing that no longer happens.
Two corrections of the same kind travel with it. The thinking-mode note
claimed the V4 models run at `high` effort, but the adapter forwards whatever
effort the caller passes and sets none itself, so the claim is dropped rather
than restated. Three `examples/basics` headers offered `deepseek-chat`, which
DeepSeek retired outright on 2026-07-24 and which no longer resolves at all.
The release bot moves to the same name. Callers that pass an explicit model
are unaffected, and the old name keeps working for as long as DeepSeek
accepts it.
Left unchanged on purpose: `bench/config.json`, whose values are frozen
controls for an already-run benchmark; the AtlasCloud pages, whose IDs come
from that vendor's own model library; and the changelog history.
The field was public and typed but never read, so a caller who set
`maxConcurrency: 3` on a team typechecked cleanly, got no warning, and ran
at the orchestrator's value (5 by default). The class JSDoc example
demonstrated the dead field, and 22 examples copied it.
Wire it to the pool that runs the team. The team cap intersects with
`OrchestratorConfig.maxConcurrency` rather than replacing it: the smaller
of the two wins, so a team can narrow the pool for its own runs but never
widen it past the orchestrator ceiling. This covers all three `buildPool`
call sites, each of which already has the team in scope.
An unusable cap reports an `INVALID_TEAM_MAX_CONCURRENCY` warning on
`onProgress` and falls back to the orchestrator value, which is what those
configs already did while the field was ignored. Rejecting them would make
this a breaking change under the definition in RELEASING.md, and a value
computed from the environment is a realistic way to arrive at `NaN`. The
check still cannot be left to `Semaphore`, which rejects only values below
1 and would let `NaN` through to a pool that never grants a slot.
Every in-repo example sets the team cap equal to its orchestrator cap, so
their behavior is unchanged.
A checkpoint records what a run can resume from, not who is allowed to
advance it. Two processes can restore the same snapshot and both continue
it, and a worker that stalled past its own liveness can wake up and
overwrite the state a replacement has since written.
Add an opt-in `runStore`: one authoritative, versioned record per logical
run holding its lifecycle status, execution lease, and a monotonic fencing
token.
- `RunRecord` keeps `version` for optimistic concurrency and `fencingToken`
for ownership, so a worker holding an old token stays detectable across
any number of unrelated writes. Legal transitions are enumerated and
terminal statuses have no outgoing edge.
- `RunStore` is three methods plus an explicit `atomicity` declaration.
Lease expiry, fencing, transition legality, and command idempotency live
above the seam in `RunLedger`, so a Postgres or Redis backend reproduces
storage semantics rather than a state machine.
- `MemoryStoreRunStore` adapts any `MemoryStore` with compare-and-set and
defaults to `atomicity: 'process'`. `FileStore` remains the sequential
restart reference store and is not presented as a lease backend.
- The orchestrator acquires the lease before it dispatches, fences every
checkpoint write with its token, stops the run at the dispatch gate when
the lease is lost, and records the terminal transition. A worker that
lost its lease writes no terminal status and never reports success.
- `restore()` is the resume command: it makes a suspended record eligible
and takes ownership before reconciling the approval ledger.
Ownership and lifecycle writes fail closed rather than inheriting the
best-effort semantics of checkpoint and telemetry writes. With no run store
configured, existing single-process checkpoint and restore behavior is
unchanged.
Ships a reusable `RunStore` conformance suite covering create races,
version-matched compare-and-set, lease expiry, takeover, stale-writer
rejection, and terminal closure, so an out-of-repository backend can prove
the same contract.
Closes#481
Systematic documentation review: 12 new docs pages (coordinator, streaming, budgets and limits, errors, hooks and callbacks, glossary, production checklist, Run Viewer, MCP, sandbox and shell, evaluation in CI, routing evaluation) plus a docs index; the three longest guides split with stub sections that keep existing anchors; observability release records moved to docs/internal; statements re-verified against the code and corrected; READMEs, AGENTS.md, CONTRIBUTING.md, and issue templates synced. Documentation only, no behavior change.
Reword the local-model claim in providers.md to the models behind runnable examples, add framework-versus-integrator boundaries to durable-approvals.md, external-agents.md, and run-journal.md, and add docs/self-hosting.md covering runtime footprint, network scope, state locations, redaction, and air-gapped deployment.
`judgePrompt` ran once per `score()` call and could only return a string, so
every judge in a quorum received the same text prompt. A judge scorer could
therefore never see a non-text artifact: an agent that produces an image or a
rendered document could only be scored on whatever text it emitted alongside
the work, which is the agent describing itself rather than the work.
It now runs once per judge and may return either a string, wrapped by the
existing text template exactly as before, or a complete `readonly LLMMessage[]`
passed straight to `Agent.run()`. A caller can branch on `judge.capabilities`
to give a vision-tagged judge an image block while a cheaper text-only judge in
the same quorum gets a text fallback. The string path is unchanged, and the old
`(context) => string` signature still assigns to the widened type.
The structured path deliberately hands the caller everything: OMA adds neither
the case template nor the verdict-schema instruction, so a caller returning
messages must append `buildStructuredOutputInstruction` itself or the judge is
never told to emit the JSON `parseVerdict` requires. `docs/evaluation.md` says
so in prose, and the tests fail when that instruction is missing rather than
passing on a mock that returns JSON regardless.
Locating the artifact stays the caller's job. `ScorerContext` exposes the run
transcript, not a produced artifact, and a tool-produced image sits nested in a
`tool_result` block rather than as a top-level image block, so the documented
helper searches both shapes.
Still out of scope and unchanged here: no per-judge failure isolation, so one
judge failing on content it cannot handle fails the whole `score()` call. No
new options, no `ScorerContext` change, no persistence change.
Follow-up to #548, which introduced VideoBlock. Two gaps were missed when that
PR was reviewed.
The four adapters that cannot map video threw a bare Error. isRetryableError
returns true for any Error without a status, so executeWithRetry treated a
deterministic capability gap as transient: it ran the full backoff ladder and
rewrote a checkpoint per attempt before failing the same way, and the failure
was recorded as retryable. UnsupportedContentBlockError makes that terminal,
names the adapter that rejected the block, and carries a code alongside the
existing UnsupportedToolResultContentError.
VideoBlock is the first top-level content block with a URL source, so it
inherited no validation. That source is dereferenced by the provider rather
than by OMA, and a file: path or a data URL pasted into the field was forwarded
verbatim; an unvalidated media_type could also alter the data URL built for
inline bytes. Public entry points now run video sources through the same
copyMediaSource rules already applied to tool-result media, so the documented
HTTP(S) constraint is enforced rather than aspirational. Provider-private
upload handles such as MiniMax's mm_file:// are rejected: supporting them needs
a source variant of its own, not an unchecked string.
Also from the same review:
- estimateTokens charged an inline video the 64-character fixed cost images
get, hiding a multi-megabyte payload from every context strategy keyed off
the estimate. It is now sized like rich tool-result media, so the attachment
that needs compacting is the one that triggers compaction.
- stripImageBlocksForSummary handles video too, so it is now
stripMediaBlocksForSummary.
- The two double casts in openai-common are narrowed to a single seam, and the
video_url extension type now records that it is not an OpenAI part.
- docs/structured-input.md states the rule the adapters actually follow:
OpenAI-shaped adapters forward and let the provider decide, adapters with a
typed content union reject locally. Bedrock and Gemini support video
upstream, so the gap there is OMA's mapping rather than theirs.
Adds a no-key cookbook that walks the full repairable-recovery lifecycle on a synthetic commission reconciliation: three source-scoped investigations produce a typed temporal-evidence gap, a named Replanner appends targeted agreement and policy history lookups plus replacement reconciliation and output tasks, and the run either completes with a RECONCILED result or stops at the configured revision limit and fails closed to MANUAL_REVIEW_REQUIRED outside the task graph. Documents the recipe in docs/adaptive-recovery.md and registers it in the example catalog.
Adds a public VideoBlock content block with inline base64 and URL sources, serialized as an OpenAI-compatible video_url content part so MiniMax-M3 can receive video input. Video attachments are replaced with placeholders when preparing context summaries, keeping binary payloads out of the summarisation prompt. Adapters with a typed content union reject the block explicitly.
Every observability example used an in-process exporter, which proves the
adapter's semantics but never serializes a span or opens a socket. Nothing in
the repository showed the path an OpenTelemetry user actually takes, so the
exporter choice, Resource, auth headers, and shutdown order were left for each
reader to reconstruct.
`otlp-backend.ts` runs OTel SDK to OTLP/HTTP exporter to HTTP to a backend, and
prints what crossed the wire. The backend is a loopback listener the example
starts itself, so it still needs no API key, no external collector, and no
internet access; `OTLP_URL` and `OTLP_HEADERS` redirect the same payload to a
hosted vendor.
It also demonstrates two things that are easy to get wrong. `service.name`
belongs to the provider's Resource, and a provider built without one files
every span under `unknown_service:node`. Tool input and output require both
halves of the capture opt-in, so the example sets the core policy and the
adapter mode together and states what the run exports without them.
`runToolDemo` joins the shared demo runtime so the example can show a real
`execute_tool` span; the existing `runDemo` export is unchanged. The example is
registered in the catalog and in the smoke script, which now runs eight
examples.
The v2 sink path dropped tool arguments and results that the legacy `onTrace`
callback still delivered, so migrating to `observability.sinks` (and from there
to any OpenTelemetry backend) silently lost data the old callback provided.
`TraceCapturePolicy` already declared `toolInput`/`toolOutput`, and
`SensitiveDataProcessor` already filtered on them, but no instrumentation site
produced the attributes, leaving both switches inert.
`AgentRunner` now attaches `oma.tool.input` and `oma.tool.output` to the
`execute_tool` span when the policy opts in. `TraceRuntime.capturesToolIO`
carries that decision so the serialization is skipped entirely by default,
keeping unopted runs byte-for-byte unchanged. Values are redacted at the
instrumentation site as well as in the processor, so a runtime constructed
outside the orchestrator does not bypass redaction.
`@open-multi-agent/otel` gains `contentCapture: { mode: 'upstream-policy' }`,
which forwards those two attributes and nothing else. It is the separately
reviewed upstream policy the package's reserved extension point was waiting
for: the adapter setting exports nothing on its own, because without the core
policy the attributes are never recorded. Prompt, completion, raw payload,
credential, and reasoning content stay excluded under every mode.
Docs now state which capture fields have producers, since `prompt` and
`completion` remain filters over attributes no v2 site emits, and record that
`service.name` comes from the application's own TracerProvider Resource rather
than from adapter metadata.
Reconstructing what an agent saw is only worth something if the log can be
checked against itself. verifyRun() reads a finished run journal cold and
proves every model-visible block is reproducible from the events it names: a
message-bearing event must carry a block with the same canonical hash, or a
context/replace must carry the replacement it produced.
Naming a lineage is not the same as being reproducible from it. A rewrite
that silently replaced a conversation still cites prior events, so a presence
check accepts it; requiring the named event to reproduce the bytes is what
makes an unrecorded rewrite detectable without a full replay.
Eviction and dropped batches are reported as inconclusive rather than counted
against a run, so a bounded journal stays honest about what it can and cannot
answer.
Also fixes two defects in the journal subsystem: a literal NUL byte in
tail-replay.ts made git treat the whole file as binary, and the JSONL
batching-window test asserted against a 5ms timing margin that failed under
parallel load.
The oma verify-run CLI stays deferred.
Context strategies emit a context/replace journal event for every
persistent rewrite, naming what was dropped and carrying every derived
block verbatim, so the conversation the model actually saw stays
reconstructable. enforceLineage now passes with every built-in strategy.
Journaled runs write checkpoint schema v5: v4 plus a journal watermark
captured at snapshot build time, per-task watermarks on in-flight
entries, an optional journal backend reference, and per-block
conversation lineage. Restore replays the journal tail after the stalest
per-task watermark, folding only in-flight runner state under defensive
anchoring checks; any inconsistency discards the whole tail and resumes
from the snapshot alone. A committed tool result that reached the
journal but not the checkpoint is replayed instead of re-executed.
Runs without a journal keep writing v4 byte for byte, and v1 through v4
snapshots keep loading.
Add a per-run event journal that records what happened inside a run and,
for every adapter call, which journal event produced each block the model
saw. Lineage is per block rather than per request, so a request whose
blocks have different origins can say so.
Ships the RunEvent vocabulary, the RunJournal interface with bundled
InMemoryRunJournal and JsonlRunJournal backends, a per-run
JournalRecorder with block-identity lineage tracking, emission at every
existing durable boundary, and the enforceLineage switch.
Journaling is off by default and costs nothing when off: no recorder is
allocated and every emission site is guarded. Appends are best-effort,
surfacing through onProgress as journal_append_failed and never failing
the run they observe.
Claiming a $5 voucher required emailing for a code, which is more friction than the credit is worth, so most readers who would have used one never asked. List the codes directly in the EN and ZH guides instead, with the Atlas Cloud redemption link beside them.
The section states no total. The list is the state, so refreshing a batch means editing the list rather than rewording the section. An email fallback stays for readers who arrive after every listed code is claimed.
Update the root and core package READMEs (EN + ZH) so the sponsor bullet no longer says vouchers are requested by email.
Refs #415
deepseek-v4-pro now serves the GA snapshot DeepSeek-V4-Pro-0813 (2026-08-13);
deepseek-v4-flash resolves to DeepSeek-V4-Flash-0731. Model names are
unchanged, so the adapter needs no code changes. Update stale "Preview API" /
"public beta" wording in the adapter docs, provider table, CLI notes, and the
provider example, and document that V4 enables thinking by default at high
effort.
* fix(release-bot): disable thinking for synthesis agents
The release-reviewer runs with thinking effort high and maxTokens 3500. Its reasoning consumed the whole output budget (two attempts at ~3500 reasoning tokens each, empty text both times), so it never emitted the JSON releaseReviewSchema requires and structured-output validation failed after retry, failing the release closed. Disable extended thinking for the two synthesis agents (planner and reviewer) so they emit structured output directly. The two evidence agents keep high-effort thinking.
* chore: remove maintainer issue-to-PR automation
Removes the maintainer-bot, maintainer-host, maintainer-runtime, and
maintainer-harness-canary workspaces, their GitHub Actions workflows, and
their documentation. This issue-to-Draft-PR automation is removed in favor
of a local maintainer workflow.
Surface the already-computed OMA token usage and estimated cost on the bounded pipeline trace. Matches PRD 3.3; Claude Code coding usage remains not reported. No execution boundary changes.
* docs: add Atlas Cloud provider sponsor listing and setup guide
Add Atlas Cloud as a paid Provider sponsor across the root and core package READMEs (EN + ZH), plus a docs/providers/atlascloud.md setup guide covering the OpenAI-compatible connection, model list, and voucher application by email.
Docs-only; no code changes. Sponsorship disclosure included.
* docs: point Atlas Cloud model list to the live catalog
Replace the stale hardcoded model-ID table with a pointer to Atlas Cloud's live model library, and list current model families without pinning versions. Add a guidance comment on the example model value.
* docs: make the Atlas Cloud guide reachable from the provider reference
Fold the Atlas Cloud listing into the paths developers already use when
picking a provider, instead of leaving it reachable only from a README
sponsor bullet.
- Add Atlas Cloud to the OpenAI-compatible table in docs/providers.md,
the documented entry point for provider setup, with the paid-sponsor
relationship disclosed inline.
- Flatten docs/providers/atlascloud.md to docs/providers-atlascloud.md so
docs/ stays one level deep and no directory sits beside a same-named
providers.md.
- Split the bilingual guide into providers-atlascloud.md and
providers-atlascloud_zh.md, matching the README.md / README_zh.md
convention used elsewhere, and cross-link the two.
- Give the root READMEs a dedicated Sponsors section so the "want to get
listed?" call to action stays attached to the integrations list.
- Correct the adapter reference to packages/core/src/llm/minimax.ts and
state plainly that no native adapter exists today.
Refs #415
* docs: correct Atlas Cloud sponsor listing facts
Update example model ID to deepseek-v4-flash-0731; move Atlas Cloud out of the verified providers table into a separate sponsor listing; remove the unverifiable hosted Claude and Gemini claim
Generate and verify fixed loopback-only resolver files for Bubblewrap validation, mount them read-only, and extend preflight coverage before provider credentials are introduced.\n\nFollow-up to the Issue #491 canary evidence.
Allow the exact OTel README target from #501 while keeping other OTel files denied. Preserve clarification for incomplete Issue markdown and document the reauthorization flow.
Keep the candidate checkout immutable while allowing deterministic tools such as Vite to write temporary files inside a separately verified sandbox snapshot.
Load a narrowly scoped AppArmor profile for /usr/bin/bwrap on Ubuntu 24.04 while preserving the global user namespace restriction and fail-closed diagnostics.
Add a read-only Claude Code and DeepSeek harness canary with deterministic validation, credential isolation, bounded artifacts, and documented promotion gates.
The live maintainer canary showed that IssueComment GraphQL actors expose the App slug without the REST [bot] suffix, causing the strict provenance check to fail after STARTED. Verify the exact Bot database ID and type across workflow and host paths while retaining REST identity and editor checks.
- separate application-owned data from model-visible text, image, and file content
- map rich results across built-in adapters and preserve them through recovery
- retain backward compatibility for string tools and text-only errors
Closes#466