/** * 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): Promise { 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 { // --- 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; });