83 lines
3.8 KiB
TypeScript
83 lines
3.8 KiB
TypeScript
/**
|
|
* 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;
|
|
});
|