Skip to content

Profiles

llms.txt lists every page for an agent

fountainApply and fountainRun need an endpoint and a token. Before #2124 that meant FOUNTAIN_ENDPOINT / FOUNTAIN_TOKEN in the shell, which works for one instance and one account but not for a project that targets a self-hosted instance for staging and the hosted one for prod. The fountain.profiles namespace in chant.config.ts names one or more endpoint/token pairs so a project can switch between them with --profile.

import type { ChantConfig } from "@intentius/chant/config";
// Brings the `fountain` key into ChantConfig.
import "@intentius/chant-lexicon-fountain";
export default {
lexicons: ["fountain"],
fountain: {
profiles: {
staging: {
endpoint: "https://fountain-staging.example.com",
token: { env: "FOUNTAIN_STAGING_TOKEN" },
steward: "staging-steward",
},
prod: {
endpoint: "https://fountain.inevitable.fyi",
token: { env: "FOUNTAIN_PROD_TOKEN" },
},
},
defaultProfile: "staging",
},
} satisfies ChantConfig;
FieldTypeDescription
endpointstringBase URL of the fountain instance this profile targets.
token{ env: string }The name of the environment variable that holds the bearer token, never a literal value. FTN001 refuses a literal string here.
stewardstring (optional)The default steward, the name of a Steward, that chant run --on fountain runs on when the op is listed on no steward of its own. The run resumes that steward’s conversation by channel_id chant:steward:<name>.
teamstring (optional, deprecated)The old name for steward, from when a steward was a team seat. Read as steward, with a warning.

An unknown key anywhere in fountain, at the namespace level or inside a profile, fails config load naming the key, the same way forgejo does (chant #1344).

resolveProfile(config, name?) returns:

  1. The profile named by name, if one exists.
  2. Otherwise, the profile named by defaultProfile, if one exists.
  3. Otherwise, undefined.

fountainApply and fountainRun resolve endpoint and token field by field: an explicit endpoint or token argument always wins over the profile. For whichever field an explicit argument doesn’t cover, they call resolveProfile and, if it returns a profile, read that field from it. Once a profile resolves, it is authoritative: an unset token env var is an error naming the variable, and FOUNTAIN_TOKEN is not read. Only when resolveProfile returns undefined (no fountain.profiles declared, or neither the named nor the default profile exists) do the activities fall back to the pre-#2124 behavior: FOUNTAIN_ENDPOINT / FOUNTAIN_TOKEN, then the hosted default endpoint.

// Explicit args always win, per field.
await fountainApply({ manifestPath: "build/fountain.yaml", endpoint: "http://localhost:4000" });
// Resolves through fountain.profiles.staging (or defaultProfile, if "staging" doesn't exist).
await fountainApply({ manifestPath: "build/fountain.yaml", profile: "staging" });
// No profile configured at all: falls back to FOUNTAIN_ENDPOINT / FOUNTAIN_TOKEN.
await fountainApply({ manifestPath: "build/fountain.yaml" });
Terminal window
# Uses defaultProfile
chant run steward-converge --on fountain
# Overrides it
chant run steward-converge --on fountain --profile prod

--profile (-p) is core’s flag on every chant run subcommand; it reaches a lexicon’s runtime as OpRunStartOptions.profile, and fountain resolves it against fountain.profiles. A name that is not declared there is refused by name rather than falling back to defaultProfile, so a misspelt profile cannot silently run against the wrong instance.

A profile’s token is always { env: "VAR_NAME" }: an environment variable name, never a secret value. === FTN001 refuses a literal string in that position under fountain.profiles, regardless of whether it happens to match a known credential shape:

fountain: {
profiles: {
// FTN001: literal token under fountain.profiles
staging: { endpoint: "https://x", token: "sk-abc123" },
},
},

chant