Skip to content

Retro Agent Implementation Plan ​

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a retrospective agent that analyzes completed or in-progress agent workflows and proposes improvements by filing GitHub issues.

Architecture: The retro agent follows the same harness/sandbox/runtime/post-script pattern as all other agents (ADR 0024). It is triggered by PR close events (automatic) and /fs-retro commands (on-demand). The agent explores workflow history at runtime using gh CLI and the finding-agent-runs skill, then writes structured proposal files that the post-script converts into GitHub issues and summary comments.

Tech Stack: Bash (pre/post scripts), YAML (harness config, workflow, policy), JSON Schema (output validation), Markdown (agent definition), GitHub Actions (dispatch workflow)


File Structure ​

New files in internal/scaffold/fullsend-repo/ ​

FileResponsibility
agents/retro.mdAgent system prompt — role, optimization goals, exploration instructions, output format
harness/retro.yamlHarness config — links agent, policy, scripts, skills, env
policies/retro.yamlSandbox policy — read-only filesystem, network access for GitHub API
env/retro.envEnvironment variables injected into sandbox
scripts/pre-retro.shMinimal pre-script — validates inputs, writes trigger context to agent_input
scripts/post-retro.shPost-script — reads proposal files, files GitHub issues, posts summary comment
schemas/retro-result.schema.jsonJSON Schema for retro agent output
.github/workflows/retro.ymlDispatch target workflow — triggered by dispatcher for stage=retro
skills/retro-analysis/SKILL.mdSkill teaching the retro agent how to analyze workflows and write proposals

Modified files ​

FileChange
internal/scaffold/fullsend-repo/templates/shim-workflow.yamlAdd dispatch-retro and dispatch-retro-command jobs
internal/scaffold/fullsend-repo/.github/workflows/dispatch.ymlUpdate description to include "retro" in stage list

Task 1: Retro agent output schema ​

Files:

  • Create: internal/scaffold/fullsend-repo/schemas/retro-result.schema.json

The output schema defines what the retro agent must produce. The post-script depends on this structure, so it comes first.

  • [ ] Step 1: Create the output schema
json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "retro-result.schema.json",
  "title": "Retro Agent Result",
  "description": "Structured output from the retro agent, validated by the harness before the post-script runs (ADR 0022).",
  "type": "object",
  "additionalProperties": false,
  "required": ["summary", "proposals"],
  "properties": {
    "summary": {
      "type": "string",
      "minLength": 1,
      "maxLength": 16384,
      "description": "Markdown summary to post as a comment on the originating PR/issue. Links to each filed proposal issue."
    },
    "proposals": {
      "type": "array",
      "items": { "$ref": "#/$defs/proposal" },
      "description": "List of improvement proposals. Each becomes a GitHub issue."
    }
  },
  "$defs": {
    "proposal": {
      "type": "object",
      "additionalProperties": false,
      "required": ["target_repo", "title", "what_happened", "what_could_go_better", "proposed_change", "validation_criteria"],
      "properties": {
        "target_repo": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$",
          "description": "Full owner/repo where the issue should be filed."
        },
        "title": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "description": "Concise issue title."
        },
        "what_happened": {
          "type": "string",
          "minLength": 1,
          "description": "Timeline of events with links to logs, PR comments, and agent runs."
        },
        "what_could_go_better": {
          "type": "string",
          "minLength": 1,
          "description": "Improvement opportunities with honest uncertainty assessment."
        },
        "proposed_change": {
          "type": "string",
          "minLength": 1,
          "description": "What to change and where, specific enough for an implementer to act on."
        },
        "validation_criteria": {
          "type": "string",
          "minLength": 1,
          "description": "How to know the change had the desired effect. Measurable or observable outcomes."
        }
      }
    }
  }
}

Write this to internal/scaffold/fullsend-repo/schemas/retro-result.schema.json.

  • [ ] Step 2: Validate the schema is well-formed

Run: python3 -c "import json; json.load(open('internal/scaffold/fullsend-repo/schemas/retro-result.schema.json'))" Expected: No output (valid JSON)

  • [ ] Step 3: Commit
bash
git add internal/scaffold/fullsend-repo/schemas/retro-result.schema.json
git commit -m "feat(retro): add output schema for retro agent"

Task 2: Sandbox policy ​

Files:

  • Create: internal/scaffold/fullsend-repo/policies/retro.yaml

The retro agent needs read-only filesystem access and network access for gh CLI (GitHub API). Same pattern as policies/triage.yaml.

  • [ ] Step 1: Create the policy file
yaml
version: 1

filesystem_policy:
  include_workdir: true
  read_only: [/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log]
  read_write: [/sandbox, /tmp, /dev/null]
