Agent Protocol
Agent JSON Protocol
The CLI must expose stable JSON contracts so AI agents and harnesses can operate without scraping human text.
recommendedCommand and suggestedCommands use the canonical contribflow executable. Agents should execute those commands exactly as returned. cflow is a human-friendly alias and may be used in prose examples, but JSON contracts should stay canonical.
Status Result
{
"state": "ready_for_discovery",
"configPath": ".contribflow/config.json",
"configExists": true,
"ghAuthenticated": true,
"requiresHumanApproval": false,
"nextAction": "discover",
"recommendedCommand": "contribflow discover --json",
"reason": "No active contribution task exists."
}
Discovery Result
Successful discovery:
{
"status": "ok",
"query": "is:issue is:open archived:false language:TypeScript cli",
"candidates": [],
"suggestedCommands": ["contribflow discover --lang TypeScript --json"],
"suggestedQueries": ["is:issue is:open archived:false language:TypeScript"],
"nextAction": "refine_discovery",
"recommendedCommand": "contribflow discover --lang TypeScript --json",
"reason": "No candidates matched the current discovery profile."
}
GitHub search throttling or secondary protection:
{
"status": "blocked",
"query": "is:issue is:open archived:false",
"candidates": [],
"blocked": {
"kind": "secondary_rate_limit",
"scope": "discovery_query",
"failingQuery": "is:issue is:open archived:false",
"message": "You have exceeded a secondary rate limit. Please wait before retrying.",
"retryAfterSeconds": null,
"retryAfterAt": null,
"retryGuidance": "Do not immediately retry the same search. Wait for GitHub search protection to cool down or rerun discovery with narrower --lang, --topic, or --type flags."
},
"nextAction": "wait_or_refine_discovery",
"recommendedCommand": "gh api rate_limit --jq '.resources.search'",
"reason": "GitHub issue search is currently throttled. Wait before retrying or narrow the discovery profile."
}
Agents must treat status: "blocked" as a stop-and-backoff state. They should not immediately retry blocked.failingQuery; they should wait, inspect GitHub search quota, or rerun discovery with narrower preferences after human-visible context is recorded.
Discovered Issue Candidate
{
"id": "github:owner/repo#123",
"owner": "owner",
"repo": "repo",
"fullName": "owner/repo",
"number": 123,
"title": "Issue title",
"url": "https://github.com/owner/repo/issues/123",
"labels": ["good first issue"],
"language": "TypeScript",
"stars": 1000,
"duplicateEvidence": [],
"score": 72,
"recommendation": "Good candidate",
"scoreExplanation": [
{
"rule": "base",
"impact": 60,
"message": "Starts from the baseline score for an open issue candidate."
}
],
"riskLevel": "medium",
"riskFlags": {
"stale": false,
"duplicatePrRisk": "low",
"securitySensitive": false
},
"estimatedEffortHours": 2
}
Score Result
{
"candidate": {
"id": "github:owner/repo#123",
"fullName": "owner/repo",
"number": 123,
"title": "Issue title"
},
"score": 72,
"scoreBand": "Good candidate",
"recommendation": "Good candidate",
"riskLevel": "medium",
"duplicateEvidence": [
{
"kind": "title_similarity",
"confidence": "medium",
"title": "Improve README examples",
"url": "https://github.com/owner/repo/pull/456",
"reason": "Open PR title overlaps candidate terms: improve, readme, examples."
},
{
"kind": "existing_content",
"confidence": "high",
"title": "content/quotes.json",
"url": "https://github.com/owner/repo/blob/main/content/quotes.json",
"reason": "Requested content already appears in repository: I will keep moving forward until..."
}
],
"breakdown": {
"base": 60,
"clarity": 10,
"labels": 10,
"profileFit": 0,
"qualityFit": 0,
"staleness": 0,
"duplicatePrRisk": 0,
"securitySensitive": 0
},
"explanation": [
{
"rule": "base",
"impact": 60,
"message": "Starts from the baseline score for an open issue candidate."
},
{
"rule": "clarity",
"impact": 10,
"message": "Issue body has enough context for a focused contribution."
}
],
"nextAction": "plan",
"recommendedCommand": "contribflow plan owner/repo#123 --json"
}
Contribution Plan
{
"issue": "owner/repo#123",
"issueUrl": "https://github.com/owner/repo/issues/123",
"title": "Issue title",
"summary": "Short issue summary",
"proposedFix": "Minimal proposed fix",
"likelyFiles": ["README.md", "docs/**/*.md"],
"repositoryContext": {
"guidanceFiles": ["CONTRIBUTING.md", "README.md", ".github/pull_request_template.md"],
"packageManager": "npm",
"packageScripts": ["build", "lint", "test"],
"testLayouts": ["tests"],
"generatedFileRules": []
},
"validationCommands": ["npm run lint", "npm test", "npm run build"],
"testPlan": [
"Run `npm run lint` in the sandbox if it is relevant to the change.",
"Run repository documentation, lint, or formatting checks if available.",
"Manually verify changed links, examples, and commands."
],
"prTitleGuidance": ["Use a concise title that names the focused change."],
"prBodyGuidance": [
"Reference the issue, summarize the smallest change, list validation evidence, and include AI assistance disclosure."
],
"stopConditions": [
"Stop before public GitHub writes unless the user explicitly approves.",
"Stop if the patch grows beyond the configured file or line-change limits.",
"Stop if repository guidance conflicts with the planned change."
],
"riskLevel": "medium",
"riskAssessment": "Medium risk: candidate may be viable, but scope should be checked before checkout.",
"score": 72,
"scoreBand": "Good candidate",
"recommendation": "Good candidate",
"assumptions": [],
"questions": [],
"requiresHumanApproval": false,
"nextAction": "checkout",
"recommendedCommand": "contribflow checkout owner/repo#123 --json"
}
Workspace State
{
"issue": "owner/repo#123",
"workspacePath": ".contribflow/workspaces/owner-repo-123",
"controllerWorkspacePath": "/Users/alex/contribflow-work",
"runFrom": "/Users/alex/contribflow-work",
"repo": "owner/repo",
"branch": "contribflow/issue-123",
"created": true,
"clean": true,
"logPath": ".contribflow/logs/checkout-owner-repo-123.log",
"lastCommand": "git status --porcelain",
"submodules": {
"status": "requires_initialization",
"paths": ["packages/sdk"],
"reason": "Workspace declares 1 submodule(s) that require initialization.",
"recommendedCommand": "git -C .contribflow/workspaces/owner-repo-123 submodule update --init --recursive"
}
}
When no submodules are declared, submodules.status is none. Public-write dry runs are blocked while declared submodules require initialization.
Validation Result
Default detection-only result:
{
"status": "blocked",
"workspacePath": ".contribflow/workspaces/owner-repo-123",
"setupCommands": [
{
"command": "npm ci --ignore-scripts",
"status": "blocked",
"exitCode": null,
"reason": "Dependency setup detected but not run. Use --run --sandbox docker to execute setup in an isolated sandbox before validation."
}
],
"commands": [
{
"command": "npm test",
"status": "blocked",
"exitCode": null,
"reason": "Command detected but not run. Use --run --sandbox docker to execute validation in an isolated sandbox."
},
{
"command": "npm run typecheck",
"workingDirectory": "examples/demo",
"status": "blocked",
"exitCode": null,
"reason": "Command detected but not run. Use --run --sandbox docker to execute validation in an isolated sandbox."
}
],
"blocksPr": true,
"reason": "Validation commands were detected but not run. PR creation remains blocked.",
"nextAction": "sandbox_validation",
"recommendedCommand": "contribflow validate --run --sandbox docker --json"
}
Explicit Docker execution result:
{
"status": "passed",
"workspacePath": ".contribflow/workspaces/owner-repo-123",
"setupCommands": [
{
"command": "npm ci --ignore-scripts",
"status": "passed",
"exitCode": 0,
"reason": "Dependency setup passed in the Docker sandbox.",
"sandbox": "docker",
"sandboxImage": "node:22-bookworm-slim",
"durationMs": 4321,
"stdout": "install output",
"stderr": ""
}
],
"commands": [
{
"command": "npm test",
"status": "passed",
"exitCode": 0,
"reason": "Validation passed in the Docker sandbox.",
"sandbox": "docker",
"sandboxImage": "node:22-bookworm-slim",
"durationMs": 1234,
"stdout": "test output",
"stderr": ""
},
{
"command": "npm run typecheck",
"workingDirectory": "examples/demo",
"status": "passed",
"exitCode": 0,
"reason": "Validation passed in the Docker sandbox.",
"sandbox": "docker",
"sandboxImage": "node:22-bookworm-slim",
"durationMs": 987,
"stdout": "typecheck output",
"stderr": ""
}
],
"blocksPr": false,
"reason": "All validation commands passed in the Docker sandbox.",
"nextAction": "draft_pr",
"recommendedCommand": "contribflow pr --draft --json"
}
workingDirectory is present when a command belongs to a nested package that owns one or more changed files from the current patch artifact. Nested dependency setup follows the same lockfile safety rules as the repository root.
Failed native dependency example:
{
"status": "failed",
"workspacePath": ".contribflow/workspaces/owner-repo-123",
"commands": [
{
"command": "make test",
"status": "failed",
"exitCode": 1,
"reason": "Validation failed in the Docker sandbox. Likely missing system packages: libx11-dev.",
"sandbox": "docker",
"sandboxImage": "golang:1.26-bookworm",
"suggestedSystemPackages": ["libx11-dev"],
"stderr": "fatal error: X11/Xlib.h: No such file or directory"
}
],
"blocksPr": true,
"reason": "One or more validation commands failed in the Docker sandbox.",
"nextAction": "fix_validation_failures",
"recommendedCommand": "Fix validation failures and rerun: contribflow validate --run --sandbox docker --json"
}
PR Dry-Run Result
{
"status": "dry_run",
"draft": true,
"blocksPr": false,
"requiresHumanApproval": true,
"issue": "owner/repo#123",
"target": "owner/repo",
"branch": "contribflow/issue-123",
"title": "Address owner/repo#123",
"bodyPreview": "## Summary\n- Addresses #123.\n...",
"changedFiles": [
{
"path": "README.md",
"status": "M",
"additions": 2,
"deletions": 0
}
],
"validationStatus": "passed",
"wouldRun": [
"gh auth status",
"git add -A",
"git commit -m \"Address owner/repo#123\"",
"gh pr create --repo owner/repo --draft ..."
],
"aiDisclosure": true,
"reviewGate": {
"state": "ready_for_human_review",
"checks": [
{
"name": "patch_artifact",
"status": "passed",
"reason": "Patch artifact exists with 1 changed file(s)."
},
{
"name": "patch_size",
"status": "passed",
"reason": "Patch size is within config limits: 1/8 files and 2/300 lines."
},
{
"name": "sensitive_files",
"status": "passed",
"reason": "Patch artifact reports no sensitive files."
},
{
"name": "duplicate_content",
"status": "passed",
"reason": "Patch artifact reports no duplicate content warnings."
},
{
"name": "sandbox_validation",
"status": "passed",
"reason": "1 validation command(s) passed in the Docker sandbox."
},
{
"name": "public_commands",
"status": "passed",
"reason": "9 planned command(s) are listed before public GitHub writes."
},
{
"name": "ai_disclosure",
"status": "passed",
"reason": "PR body includes the ContribFlow AI disclosure."
},
{
"name": "pr_dry_run",
"status": "passed",
"reason": "Draft PR dry-run exists and performed no GitHub writes."
}
]
},
"prUrl": null,
"reason": "Draft PR dry-run passed the review gate. Stop for human review before public GitHub writes.",
"nextAction": "human_review",
"recommendedCommand": "contribflow pr --draft --yes --json"
}
Agents must treat reviewGate.state: "ready_for_human_review" as a hard stop. The recommended command performs public GitHub writes and must not run until the user explicitly approves it in the current session.
Approved creation result:
{
"status": "created",
"draft": true,
"blocksPr": false,
"requiresHumanApproval": false,
"issue": "owner/repo#123",
"target": "owner/repo",
"branch": "contribflow/issue-123",
"title": "Address owner/repo#123",
"prUrl": "https://github.com/owner/repo/pull/456",
"validationStatus": "passed",
"aiDisclosure": true,
"reviewGate": {
"state": "ready_for_human_review",
"checks": [
{
"name": "pr_dry_run",
"status": "passed",
"reason": "Draft PR dry-run exists and performed no GitHub writes."
}
]
},
"nextAction": "done",
"recommendedCommand": "https://github.com/owner/repo/pull/456"
}
Patch Artifact
{
"issue": "owner/repo#123",
"workspacePath": ".contribflow/workspaces/owner-repo-123",
"artifactPath": ".contribflow/artifacts/patch-owner-repo-123.json",
"createdAt": "2026-06-04T00:00:00.000Z",
"hasChanges": true,
"summary": "1 changed file detected.",
"changedFiles": [
{
"path": "README.md",
"status": "M",
"additions": 2,
"deletions": 0
}
],
"riskFlags": {
"sensitiveFiles": [],
"duplicateContent": [
{
"path": "README.md",
"addedText": "## Existing setup guide",
"existingLocation": "HEAD:docs/setup.md:12:## Existing setup guide",
"confidence": "medium",
"reason": "Added content already appears in repository history."
}
]
},
"diff": "diff --git a/README.md b/README.md\n...",
"nextAction": "validate",
"recommendedCommand": "contribflow validate --json"
}
Next Recommended Action
{
"state": "ready_for_discovery",
"nextAction": "discover",
"recommendedCommand": "contribflow discover --json",
"requiresHumanApproval": false,
"reason": "No active contribution task exists."
}
Common states include:
needs_initneeds_github_authready_for_discoveryneeds_checkoutready_for_diffneeds_workspace_changesneeds_validationvalidation_blockedvalidation_failedready_for_pr_dry_runready_for_human_reviewpr_review_blockedpr_creation_faileddone
Source markdown: docs/AGENT_PROTOCOL.md