Skip to main content

Single-shot CI with mecatequi

Use mecatequi to run one bounded Mecatl task in CI. It accepts one prompt, writes a patch and machine-readable results, then exits. It starts no network listeners and keeps no session state between runs.

mecatequi is forge-independent. Your CI workflow supplies the task, separates credentials, and publishes any resulting change. The included GitHub Actions workflow provides this integration for GitHub repositories.

It reads the same global MCP profiles as mecated, but cannot launch an OAuth browser. Authorize credentials before the job, or inject a provisioned environment credential. See MCP client.

What mecatequi produces

A run can produce three artifacts and an exit code:

ArtifactFlagDefaultDescription
Git diff patch--out-diffdisabled (empty)A unified patch that reproduces modified, added, and deleted files. Suitable for git apply.
Summary JSON--out-summarystdout (-)Machine-readable run result using schema version 1.
JSONL event log--out-eventsdisabled (empty)One redacted session.Event per line. An artifact for forensics, not a rehydration source.

Summary JSON

The summary is the stable contract for the publish job and other automation:

FieldJSON keyNotes
SchemaVersionschema_versionAlways 1; changes only for a breaking schema change
SessionIDsession_idThe session that drove this run
StopReasonstop_reasonVerbatim session.StopReason string (see below)
NonEmptyDiffnon_empty_diffWhether the run left a working-tree change
DiffBytesdiff_bytesByte length of the computed patch
Usageusage{input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens, total_tokens}
FinalTextfinal_textTerminal assistant prose
ErrorerrorNon-empty only when stop_reason == "error"

The default summary is indented and written to standard output, so you can inspect it directly:

mecatequi <FLAGS> | jq .stop_reason

Passing --out-summary=- explicitly selects compact mode. In that mode, the summary is the final standard-output line as single-line JSON, which lets a log consumer parse it without buffering the full output.

The patch

The patch includes tracked modifications, deletions, and untracked files that are not ignored. Both non_empty_diff and diff_bytes describe the complete patch. A single new file sets non_empty_diff to true.

Mecatl scrubs Git-specific environment variables and disables external diff drivers while producing the patch.

Exit codes

Exit codes are coarse. Always read stop_reason and non_empty_diff from the summary to judge whether real work landed.

Exit codeMeaning
0The run reached end_turn, no_progress, budget, max_turns, max_tool_calls, max_consecutive_failures, or structured_output
1Run failure: model error (stop_reason: error), cancelled run, no-approver cancel-on-ask, --timeout exceeded
2Setup failure: bad flags, missing prompt, app.Build / CreateSession error, non-git or non-top-level workspace, colliding output sinks, write failure
Exit 0 is not "task accomplished"

no_progress, budget, and max_turns all produce exit 0. The model ran without an execution error but may not have finished. A CI step that shows stop_reason: no_progress and non_empty_diff: false means the agent ran without errors and left no changes. Treat those two fields as the real signal.

The split-privilege model

The included CI integration separates the LLM credential from the GitHub write credential across three jobs:

acknowledge holds only issues:write. It posts one comment confirming that the run started. It has no LLM key or contents permission.

implement holds contents:read and the LLM key. It runs the agent and uploads the patch, summary, and event log. It has no GitHub write token or id-token, so agent-controlled code cannot push, open a pull request, or post a comment.

publish holds contents:write, pull-requests:write, and issues:write, but runs no agent code. It downloads the artifacts and applies the patch as data. It also reports a failed implementation run on the issue.

The job that runs agent code never holds a GitHub write token. The job with the write token never runs agent code. Both the reusable workflow and the customizable example preserve this boundary.

Prompt trust boundary

SourceTrustHandling
Issue or comment textUntrusted and attacker-controlledPassed through --prompt-file and fenced by --untrusted-prompt
Operator workflow config (posture, flags, model)TrustedSet by a maintainer in the workflow; passed as action inputs
--instructions contentTrustedEmitted outside the untrusted-prompt fence, never fenced