landlock:
  compatibility: best_effort
process:
  run_as_user: sandbox
  run_as_group: sandbox

network_policies:
  vertex_ai:
    name: vertex-ai
    endpoints:
      - host: "*.github.com"
        port: 443
        protocol: tcp
        enforcement: enforce
        access: allow
      - host: "*.googleapis.com"
        port: 443
        protocol: tcp
        enforcement: enforce
        access: allow
    binaries:
      - path: "**/curl"
      - path: "**/claude"
      - path: "**/node"

Write this to internal/scaffold/fullsend-repo/policies/retro.yaml.

  • [ ] Step 2: Commit
bash
git add internal/scaffold/fullsend-repo/policies/retro.yaml
git commit -m "feat(retro): add sandbox policy for retro agent"

Task 3: Environment file ​

Files:

  • Create: internal/scaffold/fullsend-repo/env/retro.env

The retro agent needs the originating URL, comment text (if /fs-retro), the repo name, and a GH_TOKEN for API access inside the sandbox.

  • [ ] Step 1: Create the env file
bash
export ORIGINATING_URL="${ORIGINATING_URL}"
export RETRO_COMMENT="${RETRO_COMMENT}"
export REPO_FULL_NAME="${REPO_FULL_NAME}"
export GH_TOKEN=${GH_TOKEN}

Write this to internal/scaffold/fullsend-repo/env/retro.env.

  • [ ] Step 2: Commit
bash
git add internal/scaffold/fullsend-repo/env/retro.env
git commit -m "feat(retro): add environment file for retro agent"

Task 4: Pre-script ​

Files:

  • Create: internal/scaffold/fullsend-repo/scripts/pre-retro.sh

Minimal — validates inputs and writes trigger context. Follows the same set -euo pipefail + validation pattern as pre-triage.sh.

  • [ ] Step 1: Create the pre-script
bash
#!/usr/bin/env bash
# pre-retro.sh — Validate inputs for the retro agent.
#
# Runs on the host via the harness pre_script mechanism. Validates the
# originating URL (PR or issue) and logs the trigger context.
#
# Required env vars:
#   ORIGINATING_URL — HTML URL of the PR or issue that triggered retro
#   GH_TOKEN        — GitHub token with read scope
#
# Optional env vars:
#   RETRO_COMMENT   — The /retro comment text (empty for automatic triggers)

set -euo pipefail

echo "::notice::🔗 Retro target: ${ORIGINATING_URL}"

