Changelog Loop Workflow Design¶
Workflow and domain design for the changelog loop.
| Layer | Document |
|---|---|
| Platform | Multi-Branch Loops Design |
| Caller shell | Loop Caller Workflows Design |
| Invariants | Loop Engineering Design |
Artifacts: on-loop-changelog.yaml · skill changelog · changelog/scripts/detect_changelog_commits.sh
Shared caller keys: Loop Caller Inputs Reference.
Purpose¶
Maintain Keep a Changelog CHANGELOG.md on integration branches: preserve existing release sections, append to ## [Unreleased], promote undocumented releases detected from git tags and pin/finalize commits, then open one domain-only L2 review PR (CHANGELOG.md only). Loop state advances on merge via on-loop-state-promote.
Supported use cases¶
- Preserve existing
## [x.y.z] - datesections and formatting - Create a Keep a Changelog template when
CHANGELOG.mdis missing, then populate## [Unreleased] - Ingest Conventional Commits and other explicit prefixed subjects (for example
renovate(scope):,chore(deps):) - Promote detect
releases[]into## [x.y.z] - datesections (from git tags and pin/finalize subjects) - Add commit links when
repository_urlis resolved (GitHub ActionsGITHUB_*or git remote; optionalCHANGELOG_REPOSITORY_URLoverride) - Open an L2 review PR to the watch integration branch; L3 enables GitHub auto-merge on that fix PR. Set
delivery: open_pron the caller (git landing is derived insideloop-detect).
Out of scope¶
- Creating git tags (tags are inputs to detect; the loop documents them in
CHANGELOG.mdonly) - PR head mode (
pr_enableddefault off) — changelog updates target integration branches only - Commits without a clear
prefix: descriptionorprefix(scope): descriptionshape - Maker edits to loop state (finalize bundles state after verification)
Skill execution boundaries: changelog SKILL.md (USE FOR / DO NOT USE FOR).
User-facing invariants¶
| Invariant | Rationale |
|---|---|
| One review PR (domain only) | Reviewers judge CHANGELOG.md only; loop state advances on merge via on-loop-state-promote |
| No orphan state PRs | Workflow concurrency (loop-state-main) serializes with peer loops; merge-gated pending lands on branch_state |
| Release sections from facts | Skill may add ## [version] - date only for versions in detect releases[] — never invented versions |
Modes¶
| Mode | Default | Behavior |
|---|---|---|
integration |
on | Detect on watch branch → fix PR to same branch |
pull_request |
off | not supported for this loop |
Caller inputs¶
Keys are passed in on-loop-changelog.yaml via with: on ci-loop-caller.yaml (alphabetically ordered). Multiline values (agent_checker_instructions, agent_maker_instructions) are defined inline in the caller workflow.
Shared semantics: Loop Caller Inputs Reference. Platform branch/finalize caps: canonical table.
| Input / JSON key | Description | Dogfood value |
|---|---|---|
agent_maker_max_turns |
Max maker agent turns per loop attempt (one Agent→Verify cycle). | 100 |
agent_maker_model |
Maker model ID. Cursor: agent --list-models. |
claude-sonnet-5 |
agent_maker_effort |
Maker reasoning effort. Only engines with an effort flag (claude) consume it | medium |
agent_loop_max_attempts |
Max Agent→Verify retry cycles before finalize records failure. | 3 |
agent_checker_instructions |
Checker rubric: Unreleased mapping, detect releases[] promotion, no hallucinated versions. |
Inline in caller workflow |
agent_checker_max_turns |
Max checker agent turns per verification. | 100 |
agent_checker_model |
Checker model ID. Cursor: agent --list-models. |
claude-opus-5 |
agent_checker_effort |
Checker reasoning effort. Only engines with an effort flag (claude) consume it | medium |
allowlist |
Comma-separated globs the maker may modify. Enforced in loop-execute. |
CHANGELOG.md |
branch_match |
Comma-separated branch patterns to watch for changelog drift. | main |
branch_state |
Branch for run-log/budget persistence and state read baseline. | main |
budget_max_runs_per_day |
Daily run cap keyed by loop_name. Caller input; .loop/loop-budget.json overrides when present. Exceeded → skip_reason=budget. |
1 (caller); effective 5 via .loop/loop-budget.json |
budget_max_tokens_per_day |
Daily aggregated token cap across loops. | 1000000 |
detect_script |
Domain detect script path. Invoked once per scan context by loop-detect. |
.agents/skills/changelog/scripts/detect_changelog_commits.sh |
engine |
AI engine (claude, copilot, codex, cursor). Maps AGENT_TOKEN to engine env. |
claude |
infer_files_pattern |
Extended regex to infer file paths from checker text for allowlist checks. | CHANGELOG\.md |
level |
Autonomy: L2 human merge on bot fix PR; L3 GitHub auto-merge on bot fix PR. |
L2 |
loop_name |
Loop identifier; state file .loop/state-changelog.json. Align workflow name on-loop-<loop_name>.yaml. |
changelog |
max_targets_per_schedule |
Max targets per cron tick after priority filters. | 3 |
no_changes_verdict |
APPROVE or REJECT when maker produces no file diff. |
REJECT |
pr_body |
Optional static prefix (dogfood: ""). loop-finalize composes agent Overview/Summary + mechanical sections. See Loop PR Body Readable Design. |
"" |
pr_title |
PR title when finalize strategy is open_pr. |
chore(changelog): update CHANGELOG.md (loop-changelog) |
agent_maker_instructions |
Domain instructions appended to maker prompt by loop-detect. |
Inline in caller workflow |
pr_enabled |
Enumerate open PR heads. Changelog uses integration branches only. | false |
agent_maker_skill_name |
Skill package to invoke. Must match .agents/skills/changelog/. |
changelog |
Domain detect environment (detect_domain_env_json)¶
detect_domain_env_json: >-
{"CHANGELOG_FILE":"CHANGELOG.md","CHANGELOG_MERGE_COMMITS":"false"}
| JSON key | Description | Dogfood value |
|---|---|---|
CHANGELOG_FILE |
Target changelog path | CHANGELOG.md |
CHANGELOG_MAX_COMMITS |
Max commits for --scope all (local debugging) |
100 |
CHANGELOG_MERGE_COMMITS |
"true" includes merge commits; "false" applies --no-merges |
"false" |
Platform handler: on-loop-state-promote.yaml (pull_request_target closed) promotes pending → last_sha on merge.
Detect¶
Integration mode only¶
Per watch branch, loop-detect checks out the branch and invokes detect_changelog_commits.sh with targets["integration:<branch>"].last_sha.
Detect script outputs facts (not formatted changelog prose):
| Field | Role |
|---|---|
changelog_file |
Target path (default CHANGELOG.md) |
changelog_exists |
Whether the changelog file already exists on the scanned branch |
commit_range |
Active git range label |
commits |
Changelog-worthy commits (sha, type, subject, …) |
releases |
Undocumented versions from tags and pin/finalize subjects (version, tag, tag_sha, date, commit_shas) |
compare_url |
Optional GitHub compare URL for commit_range (empty when unknown) |
repository |
owner/repo when resolved |
repository_url |
Web base for commit links (GITHUB_*, git remote, or override) |
skip |
true when no unreleased commits and no undocumented releases |
Skill (changelog) creates the Keep a Changelog template when changelog_exists is false, groups commits under ## [Unreleased], and promotes releases[] into versioned sections.
loop-detect emits per-branch target_json:
from.ref= HEAD on watch branchto.branch= watch branchfinalize=open_pr
Stable filters (detect only)¶
- Explicit
prefix: descriptionorprefix(scope): descriptionsubjects - Conventional types (
feat:,fix:,chore:, …) and tool prefixes (renovate,dependabot, …) - Skip loop maintenance commits (
chore(changelog):, subjects containing(loop-changelog)) - Release detection: semver git tags (
v*.*.*) and pin/finalize/align subjects in range not yet inCHANGELOG.md - Optional
--no-mergeswhenCHANGELOG_MERGE_COMMITSisfalse --scope allis bounded byCHANGELOG_MAX_COMMITS(default 100) for local debugging only- Circuit breaker on
targets[key].consecutive_failures - Budget / workflow
concurrency(platform)
State fields (per target key)¶
| Field | Role |
|---|---|
last_sha |
Scan cursor; advances when fix PR merges (on-loop-state-promote) |
pending |
Written at finalize (pr-created); holds { sha, pr, … } until merge |
outcome |
pr-created, rejected, no-op, … |
consecutive_failures |
Circuit breaker |
Execute¶
- Worktree from
target.fromon integration branch - Checker diff baseline:
to.branch verifier_context: detect commit and release summarySet Acting Onduring execute (coordination only; no state PR)
Finalize¶
PR body is composed by loop-finalize from agent ## Overview / ## Summary (skill-owned) plus mechanical sections. Dogfood sets pr_body: "". See Loop PR Body Skill Contract.
Always open_pr to to.branch at L2.
- Optional domain persistence (none for changelog)
- Create PR (
CHANGELOG.mdonly) - Write state to
branch_statewithpending(merge-gated;last_shaunchanged) - Append run log to
branch_state
On REJECT, state writes failure metadata to branch_state without advancing last_sha.
On fix PR merge, on-loop-state-promote promotes pending.sha → last_sha.
No domain_persistence_script.
Implementation Checklist¶
Shared platform: Multi-Branch — Shared platform checklist.
Loop-specific¶
- [x]
changelog/scripts/detect_changelog_commits.sh(facts output) - [x]
on-loop-changelog.yamldogfood caller viaci-loop-caller - [x]
verifier_contexton execute path (build_verifier_context_from_result.commitsbranch) - [x]
detect_changelog_commits.shemitsreleases[]for tag-scoped entries - [x] Dogfood action pins @ v1.8.47
Cross-Loop Note¶
Changelog runs are doc-metadata only (CHANGELOG.md). Coordinate with docs-updater via workflow concurrency when both target integration:main.