File: deploy/easypanel/template/README.md
Easypanel Template
This directory contains the Manifest template files intended for the upstreameasypanel-io/templates
repository.
To submit upstream, copy these files into:
templates/manifest/index.ts
templates/manifest/meta.yamlThe template provisions:
- Manifest app service from
manifestdotbuild/manifest:6. - PostgreSQL 16 service.
- Persistent app volume for compressed request recordings.
- Generated database password, Better Auth secret, and Manifest encryption key.
- HTTPS domain proxy on port
2099.
File: docs/providers/subscription-based-providers.md
Subscription-based providers
Reuse a paid plan you already have: ChatGPT Plus, Claude Max, GitHub Copilot, ModelArk Coding Plan, Command Code, Qwen Token Plan, Xiaomi MiMo Token Plan, Kimi Coding Plan, GLM Coding Plan, Google sign-in, MiniMax Coding Plan, Kiro, Grok, Ollama Cloud, or OpenCode Go.
If you already pay for one of the plans below, Manifest can route through that subscription instead of an API key. Auth is browser OAuth, device code, or a subscription token depending on the provider.
Supported subscriptions
| Provider | Plan | Auth flow | Model catalog |
|---|---|---|---|
| OpenAI | ChatGPT Plus/Pro/Team | OAuth (browser) | Fixed ChatGPT and Codex model list |
| Anthropic | Claude Max / Pro | Setup token | claude-opus-4, claude-sonnet-4, claude-haiku-4 |
| GitHub Copilot | GitHub Copilot subscription | Device code | Fixed Copilot model list |
| BytePlus | ModelArk Coding Plan | Subscription token | Fixed ModelArk Coding Plan model list |
| Command Code | Command Code subscription | Subscription token | Dynamic Provider API catalog |
| Qwen | Qwen Token Plan | Subscription token | Dynamic Token Plan catalog |
| Xiaomi MiMo | MiMo Token Plan | Subscription token | Fixed MiMo Token Plan model list |
| Moonshot | Kimi Coding Plan | Subscription token | Fixed Kimi Coding Plan model list |
| Z.ai | GLM Coding Plan | Subscription token | Fixed GLM Coding Plan model list |
| Sign in with Google | OAuth (browser) | Fixed Gemini CodeAssist model list | |
| MiniMax | MiniMax Coding Plan | Device code or token | Fixed MiniMax Coding Plan model list |
| Kiro | Kiro subscription | Device code | Fixed Kiro model list |
| xAI | Grok subscription | OAuth (browser) | Dynamic xAI model catalog |
| Ollama Cloud | Ollama Cloud subscription | Subscription token | Dynamic Ollama Cloud tag catalog |
| OpenCode Go | OpenCode Go (beta) | Subscription token | Dynamic OpenCode Go catalog |
Connect a subscription
- Open the Routing page in the dashboard.
- Click the provider tile.
- Pick the Subscription tab.
- Complete the provider's auth flow.
Providers that support both API keys and subscriptions show both tabs. Subscription-only providers show only the subscription flow. In routing pickers, subscription rows appear only after a usable subscription credential is saved (has_api_key=true).
How auth works per provider
OpenAI
Manifest uses browser OAuth for ChatGPT Plus, Pro, and Team accounts. Once approved, requests route through the ChatGPT Codex backend instead of the public OpenAI API. Tokens refresh automatically.
Anthropic
Run claude setup-token and paste the sk-ant-oat token into Manifest. Requests carry Anthropic subscription auth headers. Subscription mode does not support prompt caching or batching.
GitHub Copilot
Manifest uses GitHub's device-code flow. Manifest gives you a short user code, you open https://github.com/login/device, paste the code, and approve access. Requests then route to the Copilot chat or responses endpoint with a short-lived token that Manifest refreshes.
BytePlus
Paste a ModelArk Coding Plan API key from the BytePlus console. Manifest routes OpenAI-compatible and Anthropic-compatible coding-plan requests through BytePlus ModelArk endpoints.
Command Code
Paste a Command Code API key from Command Code Studio. Command Code is subscription-only in Manifest and requires Command Code Pro or higher. Manifest discovers models dynamically from Command Code's public Provider API catalog.
Qwen
Paste a Qwen Token Plan API key. Token Plan keys use the sk-sp- prefix. Manifest discovers available models from Qwen's Token Plan endpoint and can route both chat-completions and responses-style requests where supported.
Xiaomi MiMo
Paste a MiMo Token Plan API key. Token Plan keys use the tp- prefix. Manifest routes through MiMo's OpenAI-compatible Token Plan endpoint and lets self-hosted setups choose the China, Singapore, or Europe token-plan host.
Moonshot
Paste a Kimi Code API key from the Kimi Code console. Requests route through Kimi Code's Anthropic-compatible endpoint for the fixed k3, k3-256k, kimi-for-coding, and kimi-for-coding-highspeed model catalog.
Z.ai
Paste a GLM Coding Plan token from Z.ai. Manifest routes to the Z.ai coding-plan endpoint. Self-hosted setups can choose the global or China Mainland endpoint when both are available.
Manifest uses browser OAuth for Google's CodeAssist flow. The subscription route uses a fixed Gemini CodeAssist model list rather than the public Gemini API-key catalog.
MiniMax
Manifest supports the MiniMax Coding Plan device-code flow. Users with a Coding Plan token can also paste the token directly when the UI offers the token alternative.
Kiro
Manifest uses Kiro's device-code flow. The Kiro subscription exposes kiro/auto plus curated Claude, DeepSeek, MiniMax, GLM, and Qwen routes behind one subscription.
xAI
Manifest uses browser OAuth for Grok subscriptions and fetches the model list dynamically from xAI's OpenAI-compatible models endpoint.
Ollama Cloud
Paste an Ollama Cloud API key. Ollama Cloud accepts generic API-key formats; Manifest does not require a fixed prefix. Models are discovered dynamically from the cloud tag catalog.
OpenCode Go
Sign in to OpenCode Go, copy your API key, and paste it into Manifest. Model discovery is dynamic. Depending on the model, Manifest can route through OpenAI-compatible or Anthropic-compatible wire formats.
Why mix subscriptions and API keys
A common setup is a subscription as the primary route for predictable monthly cost, plus API-key providers as fallbacks for plan limits, provider outages, or models the subscription does not include. Pin subscription models to routing tiers and add API-key models to the fallback list. Manifest handles the switch.
File: docs/autofix-self-healing-poc.md
Autofix (self-healing requests) โ POC specification
Status: Backend + frontend implemented, green, verified live ยท Last updated: 2026-07-01
โ ๏ธ This spec describes the original aspirational design and has diverged from
what shipped. Where they disagree, the code is the source of truth. Known
divergences:
- No multi-attempt loop, no budget. Healing is a single attempt
(runHealOnceโ one heal, one reforward). There is no retry budget, soautofix_max_attempts, themaxAttemptsendpoint field, and theAUTOFIX_DEFAULT_MAX_ATTEMPTSenv var referenced below do not exist.agents.autofix_enabledis nullable (boolean | null, notdefault: false).NULLinherits the deployment-mode default (ON in cloud,
OFF in self-hosted); an explicittrue/falsewins.- Production default client is
NoopHealingClient(inert), not the mock โ
the mock runs only in dev/test. See ยง4.agent_messagesalso hasautofix_phoenix(jsonb โ the Phoenix{issueId, patchId, healAttemptId}), in addition to the columns listed in ยง5.2.- Autofix is now generally available. The early-access waitlist and tenant
entitlement gate have been retired; only the per-agent toggle remains.
Implementation status. Full stack built against the real Phoenix
contract (ยง4) and passing: backend unit suite green (6900 tests), frontend suite
green (3877 tests), every newrouting/autofix/*file + the new UI at 100%
coverage on changed lines. The healing client defaults to an in-process mock
(implements the MVPmax_tokensrename) untilAUTOFIX_HEALING_URLpoints at a
real Phoenix.Verified live (
/serve, cloud mode): migrations applied, the Routing-page
Autofix toggle persists to the DB, and the realAutofixService+ mock Phoenix heal the MVPmax_tokenscase end-to-end
(400 โrename_paramโ resendmax_output_tokensโ 200 healed, chain recorded).Not yet: backend e2e (needs Postgres); a live HTTP heal needs a provider
that actually rejectsmax_tokens(real OpenAI Responses key, or a localhost
mock provider in self-hosted mode). Nothing committed.
Manifest intercepts a request that failed with a repairable error, ships the
failed request + full provider response to an external healing service, gets a
patched request back, and re-sends it once โ before
the normal fallback chain runs. The attempt is recorded on the
message so you can see the first error, every request Autofix sent, what changed,
and the final result.
This is a proof of concept. The goal is to stand the loop up end-to-end against
a mock (then the real healing service) and see what comes out of it โ not to ship a
hardened feature. Decisions favour the smallest contained change over generality.
1. What already exists (important)
"Autofix" is a named, marketed feature in this repo. The original POC started
from a waitlist shell; today the engine and settings surface are generally available.
| Surface | File | What it is |
|---|---|---|
| Settings | packages/frontend/src/pages/SettingsAutofixSection.tsx |
Per-agent Autofix toggle, shown to every tenant |
| Routing API | packages/backend/src/routing/tier.controller.ts |
GET/PATCH /api/v1/routing/:agentName/autofix |
The marketing copy is the product promise we're now building the engine for:
"Autofix repairs failing requests before they reach the model"
Real-time fix ยท Zero downtime ยท Observability ยท Notifications
โ https://manifest.build/autofix/
Naming: display name is "Autofix"; backend prefix is autofix_.
2. Scope (locked for the POC)
| Decision | Choice | Rationale |
|---|---|---|
| Streaming | Non-streaming + streaming requests that fail before the first byte | A repairable 4xx returns a non-200 status inside proxyRequest(), before the controller streams anything โ healing is transparent. Mid-stream is out of scope. |
| Which errors | Request-side 4xx only (400, 404, 422 by default) |
Malformed params, wrong format, unknown model. Explicitly not 401/403 (auth), 429 (rate limit), 408 (timeout), or 5xx (provider availability โ owned by fallback). |
| Order vs. fallback | Autofix runs FIRST, fallback is the safety net | Healing a fixable 400 beats spraying the same broken body across every fallback provider. Only if healing is exhausted/unfixable does the existing fallback chain run. |
| Budget | Per-agent, default 3, next to the toggle on the Routing page | One entity (agents), one UI location. |
| Availability | Open to all (cloud + self-hosted), no entitlement gate | Simplest for the POC; revisit gating later. |
| Healing contract | Simple, self-authored (ยง4): full request + full response in, patched request out | Real repo's OpenAPI isn't available yet; we build to a stable internal port + mock and reconcile the HTTP adapter later. |
| Recording | One row per client request + full autofix_chain JSONB capturing every attempt, each tagged original vs autofix |
"Track all requests, see the full chain, know which Autofix sent" โ without distorting message-count KPIs. |
Why 4xx-only is safe to retry: a 4xx means the provider rejected the request โ
no tokens generated, no tool calls executed, no side effects. Re-sending a patched
version is idempotent-safe. (This is why we do not heal 200-but-bad responses.)
3. Flow
agent โโโบ POST /v1/chat/completions (or /messages, /responses)
โ
โผ
proxyService.proxyRequest(body) โ Autofix lives HERE, not the controller
โ
route = resolve(body)
forward = forwardToPrimary(route, body) โโโบ provider
โ
โโโโโโโโโโโโโโโโโโ AUTOFIX LOOP (before fallback) โโโโโโโโโโโโโโโโโโโ
โ while !forward.ok AND autofix_enabled โ
โ AND status โ repairable(4xx) AND attempt < autofix_max_attempts:
โ errorBody = await forward.response.text() // full response โ
โ chain.push({ origin, attempt, request: body, status, errorBody }) โ
โ heal = healingClient.heal({ request: body, response, context, chain })
โ if heal.outcome == 'unfixable': break โ
โ body = heal.patched_request โ
โ route = resolve(body) // model may have changed โ
โ forward = forwardToPrimary(route, body) โโโบ provider โ
โ attempt++ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
still !forward.ok AND shouldTriggerFallback(status)?
โ yes โ existing FALLBACK CHAIN (unchanged safety net)
โผ
return { forward, meta, autofix_chain }
โ
โผ
controller: ok? โ stream / json to client, recordSuccess(+chain)
!ok? โ handleProviderError(+chain), return last errorIntegration point: inside packages/backend/src/routing/proxy/proxy.service.ts,
right after the primary forward and before the shouldTriggerFallback(...)
check (~line 315). The controller (proxy.controller.ts:187) barely changes โ it
just receives an autofix_chain alongside forward/meta and threads it into
recording. Because proxyRequest() returns before the controller streams, we're
always in the "pre-first-byte" zone โ the streaming-safety constraint is automatic.
Re-send mechanism: a primary heal attempt re-resolves the route for the patched
body (so an unknown-model โ known-model fix actually changes provider/model) and
forwards to the primary only. Autofix also runs on each failed fallback hop
inside the fallback chain, retrying the patched body on that hop's own transport
(no re-resolve โ the fallback is already the deliberate alternative route). The
fallback chain remains the safety net once every hop's Autofix has given up.
4. The healing service (external, mocked first)
Manifest talks to the healing service through a stable internal port so proxy
code never depends on the wire format:
interface HealingClient {
heal(input: HealRequest): Promise<HealResult>;
}Three implementations, selected by config at boot:
HttpHealingClientโ used wheneverAUTOFIX_HEALING_URLis set. POSTs to that
URL, maps response โHealResult.NoopHealingClientโ the production default whenAUTOFIX_HEALING_URLis
unset. Inert: never heals, never mutates traffic. Keeps the dev mock off real
traffic when no healer is wired.MockHealingClientโ the dev/test default whenAUTOFIX_HEALING_URLis
unset. In-process, deterministic (implements the MVPmax_tokensrename) so the
heal โ resend โ confirm flow can be exercised without an external Phoenix.
Phoenix contract (implemented)
The real service is Phoenix (mnfst/phoenix). Two endpoints, mapped 1:1 inpackages/backend/src/routing/autofix/phoenix.types.ts (that + the OpenAPI draft
are the source of truth):
POST /api/healโ send the full failed request + the normalised provider
error; get back a decision discriminated onstatus:patched/unverified(both carry ahealedBodyto resend, plus ahealAttemptId;patched= the issue is already verified,unverified= a
fresh patch) ยทresolving(Phoenix is authoring a patch; nothing to resend
this round) ยทno_patch.PATCH /api/heal-attempts/{healAttemptId}โ after resending thehealedBody,
report the retry outcome{ retryStatusCode, error? }(errorrequired on โฅ400).
Phoenix decides succeeded/failed itself (cleared target vs. same error recurring) โ
we don't send a verdict. Manifest fires this fire-and-forget so it never
delays the client.
provider + api (chat_completions|responses|messages โ exactly Manifest'sapiMode) are the fingerprint dimensions. traceId (required) is the stable
per-logical-request id โ Manifest reuses the message-link group id and sends it as
Phoenix's traceId so Phoenix can group the heal-attempt timeline across retries.
The provider error is normalised to { message, type, param, code } byprovider-error-normalizer.ts.
POST /api/heal request (Manifest โ Phoenix):
{
"traceId": "โฆ", // REQUIRED โ stable per logical request (group id)
"provider": "openai",
"api": "responses", // | "chat_completions" | "messages"
"url": "https://api.openai.com/v1/responses", // optional, must be an absolute URL
"request": {
/* FULL failed request body */
},
"response": {
"statusCode": 400,
"error": { "message": "โฆ", "type": "โฆ", "param": "โฆ", "code": "โฆ" },
},
}POST /api/heal response (Phoenix โ Manifest), discriminated on status:
{
"status": "patched", // | unverified | resolving | no_patch
"issueId": "โฆ",
"patchId": "โฆ", // nullable
"healAttemptId": "โฆ", // present on patched / unverified
"operations": [{ "type": "rename_param", "from": "max_tokens", "to": "max_output_tokens" }],
"healedBody": {
/* FULL body to resend */
},
"retryAfterMs": 2000, // present only on resolving
}PATCH /api/heal-attempts/{healAttemptId} โ report the retry outcome:
{ "retryStatusCode": 200 } // cleared โ Phoenix marks succeeded
{ "retryStatusCode": 400, "error": { "code": "โฆ" } } // error present & required on โฅ400no_patch / resolving let the service stop the loop early instead of burning the
whole budget.
5. Data model changes
5.1 `agents` (toggle) โ mirrors `complexity_routing_enabled`
// Nullable: NULL inherits the mode default (ON in cloud, OFF in self-hosted);
// an explicit true/false wins. There is no max-attempts column โ healing is a
// single attempt.
@Column('boolean', { nullable: true }) autofix_enabled!: boolean | null;5.2 `agent_messages` โ two linked rows per healed request
Revised per user feedback (2026-07-01). A healed request is recorded as two
rows in the log, not one: the failed original and the successful retry,
so each real upstream request is its own line. They're linked by a sharedautofix_group_id(in the DB) and by a clickable link in the UI.
@Column('boolean', { default: false }) autofix_applied!: boolean; // part of an Autofix flow?
@Column('varchar', { nullable: true }) autofix_group_id!: string | null; // links original โ retry (indexed)
@Column('varchar', { nullable: true }) autofix_role!: string | null; // 'original' | 'retry'
@Column('jsonb', { nullable: true }) autofix_operations!: object | null; // the Phoenix edits that fixed it
@Column('jsonb', { nullable: true }) autofix_phoenix!: object | null; // Phoenix ids {issueId, patchId, healAttemptId}| Row | status |
autofix_role |
Notes |
|---|---|---|---|
| Failed original | auto_fixed (new, orange) |
original |
carries the error + autofix_operations; 0 tokens; timestamped ~1s before the retry so they sort adjacently |
| Successful retry | ok |
retry |
the real completion (tokens/cost) |
- KPI consistency:
auto_fixedis added toERROR_MESSAGE_STATUSES, so the
failed original is excluded from message-count KPIs (the retryokrow is the
single counted success โ no double-counting) and is included in the Messages-log
"errors"/"failed" filter. The Messages-log total (unfiltered) shows both rows โ
the two lines the user asked for. - The link.
autofix_group_idis the DB link. The message-detail endpoint
resolves the paired row (autofix_sibling { id, role, status }) so the UI renders
"โ View the successful autofix retry" / "โ View the original failed request",
which scrolls+highlights the sibling (mirrorsscrollToFallbackSuccess).
Recorder: recordSuccessMessage tags the retry row (autofixColumns(autofix,'retry'));
a new recordAutofixOriginals() inserts the failed original row(s), called fromrecordSuccess when outcome==='healed'.
Migrations: two ALTER TABLE migrations (agents, agent_messages, + a(tenant_id, autofix_group_id) index) with fresh unique timestamps, registered indata-source-definitions.ts. No new entities.
6. Backend components
| Component | Location | Responsibility |
|---|---|---|
AutofixModule |
packages/backend/src/routing/autofix/ |
Wires client + config |
HealingClient port + Mock/Http impls |
same | Talk to the healing service |
AutofixService |
same | The loop: repairable check, budget, re-route, chain building |
| Repairable classifier | reuse proxy-error-sanitizer.ts (classifyProviderError) + status allow-list |
Decide if an error is healable |
| Loop hook | proxy.service.ts (~L315, before shouldTriggerFallback) |
Run AutofixService on the primary forward |
| Recorder changes | proxy-message-recorder.ts |
Accept optional autofix_chain on success/error recording |
| Toggle+budget endpoints | routing/tier.controller.ts (or new autofix.controller.ts) |
GET/PATCH /api/v1/routing/:agentName/autofix |
Endpoint shape (mirrors the complexity toggle; PATCH since there are two fields):
GET /api/v1/routing/:agentName/autofix โ { enabled, maxAttempts }
PATCH /api/v1/routing/:agentName/autofix { enabled?, maxAttempts? } โ { enabled, maxAttempts }Scope via @TenantCtx() + resolveAgentService.resolve(tenantId, agentName), and
invalidate the agent cache on write (same as toggleComplexity).
7. Frontend
- Routing page โ add an "Autofix" control next to "Route by complexity" in
RoutingDefaultTierSection.tsx(samerouting-switchstyling): toggle +, when on, a
small number input for max attempts. New api fnsgetAutofix()/updateAutofix()
inservices/api/routing.ts. - Message detail (
MessageDetails.tsx) โ whenautofix_applied, render an
"Autofix" section: outcome badge, then theautofix_chainas a visible timeline
โ each attempt showing origin (agent vs Autofix), the request that was sent, the
status/response, and what changed (patch_summary/changed_fields). - Message list โ small "Autofixed" badge on healed rows. If shown in the table,
add the scalar columns toselectMessageRowColumns()+MESSAGE_ROW_SELECT_ALIASESMessageRow(keep the shared projection contract intact โ CLAUDE.md).
8. Configuration (env)
| Var | Default | Purpose |
|---|---|---|
AUTOFIX_HEALING_URL |
(unset โ mock) | External healing service endpoint |
AUTOFIX_HEALING_API_KEY |
(unset) | Sent as x-api-key to Phoenix (Phoenix fails closed in production without a key; omit for a keyless dev/test Phoenix) |
AUTOFIX_DEFAULT_MAX_ATTEMPTS |
3 |
Fallback budget when an agent has none set |
AUTOFIX_TIMEOUT_MS |
10000 |
Per heal round-trip timeout |
AUTOFIX_REPAIRABLE_STATUSES |
400,404,422 |
Allow-list of healable statuses |
AUTOFIX_GLOBAL_ENABLED |
true |
Kill switch independent of per-agent toggles |
9. Explicitly out of scope (POC)
- Mid-stream healing (after bytes are sent).
- Healing
401/403/429/408/5xx/ transport errors (owned by auth refresh, rate-limit
cooldown, model fallback). - Healing 200-but-semantically-bad responses.
- Hardened scrubbing/PII controls on request/response bodies (basic scrub only โ ยง11).
- Pre-emptive fixing before the first attempt (the marketing "before they reach the
model" โ POC is reactive). - Entitlement or rollout gating (Autofix is open to all).
10. Open questions / decisions still needed
- Chain modeling โ RESOLVED (2026-07-01): the user chose one row per attempt
(failed original + successful retry as sibling log rows, linked byautofix_group_id- a UI link), matching the
recordFailedFallbackspattern. Implemented in ยง5.2.
- a UI link), matching the
- Fallback body after Autofix fails. When healing is exhausted and the safety-net
fallback chain runs, forward the original body or the last patched body?
My lean: original (fallbacks are a separate axis; patched guesses were for the
primary). Minor. - Real healing schema โ no OpenAPI yet; building to ยง4. When it lands, reconcile
theHttpHealingClientadapter. Not a blocker for the POC.
(Resolved from discussion: streaming = non-stream + pre-first-byte ยท errors = 4xx only
ยท Autofix before fallback ยท per-agent budget default 3 ยท open to all ยท full req+resp to
the healer ยท track the full chain and label Autofix requests.)
11. Risks & considerations
- Data footprint / trust boundary. We now (a) send full request + response bodies
to an external service and (b) store them on Autofix messages โ both new for
Manifest. Fine against an in-process/same-infra mock; before the real external
service, this needs the care of the error-cluster CMS boundary (scrub + consent +
retention). Scope storage to Autofix messages only; scrub + truncate. Flagging, not
hardening, for the POC. - Latency. Worst case โ
budget ร (heal round-trip + provider re-send)before
success/failure. Transparent for non-streaming but slow; bounded byAUTOFIX_TIMEOUT_MSand the budget. - Loop safety. Budget is a hard cap;
unfixableshort-circuits; only runs while
no bytes have been sent. No unbounded retry. - Observability parity. The message + its
autofix_chainis the single source of
truth for "how it went" โ matches the "Observability" pillar the Autofix modal
already advertises.
12. Rough build order
- Entities + 2 migrations (
agents,agent_messages) +helpers.tssync. AutofixModule+HealingClientport +MockHealingClient+AutofixService
(loop, repairable classifier, re-route, chain).- Hook into
proxy.service.tsbefore the fallback trigger; threadautofix_chain
throughproxyRequestโ controller โ recorder. - Toggle + budget endpoints + Routing UI control.
- Message-detail "Autofix" timeline (+ optional list badge).
HttpHealingClientonce the real schema lands.- Tests (100% line coverage โ CLAUDE.md): loop paths (healed / exhausted / unfixable /
disabled / non-repairable / re-route-on-model-change), before-fallback ordering,
classifier, endpoints, UI, recorder.
File: docs/model-parameters-schema.md
MPS Schema
MPS stands for Model Parameters Schema.
This document defines the JSON language used by the model parameter schema
catalog. The catalog is metadata: it describes which request parameters Manifest
can configure for a provider/auth/model tuple. User-selected values still live inagent_model_params.
The runtime source is the versioned modelparams npm package, generated from
the same modelparams.dev catalog. Manifest loads that package catalog at runtime
and keeps provider/auth/model route matching insideProviderParamSpecService.
The executable validator is isParamApplicability inpackages/shared/src/provider-params-spec.ts. Any schema change must update:
- this document
- the shared TypeScript types
isParamApplicability- the shared tests that prove invalid shapes are rejected
Schema Entry
Each entry describes one provider/auth/model tuple and its available parameters.
{
"provider": "anthropic",
"authType": "api_key",
"model": "claude-haiku-4-5-20251001",
"params": [
{
"path": "top_p",
"type": "number",
"label": "Top P",
"description": "Controls nucleus sampling by limiting generation to tokens whose cumulative probability reaches this value.",
"default": 1,
"range": { "min": 0, "max": 1, "step": 0.01 },
"group": "sampling",
"applicability": {
"except": [{ "thinking.type": ["adaptive", "enabled"] }, { "temperature": { "not": 1 } }]
}
}
]
}Rules:
provider,authType, andmodelidentify exactly one model route.paramsis the non-empty list of parameters for that exact route.pathis a dot path into stored params and outbound request params.typeis semantic data type, not a UI control kind.labelis user-facing copy.descriptionis developer-facing explanatory copy for the raw parameter.defaultis the provider default Manifest should display when known.valuesis allowed only for finite choices.rangedescribes numeric bounds and optional step.groupis a semantic grouping for ordering and display.applicabilityis optional; omitted means always available.- Do not add ad hoc rule fields such as
conflictsWith,disabledWhen,ui, or provider-specific metadata. Express availability throughapplicability.
Applicability
applicability controls whether a parameter is available for the current draft
or request params.
Only two top-level keys are allowed:
only: the parameter is available only when the rule matches.except: the parameter is unavailable when the rule matches.
At least one of only or except must be present. Unknown keys are invalid.
Rule Shape
A rule is either:
- one non-empty match object
- a non-empty array of match objects
Array rules are OR semantics. A single match object is AND semantics.
{
"except": [{ "thinking.type": ["adaptive", "enabled"] }, { "temperature": { "not": 1 } }]
}This means: disable the param when thinking.type is adaptive or enabled,
or when temperature exists and is not 1.
Match Values
Each match key is a dot path. Each match value must be one of:
- JSON primitive: string, number, boolean, or null
- non-empty array of JSON primitives
{ "not": }
Objects other than { "not": ... } are invalid.
Empty arrays are invalid.
Empty match objects are invalid.
Examples:
{ "thinking.type": "enabled" }{ "thinking.type": ["adaptive", "enabled"] }{ "temperature": { "not": 1 } }Evaluation Semantics
Evaluation uses the current params object after dot-path expansion.
For a normal match:
- primitive value matches by JSON equality
- array value matches if any primitive item equals the actual value
- missing paths do not match
For { "not": value }:
- missing paths do not match
- present paths match when the actual value is not equal to
value
For a param spec:
- If
onlyis present and does not match, the param is unavailable. - If
exceptis present and matches, the param is unavailable. - Otherwise the param is available.
Storage And Request Merge
Saved model params store UI values using the same dot-path shape as path.
They do not store derived UI state or provider-specific rule state.
The outbound proxy merge:
- expands needed nested defaults for configured nested roots
- merges configured Manifest values last, so Manifest values win for the same provider path
- omits params that are not applicable under the final effective values
Client request body values that do not overlap configured Manifest model
params stay in the outbound provider request.
Adding A New Rule
Prefer expressing new provider behavior with existing applicability syntax.
Add new schema syntax only when a provider rule cannot be represented with:
- exact match
- one-of match
- negated match
- OR of match objects
- AND within one match object
When extending the language, update the executable validator and add tests for
both accepted and rejected shapes inpackages/shared/__tests__/provider-params-spec.spec.ts.
File: integrations/n8n-nodes-manifest/README.md
n8n-nodes-manifest
Use Manifest from n8n workflows. Manifest routes requests through the right AI model behind one OpenAI-compatible API.
This package ships two nodes:
Manifest Chat Model (recommended)
A language-model sub-node built on @n8n/ai-node-sdk. Connect it to the AI Agent or Basic LLM Chain node exactly like the OpenAI Chat Model node โ Manifest sits between your agent and the providers and routes each request to the cheapest capable model.
- Model list is loaded from your Manifest instance; the default
autolets Manifest route every request. - Supports streaming, tool calling, and (optionally) the OpenAI Responses API.
- Requires an n8n version that ships
@n8n/ai-node-sdk(2026 releases).
Manifest (action node)
A regular node for calling Manifest directly from a workflow, without an AI Agent:
- List models from
GET /v1/models - Create chat completions with
POST /v1/chat/completions - Create Responses API calls with
POST /v1/responses
Response output
The action node handles both response formats returned by Manifest:
- Buffered responses are returned as the API's JSON object.
- Streamed responses are returned as parsed server-sent events after the stream completes:
{
"responseMode": "stream",
"events": [
{
"event": "message",
"data": { "choices": [{ "delta": { "content": "Hello" } }] }
},
{ "event": "message", "data": "[DONE]" }
]
}The action node waits for a streamed response to finish before passing its parsed events to the next workflow node. Do not set stream in Additional Body; the node ignores that field so response behavior remains consistent with the route configured in Manifest.
Credentials
Create a Manifest API credential with:
- Base URL:
https://app.manifest.buildfor Manifest Cloud, or your self-hosted URL. - API Key: an agent API key from Manifest.
The credential sends requests with:
Authorization: Bearer <your Manifest API key>
User-Agent: n8n-nodes-manifest
X-Title: n8n
HTTP-Referer: https://n8n.ioManifest uses those headers to attribute usage to this node on request details.
Install
In n8n:
- Go to Settings > Community Nodes.
- Select Install.
- Enter
n8n-nodes-manifest. - Confirm the community-node warning and install.
For local development:
npm install
npm run devRelease
This package publishes from GitHub Actions with npm provenance (required for
n8n verified community nodes since May 2026). The committed package.json
version is the single source of truth โ never publish from a local machine.
- Bump the version in
package.json(andpackage-lock.json) and add aCHANGELOG.mdentry. - Merge to
main.
That's it. The publish-n8n-node.yml workflow detects the version change,
skips if that version is already on npm, publishes with provenance, and pushes
the matching n8n-nodes-manifest-v<version> tag automatically.
Configure npm Trusted Publishing for the workflow, or set an NPM_TOKEN
repository secret.
File: packages/manifest/README.md
manifest
This package exists to hold the canonical Manifest release version.
It contains no code. The real backend and frontend live in ../backend and ../frontend, and shared types in ../shared. The Docker image at manifestdotbuild/manifest bundles the built backend + frontend.
What it's for
- Changesets target:
.changeset/config.jsonignoresmanifest-backend,manifest-frontend, andmanifest-shared, so when contributors runnpx changesetthe only selectable package is this one. Always targetmanifestregardless of which files you actually changed โ this is the single source of truth for "the version of Manifest as a whole". - Docker release version:
.github/workflows/docker.ymlreads theversionfield from this package'spackage.jsonviajqand uses it to tag the image (manifestdotbuild/manifest:{version}+ derived{major}.{minor}/{major}/sha-<short>tags). - Changelog:
CHANGELOG.mdhere (auto-generated by changesets) is the unified Manifest release history.
Adding a changeset
npx changeset
# โ select "manifest"
# โ choose patch / minor / major
# โ write a one-line summaryCommit the generated file. On merge to main, the release workflow opens a chore: version packages PR bumping version and appending to CHANGELOG.md. Merge that PR to land the version bump, then manually run the Docker workflow (Actions โ Docker โ Run workflow with a blank input) to publish.