Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions kits/bufi-on-shrooms/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Circle CLI chooses the active Circle session; no private BUFI API is required.
CIRCLE_CHAIN=BASE

# Optional label for local demos.
BUFI_WORKSPACE_NAME="Example Agentic Workspace"
45 changes: 45 additions & 0 deletions kits/bufi-on-shrooms/GATEMAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## Gateman Verification Report

**Feature:** BU-217 public Circle starter-kit contribution
**Branch:** `codex/bu-217-bufi-on-shrooms`
**Upstream PR:** https://github.com/circlefin/agent-stack-starter-kits/pull/4
**Date:** 2026-07-10
**Verifier:** Codex

### Score

| Category | Score | Note |
| --- | ---: | --- |
| Error handling | 9 | Tool errors return model-readable results; bounded traces avoid becoming a failure path. |
| Logging | 9 | Consumer logger hook supports BUFI branding without forking Circle tool behavior. |
| Type safety | 9 | Vercel and BUFI kits typecheck and build against the public workspace packages. |
| Testability | 9 | The complete 17-tool roster, approval descriptions, and bounded redacted traces have contracts. |
| Performance | 9 | Trace storage is capped at 200 metadata-only events and does not retain model/tool payloads. |
| Security | 9 | Paid tools keep explicit in-tool approval; no private BUFI APIs, credentials, or customer data ship. |
| AI verification | 9 | Tool roster was composed without execution and all package checks were rerun after refactoring. |

### Checks Passed

- [x] Apache-2.0 Circle headers are preserved on source files.
- [x] `bufi-on-shrooms` composes public `circle-tools`, `agent-cli`, and Vercel AI kit exports.
- [x] All 17 requested tools are present, including `call_free_service` parity.
- [x] x402 payment and Gateway deposit retain a fresh human approval prompt.
- [x] Step hooks expose bounded metadata-only traces; prompts, text, payloads, and credentials are excluded.
- [x] BUFI branding is theme-only and reusable consumer logging is upstream-generic.
- [x] Typecheck passed for both Vercel and BUFI kits.
- [x] Three contract tests passed.
- [x] BUFI kit build and whitespace checks passed.
- [x] Public fork created and upstream PR opened; required StepSecurity check passed.

### Checks Not Run

- [ ] No live Circle login, wallet creation, funding, deployment, deposit, payment, or signing was run. These are intentionally excluded from unattended certification.
- [ ] Upstream maintainer review/merge remains external to BUFI.

### Risk Level

LOW for the public composition changes; HIGH-risk paid actions remain gated and were not executed.

### Sign-off

Safe to ship: **YES_WITH_FOLLOWUPS** — upstream review is the only external follow-up.
47 changes: 47 additions & 0 deletions kits/bufi-on-shrooms/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# BUFI on Shrooms Kit - Circle Agent Stack

This kit is an upstreamable reference for a BUFI-style agent workspace built on the public Circle Agent Stack primitives.

It deliberately stays generic:

- `packages/circle-tools` owns Circle CLI wrappers for wallets, balances, service discovery, and x402 payments.
- `packages/agent-cli` owns the reusable pinned-input terminal UI.
- This kit owns the workspace framing, branded console output, and the shape of a developer-facing operation surface.
- It does not call BUFI private APIs, ship customer data, or store secrets.

## Run

```bash
bun install
cp kits/bufi-on-shrooms/.env.example kits/bufi-on-shrooms/.env
bun run --cwd kits/bufi-on-shrooms demo
```

The kit composes the complete Vercel AI Circle roster: authentication, setup/sub-skills, wallet create/list/balance/deploy/fund, fiat funding links, service search/inspect/fetch, custom free-service calls, Gateway balance/deposit, and x402 payment. The two paid tools retain the Vercel kit's in-tool human approval boundary; declining returns a denial result before any payment or deposit call.

The terminal emits bounded metadata-only step traces (sequence, finish reason, tool-call count, and text length). It deliberately does not copy prompts, model text, credentials, or tool payloads into the trace buffer. A browser product can replace this emitter with its own durable trace sink.

## Why this exists

BUFI's product surface is a browser and mobile agent workspace, but the public contribution should remain useful to Circle developers without BUFI infrastructure. This kit shows the reusable boundary:

1. Circle wallet and x402 tools stay framework-agnostic.
2. The terminal console is a developer surface, similar to Stripe CLI, not a raw log stream.
3. Agent workspace traces and workflow state are readable as first-class, redacted events.
4. Branding is theme-level only; it does not leak private APIs or tenant assumptions.

## Upstream PR boundary

Safe upstream candidates:

- A branded-kit example that composes `circle-tools` and `agent-cli`.
- Vercel AI SDK exports for composing its complete tool roster in other kits.
- A consumer logger hook for branded consoles.
- Framework-neutral step hooks for trace surfaces.
- A missing `call_free_service` parity tool for GET-query and POST-body free endpoints.

BUFI-private follow-up outside this repo:

- Desk-v1 workflow graph, trace drawer, and approval queue.
- BUFI wallet provisioning policy.
- Tenant-specific knowledge graph, ERP, and MCP integrations.
29 changes: 29 additions & 0 deletions kits/bufi-on-shrooms/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@agent-stack-ecosystem-kits/kit-bufi-on-shrooms",
"version": "0.0.0",
"private": true,
"description": "BUFI-style agent workspace example using Circle Agent Stack, x402, and a branded developer console.",
"type": "module",
"engines": {
"node": ">=20"
},
"scripts": {
"demo": "bun run src/index.ts",
"test": "bun test src",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.json",
"clean": "rm -rf dist *.tsbuildinfo"
},
"dependencies": {
"@agent-stack-ecosystem-kits/agent-cli": "workspace:*",
"@agent-stack-ecosystem-kits/circle-tools": "workspace:*",
"@agent-stack-ecosystem-kits/kit-vercel-ai": "workspace:*",
"ai": "^4.3.0",
"dotenv": "^17.4.2"
},
"devDependencies": {
"@types/bun": "latest",
"@types/node": "^20.14.0",
"typescript": "^5.5.0"
}
}
74 changes: 74 additions & 0 deletions kits/bufi-on-shrooms/src/contracts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright 2026 Circle Internet Group, Inc. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { buildTools } from "@agent-stack-ecosystem-kits/kit-vercel-ai/tools";
import { describe, expect, test } from "bun:test";

