Skip to content
archmax.ai

The config

The config is the deployment’s document of what the platform offers: the design agent it runs on, the services it fronts, the Activepieces collections owners can connect, the skill registries it follows and the OAuth apps it signs owners in with, and the branding it presents itself under. Where the platform runs (its database, queues, object store, secret store, sign-in settings, ports and directories) stays in environment variables.

The config holds no secret. Every secret is a ${{NAME}} placeholder resolved from the process environment at boot, so the same document can be committed as an example, mounted as a file or pasted into one variable. Every section is optional; a deployment with no config at all offers no service, no OAuth app and no Activepieces collection, follows the platform’s public skill registry, and has no design agent, which production refuses.

The API and the dispatcher each read the config once, when they start, from exactly one source, checked in this order:

Order Source Format
1 ARCHMAX_CONFIG, when it is set and not empty One line of JSON when the value starts with {; otherwise base64, decoded and parsed as YAML (which admits JSON), so base64 < config.yaml works as well as pnpm config:env --base64. Any other value refuses the start.
2 The file ARCHMAX_CONFIG_FILE names YAML. A named file that does not exist refuses the start, naming the path. A relative path resolves against the process’s working directory, which differs between the API and the dispatcher, so name an absolute one.
3 config.yaml in the app directory, when it exists YAML. The app directory is the nearest directory above the working directory that holds pnpm-workspace.yaml: the repository root locally, /app in the image.
4 None of these The empty document.

When ARCHMAX_CONFIG is set and a file would otherwise have been read, the variable wins and one warning names the file it shadowed: set one of the two. An empty file is the empty document, and a document whose top level is not an object refuses the start.

Nothing reads the config again after the start: a changed config takes a restart, in the API and in the dispatcher alike.

Any string value, at any depth, may carry one or more placeholders of the form ${{NAME}}, where NAME is an environment variable name ([A-Za-z_][A-Za-z0-9_]*). A placeholder may stand alone or sit inside a longer string, such as http://${{BROWSER_HOST}}:8080/mcp.

  • Resolved before validation. Each placeholder is replaced by its variable’s value, so a placeholder may also stand in for a number: input: "${{DESIGN_PRICE_INPUT}}" holding 2.5 is the number 2.5. An empty string in a number field is refused rather than read as zero.

  • Values only. Keys are never substituted, there is no escape for a literal ${{, and a placeholder has no default.

  • An unset variable leaves its entry out. A placeholder whose variable is unset or empty leaves out the entry holding it: its list entry (services[0]), or outside a list its whole top-level section (designAgent). The process starts with the rest of the config and prints one line per entry left out, naming the entry, each field and its variable, and never a value:

    [config] config (/app/config.yaml): services[0] is left out: services[0].token: environment variable ARCHMAX_SERVICE_BROWSER_TOKEN is not set

    In production, a designAgent left out this way refuses the start.

Quote every placeholder in YAML ("${{NAME}}"). The syntax is one neither YAML nor a shell touches: ${NAME} is shell expansion and ${{NAME}} is not.

The resolved document is validated against one shape before the process connects to anything, and the first invalid document refuses the start.

  • Strict at every level. The top-level sections are designAgent, services, activepieces, skillRegistries, oauthApps and branding. An unknown key anywhere, at the top or inside prices or reach, refuses the start naming the key. A host setting written into the config, such as a mongodbUri, fails the same way. The one exception is inside branding, which refuses nothing: a value it cannot use is left out with a line, and archmax’s own applies.

  • Messages name the source and the path, never the value. Each problem is reported as config (<source>): <path>: <message>, where the source is ARCHMAX_CONFIG or the file’s path and the path reads as in the document: designAgent.prices.input, services[1].url.

    config (/app/config.yaml): services[0].url: Invalid URL
    config (ARCHMAX_CONFIG): (root): Unrecognized key: "mongodbUri"
  • Ids are unique. A service id, a registry id, an OAuth app id or an activepieces.collections entry listed twice refuses the start, and so does a collection listed under two OAuth apps.

  • Production requires the design agent. With NODE_ENV=production, a config without a designAgent section refuses the start, naming designAgent. Outside production the section may be absent, and a design-agent turn is then refused as not configured.

Shortly after the start, while the API and the dispatcher warm their tool catalog, the platform checks what the shape alone cannot know, and the process exits when one of these fails:

  • an activepieces.collections entry the release does not package (pnpm config:env --check reports this one too);
  • a service id that is already a platform collection id or a packaged Activepieces collection id, offered or not;
  • an OAuth app naming a collection the release does not package, or an offered one that does not authenticate with OAuth2 or whose OAuth2 endpoint host the collection does not declare;
  • an OAuth app whose reach.signsIn is set while it serves a collection that requires PKCE.

pnpm config:env turns a config.yaml into the value ARCHMAX_CONFIG holds, with every placeholder left exactly as written, so the secrets stay in the environment of the process that boots. It reads config.yaml in the app directory, or the file named as its argument, and refuses a file whose top level is not an object.

Command Output
pnpm config:env [path] The document as one line of JSON.
pnpm config:env --base64 The same, base64-encoded, for a host that templates ${{ }}.
pnpm config:env --env The line ARCHMAX_CONFIG='<json>' for a .env file. Refuses a document whose JSON contains a single quote.
pnpm config:env --env --base64 The line ARCHMAX_CONFIG=<base64>, unquoted.
pnpm config:env --check Resolves and validates the file exactly as the boot does, under the current NODE_ENV, after loading the repository root’s .env without overwriting a variable already set. Exits non-zero with the boot’s message, and also when the boot would leave an entry out.

Every form but --check lists the variables the placeholders reference on standard error, so a command substitution stays clean:

Terminal window
docker run --rm -p 8080:8080 --env-file .env \
-e ARCHMAX_CONFIG="$(pnpm -s config:env --base64)" \
ghcr.io/archmax-ai/automation:1.5.0
NODE_ENV=production pnpm config:env --check # would this boot as production?

The design agent runs on the platform’s own model, never on an owner’s connection, and the platform pays for it. Required in production. Without provider, the model is served by an OpenAI-compatible endpoint with a key. With provider: bedrock, it is AWS Bedrock’s Converse API in one region, signed with the AWS SDK’s default credential chain (on ECS Fargate, the task’s IAM role), so the section holds no key and refuses one. The endpoint and key, or the region, are required together with the model and its input and output prices, so a configured design agent is always a priced one. See the design agent, and AWS (ECS Fargate) for Bedrock.

Field Type Required Default Meaning
provider bedrock no — Left out for an OpenAI-compatible endpoint; bedrock for AWS Bedrock.
apiBaseUrl URL without provider — The OpenAI-compatible endpoint.
apiKey string without provider — Its key, written as a placeholder.
region AWS region code with provider: bedrock — The Bedrock region, such as eu-west-1.
model string yes — The model id.
prices.input number, at least 0 yes — USD per million input tokens.
prices.output number, at least 0 yes — USD per million output tokens.
prices.cacheRead number, at least 0 no the input rate USD per million tokens read from the prompt cache.
prices.cacheWrite number, at least 0 no the input rate USD per million tokens written to the prompt cache.
dailyBudgetUsd.organization number, at least 0 no 10 What the design agent may spend per UTC day for an organization whose record carries no designAgentDailyBudgetUsd of its own. Zero switches the design agent off for it.
dailyBudgetUsd.user number, at least 0 no 5 The same per user. Both ceilings apply to every turn.
recursionLimit positive integer no 600 The most graph steps one design-agent turn may take, roughly two per action it performs.

Set the two cache rates whenever prompt caching is on (ARCHMAX_PROMPT_CACHE, on by default): a cached prefix is read again on every model call of a turn, and charging those reads at the input rate spends the daily budget several times faster than the provider bills it.

Each entry is a service: an image that serves a platform collection’s actions over MCP. The display fields are the operator’s; the credential form (or none) and the tools come from the service itself when it is discovered, so nothing else is configured here. A service without a credential form is offered in every environment without a connection. Leave the list out, or write [], for no services. See connections and tools.

Field Type Required Default Meaning
id lowercase slug yes — The collection id owners see and workflows reference (<id>__<tool>), distinct from every other platform collection id and every packaged Activepieces collection id.
title string yes — The title in the connections catalog and the editor.
description string no — One sentence on the catalog card.
logoUrl URL or null no null The icon; null is the default icon.
url URL yes — The service’s MCP Streamable HTTP endpoint. It may point at a private network.
token string, at least 16 characters yes — The bearer token this one service checks, written as a placeholder and set to the same value on the service. One per service, so each is rotated on its own and a leaked one reaches no other service.

A lowercase slug, here and in the sections below, matches ^[a-z][a-z0-9-]{0,62}$.

Which Activepieces collections this deployment offers. The release packages a fixed set: each is an @activepieces/piece-<id> dependency of @archmax/core, or an @archmaxpieces/piece-<id> one for a piece archmax authors itself (such as microsoft-sharepoint), so adding one is a code change and a release. This section chooses which of the packaged collections owners can connect, in the order the add-connection catalog lists them, and names both kinds alike, by collection id. See connections and tools.

Field Type Required Default Meaning
collections list of collection ids with the section — The offered collections, each a packaged one and listed once.

Only the collections the section names are offered. Leaving the section out, or writing collections: [], offers none, so a release that packages another piece offers it nowhere until your config names it. config.yaml.example writes every packaged collection out under a commented section: uncomment it to offer them all, and comment out a line to offer fewer.

activepieces:
collections:
- microsoft-outlook
- microsoft-sharepoint
- gmail
- slack
- github

A name the release does not package refuses the start, naming activepieces.collections, the name and the packaged ids.

A collection the section leaves out is not offered anywhere: it is not in the catalog, a connection of it resolves no tools, no credential can be created or replaced for it, and an OAuth app that names it skips it with a line. Its trigger deployments fail with an error naming the collection on their next firing or reconcile pass. The dispatcher does not reconcile at startup, so after restarting with a narrower list, run the reconcile tool against the deployment to record them in error at once:

Terminal window
pnpm --filter @archmax/core exec tsx tools/reconcile-triggers.ts --apply

Each entry is a registry: a public git repository laid out as skills/<slug>/SKILL.md, whose skills an owner browses from an environment’s Skills page and installs into the environment. The API shallow-clones each registry into ARCHMAX_SKILL_REGISTRIES_DIR/<id> when it starts and fast-forwards it on later starts; an unreachable registry logs a warning and serves its last clone. See skills and registries.

Field Type Required Default Meaning
id lowercase slug yes — The registry id, recorded in an installed skill’s frontmatter.
title string yes — The title in the Browse skills dialog.
description string no — One sentence beside the title.
url https URL yes — The repository’s clone URL. Only https is accepted: the clone carries no credential.
ref string no main The branch or tag to follow.

Leaving the key out follows the platform’s own public registry:

skillRegistries:
- id: archmax
title: archmax skills
description: Skills maintained by the archmax team.
url: https://github.com/archmax-ai/skills.git
ref: main

Naming skillRegistries replaces that list, so list the archmax entry too to keep it. Writing [] follows no registry.

Each entry is an OAuth app the operator registered with an identity provider (an Entra app registration, a Google Cloud OAuth client, a GitHub App), and the collections it signs owners in for. An owner then connects one of those collections by signing in, with no client id or secret of their own. An OAuth app is delegated only: it signs a person in and is never used for the client_credentials grant. A collection no entry lists cannot be signed in to this way; leave the key out, or write [], and none can. pnpm oauth-apps:entra registers the Microsoft app and pnpm oauth-apps:github the GitHub App, and each prints its entry.

Field Type Required Default Meaning
id lowercase slug yes — The app id, recorded on every credential signed in through it.
title string yes — What owners see beside Connect.
clientId string yes — The app’s client id, written as a placeholder: each deployment registers its own app.
clientSecret string yes — The app’s client secret, written as a placeholder.
collections list of collection ids, at least one yes — The collections the app signs owners in for. Each is a packaged OAuth2 collection, named under one app only. One the activepieces section leaves out is skipped with a line, and the rest of the app boots.
reach object no — What a connection signed in through the app reaches beyond the person’s own access.
reach.note string with reach — What owners read: what the app reaches, and where that is changed.
reach.url https URL no — The identity provider’s page that changes it.
reach.label string no Manage on <host> The text of that page’s link. Needs reach.url.
reach.signsIn true or false no — Whether the page goes on to sign the person in and returns to the platform’s callback with the state it was given, so a Connect can run through it. Needs reach.url. A literal boolean: a placeholder here is refused.

Reach is decided by the identity provider, not by the platform: for a GitHub App, it is the accounts and repositories the app is installed on. A GitHub App’s signsIn also needs the app’s “Request user authorization (OAuth) during installation” setting and OAUTH2_REDIRECT_URL as its first callback URL.

The client secret is read from the config each time it is needed, for a token exchange or a refresh, so rotating it is a restart. Removing an app from the config means every credential signed in through it has to be connected again.

How the deployment presents itself: the title it names itself by, the logo that leads the icon rail, the six brand colours the interface is drawn in, and the tones of the palette authors colour their workflows with. Every field is optional and falls back to archmax’s own; leave the section out and the deployment is archmax automation.

Field Type Required Default Meaning
title string, 1 to 60 characters no archmax automation What the application names itself by: the browser tab, the tooltip of the rail’s top mark, and the sign-in notices.
logoUrl https: or data:image/ URL no the ax monogram The mark at the top of the icon rail and at the head of every sign-in notice, drawn at its own aspect ratio within about 32 × 20 pixels. A logo that fails to load shows the monogram.
darkLogoUrl https: or data:image/ URL no logoUrl The logo in the dark theme. Needs logoUrl.
colors map of brand colour to #rgb or #rrggbb no archmax’s, below The brand colours to change. A colour left out keeps archmax’s. Quote each value: YAML reads an unquoted # as a comment.
appearancePalette map of appearance hue to #rgb or #rrggbb no archmax’s, below The tones of the hues authors colour states and transitions with. A tone left out keeps archmax’s.

With a title or a logoUrl set, the rail mark’s tooltip shows the title over powered by archmax, and every sign-in notice closes with the same line. Setting colors alone changes no name. The boot screen and the favicon stay archmax’s whatever the section says: both are drawn before the application can read it.

Each brand colour is named by what it means, and every colour the interface chooses for itself is one of them:

Colour archmax’s What it draws
brand #8878a8 The deployment’s own colour: the selected state on the canvas, a human state’s badge, the pending-invitation dot, and decoration — collection and trigger glyphs, the design agent’s starters, a connection’s direction, the Chat page’s start view backdrop.
success #8c987f What went well: a succeeded turn, the finished column, an approve transition, an added file, a verified domain, the states a turn passed through.
error #bc8081 What went wrong: a failed or cancelled turn, a reject transition, a deleted file, a failed tool call, the state a failed turn stopped in.
executing #c2d0e4 What is under way: a running turn, the running column, a refine transition.
attention #bdac8a What needs a person or a look: a turn awaiting a decision, the needs_you column, warnings, unpublished changes, a modified file, an armed listen window.
waiting #85aeab What is not under way yet: a queued turn, a turn awaiting input, the planned column.

Tags print white text on a brand colour, so choose mid-tones. A glyph or text drawn in a brand colour, such as a success tick or a warning, takes a shade derived from it at a lightness that reads in each theme, so it never needs a value of its own.

The colours an author gives a state or a transition in the editor are the appearance palette’s seven hues, and so are the colours that tell a session’s tags apart. They are not brand colours: an author picks a hue, and workflow.yaml stores its name (color: sage), so the names stay whatever the branding and every published workflow keeps its colours. appearancePalette sets what each name looks like on this deployment:

Hue archmax’s
sage #8c987f
rose #bca195
blue #c2d0e4
purple #8878a8
rust #bc8081
amber #bdac8a
teal #85aeab

A state card prints dark text on its hue and a tag prints white, so keep the tones light-to-mid, as archmax’s are.

branding:
title: Acme Automation
logoUrl: https://cdn.example.com/acme-logo.svg
darkLogoUrl: https://cdn.example.com/acme-logo-dark.svg
colors:
brand: "#0b7285"
success: "#2f9e44"
error: "#c92a2a"
appearancePalette:
purple: "#6c5ce7"

Branding never stops the platform from starting. A value it cannot use — a title out of bounds, a logo that is not an https: or data:image/ URL, a colour that is not hex, a colour or field name the section does not have — is left out with one line naming its path (never the value), and archmax’s own applies in its place; pnpm config:env --check reports the same lines and exits non-zero. Once the API is up it also fetches each https: logo and logs a line for one that does not answer with an image, naming the field, the host and what came back. In the browser a logo that fails to load is replaced by the ax monogram, with a warning in the console.

[config] config (/app/config.yaml): branding.logoUrl is ignored: a logo is an https: or a data:image/ URL; the ax monogram applies
[branding] branding.logoUrl could not be loaded: cdn.example.com answered 404; the SPA shows the ax monogram instead

Like every section, it is read when the process starts: a changed branding takes a restart of the API. Remove the section before rolling back to a release that does not know it, which refuses the unknown key.

The smallest config production accepts is a priced design agent:

designAgent:
apiBaseUrl: https://api.openai.com/v1
apiKey: "${{ARCHMAX_DESIGN_AGENT_API_KEY}}"
model: gpt-6.1-sol
prices:
input: 2.00
output: 10.00

Outside production, no config at all is valid as well.

A deployment with prompt-cache rates, two services on its private network, a narrower set of Activepieces collections, its own registry beside the platform’s, and OAuth apps for Microsoft 365 and GitHub. The prices are illustrative; use the ones your provider bills.

designAgent:
apiBaseUrl: https://api.openai.com/v1
apiKey: "${{ARCHMAX_DESIGN_AGENT_API_KEY}}"
model: gpt-6.1-sol
prices:
input: 2.00
output: 10.00
cacheRead: 0.10
cacheWrite: 2.50
dailyBudgetUsd:
organization: 20
user: 5
recursionLimit: 600
services:
- id: browser
title: Browser
description: Fetch pages as markdown and search the web.
logoUrl: null
url: http://browser.internal:8080/mcp
token: "${{ARCHMAX_SERVICE_BROWSER_TOKEN}}"
- id: pdf-conversion
title: PDF Conversion
description: Convert office documents, Markdown, text and HTML to PDF.
logoUrl: null
url: http://pdf-conversion.internal:8080/mcp
token: "${{ARCHMAX_SERVICE_PDF_CONVERSION_TOKEN}}"
activepieces:
collections:
- microsoft-outlook
- microsoft-outlook-calendar
- microsoft-onedrive
- microsoft-sharepoint
- microsoft-teams
- github
- slack
- jira-cloud
skillRegistries:
- id: archmax
title: archmax skills
description: Skills maintained by the archmax team.
url: https://github.com/archmax-ai/skills.git
ref: main
- id: example
title: Example skills
url: https://git.example.com/example/skills.git
ref: v2
oauthApps:
- id: microsoft
title: Microsoft 365
clientId: "${{ARCHMAX_OAUTH_APP_MICROSOFT_CLIENT_ID}}"
clientSecret: "${{ARCHMAX_OAUTH_APP_MICROSOFT_CLIENT_SECRET}}"
collections:
- microsoft-outlook
- microsoft-outlook-calendar
- microsoft-onedrive
- microsoft-sharepoint
- microsoft-teams
- id: github
title: GitHub
clientId: "${{ARCHMAX_OAUTH_APP_GITHUB_CLIENT_ID}}"
clientSecret: "${{ARCHMAX_OAUTH_APP_GITHUB_CLIENT_SECRET}}"
collections:
- github
reach:
note: >-
This GitHub App reaches private repositories only on the accounts it is installed
on, and public ones everywhere. Install it on an account, or change which
repositories it reaches, on GitHub.
url: "https://github.com/apps/${{ARCHMAX_OAUTH_APP_GITHUB_SLUG}}/installations/new"
label: Install or choose repositories
signsIn: true

The environment of every process that reads it then holds ARCHMAX_DESIGN_AGENT_API_KEY, the two service tokens, the four OAuth app variables and ARCHMAX_OAUTH_APP_GITHUB_SLUG; see secrets the config references.

A setting belongs to one side only, and the config’s strict shape makes a setting written on the wrong side refuse the start rather than be ignored.

The config holds what the deployment offers The environment keeps where the platform runs
The design agent’s model, prices, daily budgets and recursion limit The runtime mode and the ports
The services, each with the token it checks The MongoDB, Redis and S3 connections
The Activepieces collections it offers, out of those the release packages The secret store and its settings, the master key included
The skill registries The sign-in mode and its variables
The OAuth apps The application and callback URLs, and the CORS origins
The branding The scratch, template and registry-clone directories
The private-URL allowance and the per-user rate
Every variable the SDK reads, such as prompt caching

To place a new setting: a connection string, a key that encrypts stored data, a sign-in setting, a directory on this host or a per-process setting the SDK reads belongs to the environment; a model the platform offers, a service it fronts, a collection it offers, a registry it follows or an app it signs people in with belongs to the config. A secret the config needs is still an environment variable, named by a placeholder.