Managoat is the hosted Fountain. Fountain is the open-source engine, and its name is on the CLI, the API, the SDK and this manual. Everything here applies to Managoat unless a page says it is for a self-hosted server.

A guided tour: an agent that opens a pull request

At the end of this page you have an agent that clones your repository, changes it, and opens a pull request. You can then ask for a revision. The revision lands on the same PR seconds later, because the sandbox it worked in still runs.

The tour is about forty lines. Each number and each output below came from a real run.

What you must have

  • A Fountain API key (Account → API keys, or fountain auth login).
  • A repository that you let an agent push a branch to.
  • A GitHub token that can push to it. Use a fine-grained token scoped to that one repository. The agent gets a true credential, so give it the smallest one that works.
npm install @agentshit/fountain-sdk
import { Fountain } from "@agentshit/fountain-sdk";
const fountain = new Fountain(); // FOUNTAIN_API_KEY
const REPO = "https://github.com/you/your-app";

1. The machine

An environment describes the machine the agent gets. You describe it once and use it again. Here it is a checkout of your repository.

const environment = await fountain.environments.create({
name: "tour-workspace",
repositories: [
{
url: REPO,
mount_path: "/work/app",
secret_key: "GITHUB_TOKEN", // ← see the warning below
},
],
setup_script: "cd /work/app && npm install",
});

A private repository needs secret_key

Set secret_key on a private repository. It names the secret that the clone authenticates with. Omit it and the clone fails in the sandbox. The conversation still starts, your agent opens on an empty directory, and it tells you that it cannot find the repo. This page cost one wasted run to exactly that. A public repository does not need the key.

2. The credential

A vault is a bag of secrets that you choose for one run. Fountain decrypts the values into the sandbox when the sandbox spawns. The values never enter the prompt, the model's context, or the log feed.

const vault = await fountain.vaults.create({ name: "tour-github" });
await fountain.vaults.secrets.set("tour-github", "GITHUB_TOKEN", process.env.GITHUB_TOKEN!);
await fountain.vaults.secrets.set("tour-github", "GH_TOKEN", process.env.GITHUB_TOKEN!);

Two keys, because two things need the token. The clone reads GITHUB_TOKEN, which is the secret_key above. The gh command reads GH_TOKEN when the agent opens the PR.

You cannot read either one back. secrets.list() returns keys and nothing else. If the agent prints the token, Fountain redacts it from the transcript before it stores it.

3. The agent

The system prompt says how you want the work done. Name the repository path, and say what "done" means.

const agent = await fountain.agents.create({
name: "tour-contributor",
runtime: "claude",
model: "anthropic/claude-sonnet-5",
description: "Opens small pull requests",
system:
"You work in /work/app, a git repository. Make the smallest change that " +
"satisfies the request, commit on a new branch, push, and open a pull " +
"request with `gh`. Report the PR url and nothing else.",
environment_id: environment.id,
allowed_vault_ids: [vault.id], // this agent can attach that vault, and no other
});

4. The run

const run = fountain.run(
"Add a --version flag that prints 0.1.0 and exits. " +
"Update the README. Then open a pull request.",
{ agent: "tour-contributor", vault: "tour-github" },
);
for await (const event of run) {
if (event.type === "tool") console.log("·", event.name);
}
const result = await run;
console.log(result.text);
· Terminal
· Read File
· Read File
· Edit
· Edit
· Terminal
· Terminal
· Terminal
· Terminal
· Terminal
https://github.com/you/your-app/pull/1

Forty-three seconds. Most of that time went to the sandbox. The pull request is a real one. It has a --version branch, two files changed, and ten lines added.

To watch nothing, drop the loop. await fountain.run(...) gives you the same result. To wait for nothing, do not await it at all. Hand run.conversationId to whatever polls later.

5. The follow-up, which is the point

Ask for a revision.

const revision = await fountain
.resume(result.conversationId)
.send("Also accept -v as an alias for --version, and push it to the same PR.");
turn 2 | done | +13s
https://github.com/you/your-app/pull/1

Thirteen seconds, and the same PR. Fountain cloned nothing again. You explained nothing again. No second branch appeared.

The sandbox is still up. The checkout sits on the branch that the first turn made, and gh still holds its authentication. The agent's session still holds what it did and why. That is the difference between an agent that has a machine and an agent that has a context window.

6. Clean up

await fountain.resume(result.conversationId).terminate(); // the sandbox
await fountain.agents.delete("tour-contributor");
await fountain.vaults.delete("tour-github"); // the credential first, in real code
await fountain.environments.delete("tour-workspace");

In a script that can fail, delete the vault in a finally. A credential must not outlive the run that needed it.

What you built

Piece What it decided
Environment Which repository, the mount path, the setup script.
Vault Which credential the sandbox gets, and not the prompt.
Agent The runtime, the model, the system prompt.
Conversation This run, and what you can still ask it.