--untrusted-prompt wraps the prompt body in Mecatl's untrusted-data fence. Use it for issue and comment text so the model treats that content as task data rather than trusted operator instructions.

--instructions provides trusted operator guidance outside the fence. The GitHub Action uses it to request a pull request description and self-check. An empty value omits this trusted channel.

Use the reusable GitHub Actions workflow

Runner version

The reusable workflow requires GitHub Actions Runner 2.336.0 or later. GitHub-hosted runners meet this requirement. An older self-hosted runner fails with an invalid uses: error before running the jobs.

.github/workflows/mecatequi-reusable.yml is an on: workflow_call workflow. A consuming repository references it with a short caller:

jobs:
mecatequi:
uses: stacklok/mecatl/.github/workflows/mecatequi-reusable.yml@v0.0.3
secrets:
openrouter-key: ${{ secrets.OPENROUTER_CI_TOKEN }}
publish-app-id: ${{ secrets.RELEASE_APP_ID }}
publish-app-private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
with:
label: ready-for-agent
model: anthropic/claude-sonnet-4.6
default-provider: openrouter

Replace v0.0.3 with the latest released tag. The workflow preserves the three jobs and their separate permissions. Its composite action builds mecatequi from the tagged Mecatl source.

Use .github/workflows/mecatequi-example.yml as a customizable alternative when you need a different trigger, approval stage, or author gate.

Trigger patterns

The reusable workflow supports two triggers through workflow_call inputs:

  • Issue label: A maintainer applies the configured label, which defaults to mecatequi. The workflow runs against the issue body.

  • Issue comment: A maintainer posts the configured mention, which defaults to @mecatequi. The workflow runs against the comment body.

On private repositories, applying a label or posting the trigger requires triage or write access. On public repositories, add a dedicated permission-check job that calls the collaborators/{user}/permission API.

Publish token

The publish job selects its write token in this order:

  1. A GitHub App installation token, minted when publish-app-id and publish-app-private-key are present and scoped to the repository.
  2. The pre-minted token in publish-token.
  3. The workflow's GITHUB_TOKEN. This fallback emits a warning that recommends one of the narrower options.

Customize the PR description

By default, publish writes a pull request body with the agent summary, changed files, run link, and Closes #<N>. To customize it, add a template at .github/mecatequi/pr-body.md. Use the pr-body-template input to select a different path.

The publish step always prepends its agent-authored-content warning. Do not repeat that warning in your template. A custom template also owns its issue relationship, so include Closes {{issue_ref}} or Refs {{issue_ref}} as appropriate.

