manifest Documentation

Open Source LLM Gateway ๐Ÿฆš

7,595 stars TypeScript TypeScript 14 files ยท ~38,330 tokens #ai#ai-#ai-sdk#anthropic#byok#cost-tracking#gateway#hermes-agent
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy manifest on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name manifest --restart always manifestdotbuild/manifest:6
Configured Services & Ports
โš™๏ธ manifest
image: manifestdotbuild/manifest:6
โš™๏ธ postgres
image: postgres:16-alpine
docker-compose.yml
yaml (docker-compose.yml)
services:
  manifest:
    image: manifestdotbuild/manifest:6
    environment:
      - SERVICE_URL_MANIFEST_2099
      - PORT=2099
      - BIND_ADDRESS=0.0.0.0
      - DATABASE_URL=postgresql://manifest:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/manifest
      - BETTER_AUTH_SECRET=${SERVICE_HEX_64_AUTH}
      - MANIFEST_ENCRYPTION_KEY=${SERVICE_HEX_64_ENCRYPTION}
      - BETTER_AUTH_URL=${SERVICE_URL_MANIFEST_2099}
      - MANIFEST_MODE=selfhosted
      - NODE_ENV=production
      - DB_POOL_MAX=10
      - AUTH_DB_POOL_MAX=5
      - REQUEST_RECORDING_STORAGE=filesystem
      - REQUEST_RECORDING_FILESYSTEM_PATH=/data/request-recordings
    volumes:
      - manifest-recordings-data:/data/request-recordings
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test:
        - CMD
        - node
        - -e
        - "const p=process.env.PORT||'2099';fetch(`http://127.0.0.1:$${p}/api/v1/health`).then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
      interval: 30s
      timeout: 5s
      start_period: 90s
      retries: 3

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=manifest
      - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
      - POSTGRES_DB=manifest
    volumes:
      - manifest-postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready -U manifest
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  manifest-postgres-data:
  manifest-recordings-data:

File: deploy/easypanel/template/README.md

Easypanel Template

This directory contains the Manifest template files intended for the upstream
easypanel-io/templates
repository.

To submit upstream, copy these files into:

text
templates/manifest/index.ts
templates/manifest/meta.yaml

The 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
Google 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

  1. Open the Routing page in the dashboard.
  2. Click the provider tile.
  3. Pick the Subscription tab.
  4. 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.