# Accept both issue and PR URLs.
if [[ ! "${ORIGINATING_URL}" =~ ^https://github\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+/(issues|pull)/[0-9]+$ ]]; then
  echo "ERROR: ORIGINATING_URL does not match expected pattern: ${ORIGINATING_URL}"
  exit 1
fi

if [[ -n "${RETRO_COMMENT:-}" ]]; then
  echo "Retro triggered on-demand with comment."
else
  echo "Retro triggered automatically (PR close)."
fi

echo "Pre-retro validation complete."

Write this to internal/scaffold/fullsend-repo/scripts/pre-retro.sh and make it executable.

  • [ ] Step 2: Make executable

Run: chmod +x internal/scaffold/fullsend-repo/scripts/pre-retro.sh

  • [ ] Step 3: Commit
bash
git add internal/scaffold/fullsend-repo/scripts/pre-retro.sh
git commit -m "feat(retro): add pre-script for retro agent"

Task 5: Post-script ​

Files:

  • Create: internal/scaffold/fullsend-repo/scripts/post-retro.sh

Reads the retro agent's JSON output, files a GitHub issue for each proposal, and posts a summary comment on the originating PR/issue. Follows the same patterns as post-triage.sh.

  • [ ] Step 1: Create the post-script
bash
#!/usr/bin/env bash
# post-retro.sh — File GitHub issues from retro agent proposals and post summary.
#
# Runs on the host after sandbox cleanup. Working directory is the fullsend
# run output directory.
#
# Required env vars:
#   ORIGINATING_URL — HTML URL of the originating PR or issue
#   GH_TOKEN        — GitHub token with issues write scope
#
# The agent writes its result to output/agent-result.json (relative to
# the iteration directory). This script finds the most recent iteration's output.

set -euo pipefail

# Find the retro result JSON (same pattern as post-triage.sh).
RESULT_FILE=""
for dir in iteration-*/output; do
  if [[ -f "${dir}/agent-result.json" ]]; then
    RESULT_FILE="${dir}/agent-result.json"
  fi
done

if [[ -z "${RESULT_FILE}" ]]; then
  echo "ERROR: agent-result.json not found in any iteration output directory"
  exit 1
fi

echo "Reading retro result from: ${RESULT_FILE}"

# Validate JSON is parseable.
if ! jq empty "${RESULT_FILE}" 2>/dev/null; then
  echo "ERROR: ${RESULT_FILE} is not valid JSON"
  exit 1
fi

# Extract repo and number from ORIGINATING_URL.
# Accepts both /issues/N and /pull/N.
if [[ ! "${ORIGINATING_URL}" =~ ^https://github\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+/(issues|pull)/[0-9]+$ ]]; then
  echo "ERROR: ORIGINATING_URL does not match expected pattern: ${ORIGINATING_URL}"
  exit 1
fi
ORIGINATING_REPO=$(echo "${ORIGINATING_URL}" | sed 's|https://github.com/||; s|/\(issues\|pull\)/.*||')
ORIGINATING_NUMBER=$(basename "${ORIGINATING_URL}")

echo "Originating: ${ORIGINATING_REPO}#${ORIGINATING_NUMBER}"

# File an issue for each proposal.
PROPOSAL_COUNT=$(jq '.proposals | length' "${RESULT_FILE}")
echo "Found ${PROPOSAL_COUNT} proposal(s)"

ISSUE_LINKS=""
for i in $(seq 0 $((PROPOSAL_COUNT - 1))); do
  TARGET_REPO=$(jq -r ".proposals[$i].target_repo" "${RESULT_FILE}")
  TITLE=$(jq -r ".proposals[$i].title" "${RESULT_FILE}")

  # Build the issue body from the four sections.
  BODY=$(jq -r "
    .proposals[$i] |
    \"## What happened\n\n\" + .what_happened +
    \"\n\n## What could go better\n\n\" + .what_could_go_better +
    \"\n\n## Proposed change\n\n\" + .proposed_change +
    \"\n\n## Validation criteria\n\n\" + .validation_criteria +
    \"\n\n---\n_Generated by retro agent from ${ORIGINATING_URL}_\"
  " "${RESULT_FILE}")

  echo "Filing issue in ${TARGET_REPO}: ${TITLE}"
  ISSUE_URL=$(gh issue create \
    --repo "${TARGET_REPO}" \
    --title "${TITLE}" \
    --body "${BODY}" \
    --label "" 2>&1 | tail -1)

  if [[ -z "${ISSUE_URL}" ]]; then
    echo "ERROR: failed to create issue in ${TARGET_REPO}"
    exit 1
  fi

  echo "Created: ${ISSUE_URL}"
  ISSUE_LINKS="${ISSUE_LINKS}- [${TITLE}](${ISSUE_URL}) (in \`${TARGET_REPO}\`)\n"
done

# Post summary comment on the originating PR/issue.
# gh issue comment works for both issues and PRs.
SUMMARY=$(jq -r '.summary' "${RESULT_FILE}")

if [[ "${PROPOSAL_COUNT}" -gt 0 ]]; then
  COMMENT=$(printf '%s\n\n### Proposals filed\n\n%b' "${SUMMARY}" "${ISSUE_LINKS}")
else
  COMMENT="${SUMMARY}"
fi

echo "Posting summary comment on ${ORIGINATING_REPO}#${ORIGINATING_NUMBER}"
printf '%s' "${COMMENT}" | gh issue comment "${ORIGINATING_NUMBER}" \
  --repo "${ORIGINATING_REPO}" \
  --body-file -

echo "Post-retro complete."

Write this to internal/scaffold/fullsend-repo/scripts/post-retro.sh and make it executable.

  • [ ] Step 2: Make executable

Run: chmod +x internal/scaffold/fullsend-repo/scripts/post-retro.sh

  • [ ] Step 3: Commit
bash
git add internal/scaffold/fullsend-repo/scripts/post-retro.sh
git commit -m "feat(retro): add post-script to file issues and post summary"

Task 6: Retro analysis skill ​

Files:

  • Create: internal/scaffold/fullsend-repo/skills/retro-analysis/SKILL.md

This skill teaches the retro agent how to trace workflows, explore context, and structure its analysis. It references the finding-agent-runs skill patterns (from PR #568) inline so the retro agent can work even before that PR merges.

  • [ ] Step 1: Create the skill
markdown
---
name: retro-analysis
description: >
  Use when performing a retrospective on an agent workflow. Teaches how to
  trace workflow runs, explore context with subagents, and write structured
  improvement proposals.
---

# Retro Analysis

## Tracing the workflow graph

Given the originating PR or issue, reconstruct what agents ran and in what order.

### Setup

```bash
ORG=$(echo "$REPO_FULL_NAME" | cut -d/ -f1)
DISPATCH_REPO="${ORG}/.fullsend"

From an issue ​

  1. Find triage dispatches (triggered by /fs-triage or needs-info comments):
bash
gh run list --repo "$REPO_FULL_NAME" --workflow=fullsend.yaml \
  --json databaseId,status,conclusion,event,createdAt \
  -q '.[] | select(.event == "issue_comment" or .event == "issues")'
  1. Find the corresponding agent runs in the dispatch repo:
bash
gh run list --repo "$DISPATCH_REPO" --workflow=triage.yml --limit 10 \
  --json databaseId,status,conclusion,createdAt
  1. If the issue reached ready-to-code, find code dispatches:
bash
gh run list --repo "$DISPATCH_REPO" --workflow=code.yml --limit 10 \
  --json databaseId,status,conclusion,createdAt

From a PR ​

  1. The PR branch follows agent/{issue}-{slug}. Extract the issue number to trace the full history.

  2. Find review dispatches:

bash
gh run list --repo "$DISPATCH_REPO" --workflow=review.yml --limit 10 \
  --json databaseId,status,conclusion,createdAt
  1. Find fix dispatches (if review requested changes):
bash
gh run list --repo "$DISPATCH_REPO" --workflow=fix.yml --limit 10 \
  --json databaseId,status,conclusion,createdAt

Reading agent logs and artifacts ​

bash
# View job outcomes
gh run view <RUN_ID> --repo "$DISPATCH_REPO" --json jobs \
  -q '.jobs[] | "\(.name) \(.status)/\(.conclusion)"'

# Search logs for errors
gh run view <RUN_ID> --repo "$DISPATCH_REPO" --log 2>&1 \
  | grep -i "error\|fail\|exit code"

# Download session artifacts (JSONL traces)
gh run download <RUN_ID> --repo "$DISPATCH_REPO"

Exploration strategy ​

You have a large amount of context to cover. Use subagents to avoid overflowing your main context window.

Dispatch subagents for each investigation thread ​

  • Workflow tracer: "Find all agent workflow runs related to issue/PR #N. List each run with its stage, status, conclusion, and timestamp."
  • Trace reader: "Download and read the JSONL reasoning trace for run <RUN_ID>. Summarize what decisions the agent made and why."
  • Comment analyzer: "Read all comments on PR #N. Categorize them: agent review comments, human review comments, CI results, human interventions."
  • Pattern searcher: "Search the last 10 retro agent issues in <REPO>. List any recurring themes or prior proposals related to <TOPIC>."
  • Harness inspector: "Read the harness config at .fullsend/harness/<AGENT>.yaml and the agent definition at .fullsend/agents/<AGENT>.md. Summarize the agent's configuration and constraints."

Keep your main context for synthesis ​

After subagents return their findings, use your main context to:

  1. Reconstruct the timeline
  2. Identify where things could have gone better
  3. Form hypotheses about root causes
  4. Decide what changes to propose and where

Localization guidance ​

When deciding where a proposed change belongs:

  1. Prefer upstream first. If the improvement would benefit all fullsend users, target fullsend-ai/fullsend.
  2. Org-level for org-specific configuration: target the .fullsend repo (e.g., ORG/.fullsend).
  3. Repo-level only for fixes truly specific to one repo (e.g., a test command, a repo-specific linter config): target the source repo itself.

Do not push repo-specific details upstream.

Output format ​

Write a single JSON file to $FULLSEND_OUTPUT_DIR/agent-result.json with this structure:

json
{
  "summary": "Markdown summary for the originating PR/issue comment.",
  "proposals": [
    {
      "target_repo": "owner/repo-name",
      "title": "Concise proposal title",
      "what_happened": "Timeline with links...",
      "what_could_go_better": "Assessment with uncertainty...",
      "proposed_change": "Specific change description...",
      "validation_criteria": "How to verify the improvement..."
    }
  ]
}

Writing good proposals ​

  • what_happened: Tell the story chronologically. Link to specific workflow runs, log lines, PR comments, and review verdicts. Use markdown links.
  • what_could_go_better: Be honest about your uncertainty. If you are confident, say so and why. If you are speculating, say that too. Explain your reasoning.
  • proposed_change: Name the specific file, config, skill, or prompt that should change. Describe what the change looks like. Be specific enough for an implementer to act on it.
  • validation_criteria: Define measurable or observable outcomes. Include a timeframe or sample size. For example: "The next 5 code agent runs on this repo should not trigger the same review comment about missing error handling."

When to propose nothing ​

If the workflow went well and you cannot identify meaningful improvements, write a summary saying so and return an empty proposals array. A retro that finds nothing wrong is a valid outcome.


Write this to `internal/scaffold/fullsend-repo/skills/retro-analysis/SKILL.md`.

- [ ] **Step 2: Commit**

```bash
git add internal/scaffold/fullsend-repo/skills/retro-analysis/SKILL.md
git commit -m "feat(retro): add retro-analysis skill"

Task 7: Agent definition ​

Files:

  • Create: internal/scaffold/fullsend-repo/agents/retro.md

The system prompt defining the retro agent's role, goals, and behavior. References the retro-analysis skill for detailed exploration and output instructions.

  • [ ] Step 1: Create the agent definition
markdown
---
name: retro
description: >-
  Perform a retrospective on an agent workflow. Analyze what happened,
  identify improvement opportunities, and propose changes by writing
  structured proposals that become GitHub issues.
skills:
  - retro-analysis
  - finding-agent-runs
tools: Bash(gh,jq)
model: opus
---

You are a retrospective analyst. You examine agent workflows — completed, rejected, or in-progress — and propose improvements to the system.

## Inputs

- `ORIGINATING_URL` — HTML URL of the PR or issue that triggered this retro.
- `RETRO_COMMENT` — (optional) The human's `/fs-retro` comment, if this was triggered on-demand. This is high-signal context: the human is telling you what to focus on. Read it carefully.
- `REPO_FULL_NAME` — The source repository (owner/repo).

## Your role

You are an analyst, not a fixer. Your job is to:

1. **Explore** — Reconstruct what happened across the full workflow graph (triage, code, review, fix agents and human interactions).
2. **Analyze** — Evaluate what could go better, considering the optimization goals below.
3. **Propose** — Write structured improvement proposals with clear validation criteria.

You do NOT implement fixes, push code, or modify configuration. You propose changes and let existing agent and human workflows handle implementation.

## Optimization goals

Evaluate workflows through these lenses (in priority order):

1. **Review quality** — Are reviews catching real issues? Are they missing things? Are they flagging false positives that waste human time?
2. **Rework rate** — How many iterations did it take? Could the code agent have gotten it right the first time with better context or instructions?
3. **Token cost** — Are agents doing redundant work? Reading files they don't need? Exploring dead ends?
4. **Time to resolution** — Could the pipeline have moved faster without sacrificing quality?

These are defaults. If RETRO_COMMENT provides different focus areas, prioritize those instead.

## Exploration approach

Use the `retro-analysis` skill for detailed workflow tracing recipes.

**Dispatch subagents for every read-heavy operation.** Your main context window is for synthesis, not data gathering. Examples:

- "Read the JSONL trace for workflow run <ID> and summarize the agent's key decisions"
- "Gather all review comments on PR #N and categorize them by source (agent vs human) and type (approval, change request, comment)"
- "Check the last 10 retro proposals in this repo for recurring patterns"
- "Read the harness config and agent definition for the code agent and summarize its setup"

Go deep. Follow threads. If you notice a pattern, investigate whether it occurs on other PRs too.

## Analysis approach

After gathering findings from subagents:

1. **Reconstruct the timeline** — What happened, in what order, and why?
2. **Identify improvement opportunities** — What could go better next time?
3. **Check for patterns** — Is this a one-off or recurring issue?
4. **Assess uncertainty** — How confident are you? What evidence supports your hypothesis? What could you be wrong about?
5. **Localize the fix** — Where does the change belong? Prefer upstream (fullsend-ai/fullsend) if the improvement is universal. Use org config (.fullsend repo) for org-specific tuning. Use the source repo only for repo-specific fixes.

## Output

Write a single JSON file to `$FULLSEND_OUTPUT_DIR/agent-result.json`. See the `retro-analysis` skill for the exact schema and writing guidance.

## Output rules

- Write ONLY the JSON file. No other output files.
- The JSON must be valid and parseable. No markdown fences around it, no trailing text.
- Do NOT post comments, create issues, or perform any GitHub mutations. The post-script handles all writes.
- Do NOT echo untrusted content (issue bodies, PR descriptions, comment text) verbatim into your proposals. Summarize or paraphrase instead.
- If the workflow went well and you find no meaningful improvements, return an empty proposals array with a summary saying so.

Write this to internal/scaffold/fullsend-repo/agents/retro.md.

  • [ ] Step 2: Commit
bash
git add internal/scaffold/fullsend-repo/agents/retro.md
git commit -m "feat(retro): add agent definition for retro agent"

Task 8: Harness configuration ​

Files:

  • Create: internal/scaffold/fullsend-repo/harness/retro.yaml

Ties together the agent, policy, scripts, skills, and env. Follows the exact same structure as harness/triage.yaml.

  • [ ] Step 1: Create the harness config
yaml
agent: agents/retro.md
model: opus
image: ghcr.io/fullsend-ai/fullsend-sandbox:latest
policy: policies/retro.yaml

host_files:
  - src: env/gcp-vertex.env
    dest: /tmp/workspace/.env.d/gcp-vertex.env
    expand: true
  - src: env/retro.env
    dest: /tmp/workspace/.env.d/retro.env
    expand: true
  - src: ${GOOGLE_APPLICATION_CREDENTIALS}
    dest: /tmp/workspace/.gcp-credentials.json
  - src: ${GCP_OIDC_TOKEN_FILE}
    dest: /tmp/workspace/.gcp-oidc-token
    optional: true

skills:
  - skills/retro-analysis

pre_script: scripts/pre-retro.sh

validation_loop:
  script: scripts/validate-output-schema.sh
  max_iterations: 2

post_script: scripts/post-retro.sh

runner_env:
  ORIGINATING_URL: ${ORIGINATING_URL}
  RETRO_COMMENT: ${RETRO_COMMENT}
  REPO_FULL_NAME: ${REPO_FULL_NAME}
  GH_TOKEN: ${GH_TOKEN}
  FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/retro-result.schema.json

timeout_minutes: 30

Write this to internal/scaffold/fullsend-repo/harness/retro.yaml.

  • [ ] Step 2: Verify harness loads

Run: go test ./internal/harness/... -run TestLoad -v Expected: Existing tests pass (new harness is loaded at runtime, not by tests).

  • [ ] Step 3: Commit
bash
git add internal/scaffold/fullsend-repo/harness/retro.yaml
git commit -m "feat(retro): add harness configuration for retro agent"

Task 9: Dispatch target workflow ​

Files:

  • Create: internal/scaffold/fullsend-repo/.github/workflows/retro.yml

The workflow in .fullsend that the dispatcher triggers for stage=retro. Follows the same pattern as triage.yml and review.yml.

  • [ ] Step 1: Create the workflow
yaml
# fullsend-stage: retro
name: Retro

on:
  workflow_dispatch:
    inputs:
      event_type:
        required: true
        type: string
      source_repo:
        required: true
        type: string
      event_payload:
        required: true
        type: string

concurrency:
  group: fullsend-retro-${{ fromJSON(inputs.event_payload).pull_request.number || fromJSON(inputs.event_payload).issue.number }}
  cancel-in-progress: true

jobs:
  retro:
    name: Retro
    runs-on: ubuntu-latest
    permissions:
      actions: write
      contents: read
      id-token: write
      issues: write

    steps:
      - name: Checkout .fullsend repository
        uses: actions/checkout@v6

      - name: Validate source repo is enrolled
        env:
          SOURCE_REPO: ${{ inputs.source_repo }}
        run: |
          # Format check — must be owner/repo, safe characters only
          if [[ ! "$SOURCE_REPO" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
            echo "::error::Invalid source_repo format: must be owner/repo"
            exit 1
          fi
          # Owner check — must match this org
          REPO_OWNER="${SOURCE_REPO%%/*}"
          if [[ "$REPO_OWNER" != "$GITHUB_REPOSITORY_OWNER" ]]; then
            echo "::error::source_repo owner does not match org"
            exit 1
          fi
          # Allowlist check — repo must be enabled in config.yaml
          REPO_NAME="${SOURCE_REPO#*/}"
          ENABLED=$(yq ".repos.\"$REPO_NAME\".enabled" config.yaml 2>/dev/null)
          if [[ "$ENABLED" != "true" ]]; then
            echo "::error::repo is not enabled in config.yaml"
            exit 1
          fi

      - name: Extract target repo name
        id: repo-parts
        env:
          SOURCE_REPO: ${{ inputs.source_repo }}
        run: echo "name=${SOURCE_REPO##*/}" >> "${GITHUB_OUTPUT}"

      - name: Generate sandbox token (read-only)
        id: sandbox-token
        uses: actions/create-github-app-token@v3
        with:
          client-id: ${{ vars.FULLSEND_RETRO_CLIENT_ID }}
          private-key: ${{ secrets.FULLSEND_RETRO_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: ${{ steps.repo-parts.outputs.name }},.fullsend
          permission-contents: read
          permission-pull-requests: read
          permission-issues: read
          permission-actions: read

      - name: Generate write token (issues + comments)
        id: write-token
        uses: actions/create-github-app-token@v3
        with:
          client-id: ${{ vars.FULLSEND_RETRO_CLIENT_ID }}
          private-key: ${{ secrets.FULLSEND_RETRO_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: ${{ steps.repo-parts.outputs.name }},.fullsend
          permission-issues: write

      - name: Pre-mask GCP credential file path
        run: echo "::add-mask::${GITHUB_WORKSPACE}/gha-creds-"

      - name: Authenticate to Google Cloud (WIF)
        if: vars.FULLSEND_GCP_AUTH_MODE == 'wif'
        uses: google-github-actions/auth@v3
        with:
          workload_identity_provider: ${{ secrets.FULLSEND_GCP_WIF_PROVIDER }}

      - name: Authenticate to Google Cloud (SA key)
        if: vars.FULLSEND_GCP_AUTH_MODE != 'wif'
        uses: google-github-actions/auth@v3
        with:
          credentials_json: ${{ secrets.FULLSEND_GCP_SA_KEY_JSON }}

      - name: Set GCP_OIDC_TOKEN_FILE for non-WIF
        if: vars.FULLSEND_GCP_AUTH_MODE != 'wif'
        run: |
          touch "$RUNNER_TEMP/empty-oidc-token"
          echo "GCP_OIDC_TOKEN_FILE=$RUNNER_TEMP/empty-oidc-token" >> "${GITHUB_ENV}"

      - name: Mask GCP credential file paths
        run: |
          for var in GOOGLE_GHA_CREDS_PATH GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE; do
            val="${!var:-}"
            if [[ -n "${val}" ]]; then
              echo "::add-mask::${val}"
            fi
          done

      - name: Prepare sandbox credentials
        run: bash scripts/prepare-sandbox-credentials.sh

      - name: Setup agent environment
        env:
          AGENT_PREFIX: RETRO_
          # GH_TOKEN is auto-minted by the binary when --mint-url is provided.
          # RETRO_GH_TOKEN was removed in favor of binary-based minting.
          RETRO_ANTHROPIC_VERTEX_PROJECT_ID: ${{ secrets.FULLSEND_GCP_PROJECT_ID }}
          RETRO_CLOUD_ML_REGION: ${{ vars.FULLSEND_GCP_REGION }}
        run: bash .github/scripts/setup-agent-env.sh

      - name: Run retro agent
        uses: ./.github/actions/fullsend
        env:
          ORIGINATING_URL: ${{ fromJSON(inputs.event_payload).pull_request.html_url || fromJSON(inputs.event_payload).issue.html_url }}
          RETRO_COMMENT: ${{ fromJSON(inputs.event_payload).comment.body || '' }}
          REPO_FULL_NAME: ${{ inputs.source_repo }}
          GH_TOKEN: ${{ steps.write-token.outputs.token }}
        with:
          agent: retro

Write this to internal/scaffold/fullsend-repo/.github/workflows/retro.yml.

  • [ ] Step 2: Verify the stage marker is correct

Run: head -1 internal/scaffold/fullsend-repo/.github/workflows/retro.yml Expected: # fullsend-stage: retro

  • [ ] Step 3: Commit
bash
git add internal/scaffold/fullsend-repo/.github/workflows/retro.yml
git commit -m "feat(retro): add dispatch target workflow for retro stage"

Task 10: Shim workflow changes ​

Files:

  • Modify: internal/scaffold/fullsend-repo/templates/shim-workflow.yaml

Add two new dispatch jobs: dispatch-retro (automatic on PR close) and dispatch-retro-command (on-demand via /fs-retro).

  • [ ] Step 1: Read the current shim

Read internal/scaffold/fullsend-repo/templates/shim-workflow.yaml to confirm current state.

  • [ ] Step 2: Add closed to pull_request_target event types

The shim currently triggers on [opened, synchronize, ready_for_review]. Add closed:

Change:

yaml
  pull_request_target:
    types: [opened, synchronize, ready_for_review]

To:

yaml
  pull_request_target:
    types: [opened, synchronize, ready_for_review, closed]
  • [ ] Step 3: Add dispatch-retro job (automatic on PR close)

Append after the dispatch-fix-human job:

yaml
  dispatch-retro:
    runs-on: ubuntu-latest
    concurrency:
      group: retro-${{ github.event.pull_request.number }}
      cancel-in-progress: true
    if: >-
      github.event_name == 'pull_request_target'
        && github.event.action == 'closed'
    steps:
      - name: Build minimal payload
        id: payload
        env:
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_HTML_URL: ${{ github.event.pull_request.html_url }}
        run: |
          set -euo pipefail
          PAYLOAD=$(jq -cn \
            --arg pn "${PR_NUMBER}" \
            --arg pu "${PR_HTML_URL}" \
            '{pull_request: {number: ($pn | tonumber), html_url: $pu}}')
          echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
      - name: Dispatch retro stage
        env:
          GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
          EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
          EVENT_TYPE: ${{ github.event_name }}
          SOURCE_REPO: ${{ github.repository }}
          DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
        run: |
          gh workflow run dispatch.yml \
            --repo "$DISPATCH_REPO" \
            -f stage=retro \
            -f event_type="$EVENT_TYPE" \
            -f source_repo="$SOURCE_REPO" \
            -f event_payload="$EVENT_PAYLOAD"

  dispatch-retro-command:
    runs-on: ubuntu-latest
    concurrency:
      group: retro-${{ github.event.issue.number }}
      cancel-in-progress: true
    if: >-
      github.event_name == 'issue_comment'
        && (
          github.event.comment.body == '/retro'
          || startsWith(github.event.comment.body, '/retro ')
        )
        && (
          github.event.comment.author_association == 'OWNER'
          || github.event.comment.author_association == 'MEMBER'
          || github.event.comment.author_association == 'COLLABORATOR'
        )
    steps:
      - name: Build minimal payload
        id: payload
        env:
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          ISSUE_HTML_URL: ${{ github.event.issue.html_url }}
          COMMENT_BODY: ${{ github.event.comment.body }}
        run: |
          set -euo pipefail
          # Determine if this is a PR or issue comment.
          # GitHub API: issue_comment events on PRs have issue.pull_request set.
          # We use gh api to get the PR HTML URL if needed.
          ORIGINATING_URL="${ISSUE_HTML_URL}"
          PAYLOAD=$(jq -cn \
            --arg in "${ISSUE_NUMBER}" \
            --arg iu "${ISSUE_HTML_URL}" \
            --arg ou "${ORIGINATING_URL}" \
            --arg cb "${COMMENT_BODY:-}" \
            '{issue: {number: ($in | tonumber), html_url: $iu},
              pull_request: {html_url: (if $ou != $iu then $ou else null end)},
              comment: {body: $cb}}')
          echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
      - name: Dispatch retro stage
        env:
          GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
          EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
          EVENT_TYPE: ${{ github.event_name }}
          SOURCE_REPO: ${{ github.repository }}
          DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
        run: |
          gh workflow run dispatch.yml \
            --repo "$DISPATCH_REPO" \
            -f stage=retro \
            -f event_type="$EVENT_TYPE" \
            -f source_repo="$SOURCE_REPO" \
            -f event_payload="$EVENT_PAYLOAD"
  • [ ] Step 4: Commit
bash
git add internal/scaffold/fullsend-repo/templates/shim-workflow.yaml
git commit -m "feat(retro): add dispatch-retro jobs to shim workflow"

Task 11: Update dispatch workflow description ​

Files:

  • Modify: internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml

  • [ ] Step 1: Update the stage description to include retro

Change line 8:

yaml
        description: 'Stage name (triage, code, review, fix)'

To:

yaml
        description: 'Stage name (triage, code, review, fix, retro)'
  • [ ] Step 2: Commit
bash
git add internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml
git commit -m "feat(retro): add retro to dispatch stage list"

Task 12: Run linter and verify ​

  • [ ] Step 1: Run make lint

Run: make lint Expected: All checks pass.

  • [ ] Step 2: Run Go tests

Run: make go-test Expected: All tests pass.

  • [ ] Step 3: Fix any failures and commit

If lint or tests fail, fix the issues and commit the fixes.


Task 13: Update architecture docs ​

Files:

  • Modify: docs/architecture.md (add retro agent to the agent inventory)

Per the issue's acceptance criteria: "docs/architecture.md is updated to reflect any architectural changes."

  • [ ] Step 1: Read docs/architecture.md

Find the section that lists agent roles or stages.

  • [ ] Step 2: Add retro agent to the agent inventory

Add an entry for the retro agent alongside triage, code, review, and fix. Describe it as: "Retrospective analyst — examines completed or in-progress agent workflows, identifies improvement opportunities, and files proposals as GitHub issues."

  • [ ] Step 3: Commit
bash
git add docs/architecture.md
git commit -m "docs: add retro agent to architecture inventory"

Task 14: Update bugfix workflow guide ​

Files:

  • Modify: docs/guides/user/bugfix-workflow.md

The bugfix workflow guide has a "Planned" placeholder for the retro agent. Update it to reflect the actual design.

  • [ ] Step 1: Read docs/guides/user/bugfix-workflow.md

Find the retro agent placeholder text.

  • [ ] Step 2: Update the retro agent section

Replace the "Planned" placeholder with a description of what the retro agent does: runs automatically on PR close and on-demand via /fs-retro, analyzes the workflow graph, and files improvement proposals as GitHub issues.

  • [ ] Step 3: Commit
bash
git add docs/guides/user/bugfix-workflow.md
git commit -m "docs: update bugfix workflow with retro agent details"