Change one of them and the job changes. The other three stay as they are. A read-only token turns the same agent into one that can only report. A second environment points it at a different repository.

The whole thing, in one script

Here is everything above in one file that you can copy and run. It is not a transcription of the steps. The file is sdk/typescript/examples/pull-request.ts. This page holds it word for word, and that one file produced each output above.

/**
* The guided tour, complete and runnable: an agent that clones your repository,
* opens a pull request, and then amends the same PR on a follow-up turn.
*
* FOUNTAIN_API_KEY=... # Account → API keys, or `fountain auth login`
* GITHUB_TOKEN=... # fine-grained, scoped to the one repository
* REPO_URL=https://github.com/you/your-app
*
* node pull-request.ts
*
* Everything it creates — environment, vault, agent, sandbox — is deleted on
* the way out, including when a step throws.
*
* Walkthrough: https://managoat.com/docs/tour
*
* (Inside this repository the import below resolves to the SDK itself, so run
* `npm run build` in sdk/typescript first. Installed from npm it just works.)
*/
import { Fountain } from "@agentshit/fountain-sdk";
const repo = process.env.REPO_URL;
const token = process.env.GITHUB_TOKEN;
if (!repo || !token) {
console.error("set REPO_URL and GITHUB_TOKEN");
process.exit(1);
}
const fountain = new Fountain();
const names = { environment: "tour-workspace", vault: "tour-github", agent: "tour-contributor" };
let conversationId: string | null = null;
try {
// ── 1. the computer ──────────────────────────────────────────────────────
// `secret_key` names the secret the clone authenticates with. Leave it out on
// a private repository and the clone fails *inside the sandbox*: the agent
// starts anyway, on an empty directory.
const environment = await fountain.environments.create({
name: names.environment,
repositories: [{ url: repo, mount_path: "/work/app", secret_key: "GITHUB_TOKEN" }],
});
// ── 2. the credential ────────────────────────────────────────────────────
// Decrypted into the sandbox at spawn: never in the prompt, never in the
// model's context, and redacted out of the transcript if the agent prints it.
const vault = await fountain.vaults.create({ name: names.vault });
await fountain.vaults.secrets.set(names.vault, "GITHUB_TOKEN", token); // the clone reads this
await fountain.vaults.secrets.set(names.vault, "GH_TOKEN", token); // `gh` reads this
// ── 3. the agent ─────────────────────────────────────────────────────────
await fountain.agents.create({
name: names.agent,
runtime: "claude",
model: "anthropic/claude-sonnet-5",
description: "Opens small pull requests",
system:
"You work in /work/app, a git repository. Make the smallest change that " +
"satisfies the request, commit on a new branch, push, and open a pull request " +
"with `gh`. Report the PR url and nothing else.",
environment_id: environment.id,
allowed_vault_ids: [vault.id], // this agent may attach that vault, and no other
});
// ── 4. the run ───────────────────────────────────────────────────────────
const run = fountain.run(
"Add a --version flag that prints 0.1.0 and exits. " +
"Update the README. Then open a pull request.",
{ agent: names.agent, vault: names.vault, timeoutMs: 15 * 60_000 },
);
for await (const event of run) {
if (event.type === "tool") console.log("·", event.name);
}
const result = await run;
conversationId = result.conversationId;
console.log(`\n${result.text}\n`);
// ── 5. the follow-up, which is the point ─────────────────────────────────
// The sandbox is still up, the checkout is on the branch the first turn made,
// and `gh` is still authenticated. No re-clone, no re-explaining.
const revision = await fountain
.resume(result.conversationId)
.send("Also accept -v as an alias for --version, and push it to the same PR.");
console.log(`turn ${revision.turnNumber}: ${revision.text}`);
} finally {
// ── 6. clean up ──────────────────────────────────────────────────────────
// The credential goes first, whatever happened above.
await fountain.vaults.delete(names.vault).catch(() => {});
if (conversationId) await fountain.resume(conversationId).terminate().catch(() => {});
await fountain.agents.delete(names.agent).catch(() => {});
await fountain.environments.delete(names.environment).catch(() => {});
console.log("cleaned up");
}
FOUNTAIN_API_KEY= GITHUB_TOKEN= REPO_URL=https://github.com/you/your-app \
node pull-request.ts

How to make it yours

  • Run it from CI. The same forty lines work in an action. The agent needs no checkout on the runner, because the checkout is in the sandbox.
  • Make it a teammate. fountain.team.add("tour-contributor") gives it a durable thread and a cron routine. "Each Monday, open a PR that bumps the dependencies" is then one team.schedules.create call. Read the SDK page.
  • Fan it out. Call fountain.run() once for each repository, and await them together. Each call gets its own sandbox.
  • Narrow the allowlist. allowed_vault_ids names the vaults this agent can attach. A vault outside that list stays out of reach.