@beyland/piper
v0.2.0
Published
A meta-harness for designing durable loops that prompt coding agents
Maintainers
Readme
Piper
Piper is a meta-harness for designing loops that prompt coding agents.
It does not replace Pi, Claude, Codex, Copilot, Cursor, or other coding agent harnesses. It coordinates them through explicit roles, prompts, artifacts, evaluators, policies, retries, feedback, and stopping conditions.
Install
pnpm add -D @beyland/piper
npm install --save-dev @beyland/piperThe package provides:
- A TypeScript SDK for defining reusable agent loops
- A
piperCLI for running, previewing, generating, and inspecting loop files
At a glance
Piper aims to give coding-agent work the structure that meta-frameworks give web applications.
In the same way Svelte provides reactive rendering primitives and SvelteKit adds the application harness around those primitives (routing, data loading, mutation flows, deployment assumptions, and conventions for how work moves through the system), Piper plays a similar role for coding agents by providing orchestration, artifacts, quality checks, retries, and handoffs.
In a filename.piper.ts file, you can create declarative, structured, reproducible loops
that define agent based workflows.
import { agent, artifact, evaluate, loop, repeat, step } from "@beyland/piper";
const plan = artifact("plan", "plan");
export default loop(
{
objective: "Implement OAuth login safely",
agents: [
agent("planner", { harness: "copilotcli" }),
agent("implementer", { harness: "copilotcli" }),
],
},
step({
role: "planner",
goal: "Create an OAuth implementation plan",
produces: plan,
}),
repeat(
{ maxAttempts: 3, until: ["pnpm test"] },
step({
role: "implementer",
goal: "Implement or revise the OAuth change",
context: [plan],
}),
evaluate({
name: "tests pass",
using: "pnpm test",
feedback: "Tests failed; revise the implementation using the failure output.",
}),
),
);Artifacts persist by default to ~/.piper/runs/<run-id>/artifacts.json. Each run records artifacts, feedback, events, and a summary.
Use artifact objects in context rather than raw strings when a value should come from a prior step. Plain strings in context are treated as literal context, while produces accepts either an artifact object or an artifact name.
const plan = artifact("plan", "plan");
step({ goal: "Create a plan", harness: "copilotcli", produces: plan });
step({ goal: "Implement the plan", harness: "copilotcli", context: [plan] });Core primitives
loop— top-level objective, agents, child nodes, and stop conditionsagent— named role with harness/model preferences, capabilities, instructions, and constraintsartifact— named durable output passed between steps; the optional type label is descriptive metadatastep— one role-bound agent action with context, expected output, and validationsevaluate— command, predicate, or runtime quality checkrepeat— explicit iteration until checks pass or attempts are exhaustedparallel— concurrent investigation or decomposition branches; usefailureMode: "collect"to wait for all branches before reporting failurespolicy— guardrails for constraints and protected filesruntimeValue— structured runtime data; step context resolution is reported before the harness starts so expensive runtime values are visible and cancellable
Composition with TypeScript functions
Piper supports two levels of composition:
- Core primitives such as
step,parallel,repeat,policy, andevaluate - Functions that package reusable orchestration patterns without hiding control flow
Teams define reusable loop pieces as normal TypeScript functions. See
examples/composition-functions.piper.ts for a larger example.
import { artifact, evaluate, loop, repeat, step, type LoopTree } from "@beyland/piper";
const plan = artifact("plan", "plan");
function sharedPlan({ goal, children }: { goal: string; children: LoopTree }) {
return loop(
step({
role: "planner",
goal,
produces: plan,
}),
children,
);
}
function repairUntilTestsPass({ command, children }: { command: string; children: LoopTree }) {
return repeat(
{ maxAttempts: 3, until: [command] },
children,
evaluate({
name: "tests pass",
using: command,
feedback: "Revise only the changes from this loop.",
}),
);
}
export default sharedPlan({
goal: "Plan the checkout reliability change",
children: repairUntilTestsPass({
command: "pnpm test -- checkout",
children: step({
role: "implementer",
goal: "Implement the planned checkout change",
context: [plan],
}),
}),
});Composition functions remain transparent: they return the same loop tree as core primitives, preserve artifacts and policies, and can be previewed by the CLI.
Runtime values can be created with either a compact resolver form or an object form when dependencies and descriptions improve readability.
runtimeValue({
description: "read package metadata",
dependencies: [],
resolve: async ({ workspacePath }) => `${workspacePath}/package.json`,
});Run with the CLI
piper examples/simple-loop.piper.ts --workspace .
piper examples/simple-loop.piper.ts --dry-run
piper examples/simple-loop.piper.ts --print-compiledGenerate an inspectable loop file from a prompt:
piper "Plan and implement a small bug fix" --workspace . --output generated.piper.ts
piper "Draft a release train loop" --save-only
piper "Fix failing tests" --dry-run-generated
piper "Fix failing tests" --executeUse --harness <name> to choose the authoring harness. It defaults to copilotcli.
AHP providers
Piper executes agent work through Agent Host Protocol (AHP). Harness names in loop files are AHP provider names, and the CLI registers one provider by default: copilotcli.
pnpm exec piper examples/simple-loop.piper.ts --workspace .
PIPER_AHP_PROVIDER=copilotcli pnpm exec piper examples/simple-loop.piper.ts --workspace .Use PIPER_AHP_ADDRESS to connect to an existing Agent Host, PIPER_AHP_CODE_COMMAND to choose the VS Code command used for auto-start, and PIPER_AHP_AUTO_START=0 to require an explicit Agent Host address.
SDK usage
import { AhpHarness, PiperOrchestrator, loop, step } from "@beyland/piper";
const orchestrator = new PiperOrchestrator({
workspacePath: process.cwd(),
harnesses: [new AhpHarness({ provider: "copilotcli" })],
});
const summary = await orchestrator.execute(
loop(
{ objective: "Run an AHP-backed loop" },
step({ goal: "Produce a result", harness: "copilotcli", produces: "result" }),
),
);
console.log(summary.artifacts.result);SDK hooks expose run events, step progress, retries, feedback, and summaries so Piper can integrate into local tooling, CI, or product surfaces.
Guardrails
Policies pass constraints and protected files to harnesses and enforce protected-file checks after step attempts.
policy(
{
name: "safe migration boundary",
protectedFiles: ["infra/production.tf"],
constraints: ["do not deploy or rotate secrets"],
},
step({ goal: "Prepare migration plan", harness: "copilotcli" }),
);For architecture details, read ARCHITECTURE.md.
