goga pipeline
Run a goga pipeline by name, or inspect the available ones (--list, --info).
goga pipeline is a host-side launcher: it assembles a docker run invocation and delegates all in-container work to the goga in-container process. The pipeline engine ships inside the container image — nothing beyond Docker is required on the host.
Synopsis
goga pipeline --list # flat list: available pipeline names (in-container)
goga pipeline --list --info # overview: one bullet block per pipeline with its description
goga pipeline <name> --info # card: name, description, stages in execution order
goga pipeline <name> # run: execute the pipeline (in-container)
goga pipeline <name> -t <topic> # run: first switch onto the branch hosting the work (host-side)
goga pipeline <name> -t <topic> --todo # same, then open the topic's todo.md in the editor
Forms
The command is a single Click command (not a group). Form validation happens on the host before any docker activity — a form error exits 1 without touching the image or launching a container:
| Form | Invocation | What it does |
|---|---|---|
| Flat list | goga pipeline --list |
Prints one * {name}[ (project)] bullet per pipeline. Project pipelines are annotated with (project); user pipelines are printed bare. |
| Overview | goga pipeline --list --info |
One bullet block per pipeline: * {name}[ (project)] followed by indented name: and description: fields (the authored header values). |
| Card | goga pipeline <name> --info |
Prints name: and description: fields, a --- separator, then one * {stage-id}: bullet with an indented title: field per stage in execution order (workflow skip/extend/loop applied; loop copies appear as separate NAME-1..N rows). Nothing runs. |
| Run | goga pipeline <name> |
Executes the pipeline (see Run Mode). |
| Error | goga pipeline (bare) |
Exits 1: Missing pipeline name. Use "goga pipeline --list" …. --list plus a name is also rejected (mutually exclusive). |
The list/info forms launch the container in a minimal read-only shape: the project bind-mount and one --add-host per configured host, and nothing else — no published port, no env-file, no persistent-state mount, no credential mounts. Nothing is written on the host.
Example info output:
$ goga pipeline --list
* deploy (project)
* build
$ goga pipeline --list --info
* deploy (project)
name: Deploy
description: Deploy the service
* build
name: Build
description: Build the artifact
$ goga pipeline deploy --info
name: Deploy
description: Deploy the service
---
* build:
title: Build
* test:
title: Test
The card and the run share the same workflow rule set and the same compiler, so the stages the card lists are structurally the stages a run executes (see Workflow files).
Run Mode (goga pipeline <name>)
Run a pipeline by name. Pass the bare name only (no .yml extension); the container resolves the absolute path internally, compiles the pipeline-file into the definition the run follows, materializes the agent prompt files (applying any roles overrides from the pipeline-file header — see Custom agent prompts), and starts the run. Passing -p/--parallel N caps the number of stages executed concurrently; without it, stages run unbounded. A free port is allocated automatically and published on both sides (-p <port>:<port>); the pipeline engine listens on that port inside the container. When a workflow is applied, a single log line naming it is printed to stdout; when -t/--topic brought the repository onto the requested work, the single result line of the topic procedure (Switched to branch <name>, Created branch <name> from <remote>/<name>, Already on branch <name>, or Created branch <name> and topic <year>/<slug>) is echoed once before the launch; otherwise the launcher prints no status line.
Pipelines are flat *.yml files (one per pipeline) resolved from two directories, with the project source winning on name conflicts:
| Source | Directory | Origin |
|---|---|---|
| project | <cwd>/.goga/pipelines/ |
Checked into / authored for the current project |
| user | ~/.goga/pipelines/ |
Installed centrally by goga connect (see connect) |
Only top-level *.yml is scanned — subdirectories are ignored, and .yaml files are excluded. Pipeline path resolution and discovery happen inside the container (the host does not resolve pipeline paths).
Pipelines installed from goga_tool_* packages are namespaced as <tool>:<name>.yml and addressed as goga pipeline <tool>:<name> — the colon is part of the bare filename stem, not a separator. Internal pipelines stay un-prefixed.
goga pipeline refinement
goga pipeline refinement -s discover # skip the discovery stage
goga pipeline acme:deploy
goga pipeline deploy --workflow feature-phases
Pipeline running with workflow "feature-phases"
If the name exists in both sources, the project source wins. The container exit code is propagated as the command's exit code.
Topic switch
The run form can first bring the repository onto the requested work — continuing existing work, or creating fresh work in one command when nothing hosts the identifier:
goga pipeline development -t feat/x
goga pipeline refinement -t prune-history-and-new-status
The identifier resolves through three tiers, and the first tier with a match wins (so a non-interactive -t never reaches a prompt):
- exact branch name — a branch whose display name equals the input;
- exact topic slug — a branch hosting the topic
.goga/history/<YYYY>/<slug>/whose slug equals the normalized input (lowercase, non-ASCII dropped, anything outside[a-z0-9]as-, repeat hyphens collapsed, edges trimmed:Feature/Foo_Bar→feature-foo-bar); local branches come before remote-tracking refs; - prefix — a branch whose name, or whose hosted slug, starts with the input.
Within a tier, several candidates may match (a branch chain carries several topics). On an interactive terminal goga prints the numbered list with each candidate's statuses and prompts for a number; with no terminal (CI/scripts) the numbered list itself becomes a clean error and the command exits 1 — no image refresh, build, or launch happens. No candidate at all creates fresh work instead of failing: the branch is created with the name as entered off the current HEAD, the repository switches to it, and the topic directory of the year is created from its slug (Created branch <name> and topic <year>/<slug>). An unusable name — one that normalizes to an empty slug, or one already occupied by an existing branch, a remote-tracking twin, or the topic directory of the year — is one clean error (exit 1) with the reason; there is no re-ask.
The outcome:
- already on the hosting branch → idempotent success, nothing is touched and the working tree is not even probed;
- a local host →
git switch <branch>; - a remote-only host → the local branch is created from the remote-tracking ref (
git switch -c <branch> <remote>/<branch>); - nothing hosts the identifier → the branch is created as entered from the current HEAD and the topic directory of the year appears (uncommitted changes carry onto the fresh branch;
goga topics createinstead plants the branch at an explicit or configured base and, by default, leaves you on your branch).
A switch that would mutate checks the working tree first: a dirty tree exits 1 with working tree is dirty — commit or stash before switching before anything is touched. Every git action happens on the host, after every form check and before any docker activity. The single result line (Switched to branch <name>, Created branch <name> from <remote>/<name>, Already on branch <name>, or Created branch <name> and topic <year>/<slug>) is echoed to stdout once, before the launch. The branch name is never forwarded into the container — the container sees the branch through the mounted project, and goga does not switch back after the launch.
With --todo, the topic procedure opens the external editor with the ensured work's todo.md after the switch or the fresh creation — saving overwrites the file (no commit), cancelling leaves it untouched, as in goga topics switch --todo — but unlike switch, a hosting branch without a topic gets its topic directory created and the todo entered. The flag needs an interactive terminal and acts only together with --topic: --todo without --topic in the run form is a clean error (--todo acts only together with --topic, exit 1) fired before any git or docker activity.
The flat list, overview, and card forms silently ignore -t and --todo — passing them there is not an error and has no effect.
Prerequisites
All forms launch a Docker container via the host docker CLI:
docker info
The top-level image field in .goga/config.yml must be set (the command exits with an error mentioning image when it is unset), and the pipeline section must be present. The pipeline engine is provided by the container image — it is not required (and never invoked) on the host.
Custom agent prompts
A pipeline-file header may carry an optional roles block with three fixed keys — planner, executor, reviewer — each an inline prompt that fully replaces (does not merge with) the corresponding shipped default prompt (planner → planning, executor → implementation, reviewer → review). The summary prompt is not overridable — it is always the shipped default.
name: deploy
description: Deploy pipeline
roles:
planner: |
You are the planner for this deploy pipeline.
Break the work into reviewable steps.
reviewer: |
Review each change against the deploy checklist.
---
- name: build
title: Build
prompt: Build it
Only those three keys are valid; an unknown key (including summary), a non-string value, a non-mapping roles block, or the legacy agents key is rejected as a structural DSL error at compile time (before any prompt file is written). When the block is absent or empty, the three shipped defaults are used unchanged (summary.md is always copied from its default). The overrides are a goga-side artifact and are not carried into the compiled pipeline definition.
At run time the agent prompt files are materialized into the pipeline's persistent-state directory (mounted at /home/goga/pipeline/prompts) before the run starts. That prompts/ directory is wiped and rebuilt from the defaults plus any roles overrides on every run, so files manually placed there do not persist.
Workflow files
A pipeline run (or card) can optionally apply a workflow-file — a declarative YAML document that layers a top-level prompt, per-stage agent/prompt overrides, loop-expansion, stage skipping via skip, manual launch via manual, and new stages via extend on top of the compiled pipeline. Workflow-files live at <cwd>/.goga/workflows/<name>.yml and are project-only (the name must be a bare filename resolved inside that directory; path traversal via .. or an absolute prefix is rejected).
Three invocation modes (mutually exclusive in the explicit cases), honored by both the run and the card form:
goga pipeline deploy(no flags) — auto-match: if<cwd>/.goga/workflows/deploy.ymlexists it is applied silently; otherwise no workflow. No host-side validation.goga pipeline deploy --workflow custom— apply<cwd>/.goga/workflows/custom.yml. The host validates the file exists before launch (exit 1 if missing).goga pipeline deploy --no-workflow— disable workflow application entirely (the run writesGOGA_WORKFLOW_DISABLED=1into the container env-file).
For a run, the decision reaches the container via the env-file (GOGA_WORKFLOW_NAME=<name> for --workflow; GOGA_WORKFLOW_DISABLED=1 for --no-workflow; neither for auto-match). For a card (<name> --info), the same flags travel in the docker run argv — the composition the card prints is exactly the composition a run with the same flags executes.
When a workflow will actually be applied to a run (explicit --workflow, or an auto-match file that exists), the launcher prints Pipeline running with workflow "<name>" to stdout. When no workflow applies, the launcher prints no workflow line. The launcher surfaces only the workflow log line, the docker output stream, any pre-launch version-check warning or refusal on stderr (see Runtime — Pre-launch version check), and, in the run form with -t, the single topic result line.
Inside the container the goga in-container process resolves and parses the workflow-file, then compiles the pipeline with it: extend entries inject new stages positioned via before/after, per-stage agent overrides select which CLI agent runs the stage, per-stage prompt overrides layer additional instructions onto the stage's own prompt, skip: true removes the stage and reconnects its dependents' depends_on, a loop: N (N ≥ 2) expands the stage into NAME-1..NAME-N copies with chained internal depends_on (external references are rewritten to the LAST expanded id), manual: true|false forces or cancels the stage's manual launch mode, and a memory block with per-stage reflect / memory instructions turns on project-memory participation (only when at least one stage participates — see Workflows — Project memory).
Example workflow-file:
prompt: |
Top-level prompt injected as the first directive of the flow-file.
stages:
propose:
agent: codex
prompt: |
Additional per-stage instruction.
propose-review:
loop: 2
agent: claude
Options
| Option | Type | Default | Description |
|---|---|---|---|
name (positional) |
string | — | Pipeline name without extension. Selects the card (--info) or run form; omit it and pass --list for the listing forms. --list and a name together are rejected (exit 1) |
-l, --list |
flag | off | List available pipelines (flat list). Add --info for a one-line description per pipeline |
-i, --info |
flag | off | With --list: print the overview. With NAME: print the pipeline card instead of running it |
-t, --topic |
string | — | Bring the repository onto the requested work before the run — a branch name, a topic slug, or their prefix, created fresh when nothing hosts it; see Topic switch. Run form only — the list/info forms silently ignore it |
--todo |
flag | off | Open the editor with the ensured work's todo.md after the switch or the fresh creation (run form only; requires --topic and an interactive terminal; the info forms silently ignore it; --todo without --topic is a clean error) |
-e, --env |
string (repeatable) | — | Additional environment variable (KEY=VALUE) forwarded into the container env-file. Run form only |
--proxy |
string | config | HTTP/HTTPS proxy URL; overrides pipeline.proxy. Adds HTTP_PROXY/HTTPS_PROXY/NO_PROXY=localhost,127.0.0.1 to the container env-file. Run form only |
--add-host |
string (repeatable) | -- | Add a docker run --add-host HOST:IP entry; merges on top of pipeline.hosts (CLI wins on key conflict). Run form only — the info forms receive the configured pipeline.hosts only |
-c, --clean |
flag | off | Wipe the pipeline's persistent state directory before launch. Run form only |
-u, --update |
flag | off | Refresh the image before launch (build if a project Dockerfile is declared, else pull). Effective in the run and flat-list forms; a deliberate no-op in the --info forms |
-w, --workflow |
string | — | Apply an explicit workflow at <cwd>/.goga/workflows/<name>.yml. The file must exist on the host (exit 1 if missing). Mutually exclusive with --no-workflow. Honored by the run and card forms |
--no-workflow |
flag | off | Disable workflow application entirely (a run writes GOGA_WORKFLOW_DISABLED=1 into the container env-file). Mutually exclusive with --workflow. Honored by the run and card forms |
-s, --skip |
string (repeatable) | — | Exclude a stage from the compiled pipeline (one name per invocation). The stage is removed and its dependents' depends_on are reconnected. Forwarded into the container env-file as GOGA_SKIP_STAGES=<name>,.... Not mutually exclusive with --workflow/--no-workflow. Run form only; the host performs no name validation — unknown names surface in-container as a structural error. The card does not read it (the card answers "what is this pipeline?", not "what would this particular run skip?") |
-p, --parallel |
int | — | Cap the number of stages executed concurrently (run form only). Omitted (the default), stages run unbounded. The -p short alias is a separate namespace from the Docker -p <port>:<port> port-publish token, which is assembled inside the launcher |
Persistent pipeline state
Run mode mounts a host directory at /home/goga/pipeline inside the container, so pipeline state (the compiled definition and the run state) survives across runs of the same pipeline in the same project on the same branch. The host directory is computed as:
~/.goga/runtime/pipelines/<normalized-project-path>/<git-branch>/<name>/
A : in the pipeline name becomes - in the path segment (acme:deploy → acme-deploy).
It is created before launch and is not deleted on exit. Use --clean to wipe it before launch when you want a fresh run.
Note that the prompts/ subdirectory inside it is regenerated on every run (wiped and rebuilt from the shipped defaults plus any roles overrides) — it does not persist user-placed content even though the parent directory survives across runs.
Container environment
The run form resolves the shared container contract — the host–image pre-launch version check, proxy and hosts, and credential mounts — documented once in Runtime.
Examples
Refresh the image, then run (default skips the refresh):
goga pipeline deploy --update
Inspect without running:
goga pipeline --list --info # every pipeline with its description
goga pipeline deploy --info # the card: stages in execution order
Route container traffic through a corporate proxy and add a local host entry:
goga pipeline deploy --proxy http://corp:3128 --add-host foo.local:127.0.0.1
Wipe persistent pipeline state for this pipeline/branch before launch:
goga pipeline deploy --clean
Start the run on the branch hosting an existing piece of work:
goga pipeline development -t feat/x
Exit Codes
Host side (all forms):
| Code | Meaning |
|---|---|
0 |
The operation completed (container exit 0) |
1 |
A ClickException: a form error (bare invocation, --list + name, --workflow + --no-workflow, --todo without --topic in the run form), the pipeline section missing in .goga/config.yml, an explicit --workflow <name> naming a file that does not exist or escaping the workflows dir, a topic-procedure failure (several candidates without a terminal, a dirty working tree on a switch, an unusable — empty-slug or occupied — name, --todo without a terminal, a failed git switch or ref listing, or a missing git binary — see Topic switch), or a fatal image build/refresh. Or the pre-launch version check refusing the launch (a host–image (major, minor) mismatch, an image that cannot answer the version probe, or an undeterminable host version — a stderr message plus SystemExit, see Runtime — Pre-launch version check) |
| other | The container's exit code, propagated unchanged (including the run-mode codes below) |
Container side, run form:
| Code | Meaning |
|---|---|
0 |
The pipeline ran successfully |
1 |
The pipeline was not found, or a handled compile/malformed-file failure rendered as a clean Error: ... stderr message |
2 |
In-container argparse error (missing NAME, non-integer --port, missing --port without --info) |
126 |
The pipeline engine was present inside the image but could not be invoked (e.g. not executable) |
127 |
The pipeline engine is missing inside the container image |
130 |
Interrupted by SIGINT (128 + 2) |
143 |
Interrupted by SIGTERM (128 + 15) |
Container side, info forms: 0 on success; 1 for a damaged pipeline-file (unreadable, non-YAML, structurally invalid, or not UTF-8) rendered as Error: ... on stderr; 2 for an in-container argparse error.
On SIGTERM/SIGINT during run mode the running container is killed and the process exits with 128 + signum.
Notes
- Do not expect
lsorrunhost subcommands —goga pipelineis a single command (list/runare the in-container subcommands behind docker, not host ones). - A bare
goga pipelineis an error — usegoga pipeline --listto list available pipelines. - Do not pass a file path or a name ending in
.yml— pass the bare pipeline name only. - The pre-launch version check can be disabled per invocation with
GOGA_SKIP_VERSION_CHECK=1— it skips both the probe and the comparison for every form (see Runtime — Pre-launch version check). - The host launcher never imports the pipeline implementation — the runtime boundary is Docker.