Personal Agent on Demand specs (agents, environments, vaults). Apply with: ./aod apply -f aod.yml
  • TypeScript 95%
  • JavaScript 4.6%
  • Makefile 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jake Gaylor 7a97397b39
Add home-cloud and jobban environments (#13)
Mined from a sweep of the local per-repo agent configs. The useful half of
that dump is which repos are worth dispatching remote work into; the system
prompts themselves aren't worth vendoring, because every one of them is a
CLAUDE.md that already lives in the repo being mounted — a sprite reads it at
session start the same way Claude Code does locally, and a mount never drifts
the way a copied prompt would. So this adds environments only, and the
existing specialists attach to them.

home-cloud is build-and-lint only. `make verify` (build → lint → check) is
pure synthesis and runs fine in a sandbox, but the ansible playbooks, kubectl,
and flux all reach the Mac hosts over Tailscale, which a sprite has no route
to. There's no root package.json — `make install` is what installs the pinned
@intentius/chant under chant/.

jobban needs Elixir 1.19 + Erlang/OTP 28, several majors ahead of Debian's
`elixir`, so the toolchain comes from mise at the versions the repo's own
Dockerfile pins. `mix test` wants a live Postgres on localhost as
postgres/postgres, which config/test.exs confirms, so the setup script starts
the server and sets that password.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 06:13:06 -04:00
scripts Validate GitHub tokens before writing them into fountain 2026-08-12 18:25:57 -04:00
src Add home-cloud and jobban environments (#13) 2026-08-18 06:13:06 -04:00
.gitignore Convert agent specs from raw fountain YAML to the chant fountain lexicon (#11) 2026-07-30 18:28:39 -04:00
.infisical.json Convert agent specs from raw fountain YAML to the chant fountain lexicon (#11) 2026-07-30 18:28:39 -04:00
chant.config.ts Convert agent specs from raw fountain YAML to the chant fountain lexicon (#11) 2026-07-30 18:28:39 -04:00
Makefile Convert agent specs from raw fountain YAML to the chant fountain lexicon (#11) 2026-07-30 18:28:39 -04:00
OPERATIONS.md homelab-builder: write-only Infisical identity on the shared agent-apps project (#16) 2026-08-18 06:12:28 -04:00
package-lock.json Swap back to fountainApply now that chant 0.34.1 fixes the round-trip 2026-08-01 00:03:45 -04:00
package.json Export every resource under its own fountain name 2026-08-09 00:40:33 -04:00
README.md Add show-curator: a show name in, an across-the-eras PR out (#14) 2026-08-17 02:29:42 -04:00
tsconfig.json Convert agent specs from raw fountain YAML to the chant fountain lexicon (#11) 2026-07-30 18:28:39 -04:00

agent-specs

Personal Fountain specs — agents, environments, vaults — kept independent of the Fountain codebase so the same manifest can be applied against any Fountain instance (local dev, hosted prod, a sprite I just spun up).

Declared as typed chant resources via the fountain lexicon (@intentius/chant-lexicon-fountain) rather than hand-written YAML — real import/export, editor autocompletion, and build-time lint (dangling environment references, open networking, secret-shaped literals, etc.) before anything reaches the API.

Layout

.
├── src/
│   ├── environments/      # one Environment per file
│   ├── vaults/            # one Vault per file
│   └── agents/
│       ├── teams/         # one folder per orchestrator (tech-lead, captain-picard, …)
│       └── specialists/   # one folder per discipline (engineering, growth, design, content, …)
├── chant.config.ts        # declares the fountain lexicon
├── scripts/apply.mjs      # applies dist/fountain.yaml via bulk POST /api/apply
└── .infisical.json        # binds this repo to the right Infisical project

chant build walks src/, type-checks and lints every declared Environment / Vault / Agent, and serializes them to dist/fountain.yaml (ejectable — fountain apply -f dist/fountain.yaml accepts it verbatim). scripts/apply.mjs resolves the manifest's infisical:// secret URIs from the environment and sends it through fountain's bulk POST /api/apply.

Install

npm install   # chant + the fountain lexicon

The fountain CLI itself is still needed for running conversations (fountain run, fountain conv ... — see below), not for applying this manifest:

make install   # downloads fountain-darwin-arm64 to ~/.local/bin/fountain

Or grab a binary directly:

curl -L -o ~/.local/bin/fountain \
  https://github.com/BinaryBourbon/fountain/releases/latest/download/fountain-darwin-arm64
chmod +x ~/.local/bin/fountain

Linux + amd64 variants are attached to the same release.

Authenticate

Two separate credentials, for two separate tools:

  • scripts/apply.mjs (reconciling this manifest) needs FOUNTAIN_TOKEN (and FOUNTAIN_ENDPOINT if not the hosted default) as environment variables — mint one via POST /api/auth/token or the account UI. Add FOUNTAIN_TOKEN to Infisical (env=dev) so make apply picks it up the same way it already does for GITHUB_TOKEN etc., or export it locally.

  • The fountain CLI (running conversations) uses its own login, unrelated to the above:

    fountain auth login              # prompts for base URL + API key, writes ~/.fountain/credentials
    fountain auth whoami             # confirm
    

    For multi-instance setups use named profiles: fountain auth login --profile <name>, then FOUNTAIN_PROFILE=<name> make run AGENT=... PROMPT=... (or pass --profile).

Apply

Secrets are pulled from Infisical (env=dev, root path) via the Infisical CLI. The .infisical.json in this repo binds it to the right project; sign in once and make apply resolves the rest.

infisical login   # one time
make apply        # → infisical run --env=dev -- npm run apply
                   #   (chant build --output dist/fountain.yaml --format json && node scripts/apply.mjs)

npm run apply is idempotent by name: create-if-new, update-by-name, and (opt-in, off by default) prune of chant-owned resources — every resource here carries the managed-by: chant metadata marker so ownership is unambiguous.

Prune deletes chant-owned resources the manifest no longer declares; hand-made resources have no ownership marker and are never touched. It's off by default so a partial build can't read as "delete everything I didn't emit":

PRUNE=1 make apply

Two layers of ${VAR} substitution

Same syntax, different scopes:

  1. apply-time — secrets: entries resolve from Infisical (infisical://...), local env, or --var flags. That's how this repo stays free of literal tokens.
  2. provision-time — every other ${VAR} (e.g. mcp_servers headers) resolves at sprite spawn from the environment's secrets ∪ the conversation's vault.

For secrets: you can also use other external resolvers if you have the relevant CLI installed:

  • op://<vault>/<item>/<field> — 1Password CLI
  • bws://<secret-uuid> — Bitwarden Secrets Manager CLI
  • infisical://<project?>/<env>/<path?>/<name> — Infisical CLI (currently in use)

Adding / editing a resource

Add or edit a .ts file in the matching src/ subdirectory:

import { Environment } from "@intentius/chant-lexicon-fountain";

const env = new Environment({
  name: "team-env",
  networking_type: "limited",
  networking_config: { allowed_hosts: ["github.com"] },
  metadata: { "managed-by": "chant" },
});

export { env as "team-env" };

The export name is the upsert key — always export under the resource's own name. chant derives each resource's logical name from the exporting identifier, and that logical name (not the name you pass to the constructor) is what fountainApply sends as the upsert key and what cross-resource references serialize to. Since a fountain name like team-env isn't a valid JS identifier, bind the constructor to a camelCase const and re-export it under the literal name; importers use the matching import { "team-env" as env } from "…".

Nothing upstream catches a mismatch — exporting as export const teamEnv = … type-checks, lints, and applies cleanly, it just creates a second resource named teamEnv alongside team-env instead of updating it. The whole estate diverged that way once, so scripts/check-names.mjs runs as part of npm run build and fails the build (and therefore the apply) if any resource's export name and declared name disagree.

The filename is just for humans. Re-running make apply reconciles in place. npx chant build alone (no --output) is a fast way to lint a change without applying it.

The lexicon's own reference (fields, lint rules, secrets model, locked-sandbox posture) lives at intentius.io/chant/lexicons/fountain; the underlying manifest format is documented in the Fountain repo's help pages.