Skip to content

feat: make retro agent multi-forge (GitHub + GitLab) #816

Description

@ggallen

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):

  1. 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.
  2. 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.
  3. .src.sh source files bundled via make script-build. The .sh files are generated — # GENERATED from <name>.src.sh -- DO NOT EDIT.
  4. Harness: forge.github and forge.gitlab sections with per-forge policy, skills, host_files, env (runner + sandbox). Top-level retains forge-neutral fields only.
  5. Env normalization: Harness env sections map forge-specific input vars to uniform names (ORIGINATING_URL, FULLSEND_FORGE).
  6. Policy split: policies/github/{agent}.yaml and policies/gitlab/{agent}.yaml. GitHub allows gh not curl; GitLab allows curl not gh. Both allow node.
  7. 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.
  8. GitLab _gitlab_api() helper: Each agent's GitLab ops lib defines a private _gitlab_api() curl wrapper with timeouts, PRIVATE-TOKEN header, error handling.
  9. GitLab host validation: Allowed hosts are gitlab.com and gitlab.cee.redhat.com.
  10. _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_tokenecho "::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_labelPOST /projects/:id/labels (idempotent via || true)
  • forge_create_issuePOST /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.yamlpolicies/github/retro.yaml (add comment about curl exclusion)
  • env/retro.envenv/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

  1. Create lib files: retro-ops.lib.sh, github-retro-ops.lib.sh, gitlab-retro-ops.lib.sh
  2. Create .src.sh source files from current scripts, source the ops lib, replace gh calls with forge_*
  3. Move policies/retro.yamlpolicies/github/retro.yaml, create policies/gitlab/retro.yaml
  4. Move env/retro.envenv/github/retro.env, create env/gitlab/retro.env
  5. Split skills/retro-analysis/ and skills/finding-agent-runs/ into shared + forge subdirs
  6. Update harness/retro.yaml with forge.gitlab section
  7. Update agents/retro.md, schemas/retro-result.schema.json, docs/retro.md
  8. Update Makefile with new BUNDLE_SRCS
  9. Run make script-build to generate bundled .sh files
  10. Update scripts/post-retro-test.sh with GitLab tests
  11. Run make script-test to verify all tests pass

Verification

  1. bash scripts/post-retro-test.sh — all existing GitHub tests pass
  2. New GitLab test cases pass (mock curl, FULLSEND_FORGE=gitlab)
  3. make script-build succeeds (bundled .sh files generated)
  4. make script-test passes
  5. Schema validates both GitHub and GitLab target_repo patterns

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions