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.
Where it is read from
Section titled “Where it is read from”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.
Placeholders
Section titled “Placeholders”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}}"holding2.5is 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 setIn production, a
designAgentleft 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.
Validation
Section titled “Validation”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,oauthAppsandbranding. An unknown key anywhere, at the top or insidepricesorreach, refuses the start naming the key. A host setting written into the config, such as amongodbUri, fails the same way. The one exception is insidebranding, 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 isARCHMAX_CONFIGor 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 URLconfig (ARCHMAX_CONFIG): (root): Unrecognized key: "mongodbUri" -
Ids are unique. A service id, a registry id, an OAuth app id or an
activepieces.collectionsentry 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 adesignAgentsection refuses the start, namingdesignAgent. 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.collectionsentry the release does not package (pnpm config:env --checkreports 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.signsInis set while it serves a collection that requires PKCE.
pnpm config:env
Section titled “pnpm config:env”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:
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?designAgent
Section titled “designAgent”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.
services
Section titled “services”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}$.
activepieces
Section titled “activepieces”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 - githubA 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:
pnpm --filter @archmax/core exec tsx tools/reconcile-triggers.ts --applyskillRegistries
Section titled “skillRegistries”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: mainNaming skillRegistries replaces that list, so list the archmax entry too to keep it. Writing
[] follows no registry.
oauthApps
Section titled “oauthApps”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.
branding
Section titled “branding”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 insteadLike 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.
Examples
Section titled “Examples”Minimal
Section titled “Minimal”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.00Outside production, no config at all is valid as well.
Production-shaped
Section titled “Production-shaped”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: trueThe 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.
The config or the environment
Section titled “The config or the environment”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.