| engines | ||
| gooseloop | ||
| tests | ||
| .gitignore | ||
| CLAUDE.md | ||
| doc-map.example.toml | ||
| gooseloop.toml | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
gooseloop
An execution shell for goose recipe pipelines.
Every loop runs the same three-phase shape:
review -> body -> summary
review always runs first and emits a structured plan. body does the work
that plan routed. summary always runs last and renders the ledger. The
framework owns that order and an engine cannot change it. You fill in the three
phases. That sandwich is the whole mental model, and it is the entire extension
surface.
gooseloop is generic. It knows nothing about your domain. A loop plugs in through two objects, an Engine and an Environment, plus some recipes. Around them the framework handles retry, rate-limit backoff, session bookkeeping, recipe overlay merging, and a typed session ledger, so an engine is just the verbs and nouns of one job.
Why
goose runs a recipe. gooseloop runs a loop of recipes with a spine: a review
that decides what to do, a body that does it, and a summary that reports it
honestly, including the parts that failed. The contract between those phases is
written down (PROTOCOL.md), so a recipe author, an
engine author, and an environment author can each work against the same fixed
shape instead of re-inventing one per project.
Install
Not on PyPI yet. Install from source:
pip install git+https://gitforge.ca/Mathew/gooseloop.git
Or from a checkout:
git clone https://gitforge.ca/Mathew/gooseloop.git
cd gooseloop
pip install -e .
Requires Python 3.11+, the goose CLI on $PATH, and a YAML recipe directory.
Quickstart
from gooseloop import GooseLooper, Engine, Environment, Phase, Pipeline
class HelloEnvironment(Environment):
def env_vars(self) -> dict[str, str]:
return {"GREETING": "hello"}
class HelloEngine(Engine):
name = "hello-world"
def pipeline(self, ctx) -> Pipeline:
return Pipeline(
review=Phase(name="review", recipe_path="recipes/review.yaml"),
body=[Phase(name="greet", recipe_path="recipes/greet.yaml")],
summary=Phase(name="summary", recipe_path="recipes/summary.yaml"),
)
GooseLooper(engine=HelloEngine(), environment=HelloEnvironment()).begin_loop()
A working version of this engine ships under engines/hello_world/.
The three primitives
- Engine (the verbs). Returns a
Pipeline(review, body, summary). Owns branching rules, ships its recipe defaults, sets retry and scoring policy. Subclassgooseloop.Engine. - Environment (the nouns). Declares what the engine can see. The ABC has one
abstract method,
env_vars(). Anything a recipe needs pasted in as text is a method the recipe calls viaenv_method:<name>. Subclassgooseloop.Environment, or agooseloop.contrib.*mixin if one fits. - Pipeline (the bookend). The
review -> body -> summarydataclass an engine returns frompipeline(). Review runs first and emits structured routing; body phases run in queue order; summary runs last over the final ledger.
The one decision that shapes a new loop is who decides what the body does:
- Model-driven routing: the review looks at state and decides which body
units to run. Copy
hello_world. - Deterministic routing: the work is already known and the model's only job
is to do each unit. The engine builds the body itself in
pipeline(). Copydoc_drift.
Getting this axis right up front is the difference between a clean first loop and an awkward one.
Built-in engines
Three reference engines ship in engines/. They are a teaching set, read in
this order: each one adds exactly one layer of capability. They live alongside
the framework rather than inside the installed wheel, so run them from a
checkout (the repo root, where engines/ is importable) and select one with
-e <module>.
| Engine | Read it for | Routing | Run from the repo root |
|---|---|---|---|
hello_world |
every contract in its simplest form | model-driven | python3 -m gooseloop run -e engines.hello_world |
git_recap |
real I/O, content pasting, glob-read summary; recaps recent commits across repos into a changelog | model-driven | python3 -m gooseloop run -e engines.git_recap |
doc_drift |
the advanced end: deterministic body, file bundles, live URL fetch, cross-run state; finds derived docs/pages that fell behind their source and drafts a patch | engine-driven | python3 -m gooseloop run -e engines.doc_drift |
Drop -e to run whatever [gooseloop] engine_module points at (hello_world
by default). Run the bundled engines with python3 -m gooseloop: they live in
./engines, and a console script (unlike python -m) does not put the current
directory on sys.path, so the bare gooseloop command cannot import them
(PYTHONPATH=. gooseloop ... works too). Your own pip-installed engine needs no
such trick: plain gooseloop finds it. Add --review-only to stop after the
review phase.
Once your project has its own engine, the examples can be deleted wholesale. The framework does not depend on any of them.
Recipe overlay merge
Recipes compose docker-compose style. The looper resolves layers in order, later winning:
- Base recipe (declared in
gooseloop.toml). <name>.local.yaml(gitignored; per-machine).--review-overlay X.yaml/--summary-overlay X.yaml(ad-hoc).
Inspect the merged result with gooseloop recipe --resolve <name>. The full
merge-rules table is PROTOCOL.md §6.
Contrib mixins
Domain-shaped Environment ABCs ship under gooseloop.contrib, for loops whose
shape recurs:
CustomerPipelineEnvironment: customer-acquisition pipelines (build_digest(),journal_text(),lifecycle_dirs(), ...).ClaudeHandoffEnvironment: design-handoff engines (handoff_dir(),target_repo(),dev_up_probe(), ...).
An engine with no fit subclasses bare Environment and writes one method.
CLI
gooseloop run # run the configured engine, one pass
gooseloop run --review-only # stop after review
gooseloop run --review-overlay x.yaml --summary-overlay y.yaml
gooseloop recipe --resolve review # print the fully-merged recipe
gooseloop engines # list known engines from gooseloop.toml
gooseloop.toml at the project root configures which engine, which recipes,
retry tuning, and the sessions directory.
Documentation
gooseloop/PROTOCOL.mdis canonical: the review JSON schema, the summary contract, body-phase rules,BranchPolicy, recipe overlay merge, and the recipecontext:block. If the code disagrees with it, the code is the bug.CLAUDE.mdis the entry point for an AI agent authoring a loop.
License
MIT. See LICENSE.