docs(blog): local agents on AMD GPUs, with runnable examples (#14)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user