The optional pr-title-template input overrides <ISSUE_TITLE> (#<ISSUE_NUMBER>). Use short placeholders such as {{issue_ref}}, {{issue_title}}, {{stop_reason}}, and {{branch}}.

Key flags

mecatequi reads MCP profiles from the global settings.yaml. Repeat --permission-config <PATH> to add trusted settings files with higher precedence.

Supply provider credentials through auth.yaml or the documented environment variables: OPENAI_API_KEY, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, and OPENCODE_API_KEY. Keep secrets out of command-line arguments.

Prompt and output

FlagDefaultNotes
--promptNoneLiteral prompt text. Supply this flag or --prompt-file.
--prompt-fileNonePath to a file containing the prompt body.
--untrusted-promptfalseWrap the prompt body in the untrusted-data fence. Set true for issue-text input.
--instructions""Trusted operator framing emitted outside the fence. The GitHub Action sets a non-empty default (PR-description + self-verify framing).
--out-summary- (stdout)Where to write the Summary JSON.
--out-diff"" (disabled)Where to write the git patch. Opt in with a path.
--out-events"" (disabled)Where to write the JSONL event log. Opt in with a path.

No two outputs may share a sink. Two writers on one stream interleave and corrupt both.

Engine and run control

FlagDefaultNotes
--workspacecwdSession workspace root. Must be a git repository top level.
--default-provider""Provider ID: openai, openai-codex, openrouter, anthropic, or opencode.
--model""Per-session passthrough model ID. Accepts any ID the provider serves, including IDs newer than the embedded catalog. Prefer this over --default-model for newer models.
--posture"" (strict)Permission posture. Use auto for autonomous CI. With strict and --headless, a main-agent permission ask cancels the run and exits 1.
--trust-projectfalseTrust this workspace for project content and read-only child shell access. No posture grants project trust in headless mode. Without this flag or a declared or remembered trust decision, Mecatl ignores project content such as AGENTS.md and does not give read-only children a shell.
--headlesstrueA single-shot CI run has no human approver. Child asks are denied or routed to the optional ask reviewer.
--timeout0 (disabled)Wall-clock bound on the whole run (e.g. 40m). A timeout-cancelled run exits 1 with stop_reason: cancelled.
--max-run-tokens0 (unlimited)Per-engine input and output token limit. Each child enforces its own inherited limit, so aggregate delegated usage can exceed this value. Crossing the limit produces stop_reason: budget.
--max-team-tokens0 (unlimited)Separate team-round aggregate token ceiling, not a per-engine run ceiling. When crossed, it prevents new team rounds; the current round and lead synthesis still complete. It does not enforce or report a cross-tree aggregate outside that team.
--max-turns0 (deployment default)Turn cap for this run. 0 inherits the composition default.

Provider keys

--default-provider selects a provider. The matching credential must be available, or the run fails with no LLM provider available. OPENAI_API_KEY enables OpenAI without an explicit provider selection. For OpenRouter, Anthropic, or OpenCode Go, set the corresponding key and select the provider explicitly.

The experimental openai-codex provider has no environment key. Put a manual ChatGPT Codex OAuth snapshot in owner-only auth.yaml, pass --api-key-file PATH --default-provider openai-codex, and replace the token and restart the job when it expires or is rejected. It uses an undocumented private backend and a separate billing identity from public OpenAI API credit. There is no login or refresh flow. See Configure provider credentials.

Telemetry

mecatequi can push metrics and traces to an OTLP collector and flush them before exit. Telemetry is off when both endpoint flags are empty. See ADR 0098 and Key flags.

FlagDefaultNotes
--otlp-endpoint"" (off)OTLP trace collector endpoint; empty disables tracing.
--otlp-protocolgrpcOTLP transport for traces (grpc or http).
--otlp-insecurefalseUse plaintext when dialing the collector. Use only for local development.
--otlp-metrics-endpoint"" (off)OTLP METRICS collector endpoint; empty disables metrics push.
--otlp-metrics-protocolgrpcOTLP transport for metrics (grpc or http).
--otlp-shutdown-timeout5sMaximum time to flush telemetry during shutdown.

Metrics use bounded role labels and do not include session or model IDs.

Headless posture and permission asks

mecatequi defaults to --headless=true. Permission asks behave differently for the main agent and its children.

Child asks from subagents, team members, and parallel branches are denied by default. --subagent-ask-reviewer enables a tool-less, one-turn LLM reviewer that can approve the current call only. A reviewer error leaves the call denied. This flag applies only in headless mode.

With --posture strict and --headless, the main agent has no approver. Its first permission ask cancels the run, produces stop_reason: cancelled, and exits 1 with guidance to add allow rules or choose another posture.

Use --posture auto for autonomous CI. It allows main-agent and child tool calls while retaining child prompt-injection defenses. Reserve yolo for a disposable, isolated, single-tenant environment.

What mecatequi does not support

CapabilityWhere to look instead
Session continuity across runsmecated or mecak8s with a durable session store
Resuming prior runsmecated (Service.StartRunContent on a prior session id)
Interactive clients (TUI, IDE)mecated + mecatui
Inspecting subagents across invocationsmecated with InspectSubagent and a persisted subagent store
Multi-turn conversational applicationmecated or mecak8s

A single-shot run reads its task, produces artifacts, and exits. The issue and pull request provide the durable workflow record.

Workspace validation

The workspace must be the top level of a Git repository. A subdirectory could produce a patch for the enclosing repository, so mecatequi validates the resolved workspace against git rev-parse --show-toplevel. A validation failure exits 2.

Next steps