Context
The retro agent (Phase 5 in the multi-forge work breakdown) needs GitLab support. The triage agent (Phase 1) is already multi-forge on main. The code agent (PR #813) and review agent (PR #815) are in-progress. The retro agent is independent of both — no shared library deps, standalone scripts — so it can proceed in parallel.
The upstream runtime (fullsend-ai/fullsend) already supports ResolveForge(platform) which merges forge.<platform> sections into top-level harness fields. No runtime changes are needed.
Goal: The retro agent harness runs on both GitHub and GitLab, dispatching forge-specific operations via FULLSEND_FORGE env var.
Patterns to Follow
Established by triage (merged), code (#813), and review (#815):
- Three-file lib pattern:
{agent}-ops.lib.sh (dispatcher) + github-{agent}-ops.lib.sh + gitlab-{agent}-ops.lib.sh. All forge functions prefixed forge_*. Dispatcher uses include guard + case on FULLSEND_FORGE.
- Same pre/post scripts for both forges. Scripts source the dispatcher lib and call
forge_* functions. No if github/if gitlab in the main script — the dispatch is inside the ops lib.
.src.sh source files bundled via make script-build. The .sh files are generated — # GENERATED from <name>.src.sh -- DO NOT EDIT.
- Harness:
forge.github and forge.gitlab sections with per-forge policy, skills, host_files, env (runner + sandbox). Top-level retains forge-neutral fields only.
- Env normalization: Harness
env sections map forge-specific input vars to uniform names (ORIGINATING_URL, FULLSEND_FORGE).
- Policy split:
policies/github/{agent}.yaml and policies/gitlab/{agent}.yaml. GitHub allows gh not curl; GitLab allows curl not gh. Both allow node.
- Skills split: Agent-specific skills with forge-specific CLI recipes in subdirs (
skills/{skill}/github/SKILL.md, skills/{skill}/gitlab/SKILL.md). Shared methodology stays at top level.
- GitLab
_gitlab_api() helper: Each agent's GitLab ops lib defines a private _gitlab_api() curl wrapper with timeouts, PRIVATE-TOKEN header, error handling.
- GitLab host validation: Allowed hosts are
gitlab.com and gitlab.cee.redhat.com.
_gha_sanitize(): Defined in the dispatcher lib (triage and review do this; code does not). Retro should include it since post-retro.sh uses GHA workflow commands.
Files to Create
1. scripts/lib/retro-ops.lib.sh — Forge dispatcher (~25 lines)
Include guard (RETRO_OPS_SH_LOADED), _gha_sanitize(), case switch on FULLSEND_FORGE sourcing the correct forge ops lib. Copy the pattern from scripts/lib/triage-ops.lib.sh.
2. scripts/lib/github-retro-ops.lib.sh — GitHub forge ops
Extract from current post-retro.sh. Functions needed:
forge_validate_originating_url — regex validates https://github.com/.../issues|pull/N
forge_parse_originating_url — sets ORIGINATING_REPO, ORIGINATING_NUMBER
forge_create_label "$repo" "$name" "$description" "$color" — gh label create
forge_create_issue "$repo" "$title" "$body" "$label" — gh issue create --label, returns URL
forge_post_comment "$repo" "$number" "$body" — gh api repos/.../issues/.../comments
forge_mask_token — echo "::add-mask::${GH_TOKEN}"
forge_get_config_workspace — returns ${GITHUB_WORKSPACE:-/tmp}
forge_get_comment_max_len — returns 65000 (GitHub limit)
3. scripts/lib/gitlab-retro-ops.lib.sh — GitLab forge ops
Same interface, using curl + GitLab REST API:
_gitlab_api() — private curl wrapper with PRIVATE-TOKEN: ${GITLAB_TOKEN}, timeouts
forge_validate_originating_url — validates GitLab issue/MR URL pattern + allowed hosts
forge_parse_originating_url — extracts GITLAB_HOST, ORIGINATING_REPO, REPO_ENCODED, ORIGINATING_NUMBER from GitLab URL (handles /-/issues/N and /-/merge_requests/N)
forge_create_label — POST /projects/:id/labels (idempotent via || true)
forge_create_issue — POST /projects/:id/issues with labels=, returns web_url
forge_post_comment — detects issue vs MR from URL, posts to /issues/:iid/notes or /merge_requests/:iid/notes
forge_mask_token — no-op or GitLab CI equivalent (echo "::add-mask::" is GHA-only, harmless on other runners)
forge_get_config_workspace — returns ${CI_PROJECT_DIR:-/tmp}
forge_get_comment_max_len — returns 1000000 (GitLab limit is ~1MB)
4. scripts/pre-retro.src.sh — Source file for pre-retro (~35 lines)
Convert current pre-retro.sh to .src.sh. Add:
FULLSEND_FORGE required env var
- Source
retro-ops.lib.sh
- Replace hardcoded github.com regex with
forge_validate_originating_url
- Use
_gha_sanitize() for the ::notice:: output
5. scripts/post-retro.src.sh — Source file for post-retro
Convert current post-retro.sh to .src.sh. Replace all GitHub-specific calls:
- Token handling:
forge_mask_token instead of echo "::add-mask::${GH_TOKEN}"
- URL parsing:
forge_validate_originating_url + forge_parse_originating_url instead of inline regex
- Config workspace:
forge_get_config_workspace instead of ${GITHUB_WORKSPACE:-/tmp}
- Label creation:
forge_create_label instead of gh label create
- Issue creation:
forge_create_issue instead of gh issue create
- Comment posting:
forge_post_comment instead of gh api .../comments
- Comment truncation:
forge_get_comment_max_len instead of hardcoded 65000
- GHA workflow commands (
::warning::): wrap in _gha_sanitize or emit conditionally
- Remove
GH_TOKEN required check — replaced with forge-aware token check in ops lib
- Keep all business logic unchanged: evidence-for filtering, allowlist gates, proposal validation, comment assembly
6. policies/gitlab/retro.yaml
Based on policies/retro.yaml (which becomes policies/github/retro.yaml). Changes:
- Replace
github_api with gitlab_api: allow gitlab.com, gitlab.cee.redhat.com on port 443
- Binary allowlist:
curl, node (no gh)
- Replace
github_artifacts with gitlab_artifacts: allow GitLab CI artifact hosts (or remove if not needed — GitLab CI artifacts may use different endpoints)
- Keep Vertex AI section unchanged
7. env/github/retro.env (~5 lines)
Move content from env/retro.env, add FULLSEND_FORGE=github.
8. env/gitlab/retro.env (~5 lines)
export ORIGINATING_URL="${ORIGINATING_URL}"
export RETRO_COMMENT="${RETRO_COMMENT:-}"
export REPO_FULL_NAME="${REPO_FULL_NAME}"
export GITLAB_TOKEN="${GITLAB_TOKEN}"
export FULLSEND_FORGE=gitlab
9. Skills — retro-analysis split
skills/retro-analysis/SKILL.md — Keep shared methodology only:
- Exploration strategy (subagent dispatch patterns)
- Test flakiness detection and what to propose
- Duplicate checking guidance (make forge-neutral: "use your forge skill's search commands")
- Localization guidance
- Output format
- Writing good proposals
- Remove all
gh run list, gh run view, gh run download, gh api "search/issues" recipes
skills/retro-analysis/github/SKILL.md — GitHub CLI recipes:
- Workflow tracing:
gh run list, gh run view, gh run download
- Duplicate search:
gh api "search/issues?q=..."
- Agents repo discovery from run logs
skills/retro-analysis/gitlab/SKILL.md — GitLab CLI recipes:
- Pipeline tracing:
curl to /projects/:id/pipelines, /projects/:id/pipelines/:id/jobs
- Job logs:
curl to /projects/:id/jobs/:id/trace
- Artifact download:
curl to /projects/:id/jobs/:id/artifacts
- Duplicate search:
curl to /projects/:id/issues?search=...
- Environment setup: derive
GITLAB_HOST, REPO_ENCODED from REPO_FULL_NAME
10. Skills — finding-agent-runs split
skills/finding-agent-runs/SKILL.md — Keep shared methodology only:
- Dispatch repo pattern (
${ORG}/.fullsend)
- Issue → agent runs flow (conceptual)
- PR → agent runs flow (conceptual)
- Common failure signatures table
- Remove all
gh run list, gh run view, gh run download recipes
skills/finding-agent-runs/github/SKILL.md — GitHub CLI recipes:
- All current
gh run list --workflow=... commands
gh run view <RUN_ID> for job outcomes and logs
gh run download <RUN_ID> for artifacts
skills/finding-agent-runs/gitlab/SKILL.md — GitLab CLI recipes:
- Pipeline listing:
curl to /projects/:id/pipelines?ref=main
- Job listing:
curl to /projects/:id/pipelines/:id/jobs
- Job logs:
curl to /projects/:id/jobs/:id/trace
- Artifact download:
curl to /projects/:id/jobs/:id/artifacts
Files to Move/Rename
policies/retro.yaml → policies/github/retro.yaml (add comment about curl exclusion)
env/retro.env → env/github/retro.env (add FULLSEND_FORGE=github)
Files to Modify
harness/retro.yaml
- Remove top-level
policy: (moves into forge sections)
- Remove top-level
pre_script: / post_script: (moves into forge sections)
- Update
skills: to retain only forge-neutral skills at top level: agent-scaffolding, autonomy-readiness
- Add forge-specific skills to each forge section
- Expand
forge.github with: policy, skills (add github-forge, retro-analysis/github, finding-agent-runs/github), host_files (with env/github/retro.env), env.sandbox
- Add
forge.gitlab with: policy, pre_script, post_script, skills (add gitlab-forge, retro-analysis/gitlab, finding-agent-runs/gitlab), host_files (with env/gitlab/retro.env), env (runner + sandbox with GITLAB_TOKEN, FULLSEND_FORGE: gitlab)
- Add
FULLSEND_FORGE to both forge env blocks
agents/retro.md
- Line 6: "GitHub issues" → "issues" in description
- Line 128: "GitHub mutations" → "forge mutations"
- Add
curl to tools list: tools: Bash(gh,curl,jq,yq)
- Add
FULLSEND_FORGE as an input
schemas/retro-result.schema.json
- Line 19: "Each becomes a GitHub issue." → "Each becomes an issue on the source forge."
target_repo pattern: relax from ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ (two segments) to ^[a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)+$ (one or more /-separated segments, supports GitLab subgroups)
docs/retro.md
- "GitHub issues" → "issues" (lines 4, 71)
Makefile
- Add
scripts/pre-retro.src.sh and scripts/post-retro.src.sh to BUNDLE_SRCS
scripts/post-retro-test.sh
- Update existing GitHub tests to set
FULLSEND_FORGE=github and use ORIGINATING_URL
- Add GitLab test section (~200 lines): mock
curl, set FULLSEND_FORGE=gitlab, GITLAB_TOKEN, GitLab ORIGINATING_URL, verify no gh calls, test issue creation flow, test comment posting flow, test evidence-for filtering on GitLab
Implementation Order
- Create lib files:
retro-ops.lib.sh, github-retro-ops.lib.sh, gitlab-retro-ops.lib.sh
- Create
.src.sh source files from current scripts, source the ops lib, replace gh calls with forge_*
- Move
policies/retro.yaml → policies/github/retro.yaml, create policies/gitlab/retro.yaml
- Move
env/retro.env → env/github/retro.env, create env/gitlab/retro.env
- Split
skills/retro-analysis/ and skills/finding-agent-runs/ into shared + forge subdirs
- Update
harness/retro.yaml with forge.gitlab section
- Update
agents/retro.md, schemas/retro-result.schema.json, docs/retro.md
- Update
Makefile with new BUNDLE_SRCS
- Run
make script-build to generate bundled .sh files
- Update
scripts/post-retro-test.sh with GitLab tests
- Run
make script-test to verify all tests pass
Verification
bash scripts/post-retro-test.sh — all existing GitHub tests pass
- New GitLab test cases pass (mock
curl, FULLSEND_FORGE=gitlab)
make script-build succeeds (bundled .sh files generated)
make script-test passes
- Schema validates both GitHub and GitLab
target_repo patterns
Context
The retro agent (Phase 5 in the multi-forge work breakdown) needs GitLab support. The triage agent (Phase 1) is already multi-forge on main. The code agent (PR #813) and review agent (PR #815) are in-progress. The retro agent is independent of both — no shared library deps, standalone scripts — so it can proceed in parallel.
The upstream runtime (
fullsend-ai/fullsend) already supportsResolveForge(platform)which mergesforge.<platform>sections into top-level harness fields. No runtime changes are needed.Goal: The retro agent harness runs on both GitHub and GitLab, dispatching forge-specific operations via
FULLSEND_FORGEenv var.Patterns to Follow
Established by triage (merged), code (#813), and review (#815):
{agent}-ops.lib.sh(dispatcher) +github-{agent}-ops.lib.sh+gitlab-{agent}-ops.lib.sh. All forge functions prefixedforge_*. Dispatcher uses include guard +caseonFULLSEND_FORGE.forge_*functions. Noif github/if gitlabin the main script — the dispatch is inside the ops lib..src.shsource files bundled viamake script-build. The.shfiles are generated —# GENERATED from <name>.src.sh -- DO NOT EDIT.forge.githubandforge.gitlabsections with per-forgepolicy,skills,host_files,env(runner + sandbox). Top-level retains forge-neutral fields only.envsections map forge-specific input vars to uniform names (ORIGINATING_URL,FULLSEND_FORGE).policies/github/{agent}.yamlandpolicies/gitlab/{agent}.yaml. GitHub allowsghnotcurl; GitLab allowscurlnotgh. Both allownode.skills/{skill}/github/SKILL.md,skills/{skill}/gitlab/SKILL.md). Shared methodology stays at top level._gitlab_api()helper: Each agent's GitLab ops lib defines a private_gitlab_api()curl wrapper with timeouts,PRIVATE-TOKENheader, error handling.gitlab.comandgitlab.cee.redhat.com._gha_sanitize(): Defined in the dispatcher lib (triage and review do this; code does not). Retro should include it sincepost-retro.shuses GHA workflow commands.Files to Create
1.
scripts/lib/retro-ops.lib.sh— Forge dispatcher (~25 lines)Include guard (
RETRO_OPS_SH_LOADED),_gha_sanitize(), case switch onFULLSEND_FORGEsourcing the correct forge ops lib. Copy the pattern fromscripts/lib/triage-ops.lib.sh.2.
scripts/lib/github-retro-ops.lib.sh— GitHub forge opsExtract from current
post-retro.sh. Functions needed:forge_validate_originating_url— regex validateshttps://github.com/.../issues|pull/Nforge_parse_originating_url— setsORIGINATING_REPO,ORIGINATING_NUMBERforge_create_label "$repo" "$name" "$description" "$color"—gh label createforge_create_issue "$repo" "$title" "$body" "$label"—gh issue create --label, returns URLforge_post_comment "$repo" "$number" "$body"—gh api repos/.../issues/.../commentsforge_mask_token—echo "::add-mask::${GH_TOKEN}"forge_get_config_workspace— returns${GITHUB_WORKSPACE:-/tmp}forge_get_comment_max_len— returns65000(GitHub limit)3.
scripts/lib/gitlab-retro-ops.lib.sh— GitLab forge opsSame interface, using
curl+ GitLab REST API:_gitlab_api()— private curl wrapper withPRIVATE-TOKEN: ${GITLAB_TOKEN}, timeoutsforge_validate_originating_url— validates GitLab issue/MR URL pattern + allowed hostsforge_parse_originating_url— extractsGITLAB_HOST,ORIGINATING_REPO,REPO_ENCODED,ORIGINATING_NUMBERfrom GitLab URL (handles/-/issues/Nand/-/merge_requests/N)forge_create_label—POST /projects/:id/labels(idempotent via|| true)forge_create_issue—POST /projects/:id/issueswithlabels=, returnsweb_urlforge_post_comment— detects issue vs MR from URL, posts to/issues/:iid/notesor/merge_requests/:iid/notesforge_mask_token— no-op or GitLab CI equivalent (echo "::add-mask::"is GHA-only, harmless on other runners)forge_get_config_workspace— returns${CI_PROJECT_DIR:-/tmp}forge_get_comment_max_len— returns1000000(GitLab limit is ~1MB)4.
scripts/pre-retro.src.sh— Source file for pre-retro (~35 lines)Convert current
pre-retro.shto.src.sh. Add:FULLSEND_FORGErequired env varretro-ops.lib.shforge_validate_originating_url_gha_sanitize()for the::notice::output5.
scripts/post-retro.src.sh— Source file for post-retroConvert current
post-retro.shto.src.sh. Replace all GitHub-specific calls:forge_mask_tokeninstead ofecho "::add-mask::${GH_TOKEN}"forge_validate_originating_url+forge_parse_originating_urlinstead of inline regexforge_get_config_workspaceinstead of${GITHUB_WORKSPACE:-/tmp}forge_create_labelinstead ofgh label createforge_create_issueinstead ofgh issue createforge_post_commentinstead ofgh api .../commentsforge_get_comment_max_leninstead of hardcoded65000::warning::): wrap in_gha_sanitizeor emit conditionallyGH_TOKENrequired check — replaced with forge-aware token check in ops lib6.
policies/gitlab/retro.yamlBased on
policies/retro.yaml(which becomespolicies/github/retro.yaml). Changes:github_apiwithgitlab_api: allowgitlab.com,gitlab.cee.redhat.comon port 443curl,node(nogh)github_artifactswithgitlab_artifacts: allow GitLab CI artifact hosts (or remove if not needed — GitLab CI artifacts may use different endpoints)7.
env/github/retro.env(~5 lines)Move content from
env/retro.env, addFULLSEND_FORGE=github.8.
env/gitlab/retro.env(~5 lines)9. Skills —
retro-analysissplitskills/retro-analysis/SKILL.md— Keep shared methodology only:gh run list,gh run view,gh run download,gh api "search/issues"recipesskills/retro-analysis/github/SKILL.md— GitHub CLI recipes:gh run list,gh run view,gh run downloadgh api "search/issues?q=..."skills/retro-analysis/gitlab/SKILL.md— GitLab CLI recipes:curlto/projects/:id/pipelines,/projects/:id/pipelines/:id/jobscurlto/projects/:id/jobs/:id/tracecurlto/projects/:id/jobs/:id/artifactscurlto/projects/:id/issues?search=...GITLAB_HOST,REPO_ENCODEDfromREPO_FULL_NAME10. Skills —
finding-agent-runssplitskills/finding-agent-runs/SKILL.md— Keep shared methodology only:${ORG}/.fullsend)gh run list,gh run view,gh run downloadrecipesskills/finding-agent-runs/github/SKILL.md— GitHub CLI recipes:gh run list --workflow=...commandsgh run view <RUN_ID>for job outcomes and logsgh run download <RUN_ID>for artifactsskills/finding-agent-runs/gitlab/SKILL.md— GitLab CLI recipes:curlto/projects/:id/pipelines?ref=maincurlto/projects/:id/pipelines/:id/jobscurlto/projects/:id/jobs/:id/tracecurlto/projects/:id/jobs/:id/artifactsFiles to Move/Rename
policies/retro.yaml→policies/github/retro.yaml(add comment aboutcurlexclusion)env/retro.env→env/github/retro.env(addFULLSEND_FORGE=github)Files to Modify
harness/retro.yamlpolicy:(moves into forge sections)pre_script:/post_script:(moves into forge sections)skills:to retain only forge-neutral skills at top level:agent-scaffolding,autonomy-readinessforge.githubwith:policy,skills(addgithub-forge,retro-analysis/github,finding-agent-runs/github),host_files(withenv/github/retro.env),env.sandboxforge.gitlabwith:policy,pre_script,post_script,skills(addgitlab-forge,retro-analysis/gitlab,finding-agent-runs/gitlab),host_files(withenv/gitlab/retro.env),env(runner + sandbox withGITLAB_TOKEN,FULLSEND_FORGE: gitlab)FULLSEND_FORGEto both forge env blocksagents/retro.mdcurlto tools list:tools: Bash(gh,curl,jq,yq)FULLSEND_FORGEas an inputschemas/retro-result.schema.jsontarget_repopattern: relax from^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$(two segments) to^[a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)+$(one or more/-separated segments, supports GitLab subgroups)docs/retro.mdMakefilescripts/pre-retro.src.shandscripts/post-retro.src.shtoBUNDLE_SRCSscripts/post-retro-test.shFULLSEND_FORGE=githuband useORIGINATING_URLcurl, setFULLSEND_FORGE=gitlab,GITLAB_TOKEN, GitLabORIGINATING_URL, verify noghcalls, test issue creation flow, test comment posting flow, test evidence-for filtering on GitLabImplementation Order
retro-ops.lib.sh,github-retro-ops.lib.sh,gitlab-retro-ops.lib.sh.src.shsource files from current scripts, source the ops lib, replaceghcalls withforge_*policies/retro.yaml→policies/github/retro.yaml, createpolicies/gitlab/retro.yamlenv/retro.env→env/github/retro.env, createenv/gitlab/retro.envskills/retro-analysis/andskills/finding-agent-runs/into shared + forge subdirsharness/retro.yamlwithforge.gitlabsectionagents/retro.md,schemas/retro-result.schema.json,docs/retro.mdMakefilewith newBUNDLE_SRCSmake script-buildto generate bundled.shfilesscripts/post-retro-test.shwith GitLab testsmake script-testto verify all tests passVerification
bash scripts/post-retro-test.sh— all existing GitHub tests passcurl,FULLSEND_FORGE=gitlab)make script-buildsucceeds (bundled.shfiles generated)make script-testpassestarget_repopatterns