docs(blog): local agents on AMD GPUs, with runnable examples (#14)

This commit is contained in:
Zhang Jason
2026-07-21 20:20:51 +08:00
committed by GitHub
parent 0e134e9c6c
commit d5c2bc001d
10 changed files with 1095 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
<!-- English | [简体中文](README.zh.md) -->
# Example: an Agent that builds another Agent (local, on an AMD GPU via Ollama)
This example is the **"Harness for Building Agents"** pillar in runnable code. Using only the
PenguinHarness SDK, it:
1. **builds** a brand-new agent (`commit-helper`) by driving `default_agent` with the
`agent-creation` skill from a plain-language requirement, then
2. **runs** that freshly-created agent to prove the generated `AGENTS.md` actually shapes its
behavior (it writes a Conventional Commits message).
Everything runs on a **local open-weight model** — `qwen3.6:35b` served by Ollama — so no cloud
API and no data leaving the machine. Ollama's ROCm backend runs this natively on AMD GPUs
(from Radeon PRO workstation cards up to Instinct accelerators).
## 1. Serve the model locally with Ollama
```bash
# Ollama detects an AMD GPU (ROCm) automatically; pin a specific card if you like:
export HIP_VISIBLE_DEVICES=0
ollama serve & # if not already running as a service
ollama pull qwen3.6:35b
```
## 2. Point PenguinHarness at it (once)
```bash
penguin config model add \
--model-id qwen3.6:35b \
--provider custom --client-type openai \
--base-url http://localhost:11434/v1 \
--api-key ollama --set-default
```
This writes the model into `~/.penguin/data/default_project/.project_config.toml`. The example
uses the project's default model, so no model id is hard-coded in the script.
## 3. Run the example
From the repo root (build the workspace first so `@prismshadow/penguin-core` resolves to its
`dist/`):
```bash
pnpm install
pnpm build
pnpm --dir examples/build-agent-with-agent start
# or directly: npx tsx examples/build-agent-with-agent/build-agent.ts
```
## What you should see
- **Phase 1** — `default_agent` scaffolds `agents/commit-helper/` under the project: its
directory layout, a copied `system_config.yaml` (with name + description), and an `AGENTS.md`
encoding the Conventional Commits rules.
- **Phase 2** — the new `commit-helper` agent, following only that generated `AGENTS.md`,
produces something like:
```text
fix(payment): add retry-with-backoff for transient gateway 503 errors
Transient 503 responses from the payment gateway were causing checkout
failures during peak traffic. Retry with exponential backoff gives the
gateway time to recover, preventing spurious user-facing errors.
```
## Notes
- Output quality tracks the model. `qwen3.6:35b` handles this task well; much smaller models
may not follow the tool-calling protocol reliably.
- A capable model occasionally introduces a mechanical slip when re-serializing the base config
(e.g. an invalid YAML escape) — every file is plain text and every run is traced, so such
slips are quick to spot and fix. This is the realistic shape of "agents building agents":
the requirement→`AGENTS.md` heavy lifting is automated; a human reviews the mechanical edges.
- Re-running Phase 1 will update the existing `commit-helper` agent in place.
@@ -0,0 +1,70 @@
<!-- [English](README.md) | 简体中文 -->
# 示例:用一个 Agent 构建另一个 Agent(本地,经 Ollama 跑在 AMD GPU 上)
这个示例是 **“构建 Agent 的 Harness”** 支柱的可运行版本。仅用 PenguinHarness SDK,它会:
1. **构建**一个全新的 Agent(`commit-helper`)——用 `agent-creation` skill 驱动 `default_agent`,
根据一句大白话需求把它搭建出来;然后
2. **运行**这个刚创建的 Agent,证明生成的 `AGENTS.md` 确实塑造了它的行为(它会写出一条
Conventional Commits 提交信息)。
全程跑在一个**本地开源权重模型**上——由 Ollama 提供的 `qwen3.6:35b`——所以不用任何云端 API、
数据也不离开本机。Ollama 的 ROCm 后端能原生在 AMD GPU 上运行它(从 Radeon PRO 工作站显卡一直到
Instinct 加速卡)。
## 1. 用 Ollama 在本地提供模型
```bash
# Ollama 会自动识别 AMD GPU(ROCm);也可以指定某张卡:
export HIP_VISIBLE_DEVICES=0
ollama serve & # 若尚未作为服务运行
ollama pull qwen3.6:35b
```
## 2. 把 PenguinHarness 指向它(一次即可)
```bash
penguin config model add \
--model-id qwen3.6:35b \
--provider custom --client-type openai \
--base-url http://localhost:11434/v1 \
--api-key ollama --set-default
```
这会把模型写进 `~/.penguin/data/default_project/.project_config.toml`。示例使用项目的默认模型,
所以脚本里没有硬编码任何 model id。
## 3. 运行示例
从仓库根目录(先构建 workspace,好让 `@prismshadow/penguin-core` 解析到它的 `dist/`):
```bash
pnpm install
pnpm build
pnpm --dir examples/build-agent-with-agent start
# 或直接运行: npx tsx examples/build-agent-with-agent/build-agent.ts
```
## 你应当看到什么
- **第一阶段** —— `default_agent` 在项目下搭建出 `agents/commit-helper/`:它的目录布局、一份复制
来的 `system_config.yaml`(含 name + description),以及一份编码了 Conventional Commits 规则的
`AGENTS.md`。
- **第二阶段** —— 这个新的 `commit-helper` Agent,只依据那份生成的 `AGENTS.md`,产出类似这样的结果:
```text
fix(payment): add retry-with-backoff for transient gateway 503 errors
Transient 503 responses from the payment gateway were causing checkout
failures during peak traffic. Retry with exponential backoff gives the
gateway time to recover, preventing spurious user-facing errors.
```
## 说明
- 输出质量取决于模型。`qwen3.6:35b` 能很好地完成这个任务;更小的模型可能无法可靠地遵循工具调用协议。
- 有能力的模型偶尔会在重新序列化 base config 时引入一个机械性的小失误(例如非法的 YAML 转义)——
因为每个文件都是纯文本、每次运行都被追踪,这类失误很容易被发现并修复。这正是“用 Agent 造 Agent”
真实的样子:把需求变成 `AGENTS.md` 的重活被自动化了,而机械层面的毛刺由人类来审阅。
- 再次运行第一阶段会就地更新已存在的 `commit-helper` Agent。
@@ -0,0 +1,82 @@
/**
* Example: an Agent that builds another Agent — programmatically, via the SDK.
*
* This is the "Harness for Building Agents" pillar in code. It runs entirely on a local
* open-weight model (Ollama serving qwen3.6:35b) — see README.md for the one-time setup.
*
* Two phases, both driven purely through the SDK:
* 1. BUILD — drive `default_agent` with the `agent-creation` skill to scaffold a brand-new
* agent (`commit-helper`) from a plain-language requirement.
* 2. RUN — load the freshly-created agent and have it do its job (write a commit message),
* proving the generated AGENTS.md actually shapes its behavior.
*
* Run: pnpm --dir examples/build-agent-with-agent start
* or: npx tsx examples/build-agent-with-agent/build-agent.ts
*/
import {
createAgent,
userText,
isCompleteModelMessage,
type OmniMessage,
} from "@prismshadow/penguin-core";
/** Stream a Session run to stdout and collect the assistant's final text. */
async function runToStdout(run: AsyncGenerator<OmniMessage>): Promise<string> {
let finalText = "";
for await (const msg of run) {
if (isCompleteModelMessage(msg) && msg.payload.type === "text") {
finalText += msg.payload.text;
process.stdout.write(msg.payload.text + "\n");
}
}
return finalText;
}
const BUILD_REQUEST = `Use the agent-creation skill to create a brand-new agent in this project.
Requirement: an agent called "commit-helper" that writes high-quality git commit messages.
Given a diff or a description of changes, it must produce a Conventional Commits message: a
\`type(scope): subject\` header (type one of feat/fix/docs/refactor/test/chore), subject in
imperative mood and under 50 characters, then a blank line and a short body explaining the "why".
Do everything the agent-creation skill specifies: create the agent directory layout, copy the
base system_config.yaml, write a concise AGENTS.md capturing this requirement, and set the new
agent's name and description in system_config.yaml. Report the files you created when done.`;
const COMMIT_TASK = `Write a commit message for this change: I added retry-with-backoff logic to
the payment API client because transient 503s from the gateway were causing checkout failures.
Touched packages/core/src/payment/client.ts.`;
async function main(): Promise<void> {
// --- Phase 1: an Agent builds an Agent ------------------------------------------------
console.log(
"=== Phase 1: default_agent is building a new agent via the agent-creation skill ===\n",
);
const builder = await createAgent({ agentId: "default_agent" });
const buildSession = await builder.createSession({ workspaceDir: process.cwd() });
// approve: () => "allow" lets the builder run its shell tool calls unattended. In a real
// integration you would inspect each tool_call and decide — that is the whole point of the
// per-call approval callback.
await runToStdout(buildSession.run([userText(BUILD_REQUEST)], { approve: async () => "allow" }));
// --- Phase 2: run the agent that was just built ---------------------------------------
console.log("\n=== Phase 2: running the freshly-created commit-helper agent ===\n");
const helper = await createAgent({ agentId: "commit-helper" });
const helperSession = await helper.createSession({ workspaceDir: process.cwd() });
const commitMessage = await runToStdout(
helperSession.run([userText(COMMIT_TASK)], { approve: async () => "allow" }),
);
console.log("\n=== Done. commit-helper produced the message above. ===");
if (!commitMessage.trim()) {
console.error("(No text output — check that Ollama is running and the model is configured.)");
process.exitCode = 1;
}
}
main().catch((err) => {
console.error("Example failed:", err);
process.exitCode = 1;
});
@@ -0,0 +1,14 @@
{
"name": "@prismshadow/example-build-agent-with-agent",
"private": true,
"type": "module",
"scripts": {
"start": "tsx build-agent.ts"
},
"dependencies": {
"@prismshadow/penguin-core": "workspace:*"
},
"devDependencies": {
"tsx": "^4.20.0"
}
}