You are working in a repo that may contain an AI agent — likely one of: an MCP server tool list (*mcp*.json or .agents-shipgate/*.json), an OpenAPI spec the agent calls, a Codex plugin package (.codex-plugin/plugin.json) or marketplace (.agents/plugins/marketplace.json), a Python file with @function_tool / @tool decorators (OpenAI Agents SDK, LangChain, CrewAI), a Google ADK agent in agent.py, an Anthropic Messages API artifact set under prompts//tools/anthropic-tools.json/policies/anthropic-policy.yaml, or an OpenAI API artifact set under prompts//tools/openai-tools.json/openai-config.json.
Your job is to drive the first-adoption helper flow end-to-end in one
tool-using turn, which adds the deterministic merge gate for AI-generated agent
capability changes — a local-first, static Tool-Use Readiness review. Ongoing
agent-related PRs should use agents-shipgate verify after this adoption step.
-
Install the tool - pin the version so a stale build can't shadow it. This flow uses the permission-scoped multi-host boundary contract and requires runtime contract 21 (
agents-shipgate0.16.0 or newer); an older copy lingering onPATHmay lack the command or schema fields this prompt expects. Prefer a pinned, zero-install runner that fetches the exact version every time instead of trusting whatever is already onPATH. Pin it into one variable and use that for every step below, so no single command can fall through to a stale binary:SG="uvx agents-shipgate@0.16.0" # uv: ephemeral, pinned to this exact build # or: SG="pipx run agents-shipgate==0.16.0" $SG --version # confirm the pinned runner resolves
Every step below calls
$SG …; e.g.$SG verify --preview --jsonruns the verify preview through the pinned runner, never aPATHcopy.If you would rather install onto
PATH, pin the floor and fail loudly when it resolves older — a plainpipx install agents-shipgateis a no-op when an older build already exists — then setSG=agents-shipgate:python -m pip install -U --pre agents-shipgate agents-shipgate contract --json # STOP unless minimum_control_contract_version is 21 SG=agents-shipgate # only after the line above confirms contract 21
-
Sanity-check the install before touching the user's code:
$SG self-check --jsonConfirm
"ready": true. If not, surface the failure to the user.When available, verify the installed CLI contract locally:
$SG contract --jsonRead
report_schema_version,packet_schema_version,gating_signal, andmanual_review_signals[]; prefer these local values over stale docs. If the command is not recognized on an older install, continue afterself-checkusingdocs/agent-contract-current.mdand upgrade before relying on local contract verification in automation. -
Preview the verify flow:
$SG verify --preview --jsonRead the response and next action. Preview is the first-look verify entry point: it does not require a manifest, does not scan, and tells you whether to configure Shipgate, skip, or run the full verifier.
If preview exposes trigger/detection metadata, stop only when all relevance signals are absent and the user did not explicitly request Shipgate. Otherwise proceed. MCP/OpenAPI tool-surface repos and Codex plugin package repos can be valid Shipgate targets even when Python framework detection would classify
is_agent_project: false; look forsuggested_sourcesandcodex_plugin_candidateswhen those fields are present.Read
control.next_action— do not run it yet. Step 4 performs the setup exactly once; running the emittedinithere and the command in step 4 as well makes the second one exit 2 withmanifest_status: "skipped_existing". Take the--workspacevalue out of the emitted command and keep it asWS:WS=$(… control.next_action.command …) # the --workspace value, verbatim
WSis.only when preview said so. When every capability-bearing changed path belongs to one self-contained project, preview scopes setup to that project instead of the repository root; re-spelling it as.in a monorepo writes one manifest covering every unrelated agent in the repo.Three
next_actionkinds mean do not continue to step 4:kind: "verify"— the changed project is already configured. Run that command; adoption is done.kind: "discover"— the change spans several projects, or the scope could not be established. Run the emitteddetectcommand, pick the project your change belongs to, and use it asWS.actor: "human"— several configured projects are involved. Surface the result; each one is its own gate.
-
Generate a starter manifest + GitHub Actions workflow — this is the only
initthe flow runs:$SG init --workspace "$WS" --write --ci --json
The
--jsonform returns:manifest_status:"written"|"skipped_existing"|"refused_unresolved_scope"|"not_attempted"workflow.status(with--ci):"written"|"skipped_existing_target"|"skipped_cross_reference"placeholders[]— entries the template intentionally left asCHANGE_MEbecause no high-confidence signal was availableauto_detected.agent_name— the value the manifest carries (nullwhen the template fell back toCHANGE_ME)auto_detected.agent_scope:"single"|"ambiguous"|"unknown", withauto_detected.agent_project_candidates[]naming every self-contained project that defines an agent
--ciwrites the workflow orthogonally to--write; each gets its own overwrite-refusal check, and a workflow that already gates this manifest skips with a distinctcross_reference_path. The workflow always lands at the repository root — GitHub loads workflows from nowhere else — withconfig:naming the manifest relative to that root. A root manifest getsagents-shipgate.yml; a scoped one gets its ownagents-shipgate-<project>.yml, because the action takes a singleconfigand one shared file would leave every project after the first ungated. Readworkflow.pathrather than assuming the name.refused_unresolved_scope(exit 2) means the manifest scope was not settled: agents in more than one self-contained project (agent_scope: "ambiguous"), or discovery capped before it could tell ("unknown"). No singleagent.name,declared_purpose, or tool surface describes such a workspace, so nothing was written — not the manifest, not the workflow, not the reports.gitignoreblock. Do not retry the same command: pick the project this change belongs to fromagent_project_candidates[]and re-run with--workspace <that directory>, keeping the setup flags (the emittednext_actions[]commands already carry them).--allow-unresolved-scopewrites one manifest for the workspace as a whole, and is right only when a single agent surface genuinely spans it. -
Replace placeholders. Walk
placeholders[]from the JSON output. On a fresh workspace the template typically leaves two:agent.name: CHANGE_ME— replace with the agent's actual role (no strongAgent(name="…")literal was found in the source).agent.declared_purpose[]: CHANGE_ME— replace with a one-line description of what the agent should do (auto-init can't infer this; the schema requires a non-empty value).
Read the agent's prompt or main file to derive both. Skipping this leaves an invalid adoption artifact — the manifest validates but downstream consumers see meaningless defaults.
-
Run the scan with patch suggestions — in the workspace you initialized:
cd "$WS" && $SG scan -c shipgate.yaml --suggest-patches --format json --ci-mode advisory
The report lands at
$WS/agents-shipgate-reports/report.json, beside the manifest it describes; the supporting Release Evidence Packet lands at$WS/agents-shipgate-reports/packet.{md,json,html}. (Reports follow the workspace, so two projects in one repository never overwrite each other's results.) Every path below is relative to$WS. init's ownnext_actionalready names the manifest correctly for wherever you are — prefer running that command verbatim over retyping this one. Parsereport.json; Codex plugin facts, when present, live undercodex_plugin_surface.Read these first for release gating (v0.8+):
release_decision.decision∈{"blocked", "review_required", "insufficient_evidence", "passed"}— baseline-aware. This is the gating signal.insufficient_evidence(v0.14+) fires when evidence coverage is degraded past threshold; treat unknown future values asreview_required.release_decision.{reason, blockers, review_items, fail_policy.would_fail_ci}
Read these for release review (v0.9+):
capability_facts[],declared_intentions[],misalignments[],release_consequence,suggested_scenarios[]
Per-finding fields:
check_id,severity,category,tool_name,recommendation,suppressedautofix_safe,requires_human_review,suggested_patch_kind,docs_url(v0.7+)patches[](only with--suggest-patches) — each haskind∈{set_pointer, append_pointer, remove_pointer, manual}plusconfidence+target_file+ etc. for non-manual kinds.
Top-level:
manifest_dir(absolute path of the manifest's directory — used byapply-patchesfor the containment check).summary.{status, critical_count, high_count, medium_count}is preserved for v0.7 callers and is baseline-blind — do not gate onsummary.statusfor new consumers. Full contract:docs/agent-contract-current.md. -
Apply the safe patches:
$SG apply-patches --from agents-shipgate-reports/report.json --confidence high --apply --jsonDefault
--confidence highonly mutates patches whoseconfidencefield is"high". Today that's the 3 stale-manifest removals. Scope-coverage appends ship atmediumand require explicit--confidence mediumto apply. ManualPatches are never auto-applied.Decision tree for walking the report:
for finding in active_findings: if finding.suggested_patch_kind in ("manual", "none"): surface_to_user(finding) # Surface; do NOT auto-apply. continue if finding.autofix_safe is True: plan_to_apply(finding) # Will be applied at --confidence high. continue surface_for_medium_review(finding) # Medium-confidence — opt-in only.Trace findings (
SHIP-API-TRACE-{APPROVAL,CONFIRMATION}-MISSING) are permanent ManualPatch by policy. Implement the runtime gate; never edit the trace recording — that patches the evidence, not the agent. Seedocs/autofix-policy.mdfor the full classification. -
Add
agents-shipgate-reports/to.gitignoreif it isn't already. The reports are scan artifacts, not source. -
Report back to the user:
release_decision.decisionandrelease_decision.reason(the gating signal — baseline-aware, v0.8+)- Blocker / review-item counts (
len(release_decision.blockers)/len(release_decision.review_items)) - The path to the supporting Release Evidence Packet (
agents-shipgate-reports/packet.md) for reviewer-shaped output - The top 3 active critical/high findings (use
report.json, not stdout) - Which patches were applied (count from
apply-patches --jsonoutput'sfiles) - Any check IDs the user should investigate first — link to
docs_urlfrom the finding for full rationale, or use$SG explain <CHECK_ID> --jsonfor the same content via CLI
Re-run the failing $SG … command with AGENTS_SHIPGATE_AGENT_MODE=1 set. The CLI will append a JSON line to stderr with {error, message, next_action}. Follow the next_action.
Common errors and fixes:
| Error | Fix |
|---|---|
Config file not found: shipgate.yaml |
Run $SG init --workspace . --write first |
Input path '...' resolves outside manifest directory |
The declared tool_sources[].path is outside the manifest dir. Move the spec inside the tree, symlink it, or copy it |
Invalid shipgate.yaml: ... Did you mean X? |
A field is at the wrong nesting level; move it as suggested |
Containment violation (apply-patches exit 5) |
A patch's target_file resolved outside report.manifest_dir. Re-run scan to refresh; never patch arbitrary system files |
- Do not commit
agents-shipgate-reports/— it's regenerated each run. - Do not run
$SG baseline saveuntil the user has reviewed the initial findings. Baselining ratchets in noise that strict CI will silently ignore. The right time to baseline is after the user has decided which findings they accept. - Do not suppress findings without a real
reason— the manifest validator rejects empty reasons, and thereasonfield is the audit trail when someone asks "why is this OK?" - Do not use
risk_overrides.tools.{tool}.remove_tagsto silence a finding without checking whether the heuristic is actually wrong. Preferchecks.ignorewith a reason. - Do not edit a trace recording to flip
approvedorconfirmed— implement the runtime gate instead.
agents-shipgate-reports/report.jsonexists and parses as JSONreport.jsoncarriesreport_schema_version: "0.11"(or higher) and a non-emptymanifest_dirreport.jsoncarries a non-nullrelease_decision.decision— this is the field to surface to the usershipgate.yamlhas noCHANGE_MEvalues (comments containing the literalCHANGE_MEare informational and OK).gitignorecontainsagents-shipgate-reports/(or equivalent)- If
--ciran withworkflow.status: "written":.github/workflows/agents-shipgate.ymlexists and referencesThreeMoonsLab/agents-shipgate@v… - The user knows the top 3 findings and at least one suggested next step