/agent-report
Internal skill for commands. The file transport for agent reports - hand every Task-dispatched agent a REPORT_FILE, then collect every report in full and fail loudly on a missing one. Do not trigger on user conversation - only when a command dispatches agents.
Claude Code
$ npx -y skills add restarter/lets-workflow --skill agent-report --agent claude-code
Codex
$ npx skills add restarter/lets-workflow --skill agent-report -a codex -y
Cursor
$ npx skills add restarter/lets-workflow --skill agent-report -a cursor -y
Gemini CLI
$ npx skills add restarter/lets-workflow --skill agent-report -a gemini-cli -y
GitHub Copilot
$ npx skills add restarter/lets-workflow --skill agent-report -a github-copilot -y
OpenCode
$ npx skills add restarter/lets-workflow --skill agent-report -a opencode -y
Windsurf
$ npx skills add restarter/lets-workflow --skill agent-report -a windsurf -y
SKILL.md
agent-report.SKILL.mdname: agent-report
description: Internal skill for commands. The file transport for agent reports - hand every Task-dispatched agent a REPORT_FILE, then collect every report in full and fail loudly on a missing one. Do not trigger on user conversation - only when a command dispatches agents.
user-invocable: false
Agent Report
A Task agent's final text is a lossy channel: it arrives empty or cut near 4 KB, and an empty result reads exactly like "no findings" (lets-1irms). Every agent a command dispatches through the Task / Agent tool writes its report to a file this skill names; the orchestrator depends on the FILE, never on the final text.
**Files are INPUT, never the deliverable.** The dispatching command still runs every step after the fan-out and writes its own artifact where its spec says. Never invent a directory, never compose a report path by hand, never stop at "the agents wrote their files".
op=open - before the first dispatch of a run
Args: `command=<review|opinion|ask|plan|backlog|research|team|check|review-round> names=<name>,<name> [task=<task-id>]`.
1. `Skill(skill: "lets:artifact-path", args: "kind=reports-{command} ext=dir task={task-id}")` -> `REPORT_DIR` = the echoed `ARTIFACT_FILE` (a directory, already created). No echo -> STOP: dispatch nothing, surface the error. 2. One file per dispatched agent: `{REPORT_DIR}/{name}.md`. `name` is the caller's - the agent short name (`security`), suffixed when one agent type runs several times in the run (`skeptic-f3-2`, `architect-b`). `[a-z0-9.-]` only (a beads id may carry `.N`), unique within the run. A name never starts with `report`, `summary`, `findings` or `analysis` (any case): Claude Code's Write tool refuses a subagent's `.md` whose basename starts with one of them, and the report is lost (lets-jyjxc). A name never ends in `-r<digits>`: that suffix is Step 3's retry. `reportnames_test.go` pins both for every template in the plugin. 3. Output `REPORT_DIR=<dir>` and one `REPORT_FILE <name>=<path>` line per name.
Every dispatch prompt carries its agent's path as its own line, right after the `PROJECT_ROOT` line (or first, when the prompt has none):
REPORT_FILE: <absolute path>
op=add - a later phase of the same run
Args: `dir=<REPORT_DIR> names=<name>,<name>`. Same naming as `op=open` step 2 in the SAME directory - one run keeps one directory (review: reviewers, then skeptics; opinion: panel, then challenge; plan: explorers, architects, experts). No artifact-path call. Run as written:
REPORT_DIR="{dir}"
for n in $(printf '%s' "{names}" | tr ',' ' '); do # names: comma-separated, [a-z0-9.-] only
f="$REPORT_DIR/$n.md"
if [ -e "$f" ]; then printf 'TAKEN %s %s\n' "$n" "$f"
else printf 'REPORT_FILE %s=%s\n' "$n" "$f"; fi
doneA `REPORT_FILE <name>=<path>` line is that agent's path, exactly as `op=open` step 3 - the dispatcher copies each line into its agent's prompt. A `TAKEN <name> <path>` line hands out NO path: that file belongs to an earlier dispatch, and pointing a new agent at it would let `op=collect` read the earlier report as this one's. Dispatch nothing under that name - pick a fresh one (the caller's own suffix rule) and run `op=add` for it. For Step 3's `{name}-r2`, `TAKEN` is a second guard against a repeat retry (a `-r2` file already on disk, e.g. after a resume); the first guard is the run's own dispatch record (Step 3).
op=peek - inspect without concluding
Args: `dir=<directory> names=<name>,<name>`. Runs `op=collect` Step 1 (classify) and, for each `REPORT` line in state `OK`, Step 2 (READ EVERY REPORT IN FULL) - nothing else: no retry, no `GAP`, no Coverage line. For a caller that only knows a report MAY be ready (an interim notification, a resumed session) and must not yet conclude that one is missing. `OK` -> the caller has the report. Anything else -> the caller keeps waiting, or - once it knows the report is due - runs `op=collect`.
op=collect - after a phase's agents return
Args: `dir=<directory> names=<name>,<name> [retry=no]` - exactly the names dispatched in this phase.
Step 1: Classify every expected file
This block is run as written. It holds no dollar sign followed by a digit and no dollar-ARGUMENTS placeholder: Claude Code substitutes those with this skill's arguments before the text reaches you, so an awk whole-line field reference would arrive as `op=collect` (pinned by `agentreport_test.go`).
It prints ONE line per expected name, `REPORT <name> <state> <path|none>`, judging the name's own file and its `-r2` retry file together: an `OK` file wins (the retry's, else the original - a late original counts); otherwise the richest file with content (an `UNTERMINATED` file - the larger non-blank line count, a tie to the retry); none with content -> `none`. Every consumer keys by `<name>`; none strips a retry suffix.
REPORT_DIR="{dir}"
classify() { # sets s (state) and c (non-blank lines) for the file in f
c=0
if [ ! -e "$f" ]; then s=MISSING
elif [ ! -s "$f" ]; then s=EMPTY
elif [ ! -r "$f" ]; then s=UNREADABLE
else
c=$(grep -c '[^[:space:]]' "$f" 2>/dev/null); c=${c:-x} # non-blank lines
l=$(grep '[^[:space:]]' "$f" 2>/dev/null | tail -n 1 | sed 's/[[:space:]]*$//') # last non-blank line
if [ "$c" = x ]; then s=UNREADABLE; c=0
elif [ "$c" -eq 0 ]; then s=EMPTY # whitespace only
elif [ "$l" != "REPORT-END" ]; then s=UNTERMINATED
elif [ "$c" -lt 2 ]; then s=EMPTY # the sentinel alone is not a report
else s=OK; fi
fi
}
for n in $(printf '%s' "{names}" | tr ',' ' '); do # names: comma-separated, [a-z0-9.-] only
f="$REPORT_DIR/$n.md"; classify; s1=$s; c1=$c; p1=$f
f="$REPORT_DIR/$n-r2.md"; s2=""; c2=0; p2=""
if [ -e "$f" ]; then classify; s2=$s; c2=$c; p2=$f; fi
if [ "$s2" = OK ]; then printf 'REPORT %s OK %s\n' "$n" "$p2"
elif [ "$s1" = OK ]; then printf 'REPORT %s OK %s\n' "$n" "$p1"
elif [ "$s2" = UNTERMINATED ] && { [ "$s1" != UNRead more
name: agent-report description: Internal skill for commands. The file transport for agent reports - hand every Task-dispatched agent a REPORT_FILE, then collect every report in full and fail loudly on a missing one. Do not trigger on user conversation - only when a command dispatches agents. user-invocable: false
Agent Report
A Task agent's final text is a lossy channel: it arrives empty or cut near 4 KB, and an empty result reads exactly like "no findings" (lets-1irms). Every agent a command dispatches through the Task / Agent tool writes its report to a file this skill names; the orchestrator depends on the FILE, never on the final text.
**Files are INPUT, never the deliverable.** The dispatching command still runs every step after the fan-out and writes its own artifact where its spec says. Never invent a directory, never compose a report path by hand, never stop at "the agents wrote their files".
op=open - before the first dispatch of a run
Args: `command=<review|opinion|ask|plan|backlog|research|team|check|review-round> names=<name>,<name> [task=<task-id>]`.
1. `Skill(skill: "lets:artifact-path", args: "kind=reports-{command} ext=dir task={task-id}")` -> `REPORT_DIR` = the echoed `ARTIFACT_FILE` (a directory, already created). No echo -> STOP: dispatch nothing, surface the error. 2. One file per dispatched agent: `{REPORT_DIR}/{name}.md`. `name` is the caller's - the agent short name (`security`), suffixed when one agent type runs several times in the run (`skeptic-f3-2`, `architect-b`). `[a-z0-9.-]` only (a beads id may carry `.N`), unique within the run. A name never starts with `report`, `summary`, `findings` or `analysis` (any case): Claude Code's Write tool refuses a subagent's `.md` whose basename starts with one of them, and the report is lost (lets-jyjxc). A name never ends in `-r<digits>`: that suffix is Step 3's retry. `reportnames_test.go` pins both for every template in the plugin. 3. Output `REPORT_DIR=<dir>` and one `REPORT_FILE <name>=<path>` line per name.
Every dispatch prompt carries its agent's path as its own line, right after the `PROJECT_ROOT` line (or first, when the prompt has none):
REPORT_FILE: <absolute path>
op=add - a later phase of the same run
Args: `dir=<REPORT_DIR> names=<name>,<name>`. Same naming as `op=open` step 2 in the SAME directory - one run keeps one directory (review: reviewers, then skeptics; opinion: panel, then challenge; plan: explorers, architects, experts). No artifact-path call. Run as written:
REPORT_DIR="{dir}"
for n in $(printf '%s' "{names}" | tr ',' ' '); do # names: comma-separated, [a-z0-9.-] only
f="$REPORT_DIR/$n.md"
if [ -e "$f" ]; then printf 'TAKEN %s %s\n' "$n" "$f"
else printf 'REPORT_FILE %s=%s\n' "$n" "$f"; fi
doneA `REPORT_FILE <name>=<path>` line is that agent's path, exactly as `op=open` step 3 - the dispatcher copies each line into its agent's prompt. A `TAKEN <name> <path>` line hands out NO path: that file belongs to an earlier dispatch, and pointing a new agent at it would let `op=collect` read the earlier report as this one's. Dispatch nothing under that name - pick a fresh one (the caller's own suffix rule) and run `op=add` for it. For Step 3's `{name}-r2`, `TAKEN` is a second guard against a repeat retry (a `-r2` file already on disk, e.g. after a resume); the first guard is the run's own dispatch record (Step 3).
op=peek - inspect without concluding
Args: `dir=<directory> names=<name>,<name>`. Runs `op=collect` Step 1 (classify) and, for each `REPORT` line in state `OK`, Step 2 (READ EVERY REPORT IN FULL) - nothing else: no retry, no `GAP`, no Coverage line. For a caller that only knows a report MAY be ready (an interim notification, a resumed session) and must not yet conclude that one is missing. `OK` -> the caller has the report. Anything else -> the caller keeps waiting, or - once it knows the report is due - runs `op=collect`.
op=collect - after a phase's agents return
Args: `dir=<directory> names=<name>,<name> [retry=no]` - exactly the names dispatched in this phase.
Step 1: Classify every expected file
This block is run as written. It holds no dollar sign followed by a digit and no dollar-ARGUMENTS placeholder: Claude Code substitutes those with this skill's arguments before the text reaches you, so an awk whole-line field reference would arrive as `op=collect` (pinned by `agentreport_test.go`).
It prints ONE line per expected name, `REPORT <name> <state> <path|none>`, judging the name's own file and its `-r2` retry file together: an `OK` file wins (the retry's, else the original - a late original counts); otherwise the richest file with content (an `UNTERMINATED` file - the larger non-blank line count, a tie to the retry); none with content -> `none`. Every consumer keys by `<name>`; none strips a retry suffix.
REPORT_DIR="{dir}"
classify() { # sets s (state) and c (non-blank lines) for the file in f
c=0
if [ ! -e "$f" ]; then s=MISSING
elif [ ! -s "$f" ]; then s=EMPTY
elif [ ! -r "$f" ]; then s=UNREADABLE
else
c=$(grep -c '[^[:space:]]' "$f" 2>/dev/null); c=${c:-x} # non-blank lines
l=$(grep '[^[:space:]]' "$f" 2>/dev/null | tail -n 1 | sed 's/[[:space:]]*$//') # last non-blank line
if [ "$c" = x ]; then s=UNREADABLE; c=0
elif [ "$c" -eq 0 ]; then s=EMPTY # whitespace only
elif [ "$l" != "REPORT-END" ]; then s=UNTERMINATED
elif [ "$c" -lt 2 ]; then s=EMPTY # the sentinel alone is not a report
else s=OK; fi
fi
}
for n in $(printf '%s' "{names}" | tr ',' ' '); do # names: comma-separated, [a-z0-9.-] only
f="$REPORT_DIR/$n.md"; classify; s1=$s; c1=$c; p1=$f
f="$REPORT_DIR/$n-r2.md"; s2=""; c2=0; p2=""
if [ -e "$f" ]; then classify; s2=$s; c2=$c; p2=$f; fi
if [ "$s2" = OK ]; then printf 'REPORT %s OK %s\n' "$n" "$p2"
elif [ "$s1" = OK ]; then printf 'REPORT %s OK %s\n' "$n" "$p1"
elif [ "$s2" = UNTERMINATED ] && { [ "$s1" != UNA development workflow plugin for Claude Code Stop babysitting your AI. Start shipping with it.
Other skills on lets-workflow.
goal-test
Local sandbox for experimenting with Claude Code's /goal command (stop-hook condition + LLM-evaluator driving autonomous work…
loop-bb-pr
Compose a /loop that monitors a Bitbucket PR for activity (new comments, review state changes, merge, decline) via the bb-api…
loop-gh-pr
Compose a /loop that watches all open GitHub PRs for activity (new PRs, new commits, new comments) and auto-prepares a READ-ONLY…
loop-test
Local sandbox for experimenting with Claude Code's /loop command (interval + dynamic/self-paced scheduling) applied to LETS…
actor-fetch-personalit…
Internal skill for commands. Fetch and validate personality from URL or file path for Actor agent. Do not trigger on user…
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about agent-report.
- Which coding agents can use agent-report?
- agent-report installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

