packages/action
The Action
Register your machines as runners. Label an issue. Get a pull request.
Ralph is an agent orchestrator with a deliberately small surface. It does not decide how software gets built — a skill pack does that. Ralph owns the trigger, the workspace, the clock, and the reporting back to your tracker. Everything else is pluggable.
The path an issue takes
Two of these five stages are seams you can swap. The rest is plumbing Ralph owns.
Issue labeled ready-for-agent
Ralph defines no label of its own. It reuses the one your triage flow already produces, which also posts an Agent Brief — the structured comment Ralph treats as the contract.
A runner claims it
Any machine registered to the repo. One agent per issue, enforced by a concurrency group keyed on the issue number.
Install the pinned skill pack
seam · skills-repo · skills-refDefaults to mattpocock/skills at a pinned tag. Cloned, copied into the workspace, then excluded via .git/info/exclude so it never lands in the agent's diff. Point it at your own pack and the rest of the pipeline is unchanged.
Agent runs the entry skill
seam · agent · agent-argsThe AFK preamble is prepended, then the brief. The pack owns iteration, testing and the definition of done — Ralph only holds the wall clock.
Draft PR, back on the thread
ASSUMPTIONS.md lands in the PR body. Blocked runs, blown budgets and failed verification all ship as drafts that say why.
A line down the middle
Mechanism
Ralph owns
- The trigger, and which runner claims it
- Checkout, branch, and a clean workspace
- The wall clock, and what happens when it runs out
- Push, pull request, and status on the issue
- Never reporting green when nothing was produced
Judgment
The skill pack owns
- How the work gets planned and sliced
- Where tests go, and what a good one is
- How many iterations it takes
- When the work is done
- What counts as a review worth passing
Ships pointed at mattpocock/skills, pinned. Point skills-repo somewhere else and nothing on the left changes.
Why there is no loop in the bash
The entry skill already drives TDD at agreed seams, typechecks throughout, runs the full suite at the end, and reviews the diff before committing. That is the iterate-until-green loop. Reimplementing it in a shell script would produce a worse version that drifts from the pack every time it ships. So Ralph sets a budget and gets out of the way.
Running interactive skills unattended
This is the interesting problem. The pack's skills assume a human is in the room — one of them refuses to write a test at a seam you have not confirmed. On a runner there is nobody to ask, so a naive run either stalls or quietly invents an answer and never mentions it.
| Gate that expects a human | What replaces it |
|---|---|
| Agree the test seams/tdd | Derive them from the brief's acceptance criteria, and record the chosen seams before the first test |
| Pick a review fixed point/code-review | The base ref, no question asked |
| Locate the originating spec/code-review | The Agent Brief, reproduced in ASSUMPTIONS.md |
| Anything else needing confirmation/any skill | Make the call, then record it with one line of reasoning — recording is not optional |
ASSUMPTIONS.md then lands in the PR body, so review opens with every judgment call made without you. Ambiguity gets an assumption. A genuine block — a product decision, a missing credential — gets BLOCKED.md and a stop, which Ralph turns into a draft PR carrying the question.
Unattended is the default, not the only mode. With session: herdr, the agent works interactively in a session a person can join. Ralph posts the join command on the issue, and the session stays up after the pull request opens.
Defaults in the box, nothing welded shut
Ralph ships pointed at a real skill pack and a real agent, so the zero-config path works on day one. Neither is baked in. Both are inputs, and changing either is one line of YAML.
# what you get without configuring anything
skills-repo: mattpocock/skills
skills-ref: v1.1.0
agent: claudeThe pack is the bigger lever of the two. It decides how work gets planned, where tests go, and when something is done — so pointing skills-repo at your own pack changes how every agent behaves without changing a line of the orchestrator.
Your own skills, on top
You do not have to choose between the pack and skills of your own. Three sources reach the agent, and only the middle one is pinned.
Your repo
winsAnything committed under .claude/skills or .agents/skills. On a name collision the repo wins and the pack is skipped — a skill you committed is a deliberate override, so it is left alone.
The pack
pinnedCloned at the ref you named and copied in around whatever your repo already defines. It fills the gaps rather than replacing the set.
The runner
not pinnedAgents also read skills under $HOME on the machine, so whatever the person who set that runner up has installed is in scope. This is the layer Ralph cannot pin, and the first place to look when two runners disagree.
Agents
An adapter is one file implementing a three-part contract. Adding an agent never touches the orchestrator.
- claude
- codex
- opencode
- pi
AGENT_COMMITS=true|false # does the agent commit its own work?
agent_preflight() # exit non-zero with a fixable message if the
# CLI is missing or unauthenticated
agent_run <prompt_file> # run to completion against $PWD, non-interactive
# must forward "${AGENT_ARGS_ARR[@]}"pi is the one worth calling out. It already reads .agents/skills — the same path this action installs packs into — so a pack drops in with no special-casing at all. It is also multi-provider, which makes it the one adapter where bring-your-own-agent extends to bring-your-own-model.
Traps worth knowing about
Every one of these was caught by testing rather than reading. They share a shape: the failure exits zero.
pi silently runs with no skills
exits 0In non-interactive modes pi never prompts for trust — it falls back to defaultProjectTrust, which defaults to ask, and ask ignores project-local resources. An installed skill pack is a project-local resource. Without --approve you get a plausible pull request built from none of the workflow, and nothing in the log says so. The adapter always passes it.
An absent agent brief looked present
1 bytejq writes a trailing newline even for an empty result, so the brief file was never zero-length and the emptiness check always passed. Every unbriefed issue would have claimed a contract it did not have. The check now tests for non-whitespace content.
Passthrough flags vanished at the boundary
silently droppedtimeout(1) needs a command, so the adapter function is serialized into a subshell — and arrays are not exported. Model and provider flags were being dropped on exactly the path that runs in production. The array is now serialized alongside the function, verified identical on both sides.
macOS runners still ship bash 3.2
unbound variableExpanding an empty array under set -u aborts the run on bash 3.2, which is what /bin/bash still is on a current Mac. Every adapter guards the expansion. The same machines have no timeout(1) at all, so the budget falls back to the job-level timeout and says so in the log.
CI will not run on Ralph's pull requests
by designGitHub does not trigger pull_request workflows for pull requests opened with the default token, to prevent recursion. Left alone, the agent's work would be the only work arriving unchecked. Pass a PAT or App token to get CI back.
New repos will not let Actions open a pull request
after the push"Allow GitHub Actions to create and approve pull requests" is off by default, and pull-requests: write does not override it. The first real run did ten minutes of good work, pushed the branch, and died at gh pr create — the issue still said only "picked this up" and the drafted PR body was gone. The default token cannot read the setting, so preflight can usually only warn; a failed PR now leaves the branch, a compare link and the fix on the issue, and the body in the job summary.
What is in the package
- action.yml
- Composite manifest — 12 inputs, 3 outputs
- scripts/ralph.sh
- Intake, branch, prompt, run, assess, ship
- scripts/install-skills.sh
- Pinned pack install, kept out of the diff
- scripts/afk-preamble.md
- Converts interactive gates into recorded decisions
- scripts/agents/*.sh
- One file per agent, three-part contract
- examples/ralph.yml
- The workflow you copy into your repo
Status
Skills resolve in print mode, which was the assumption the design rested on. On a real self-hosted Mac runner, labelling an issue ready-for-agent has run every stage in sequence — pack install, brief, implement, verify — and ended in a draft pull request.
Still unobserved: CI on Ralph's pull requests, whether the output is worth reviewing across a sample of runs, and more than one runner. The tag stays v0 until the input names settle.