Google

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, so
    autofix_max_attempts, the maxAttempts endpoint field, and the
    AUTOFIX_DEFAULT_MAX_ATTEMPTS env var referenced below do not exist.
  • agents.autofix_enabled is nullable (boolean | null, not
    default: false). NULL inherits the deployment-mode default (ON in cloud,
    OFF in self-hosted); an explicit true/false wins.
  • Production default client is NoopHealingClient (inert), not the mock โ€”
    the mock runs only in dev/test. See ยง4.
  • agent_messages also has autofix_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 new routing/autofix/* file + the new UI at 100%
coverage on changed lines. The healing client defaults to an in-process mock
(implements the MVP max_tokens rename) until AUTOFIX_HEALING_URL points at a
real Phoenix.

Verified live (/serve, cloud mode): migrations applied, the Routing-page
Autofix toggle persists to the DB, and the real
AutofixService + mock Phoenix heal the MVP max_tokens case end-to-end
(400 โ†’ rename_param โ†’ resend max_output_tokens โ†’ 200 healed, chain recorded).

Not yet: backend e2e (needs Postgres); a live HTTP heal needs a provider
that actually rejects max_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

text
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 error

Integration 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:

ts
interface HealingClient {
  heal(input: HealRequest): Promise<HealResult>;
}

Three implementations, selected by config at boot:

  • HttpHealingClient โ€” used whenever AUTOFIX_HEALING_URL is set. POSTs to that
    URL, maps response โ†’ HealResult.
  • NoopHealingClient โ€” the production default when AUTOFIX_HEALING_URL is
    unset
    . Inert: never heals, never mutates traffic. Keeps the dev mock off real
    traffic when no healer is wired.
  • MockHealingClient โ€” the dev/test default when AUTOFIX_HEALING_URL is
    unset
    . In-process, deterministic (implements the MVP max_tokens rename) 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 in
packages/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 on status:
    patched / unverified (both carry a healedBody to resend, plus a
    healAttemptId; 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 the healedBody,
    report the retry outcome { retryStatusCode, error? } (error required 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's
apiMode) 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 } by
provider-error-normalizer.ts.

POST /api/heal request (Manifest โ†’ Phoenix):

jsonc
{
  "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:

jsonc
{
  "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:

jsonc
{ "retryStatusCode": 200 }                          // cleared โ†’ Phoenix marks succeeded
{ "retryStatusCode": 400, "error": { "code": "โ€ฆ" } } // error present & required on โ‰ฅ400

no_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`

ts
// 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 shared
autofix_group_id (in the DB) and by a clickable link in the UI.

ts
@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_fixed is added to ERROR_MESSAGE_STATUSES, so the
    failed original is excluded from message-count KPIs (the retry ok row 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_id is 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 (mirrors scrollToFallbackSuccess).

Recorder: recordSuccessMessage tags the retry row (autofixColumns(autofix,'retry'));
a new recordAutofixOriginals() inserts the failed original row(s), called from
recordSuccess when outcome==='healed'.

Migrations: two ALTER TABLE migrations (agents, agent_messages, + a
(tenant_id, autofix_group_id) index) with fresh unique timestamps, registered in
data-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):

text
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 (same routing-switch styling): toggle +, when on, a
    small number input for max attempts. New api fns getAutofix() / updateAutofix()
    in services/api/routing.ts.
  • Message detail (MessageDetails.tsx) โ€” when autofix_applied, render an
    "Autofix" section: outcome badge, then the autofix_chain as 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 to selectMessageRowColumns() + MESSAGE_ROW_SELECT_ALIASES
    • MessageRow (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

  1. Chain modeling โ€” RESOLVED (2026-07-01): the user chose one row per attempt
    (failed original + successful retry as sibling log rows, linked by autofix_group_id
    • a UI link), matching the recordFailedFallbacks pattern. Implemented in ยง5.2.
  2. 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.
  3. Real healing schema โ€” no OpenAPI yet; building to ยง4. When it lands, reconcile
    the HttpHealingClient adapter. 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 by
    AUTOFIX_TIMEOUT_MS and the budget.
  • Loop safety. Budget is a hard cap; unfixable short-circuits; only runs while
    no bytes have been sent. No unbounded retry.
  • Observability parity. The message + its autofix_chain is the single source of
    truth for "how it went" โ€” matches the "Observability" pillar the Autofix modal
    already advertises.

12. Rough build order

  1. Entities + 2 migrations (agents, agent_messages) + helpers.ts sync.
  2. AutofixModule + HealingClient port + MockHealingClient + AutofixService
    (loop, repairable classifier, re-route, chain).
  3. Hook into proxy.service.ts before the fallback trigger; thread autofix_chain
    through proxyRequest โ†’ controller โ†’ recorder.
  4. Toggle + budget endpoints + Routing UI control.
  5. Message-detail "Autofix" timeline (+ optional list badge).
  6. HttpHealingClient once the real schema lands.
  7. 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.
text

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 in
agent_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 inside
ProviderParamSpecService.

The executable validator is isParamApplicability in
packages/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.

json
{
  "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, and model identify exactly one model route.
  • params is the non-empty list of parameters for that exact route.
  • path is a dot path into stored params and outbound request params.
  • type is semantic data type, not a UI control kind.
  • label is user-facing copy.
  • description is developer-facing explanatory copy for the raw parameter.
  • default is the provider default Manifest should display when known.
  • values is allowed only for finite choices.
  • range describes numeric bounds and optional step.
  • group is a semantic grouping for ordering and display.
  • applicability is optional; omitted means always available.
  • Do not add ad hoc rule fields such as conflictsWith, disabledWhen,
    ui, or provider-specific metadata. Express availability through
    applicability.

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.

json
{
  "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:

json
{ "thinking.type": "enabled" }
json
{ "thinking.type": ["adaptive", "enabled"] }
json
{ "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:

  1. If only is present and does not match, the param is unavailable.
  2. If except is present and matches, the param is unavailable.
  3. 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:

  1. expands needed nested defaults for configured nested roots
  2. merges configured Manifest values last, so Manifest values win for the same provider path
  3. 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 in
packages/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:

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 auto lets 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:
json
{
  "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.build for Manifest Cloud, or your self-hosted URL.
  • API Key: an agent API key from Manifest.

The credential sends requests with:

text
Authorization: Bearer <your Manifest API key>
User-Agent: n8n-nodes-manifest
X-Title: n8n
HTTP-Referer: https://n8n.io

Manifest uses those headers to attribute usage to this node on request details.

Install

In n8n:

  1. Go to Settings > Community Nodes.
  2. Select Install.
  3. Enter n8n-nodes-manifest.
  4. Confirm the community-node warning and install.

For local development:

bash
npm install
npm run dev

Release

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.

  1. Bump the version in package.json (and package-lock.json) and add a CHANGELOG.md entry.
  2. 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.json ignores manifest-backend, manifest-frontend, and manifest-shared, so when contributors run npx changeset the only selectable package is this one. Always target manifest regardless 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.yml reads the version field from this package's package.json via jq and uses it to tag the image (manifestdotbuild/manifest:{version} + derived {major}.{minor} / {major} / sha-<short> tags).
  • Changelog: CHANGELOG.md here (auto-generated by changesets) is the unified Manifest release history.

Adding a changeset

bash
npx changeset
# โ†’ select "manifest"
# โ†’ choose patch / minor / major
# โ†’ write a one-line summary

Commit 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.