import { createTraceCollector } from "./trace";

const expectedTools = [
"circle_login",
"circle_logout",
"fetch_setup_skill",
"fetch_sub_skill",
"circle_list_wallets",
"circle_create_wallet",
"circle_get_balance",
"circle_deploy_wallet",
"circle_wallet_fund",
"circle_fund_fiat",
"circle_search_services",
"circle_inspect_service",
"fetch_service",
"call_free_service",
"circle_get_gateway_balance",
"circle_pay_service",
"circle_gateway_deposit",
];

describe("BUFI on Shrooms contracts", () => {
test("composes the complete Vercel AI Circle roster", () => {
const tools = buildTools(async () => "n");
expect(Object.keys(tools)).toEqual(expectedTools);
});

test("keeps approval language on both paid tools", () => {
const tools = buildTools(async () => "n");
expect(tools.circle_pay_service.description?.toLowerCase()).toContain(
"approval",
);
expect(tools.circle_gateway_deposit.description?.toLowerCase()).toContain(
"spends usdc",
);
});

test("stores bounded metadata-only step traces", () => {
const lines: string[] = [];
const trace = createTraceCollector((line) => lines.push(line), 2);
trace.record({
step: 1,
finishReason: "tool-calls",
toolCallCount: 1,
text: "secret one",
});
trace.record({
step: 2,
finishReason: "tool-calls",
toolCallCount: 2,
text: "secret two",
});
trace.record({
step: 3,
finishReason: "stop",
toolCallCount: 0,
text: "secret three",
});
const snapshot = trace.snapshot();
expect(snapshot).toHaveLength(2);
expect(snapshot[0]?.sequence).toBe(2);
expect(JSON.stringify(snapshot)).not.toContain("secret");
expect(lines.at(-1)).toContain("tools=0");
});
});
124 changes: 124 additions & 0 deletions kits/bufi-on-shrooms/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Copyright 2026 Circle Internet Group, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/

import "dotenv/config";

import { createChatUi } from "@agent-stack-ecosystem-kits/agent-cli";
import {
ensureSession,
formatUsdcBalance,
walletUsdcBalance,
} from "@agent-stack-ecosystem-kits/circle-tools";
import { runTurn } from "@agent-stack-ecosystem-kits/kit-vercel-ai/agent";
import { loadConfig } from "@agent-stack-ecosystem-kits/kit-vercel-ai/config";
import { SETUP_SKILL_URL } from "@agent-stack-ecosystem-kits/kit-vercel-ai/skill";
import { buildTools } from "@agent-stack-ecosystem-kits/kit-vercel-ai/tools";
import type { CoreMessage } from "ai";

import { bufiLine, dim, heading, red, toolLine, yellow } from "./theme";
import { createTraceCollector } from "./trace";

async function main(): Promise<void> {
const workspace = process.env.BUFI_WORKSPACE_NAME ?? "Agentic Workspace";
const ui = createChatUi({ title: heading(`BUFI on Shrooms — ${workspace}`) });
const trace = createTraceCollector((event) => ui.log(bufiLine(event)));
const ask = async (question: string): Promise<string> => {
const answer = await ui.ask(question);
if (answer.trim().toLowerCase() === "exit") {
ui.close();
process.exit(0);
}
return answer;
};
const refreshBalance = async () => {
try {
const summary = await walletUsdcBalance();
ui.setBalance(summary ? formatUsdcBalance(summary) : null);
} catch {
// A read-only balance refresh never breaks the agent session.
}
};

try {
const config = loadConfig();
ui.log(
bufiLine(
`workspace=${workspace} provider=${config.provider} model=${config.model}`,
),
);
ui.log(
bufiLine(dim("all paid tools retain an in-tool human approval prompt")),
);
await ensureSession({
ask,
log: (line) => ui.log(bufiLine(line)),
bold: heading,
});
await refreshBalance();

const tools = buildTools(ask, { log: (line) => ui.log(toolLine(line)) });
ui.log(
bufiLine(`circle tool roster ready (${Object.keys(tools).length} tools)`),
);
let messages: CoreMessage[] = [
{
role: "user",
content:
`Fetch ${SETUP_SKILL_URL} with fetch_setup_skill and follow it. ` +
"Use tools for actions, explain each result, and ask before every paid action.",
},
];

const run = async () => {
ui.setStatus("working…");
const result = await runTurn(config, messages, tools, {
onStep: (event) => {
trace.record(event);
},
});
ui.setStatus(null);
await refreshBalance();
messages = [...messages, ...result.responseMessages];
};

await run();
ui.log(
bufiLine(
'bootstrap complete — continue the workspace session or type "exit"',
),
);
while (true) {
const input = (await ask("> ")).trim();
if (!input) continue;
if (input.toLowerCase() === "quit") break;
messages.push({ role: "user", content: input });
await run();
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ui.log(bufiLine(red(`fatal: ${message}`)));
ui.log(
bufiLine(yellow("No paid action runs without a fresh human approval.")),
);
process.exitCode = 1;
} finally {
ui.close();
}
}

void main();
Loading