Skip to content

beyland-dev/piper

Repository files navigation

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/piper

The package provides:

  1. A TypeScript SDK for defining reusable agent loops
  2. A piper CLI 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 conditions
  • agent — named role with harness/model preferences, capabilities, instructions, and constraints
  • artifact — named durable output passed between steps; the optional type label is descriptive metadata
  • step — one role-bound agent action with context, expected output, and validations
  • evaluate — command, predicate, or runtime quality check
  • repeat — explicit iteration until checks pass or attempts are exhausted
  • parallel — concurrent investigation or decomposition branches; use failureMode: "collect" to wait for all branches before reporting failures
  • policy — guardrails for constraints and protected files
  • runtimeValue — 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:

  1. Core primitives such as step, parallel, repeat, policy, and evaluate
  2. 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-compiled

Generate 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" --execute

Use --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.

About

A meta-harness for coding agents.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Contributors