Run ops on a fountain steward
Show the whole prompt
Read https://intentius.io/chant/tutorials/fountain-steward/.
In a new directory, run `npx chant init --lexicon fountain --template steward` and `npm install`, then `npx chant build src --lexicon fountain -o dist/fountain.yaml` and `npx chant lint src`.
Show me the six documents the build wrote and the lint result, and stop there.
Do not run `steward-apply`, post anything with `--on fountain`, or create a fountain token.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Optional: hand this page to your coding agent
Read https://intentius.io/chant/tutorials/fountain-steward/.In a new directory, run `npx chant init --lexicon fountain --template steward` and `npm install`, then `npx chant build src --lexicon fountain -o dist/fountain.yaml` and `npx chant lint src`.Show me the six documents the build wrote and the lint result, and stop there.Do not run `steward-apply`, post anything with `--on fountain`, or create a fountain token.Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.This tutorial declares a steward: one writer for an environment, running on
fountain. A steward is an agent that speaks chant’s own ACP server, with one
long-lived conversation that chant run --on fountain opens on the first run
and resumes by its channel_id after that. Every op you run against that
environment becomes a turn in that conversation, so the conversation is the
environment’s operational history, and its sandbox keeps its disk across park
and wake.
Steps 1 and 2 run offline. Steps 3 onward need a fountain instance and a token.
The runnable project is
examples/fountain-steward.
Prerequisites
Section titled “Prerequisites”npm install --save-dev @intentius/chant @intentius/chant-lexicon-fountainFor the live half, a fountain endpoint and a token in FOUNTAIN_TOKEN. Mint
one with POST /api/auth/token or from the account UI. A local
mix phx.server fountain works the same way.
1. Scaffold the project
Section titled “1. Scaffold the project”npx chant init --lexicon fountain --template stewardnpm installFour files come out. src/fountain.ts declares the toolchain Environment,
the Vault, and the Steward that binds them. src/prod-watch.op.ts and
src/prod-converge.op.ts are the two ops, each carrying its own cadence.
chant.config.ts declares fountain.profiles and the repoUrl build
parameter.
The steward itself is five lines:
export const { agent } = Steward({ name: "prod-steward", environment: toolchain, vault: prodCreds, ops: [prodWatch, prodConverge],});Each op is listed once, with or without a schedule. Listing it is how a run of
it knows which conversation it belongs on. Managoat has no scheduler, so a
cadence is data on the Op that a clock outside Managoat reads (step 7).
2. Build the manifest
Section titled “2. Build the manifest”npx chant build src --lexicon fountain -o dist/fountain.yamlThree documents come out in dependency order: Environment, Vault and
Agent. The agent carries the steward’s defaults:
kind: Agentmetadata: name: prod-stewardspec: runtime: acp runtime_command: chant acp --steward prod-steward environment: prod-toolchain permission_policy: default: auto_allow allowed_vault_ids: - prod-credsruntime_command: chant acp --steward prod-steward is what makes a prompt in
this conversation a chant command line, and one of this steward’s turns. The
agent sets no sandbox_mode, because every Managoat conversation gets a sandbox
of its own. The steward is one computer because every run goes to one
conversation, whose sandbox keeps its checkout and tool cache. auto_allow is there because
nobody is at the keyboard to answer a permission card; approvals belong to
chant’s gates, which step 5 covers.
Lint it before going further:
npx chant lint srcFTN023 checks that an acp agent carries its command, and FTN025 that nothing uses a kind or value Managoat removed.
3. Apply it
Section titled “3. Apply it”export FOUNTAIN_TOKEN=...npx chant run steward-apply # or call fountainApply directlyfountainApply sends Environment, Vault and Agent through Managoat’s
bulk POST /api/apply in one request. Running it a second time writes nothing.
4. Run an op in the conversation
Section titled “4. Run an op in the conversation”npx chant run prod-watch --on fountainThe line chant run prod-watch is posted to the steward’s conversation, opened
with channel_id chant:steward:prod-steward on the first run and resumed by
it after that, and the CLI tails the event stream until the turn settles. What comes back is the
same run record a local chant run prod-watch would have written.
A quiet turn loses its connection after 60 seconds, which is fountain closing
an idle stream rather than the turn ending; the reader reconnects with
Last-Event-ID and picks up what it missed.
If the steward is already running something, the post is refused with the
conversation’s address rather than queued. That is the single-writer rule, and
it is the reason one conversation is the right place for an op to run: two runs
on one checkout would interleave. A cron fire that lands mid-turn gets the same
refusal, which is why a steward’s Ops schedule with overlap: "skip".
5. Read the conversation back
Section titled “5. Read the conversation back”npx chant run status prod-watch --on fountainnpx chant run log prod-watch --on fountainnpx chant run list --on fountainstatus reads the latest turn that ran this op, log every turn, and list
one row per declared Op, grouped by steward so twenty ops on one steward cost
one round trip.
Open the conversation in Managoat and you see the same history as prose: one command line per turn, with each step of a run rendered as a tool call. The whole plan appears before the first step finishes, because an Op’s declaration already says what the steps are.
6. Approve a gated apply
Section titled “6. Approve a gated apply”The example adds a third op, steward-apply, with an approval gate before the
apply.
npx chant run steward-apply --on fountainThe turn builds, reaches the gate, and ends with the approve line in its reply. Nothing is blocked, because the gate is a fact on chant’s ledger branch rather than a wait held open inside the sandbox. No machine is pinned by a decision nobody has made yet.
Record the resolution, then wake the conversation:
npx chant approve steward-apply approve-steward-apply --approver younpx chant run approve steward-apply approve-steward-apply --on fountainThe second command posts chant run steward-apply back into the steward’s
conversation, carrying --approver and --url so the conversation names who
resolved the gate, and --on local so the sandbox runs it rather than posting it again. The sandbox re-runs the op, reads the resolution off the ledger, and
walks through the gate.
7. Put the cadences on a clock
Section titled “7. Put the cadences on a clock”Managoat has no scheduler. prod-watch and prod-converge carry their crons,
and something outside Managoat fires them by running the same command you ran
in step 4:
- a CI schedule, such as a GitHub Actions
schedule:trigger whose job runsnpx chant run prod-watch --on fountain; - a Kubernetes CronJob running that command line.
chant operator --steward runs a steward’s Ops on
their crons as well, but only in an environment whose form is local. Where the
form is fountain it refuses to start, because the steward’s conversation is
the one writer there.
Where the facts live
Section titled “Where the facts live”Two stores, each authoritative for one thing.
| Fact | Store |
|---|---|
| A run: its turn, steps, output, and how it ended | fountain’s database, as the conversation |
| A gate’s resolution, and anything that outlives a run | chant’s ledger branch in git |
| The declared shape of the estate | the repo |
Nothing is written to both.
Coming from Teammate, Schedule and Webhook
Section titled “Coming from Teammate, Schedule and Webhook”Earlier releases of the lexicon built the steward on fountain’s team: the
Steward composite also returned a Teammate seat, one Schedule per cadence
and an optional Webhook, and its Agent ran with sandbox_mode: persistent.
Managoat removed the team and its schedules, webhooks and persistent sandboxes,
and their routes answer 404 or 422.
managoat/managoat#2793
tracks bringing chant along.
Teammate, Schedule and Webhook are still exported, deprecated, so a
project that declares one builds far enough for FTN025 to fail it with the
replacement. The serializer leaves them out of the manifest, and chant import
skips them in an old manifest.
| Before | Now |
|---|---|
A Teammate seat, and its fountain:team thread | One conversation per steward, opened and resumed by channel_id chant:steward:<name>. chant run --on fountain does it for you |
A Schedule per Op cadence | The cron stays on the Op. A CI schedule or a CronJob runs chant run <op> --on fountain on it, or chant operator --steward where the form is local |
A Webhook | Nothing. Read a run’s outcome from chant run status or chant run log, or from the conversation’s event stream |
sandbox_mode: "persistent" on an Agent | Drop it. The steward’s conversation keeps its sandbox’s disk across park and wake. FTN025 reports the old value |
sandbox_provider: "daytona" or "runner" | "sprites" or "e2b", or leave it unset |
fountain.profiles.<name>.team | fountain.profiles.<name>.steward. team still works, and warns |
const { agent, teammate, schedules, webhook } = Steward(...) | const { agent, declaration } = Steward(...) |
| FTN020, FTN021, FTN022 | Removed with the kinds they checked |
- The Steward for the composite’s defaults and refusals
- Running an Op on fountain for profiles and the steward lookup
- chant acp for the protocol the sandbox speaks