feat: multi-agent orchestration & token efficiency (p013/t068)#158
feat: multi-agent orchestration & token efficiency (p013/t068)#158marcusquinn merged 14 commits intomainfrom
Conversation
Create prompts/build.txt with tool preferences, security rules, git workflow, and quality standards that replace the default Claude Code system prompt. This eliminates the harness conflict where the default prompt says 'use Glob' while AGENTS.md says 'use git ls-files'. The custom prompt becomes highest priority, surviving compaction. Update generate-opencode-agents.sh to add 'prompt' field for Build+, AI-DevOps, and Sisyphus agents referencing the deployed prompt file.
Create mail-helper.sh with send/check/read/archive/prune/status/register/ deregister/agents commands. Uses TOON format for token-efficient messages. Features: - Per-agent inbox directories with unread tracking - Message types: task_dispatch, status_report, discovery, request, broadcast - Priority levels (high/normal/low) and convoy grouping - Agent registry with role, branch, worktree, and last_seen tracking - Archive-remember-prune lifecycle (captures discoveries to memory before deletion) - Auto-detects agent identity from worktree/branch name or AIDEVOPS_AGENT_ID env
…068.3) Lossless compression - all content preserved via: - Create subagent-index.toon with agents, subagents, workflows, scripts listings - Move detailed pre-edit git check to workflows/pre-edit.md (keep 15-line trigger) - Replace large markdown tables with TOON format references - Compress planning, memory, MCP, git, session sections to pointers - Add inter-agent mailbox section (new capability from t068.4) Every removed line exists in a subagent, workflow, or the new index file. Token savings: ~10K → ~3K tokens per session load.
Add mailbox integration to loop-common.sh: - Register agent on loop start (role: worker, branch auto-detected) - Check unread messages during re-anchor (included in context) - Send status_report to coordinator on task completion This enables the stateless coordinator to track worker progress and workers to receive dispatch instructions between iterations.
Create coordinator-helper.sh with pulse/status/dispatch/convoy/watch commands. Key design: STATELESS pulse pattern (not persistent like Gas Town's Mayor): - Reads: registry.toon, outbox/*.toon, TODO.md - Writes: inbox/*.toon (dispatch instructions) - Stores: notable outcomes to memory - Exits immediately (~20K token budget per pulse) Features: - Pulse: read state, process reports, dispatch to idle workers - Convoy: group related tasks for batch assignment - Watch: fswatch-driven or polling trigger - Status: show agent registry and mailbox state - Memory integration: stores worker reports for long-term context
Add model tier system for cost-effective agent routing: - Tiers: haiku (triage), sonnet (code), opus (architecture), flash/pro (Gemini) - Agents declare tier in YAML frontmatter 'model:' field - generate-opencode-agents.sh resolves tier to model identifier - Default tiers assigned per agent (overridable via frontmatter) - Model tier listing added to subagent-index.toon This enables the coordinator to dispatch tasks to appropriate model tiers - cheap models for routing, capable for code.
- SC2155: Declare and assign separately in mail-helper.sh - SC2034: Prefix unused read variables with underscore - SC2086: Use array for optional arguments instead of unquoted variable
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a TOON mailbox and stateless pulse coordinator, mailbox/loop integration, agent prompt corpus and model-tier routing, prompt-drift detection, an Aidevops compaction plugin, and condenses agent workflow/docs. Changes
Sequence Diagram(s)sequenceDiagram
participant Coord as Coordinator
participant Reg as Registry
participant Mail as Mailbox
participant TODO as TODO.md
participant Agent as Worker
loop Pulse cycle
Coord->>Reg: get_active_agents()
Reg-->>Coord: agents list
Coord->>Mail: get_worker_reports()
Mail-->>Coord: reports
Coord->>TODO: get_ready_tasks()
TODO-->>Coord: ready tasks
alt ready tasks && idle agent available
Coord->>Mail: send(task → selected_agent)
Mail-->>Agent: deliver message (inbox)
Agent-->>Mail: send(status_report)
Mail-->>Coord: archived report
else no dispatch
Coord-->>Coord: no-op / log
end
Coord-->>Coord: emit pulse_summary
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @marcusquinn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the multi-agent orchestration capabilities and token efficiency of the system. It introduces a robust inter-agent communication system, streamlines agent configuration and documentation, and implements a stateless coordinator for dynamic task dispatching. These changes aim to improve the scalability, cost-effectiveness, and overall intelligence of the AI agents by optimizing their operational context and communication overhead. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 00:03:48 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
There was a problem hiding this comment.
Code Review
This pull request introduces a major refactoring for multi-agent orchestration and token efficiency. The changes are well-structured, introducing a new mailbox system, a stateless coordinator, and custom prompts. The documentation has been significantly improved by moving details from AGENTS.md into dedicated files. My review focuses on improving the robustness and efficiency of the new shell scripts. I've identified some critical race conditions in the agent registry management and some high-severity issues with argument parsing that could lead to script failures. I've also suggested a refactoring for performance in the coordinator script and a simplification in the main agent loop.
.agent/scripts/mail-helper.sh
Outdated
| # Create or update registry | ||
| if [[ ! -f "$REGISTRY_FILE" ]]; then | ||
| cat > "$REGISTRY_FILE" << EOF | ||
| <!--TOON:agents{id,role,branch,worktree,status,registered,last_seen}: | ||
| ${agent_id},${role},${branch},${worktree},active,${timestamp},${timestamp} | ||
| --> | ||
| EOF | ||
| else | ||
| # Check if agent already registered | ||
| if grep -q "^${agent_id}," "$REGISTRY_FILE" 2>/dev/null; then | ||
| # Update last_seen and status | ||
| if command -v sed &>/dev/null; then | ||
| sed -i.bak "s|^${agent_id},.*|${agent_id},${role},${branch},${worktree},active,$(grep "^${agent_id}," "$REGISTRY_FILE" | cut -d',' -f6),${timestamp}|" "$REGISTRY_FILE" | ||
| rm -f "${REGISTRY_FILE}.bak" | ||
| fi | ||
| log_info "Updated agent: $agent_id (last_seen: $timestamp)" | ||
| else | ||
| # Add new agent before closing --> | ||
| if command -v sed &>/dev/null; then | ||
| sed -i.bak "/^-->$/i\\ | ||
| ${agent_id},${role},${branch},${worktree},active,${timestamp},${timestamp}" "$REGISTRY_FILE" | ||
| rm -f "${REGISTRY_FILE}.bak" | ||
| fi | ||
| log_success "Registered agent: $agent_id (role: $role, branch: $branch)" | ||
| fi | ||
| fi |
There was a problem hiding this comment.
The cmd_register function modifies the shared $REGISTRY_FILE without any locking mechanism. This can lead to race conditions if multiple agents try to register simultaneously, potentially corrupting the registry file.
For example, the grep on line 516 and the sed on line 519 are not an atomic operation. Another agent could modify the file between these two commands.
You should implement a file-based locking mechanism (e.g., using flock or creating a .lock file with mkdir) to ensure that modifications to $REGISTRY_FILE are atomic and prevent data corruption.
| # Mark as inactive (don't remove - preserves history) | ||
| if command -v sed &>/dev/null; then | ||
| sed -i.bak "s|^\(${agent_id},.*\),active,|\\1,inactive,|" "$REGISTRY_FILE" | ||
| rm -f "${REGISTRY_FILE}.bak" | ||
| fi |
There was a problem hiding this comment.
The cmd_deregister function modifies the shared $REGISTRY_FILE without any locking mechanism, which can lead to race conditions if multiple agents are registering or deregistering at the same time. This could corrupt the registry file.
A file-based locking mechanism should be used to ensure atomic updates to the registry.
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --task) task="$2"; shift 2 ;; | ||
| --to) to="$2"; shift 2 ;; | ||
| --priority) priority="$2"; shift 2 ;; | ||
| --convoy) convoy="$2"; shift 2 ;; | ||
| *) log_error "Unknown option: $1"; return 1 ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
The argument parsing logic is not robust. If an option like --task is provided without a corresponding value, the script will fail due to shift 2 on an empty $2. You should add checks to ensure that arguments for options are present before trying to access or shift them.
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --task) task="$2"; shift 2 ;; | |
| --to) to="$2"; shift 2 ;; | |
| --priority) priority="$2"; shift 2 ;; | |
| --convoy) convoy="$2"; shift 2 ;; | |
| *) log_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --task) | |
| if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi | |
| task="$2"; shift 2 ;; | |
| --to) | |
| if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi | |
| to="$2"; shift 2 ;; | |
| --priority) | |
| if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi | |
| priority="$2"; shift 2 ;; | |
| --convoy) | |
| if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi | |
| convoy="$2"; shift 2 ;; | |
| *) log_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done |
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --name) name="$2"; shift 2 ;; | ||
| --tasks) tasks="$2"; shift 2 ;; | ||
| *) log_error "Unknown option: $1"; return 1 ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
The argument parsing logic is not robust. If an option like --name is provided without a corresponding value, the script will fail. You should add checks to ensure that arguments for options are present.
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --name) name="$2"; shift 2 ;; | |
| --tasks) tasks="$2"; shift 2 ;; | |
| *) log_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --name) | |
| if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi | |
| name="$2"; shift 2 ;; | |
| --tasks) | |
| if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi | |
| tasks="$2"; shift 2 ;; | |
| *) log_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done |
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --to) to="$2"; shift 2 ;; | ||
| --type) msg_type="$2"; shift 2 ;; | ||
| --payload) payload="$2"; shift 2 ;; | ||
| --priority) priority="$2"; shift 2 ;; | ||
| --convoy) convoy="$2"; shift 2 ;; | ||
| --from) from="$2"; shift 2 ;; | ||
| *) log_error "Unknown option: $1"; return 1 ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
The argument parsing logic is not robust. If an option like --to is provided without a corresponding value, the script will fail due to shift 2 on an empty $2. You should add checks to ensure that arguments for options are present before trying to access or shift them. This issue is present in other command functions in this script as well.
while [[ $# -gt 0 ]]; do
case "$1" in
--to)
if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi
to="$2"; shift 2 ;;
--type)
if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi
msg_type="$2"; shift 2 ;;
--payload)
if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi
payload="$2"; shift 2 ;;
--priority)
if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi
priority="$2"; shift 2 ;;
--convoy)
if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi
convoy="$2"; shift 2 ;;
--from)
if [[ -z "${2-}" ]]; then log_error "Missing argument for $1"; return 1; fi
from="$2"; shift 2 ;;
*) log_error "Unknown option: $1"; return 1 ;;
esac
done| # 2. Process worker reports (archive after reading) | ||
| local reports | ||
| reports=$(get_worker_reports) | ||
| if [[ -n "$reports" ]]; then | ||
| log_info "Worker reports:" | ||
| echo "$reports" | while IFS= read -r report; do | ||
| echo " $report" | ||
| done | ||
|
|
||
| # Store notable reports to memory | ||
| if [[ -x "$MEMORY_HELPER" ]]; then | ||
| echo "$reports" | while IFS= read -r report; do | ||
| if [[ -n "$report" ]]; then | ||
| "$MEMORY_HELPER" store \ | ||
| --content "Coordinator received: $report" \ | ||
| --type CONTEXT \ | ||
| --tags "coordinator,status_report" 2>/dev/null || true | ||
| fi | ||
| done | ||
| fi | ||
|
|
||
| # Archive processed outbox messages | ||
| local outbox_dir="$MAIL_DIR/outbox" | ||
| if [[ -d "$outbox_dir" ]]; then | ||
| for msg_file in "$outbox_dir"/*.toon; do | ||
| [[ -f "$msg_file" ]] || continue | ||
| local msg_type | ||
| msg_type=$(grep -A1 'TOON:message{' "$msg_file" 2>/dev/null | tail -1 | cut -d',' -f4) | ||
| if [[ "$msg_type" == "status_report" ]]; then | ||
| local msg_id | ||
| msg_id=$(basename "$msg_file" .toon) | ||
| "$MAIL_HELPER" archive "$msg_id" 2>/dev/null || true | ||
| fi | ||
| done | ||
| fi | ||
| fi |
There was a problem hiding this comment.
The current implementation for processing worker reports is inefficient. It iterates over the outbox files multiple times: once in get_worker_reports to get content, once to store reports to memory, and a third time to archive the messages. This results in reading and parsing the same files repeatedly.
I suggest refactoring this section to process each report file in a single pass. This will improve performance, especially with a large number of reports, and make the code easier to maintain. With this change, you can likely remove the get_worker_reports function (lines 59-77).
# 2. Process worker reports (archive after reading)
local reports=""
local outbox_dir="$MAIL_DIR/outbox"
if [[ -d "$outbox_dir" ]]; then
log_info "Processing worker reports..."
for msg_file in "$outbox_dir"/*.toon; do
[[ -f "$msg_file" ]] || continue
local header
header=$(grep -A1 'TOON:message{' "$msg_file" 2>/dev/null | tail -1)
if [[ "$(echo "$header" | cut -d',' -f4)" != "status_report" ]]; then
continue
fi
local from payload report_line
from=$(echo "$header" | cut -d',' -f2)
payload=$(sed -n '/^-->$/,$ { /^-->$/d; p; }' "$msg_file" | sed '/^$/d')
report_line="$from: $payload"
reports+="${report_line}\n"
echo " - $report_line"
if [[ -x "$MEMORY_HELPER" && -n "$report_line" ]]; then
"$MEMORY_HELPER" store --content "Coordinator received: $report_line" --type CONTEXT --tags "coordinator,status_report" 2>/dev/null || true
fi
local msg_id
msg_id=$(basename "$msg_file" .toon)
"$MAIL_HELPER" archive "$msg_id" 2>/dev/null || true
done
fi
.agent/scripts/loop-common.sh
Outdated
| agent_id=$("$mail_helper" agents 2>/dev/null | grep -o '^[^,]*' | head -1 || echo "") | ||
| if [[ -n "$agent_id" ]]; then | ||
| "$mail_helper" send \ | ||
| --to "coordinator" \ | ||
| --type status_report \ | ||
| --payload "Task completed: $(loop_get_state ".prompt" | head -c 100). Iterations: $iteration. Branch: $(git branch --show-current 2>/dev/null || echo unknown)" \ | ||
| 2>/dev/null || true | ||
| fi |
There was a problem hiding this comment.
The logic to retrieve the agent_id here is confusing and potentially incorrect. It gets the ID of the first agent in the registry (head -1), which may not be the current agent in a multi-agent scenario. The agent_id variable is then only used for an if check, but not passed to the send command.
The mail-helper.sh send command already determines the 'from' agent ID internally. You can simplify this by removing the agent_id retrieval and the surrounding if statement, and just call the send command directly.
| agent_id=$("$mail_helper" agents 2>/dev/null | grep -o '^[^,]*' | head -1 || echo "") | |
| if [[ -n "$agent_id" ]]; then | |
| "$mail_helper" send \ | |
| --to "coordinator" \ | |
| --type status_report \ | |
| --payload "Task completed: $(loop_get_state ".prompt" | head -c 100). Iterations: $iteration. Branch: $(git branch --show-current 2>/dev/null || echo unknown)" \ | |
| 2>/dev/null || true | |
| fi | |
| # The mail-helper's send command will determine the 'from' agent ID internally. | |
| "$mail_helper" send \ | |
| --to "coordinator" \ | |
| --type status_report \ | |
| --payload "Task completed: $(loop_get_state ".prompt" | head -c 100). Iterations: $iteration. Branch: $(git branch --show-current 2>/dev/null || echo unknown)" \ | |
| 2>/dev/null || true |
🤖 Augment PR SummarySummary: Implements a token-efficient multi-agent orchestration layer for aidevops, centered around TOON-formatted messaging and a stateless “pulse” coordinator. Changes:
Technical Notes: Messaging and indexing use TOON blocks for reduced token overhead, and model routing maps tier names (e.g., 🤖 Was this summary useful? React with 👍 or 👎 |
.agent/scripts/mail-helper.sh
Outdated
| local agent_inbox="$INBOX_DIR/$agent_id" | ||
| mkdir -p "$agent_inbox" | ||
| write_message "$agent_inbox/${msg_id}.toon" "$msg_id" "$from" "$agent_id" "$msg_type" "$priority" "$convoy" "$payload" | ||
| ((count++)) |
There was a problem hiding this comment.
Because the script runs with set -e, ((count++)) exits with status 1 on the first increment (when count is 0), which can abort broadcasts/checks/pruning unexpectedly. Consider using an increment form that always returns success under set -e (or explicitly neutralize the status).
Other Locations
.agent/scripts/mail-helper.sh:271.agent/scripts/mail-helper.sh:273.agent/scripts/mail-helper.sh:372.agent/scripts/mail-helper.sh:393.agent/scripts/mail-helper.sh:439
🤖 Was this useful? React with 👍 or 👎
.agent/scripts/coordinator-helper.sh
Outdated
| if [[ "$msg_type" == "status_report" ]]; then | ||
| local msg_id | ||
| msg_id=$(basename "$msg_file" .toon) | ||
| "$MAIL_HELPER" archive "$msg_id" 2>/dev/null || true |
There was a problem hiding this comment.
This attempts to archive processed status_report files from $MAIL_DIR/outbox via mail-helper.sh archive, but archive only searches inbox paths, so these outbox messages will likely remain and be reprocessed every pulse. That can also cause repeated memory writes / duplicate reporting.
🤖 Was this useful? React with 👍 or 👎
.agent/subagent-index.toon
Outdated
| @@ -0,0 +1,95 @@ | |||
| <!--TOON:agents[14]{name,file,purpose,model_tier}: | |||
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @.agent/scripts/coordinator-helper.sh:
- Around line 131-143: The outbox status_report files are never removed because
the script in the outbox loop calls "$MAIL_HELPER" archive which only handles
inbox; update the outbox handling in the block using
outbox_dir/msg_file/msg_type so that when msg_type equals "status_report" you
move the file into the archive (or call an outbox-specific archive helper)
instead of calling the inbox-only archive: e.g. invoke a move of "$msg_file"
into the archive directory (or implement and call MAIL_HELPER outbox-archive
with msg_id) and ensure errors are suppressed like the existing call.
- Around line 147-205: The pulse "dispatched" count is inflated because
dispatch_count is computed from idle_agents rather than actual sends; modify the
dispatch logic (around first_idle, first_task and the "$MAIL_HELPER" send call)
to initialize dispatch_count=0 before use and increment dispatch_count only when
a dispatch actually succeeds (after the send returns success), then use that
dispatch_count in the TOON pulse summary instead of counting commas in
idle_agents; also ensure dispatch_count is exported/visible to the summary
section where echo
"${agent_count},${report_count},${dispatch_count},${ready_count},..." is
produced.
In @.agent/scripts/generate-opencode-agents.sh:
- Around line 312-322: The prompt existence check should use the path value from
AGENT_PROMPTS instead of a hardcoded "prompts/build.txt"; update the block that
sets config["prompt"] so it first looks up AGENT_PROMPTS[display_name], builds
prompt_file = os.path.join(agents_dir, AGENT_PROMPTS[display_name]), checks
os.path.exists(prompt_file), and only then sets config["prompt"] = "{file:" +
AGENT_PROMPTS[display_name] + "}" (keep display_name and config["prompt"] logic
intact so the referenced relative path from AGENT_PROMPTS is what’s validated
and used).
- Around line 201-210: MODEL_TIERS contains invalid Claude identifiers for the
"sonnet" and "opus" tiers causing runtime failures; update the "sonnet" value to
the correct identifier "claude-3-5-sonnet-20241022" and either replace or remove
the "opus" entry (currently set to "claude-opus-4-20250514") because the correct
Opus id was "claude-3-opus-20240229" and that model is retired—choose a
supported Claude alternative or drop the "opus" tier in the MODEL_TIERS map to
prevent runtime errors.
🧹 Nitpick comments (4)
.agent/AGENTS.md (1)
43-153: Trim root AGENTS.md further to stay within the 50–100 instruction budget.
Several long tables and code blocks (install commands, directory tree, workflow snippets) make the root file heavier than “universal rules.” Consider moving those examples into the referenced workflow docs and replacing them with short pointers/file references.Based on learnings and coding guidelines.
Also applies to: 156-219
.agent/scripts/mail-helper.sh (1)
190-205: Broadcast should target active agents only.
Right now the broadcast branch iterates every registry entry (including inactive agents), which will keep creating inboxes for deregistered workers. Filtering to,active,keeps dispatch lean.♻️ Proposed tweak
- agents=$(grep -v '^<!--' "$REGISTRY_FILE" | grep -v '^$' | grep -v '^-->' | cut -d',' -f1) + agents=$(grep ',active,' "$REGISTRY_FILE" 2>/dev/null | cut -d',' -f1).agent/prompts/build.txt (1)
21-24: Context budget guidance is sound but could include recovery strategy.The 100K token limit is sensible. Consider documenting what an agent should do when it realizes it's approaching or has exceeded this budget mid-operation—e.g., checkpoint progress, summarize findings, or request user guidance.
.agent/scripts/generate-opencode-agents.sh (1)
579-583: Duplicateclaude-code-mcpconfiguration blocks.Lines 579-583 configure
claude-code-mcpusing a forked GitHub repo, while lines 610-616 configure it again with the upstream npm package. The second block's conditionif 'claude-code-mcp' not in config['mcp']will never trigger because the first block already added it. This appears intentional based on the comment at line 578, but the dead code at 610-616 should be removed or the intent clarified.♻️ Suggested cleanup
-# Claude Code MCP - run Claude Code one-shot as MCP -# Docs: https://github.com/steipete/claude-code-mcp -# Note: this exposes tools as claude-code-mcp_* -if 'claude-code-mcp' not in config['mcp']: - config['mcp']['claude-code-mcp'] = { - "type": "local", - "command": ["npx", "-y", "@steipete/claude-code-mcp@1.10.12"], - "enabled": True - } - print(" Added claude-code-mcp MCP server")Also applies to: 610-616
| # 3. Check for idle agents that could take new work | ||
| local idle_agents="" | ||
| if [[ -n "$active_agents" ]]; then | ||
| while IFS=',' read -r agent_id role _branch _worktree _status _registered _last_seen; do | ||
| # Check if agent has unread messages (busy) or not (idle) | ||
| local inbox_dir="$MAIL_DIR/inbox/$agent_id" | ||
| local unread=0 | ||
| if [[ -d "$inbox_dir" ]]; then | ||
| unread=$(grep -rl ',unread$' "$inbox_dir" 2>/dev/null | wc -l | tr -d ' ') | ||
| fi | ||
| if [[ "$unread" -eq 0 && "$role" == "worker" ]]; then | ||
| idle_agents="${idle_agents}${agent_id}," | ||
| fi | ||
| done <<< "$active_agents" | ||
| fi | ||
|
|
||
| # 4. Find ready tasks to dispatch | ||
| local ready_tasks | ||
| ready_tasks=$(get_ready_tasks) | ||
|
|
||
| if [[ -n "$ready_tasks" && -n "$idle_agents" ]]; then | ||
| log_info "Dispatching tasks to idle workers..." | ||
| local first_idle | ||
| first_idle=$(echo "$idle_agents" | cut -d',' -f1) | ||
| local first_task | ||
| first_task=$(echo "$ready_tasks" | head -1) | ||
|
|
||
| if [[ -n "$first_idle" && -n "$first_task" ]]; then | ||
| "$MAIL_HELPER" send \ | ||
| --from "$COORDINATOR_ID" \ | ||
| --to "$first_idle" \ | ||
| --type task_dispatch \ | ||
| --payload "$first_task" \ | ||
| --priority normal 2>/dev/null || true | ||
| log_success "Dispatched to $first_idle: $(echo "$first_task" | head -c 80)" | ||
| fi | ||
| elif [[ -z "$active_agents" ]]; then | ||
| log_info "No active agents registered. Nothing to coordinate." | ||
| elif [[ -z "$ready_tasks" ]]; then | ||
| log_info "No ready tasks to dispatch." | ||
| else | ||
| log_info "All agents busy. Waiting for reports." | ||
| fi | ||
|
|
||
| # 5. Summary | ||
| local report_count=0 dispatch_count=0 ready_count=0 | ||
| if [[ -n "$reports" ]]; then | ||
| report_count=$(echo "$reports" | grep -c '.' 2>/dev/null || echo 0) | ||
| fi | ||
| if [[ -n "$idle_agents" ]]; then | ||
| dispatch_count=$(echo "$idle_agents" | tr -cd ',' | wc -c | tr -d ' ') | ||
| fi | ||
| if [[ -n "$ready_tasks" ]]; then | ||
| ready_count=$(echo "$ready_tasks" | grep -c '.' 2>/dev/null || echo 0) | ||
| fi | ||
| echo "" | ||
| echo "<!--TOON:pulse_summary{agents,reports,dispatched,ready_tasks,timestamp}:" | ||
| echo "${agent_count},${report_count},${dispatch_count},${ready_count},$(date -u +%Y-%m-%dT%H:%M:%SZ)" | ||
| echo "-->" |
There was a problem hiding this comment.
Pulse summary “dispatched” count is inflated.
dispatch_count is derived from idle agent count, but only one task is dispatched. Track actual dispatches so the TOON summary reflects reality.
🔧 Suggested adjustment
- # 3. Check for idle agents that could take new work
- local idle_agents=""
+ # 3. Check for idle agents that could take new work
+ local idle_agents=""
+ local dispatched=0
@@
log_success "Dispatched to $first_idle: $(echo "$first_task" | head -c 80)"
+ dispatched=1
fi
@@
- if [[ -n "$idle_agents" ]]; then
- dispatch_count=$(echo "$idle_agents" | tr -cd ',' | wc -c | tr -d ' ')
- fi
+ dispatch_count=$dispatched🤖 Prompt for AI Agents
In @.agent/scripts/coordinator-helper.sh around lines 147 - 205, The pulse
"dispatched" count is inflated because dispatch_count is computed from
idle_agents rather than actual sends; modify the dispatch logic (around
first_idle, first_task and the "$MAIL_HELPER" send call) to initialize
dispatch_count=0 before use and increment dispatch_count only when a dispatch
actually succeeds (after the send returns success), then use that dispatch_count
in the TOON pulse summary instead of counting commas in idle_agents; also ensure
dispatch_count is exported/visible to the summary section where echo
"${agent_count},${report_count},${dispatch_count},${ready_count},..." is
produced.
| # Model routing tiers (from subagent YAML frontmatter 'model:' field) | ||
| # Maps tier names to actual model identifiers | ||
| # Agents declare their tier; the coordinator uses this for cost-effective routing | ||
| MODEL_TIERS = { | ||
| "haiku": "claude-3-5-haiku-20241022", # Triage, routing, simple tasks | ||
| "sonnet": "claude-sonnet-4-20250514", # Code, review, implementation | ||
| "opus": "claude-opus-4-20250514", # Architecture, complex reasoning | ||
| "flash": "gemini-2.5-flash", # Fast, cheap, large context | ||
| "pro": "gemini-2.5-pro", # Capable, large context | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Claude 3.5 Haiku Sonnet Opus current model identifiers API 2025
💡 Result:
Anthropic Claude API model identifiers relevant to 2025:
-
Claude 3.5 Haiku
- Alias:
claude-3-5-haiku-latest - Snapshot (model ID):
claude-3-5-haiku-20241022[1]
- Alias:
-
Claude 3.5 Sonnet
- Alias:
claude-3-5-sonnet-latest - Snapshots (model IDs):
claude-3-5-sonnet-20241022(and olderclaude-3-5-sonnet-20240620) [1]
- Alias:
-
Claude Opus (there was no “Claude 3.5 Opus” public API model)
- Claude 3 Opus snapshot (model ID):
claude-3-opus-20240229— deprecated June 30, 2025; retired January 5, 2026 [2]
- Claude 3 Opus snapshot (model ID):
If you want, tell me whether you mean Anthropic API names specifically (above) or Bedrock/Vertex identifiers too (they use different ID formats).
Sources: [1] [2]
🌐 Web query:
Google Gemini 2.5 Flash Pro current model API identifiers January 2026
💡 Result:
There isn’t a single model called “Gemini 2.5 Flash Pro” in Google’s APIs; Flash and Pro are separate Gemini 2.5 models. The current Gemini 2.5 API model identifiers (as of January 2026) are:
- Gemini 2.5 Pro (stable):
gemini-2.5-pro[1][2] - Gemini 2.5 Flash (stable):
gemini-2.5-flash[1][3] - Gemini 2.5 Flash (preview, if you need a pinned preview build):
gemini-2.5-flash-preview-09-2025[1] - Gemini 2.5 Flash-Lite (stable):
gemini-2.5-flash-lite[1][2] - Gemini 2.5 Flash Image (stable):
gemini-2.5-flash-image[1][2]
Notes:
- Google’s docs emphasize that stable Gemini 2.5 names typically have no 3-digit suffix (for example,
gemini-2.5-flash). [2] - If you’re using Vertex AI, Gemini 2.5 preview models released after June 2025 may require the
globallocation. [2]
Sources
[1] Google Cloud Vertex AI docs – Supported models
[2] Firebase AI Logic docs – Supported Gemini model names / notes
[3] Google AI for Developers – Gemini models list
Fix invalid Claude model identifiers.
The claude-sonnet-4-20250514 and claude-opus-4-20250514 identifiers are incorrect and will fail at runtime:
- Sonnet should be
claude-3-5-sonnet-20241022(there is no "Claude Sonnet 4") - Opus should be
claude-3-opus-20240229—though note that Claude 3 Opus was retired January 5, 2026 and is no longer available via the API
The Haiku (claude-3-5-haiku-20241022) and Gemini models (gemini-2.5-flash, gemini-2.5-pro) are valid. Consider whether Opus should be replaced with an alternative Claude model or removed from the routing tiers.
🤖 Prompt for AI Agents
In @.agent/scripts/generate-opencode-agents.sh around lines 201 - 210,
MODEL_TIERS contains invalid Claude identifiers for the "sonnet" and "opus"
tiers causing runtime failures; update the "sonnet" value to the correct
identifier "claude-3-5-sonnet-20241022" and either replace or remove the "opus"
entry (currently set to "claude-opus-4-20250514") because the correct Opus id
was "claude-3-opus-20240229" and that model is retired—choose a supported Claude
alternative or drop the "opus" tier in the MODEL_TIERS map to prevent runtime
errors.
- mail-helper: add --agent flag to read/archive commands (was using caller's agent ID instead of target inbox) - coordinator: fix unbound variable with empty extra_args array (bash set -u incompatibility), resolve MAIL_HELPER to script-relative path - loop-common: resolve mail/memory helpers relative to script location instead of hardcoded ~/.aidevops path, quote all variable expansions - subagent-index.toon: fix row count declarations (agents 14→13, subagents 30→31)
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 00:15:13 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
…rides Refactors the custom system prompt to layer aidevops-specific overrides on top of the upstream Claude Code prompt rather than replacing it entirely. Changes: - Keeps upstream: tone/style, professional objectivity, task management, tool usage policy, code references - Overrides: file discovery (git ls-files), code search (Augment primary), git workflow (pre-edit-check.sh), security (credential paths), agent framework (subagents, TOON, memory, mailbox) - Removes all 'OpenCode' branding for OAuth plugin compatibility - Adds upstream source header (3c41e4e8f12b) for future diffing - 95 lines (was 88), well under 150 line target Upstream: anomalyco/opencode@3c41e4e8f12b anthropic.txt
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 00:23:40 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @.agent/scripts/coordinator-helper.sh:
- Around line 263-270: The auto-targeting in cmd_dispatch currently picks the
first worker from get_active_agents without ensuring they are idle; update
cmd_dispatch to mirror the idle-selection logic used in cmd_pulse: call
get_active_agents, filter for entries containing ',worker,', then further filter
to only agents marked idle (the same idle-check / pattern used in cmd_pulse),
and select the first idle worker as the default value for to; if no idle worker
exists, fall back to the original registry-first behavior. Ensure you modify the
cmd_dispatch flow that sets to so it references the same idle-filter logic as
cmd_pulse and still respects explicit to values.
♻️ Duplicate comments (7)
.agent/scripts/loop-common.sh (1)
746-758: Simplify status report send (agent_id check is redundant).
mail-helper sendalready resolves the sender; theagents | grep | headstep can return header text and doesn’t affect the send. Sending directly is simpler and more reliable. As per coding guidelines, prefer minimal, robust glue.♻️ Suggested simplification
- if [[ -x "$LOOP_MAIL_HELPER" ]]; then - local agent_id - agent_id=$("$LOOP_MAIL_HELPER" agents 2>/dev/null | grep -o '^[^,]*' | head -1 || echo "") - if [[ -n "$agent_id" ]]; then - "$LOOP_MAIL_HELPER" send \ - --to "coordinator" \ - --type status_report \ - --payload "Task completed: $(loop_get_state ".prompt" | head -c 100). Iterations: $iteration. Branch: $(git branch --show-current 2>/dev/null || echo unknown)" \ - 2>/dev/null || true - fi - fi + if [[ -x "$LOOP_MAIL_HELPER" ]]; then + "$LOOP_MAIL_HELPER" send \ + --to "coordinator" \ + --type status_report \ + --payload "Task completed: $(loop_get_state ".prompt" | head -c 100). Iterations: $iteration. Branch: $(git branch --show-current 2>/dev/null || echo unknown)" \ + 2>/dev/null || true + fi.agent/scripts/mail-helper.sh (3)
143-151: Harden option parsing against missing values.Several parsers
shift 2without ensuring$2exists; a flag without a value will terminate underset -e. Add a guard before consuming the value and apply the pattern across all cmd_* parsers. As per coding guidelines, prioritize robust argument handling.🛠️ Suggested guard (apply to all options)
- --to) to="$2"; shift 2 ;; + --to) + [[ -n "${2-}" ]] || { log_error "Missing argument for $1"; return 1; } + to="$2"; shift 2 ;;Also applies to: 228-233, 288-293, 329-334, 371-376, 427-431, 497-504, 562-566, 593-597
196-203: Avoid((count++))underset -e.
((count++))returns 1 when the previous value is 0, which triggersset -eand can abort broadcasts/checks/prunes. Usecount=$((count + 1))or((count+=1))instead. As per coding guidelines, keep control-flow robust.🛠️ Safer increment
- ((count++)) + count=$((count + 1))Also applies to: 270-274, 388-412, 452-458
525-549: Protect registry updates with a lock.Register/deregister perform read‑modify‑write on a shared file; concurrent agents can interleave and corrupt it. Add a lightweight lock (e.g., mkdir-based) around the update block. As per coding guidelines, ensure reliable automation under concurrency.
🔒 Add a lightweight lock around registry updates
+ local lock_dir="$MAIL_DIR/.registry.lock" + while ! mkdir "$lock_dir" 2>/dev/null; do sleep 0.05; done + trap 'rmdir "$lock_dir"' RETURN # Create or update registryAlso applies to: 578-582
.agent/scripts/coordinator-helper.sh (3)
133-144: Status reports never leave outbox.
mail-helper archiveonly searches inboxes, so outboxstatus_reportfiles are reprocessed every pulse. Move the outbox file into the archive (or add an outbox-archive command). As per coding guidelines, avoid repeated work and duplicate reporting.🐛 Move outbox reports into archive
- local msg_id - msg_id=$(basename "$msg_file" .toon) - "$MAIL_HELPER" archive "$msg_id" 2>/dev/null || true + local archive_dir + archive_dir="$MAIL_DIR/archive/$(date +%Y-%m)" + mkdir -p "$archive_dir" + mv "$msg_file" "$archive_dir/" 2>/dev/null || true
169-184: Pulse summary overstates dispatch count.Only one task is sent, but
dispatch_countis derived from idle-agent count. Track actual sends for an accurate TOON summary. As per coding guidelines, keep reporting accurate.🔧 Track actual dispatches
- local report_count=0 dispatch_count=0 ready_count=0 + local report_count=0 dispatch_count=0 ready_count=0 + local dispatched=0 @@ "$MAIL_HELPER" send \ --from "$COORDINATOR_ID" \ --to "$first_idle" \ --type task_dispatch \ --payload "$first_task" \ --priority normal 2>/dev/null || true + dispatched=1 @@ - if [[ -n "$idle_agents" ]]; then - dispatch_count=$(echo "$idle_agents" | tr -cd ',' | wc -c | tr -d ' ') - fi + dispatch_count=$dispatchedAlso applies to: 193-200
248-255: Guard option parsing against missing values.
shift 2assumes a value exists; passing--taskor--namewithout a value will abort underset -e. Add missing-arg checks before shifting. As per coding guidelines, make CLI parsing resilient.🛠️ Add missing-arg checks
- --task) task="$2"; shift 2 ;; + --task) + [[ -n "${2-}" ]] || { log_error "Missing argument for $1"; return 1; } + task="$2"; shift 2 ;;Also applies to: 300-305
🧹 Nitpick comments (1)
.agent/scripts/mail-helper.sh (1)
27-35: Resolve MEMORY_HELPER relative to the script.Hardcoding
~/.aidevops/...makes installs elsewhere fail; coordinator/loop-common already use script-relative paths. Use a local SCRIPT_DIR for portability. As per coding guidelines, keep automation location-agnostic.♻️ Suggested path normalization
+# Resolve script directory for sibling helpers +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR @@ -readonly MEMORY_HELPER="$HOME/.aidevops/agents/scripts/memory-helper.sh" +readonly MEMORY_HELPER="${SCRIPT_DIR}/memory-helper.sh"
… OpenCode branding - Add model-specific reinforcement section to build.txt (persistence, conventions, library verification, lint/typecheck) - helps weaker models while being harmless reinforcement for stronger ones - Restore 'OpenCode' branding in prompt - the opencode-anthropic-auth plugin handles the transformation to 'Claude Code' for OAuth users automatically - Add opencode-prompt-drift-check.sh to detect when upstream anthropic.txt changes vs our synced commit hash (3c41e4e8f12b) - Integrate drift check into setup.sh flow (non-blocking warning) - build.txt now 106 lines (was 95), still well under 150 target
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 00:36:37 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @.agent/prompts/build.txt:
- Around line 12-15: Locate the "Tone and style" section and replace the phrase
"Github-flavored markdown" with the correctly capitalized "GitHub-flavored
Markdown" (update both words: GitHub and Markdown) so the proper noun casing is
used; ensure any other occurrences of that phrase in the file are updated the
same way.
In `@setup.sh`:
- Around line 1452-1467: The check_opencode_prompt_drift function currently
ignores the drift script's exit status and can incorrectly report "in sync";
change it to capture the exit code when running
.agent/scripts/opencode-prompt-drift-check.sh (use a pattern like output=$(bash
"$drift_script" --quiet 2>/dev/null; echo "$?") or capture $? immediately after
execution), then branch on that status: if non-zero, emit an explicit
error/warning (using print_warning/print_error) indicating the drift check
failed and avoid printing "in sync"; if zero continue to parse output and
distinguish PROMPT_DRIFT vs in-sync as before, using the existing
local_hash/upstream_hash handling to report drift.
🧹 Nitpick comments (2)
.agent/scripts/opencode-prompt-drift-check.sh (2)
34-41: Prefer jq (already required) over python3 for JSON parsing.
python3isn’t guaranteed by setup requirements, so the drift check can fail on minimal systems. Usingjqaligns with existing dependencies and improves portability.♻️ Proposed update (jq parsing)
- local hash - hash=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['sha'][:12])" 2>/dev/null) || return 1 + local hash + hash=$(echo "$response" | jq -r '.[0].sha[0:12] // empty' 2>/dev/null) || return 1 + [[ -n "$hash" ]] || return 1
26-42: Add explicit returns to helper functions.Shell guidelines call for explicit returns in functions;
get_local_hashandget_upstream_hashcurrently rely on implicit exit codes.As per coding guidelines, explicit returns are required.♻️ Proposed update (explicit returns)
get_local_hash() { local hash hash=$(head -1 "$BUILD_TXT" | grep -oE '[0-9a-f]{12}' || echo "") echo "$hash" + return 0 } @@ get_upstream_hash() { local response response=$(curl -sf --max-time 10 \ "https://api.github.com/repos/${UPSTREAM_REPO}/commits?path=${UPSTREAM_FILE}&sha=${UPSTREAM_BRANCH}&per_page=1" \ -H "Accept: application/vnd.github.v3+json" 2>/dev/null) || return 1 @@ local hash hash=$(echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['sha'][:12])" 2>/dev/null) || return 1 echo "$hash" + return 0 }
Implements opencode-aidevops plugin that hooks into OpenCode's experimental.session.compacting event to inject dynamic context that should survive session compaction: - Agent state from registry.toon (active multi-agent orchestration) - Loop guardrails from loop-state/ (iteration count, objectives) - Relevant memories via memory-helper.sh (project-specific learnings) - Git context (current branch, recent commits) - Pending mailbox messages (inter-agent communications) - Critical rules reminder (file discovery, git workflow, security) Static rules (tool preferences, security, quality standards) are already in the system prompt (build.txt) which never gets compacted. This plugin handles the dynamic, session-specific context that changes per-session. Plugin is registered in setup.sh as a file:// local plugin. Zero dependencies - uses only Node.js builtins (fs, path, child_process).
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 00:51:26 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
Fixes raised by automated reviewers on PR #158: - Add file locking for registry modifications (race condition prevention) - Add argument validation before shift 2 (prevents crash on missing values) - Fix outbox status_reports: move directly to archive instead of using inbox-only archive command (prevents reprocessing on every pulse) - Replace ((count++)) with count=$((count + 1)) throughout (prevents exit status 1 under set -e when count starts at 0) - Fix dispatch_count: track actual dispatches (was counting idle agents) - Fix cmd_dispatch: check worker inbox for unread messages before selecting - Fix drift check: distinguish success/drift/failure exit codes properly - Fix loop-common.sh: identify current agent by worktree path match before falling back to first registered agent
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 01:25:52 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @.agent/plugins/opencode-aidevops/index.mjs:
- Around line 150-164: getMailboxState is treating any output from running
mail-helper.sh as a numeric unread count, which leaks multiline TOON blocks into
compaction; update getMailboxState to call mail-helper.sh with a dedicated count
mode (e.g., add a --count flag) or parse the runner output for a numeric unread
value, validate that pending is a non-negative integer (use regex like /^\d+$/
or Number parsing), only treat it as pending when numeric > 0, and emit just the
numeric summary line (not the raw multiline output) so the function returns a
small, sanitized message when there are unread messages; locate changes around
getMailboxState, the run(`bash "${mailHelper}" ...`) invocation and the pending
variable handling to implement this.
♻️ Duplicate comments (2)
.agent/scripts/mail-helper.sh (1)
228-233: Add missing option-argument validation to avoidset -ucrashes.Some flag handlers still assume
$2exists (e.g.,--agent,--older-than-days,--role,--branch). Withset -u, this will exit on malformed input. Mirror the guard pattern used incmd_sendacross these handlers.✅ Example pattern (apply to all flagged options)
- --agent) agent_id="$2"; shift 2 ;; + --agent) [[ $# -lt 2 ]] && { log_error "--agent requires a value"; return 1; }; agent_id="$2"; shift 2 ;;Also applies to: 371-375, 427-431, 497-503, 579-583
.agent/scripts/coordinator-helper.sh (1)
271-277: Use unread-only checks when auto-selecting idle workers.
cmd_dispatchtreats any inbox message as “busy,” butcmd_pulseuses unread-only. This can prevent dispatching to agents whose inbox only has read messages. Align the logic.🛠️ Suggested fix
- if [[ -d "$inbox_dir" ]]; then - unread=$(find "$inbox_dir" -name "*.toon" -type f 2>/dev/null | wc -l | tr -d ' ') - fi + if [[ -d "$inbox_dir" ]]; then + unread=$(grep -rl ',unread$' "$inbox_dir" 2>/dev/null | wc -l | tr -d ' ') + fi
- Validate mailbox check output is numeric before using as count (prevents TOON block leakage into compaction context) - Add argument guards to all remaining --agent, --older-than-days, --role, --branch, --worktree handlers in mail-helper.sh
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 01:47:48 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @.agent/plugins/opencode-aidevops/index.mjs:
- Around line 103-121: The project name extraction in getRelevantMemories is
fragile and unsafe: replace directory.split("/").pop() with a robust basename
extraction (use path.basename or equivalent) to correctly handle trailing
slashes and empty results, then properly escape or avoid interpolating
projectName into a shell string (use child_process spawn/execFile or a
shell-escape utility) when invoking memoryHelper; ensure you reference
getRelevantMemories, memoryHelper, projectName, and run to locate the change,
validate empty projectName before calling recall, and pass the project name as a
safe argument rather than raw string interpolation.
- Around line 16-26: The current run(cmd) executes shell commands with
interpolated directories, which risks shell injection; replace it with a new
runInDir(cmd, cwd) that calls execSync with the cwd option (and no shell
interpolation) and update callers like getGitContext to pass the directory via
cwd instead of embedding it into cmd; additionally validate/normalize the
directory argument (e.g., path.resolve and fs.stat to ensure it is an existing
directory and reject or escape paths containing shell metacharacters) and keep
the same encoding/timeout/stdio behavior and error handling pattern in the new
runInDir function.
In @.agent/scripts/mail-helper.sh:
- Around line 639-647: The agent listing prints ANSI color variables (${CYAN},
${NC}) using plain echo which won't interpret escape sequences; update the print
to use an escape-aware output (e.g., replace the echo call that prints "
${CYAN}$id${NC} ($role) on $branch - last seen: $last_seen" with an echo -e or a
printf '%b' equivalent) so the ${CYAN} and ${NC} color codes render; ensure any
other similar echoes in the same block (and the initial "Active Agents:" line if
intended to be colored) are updated consistently and that the IFS/while loop
remains unchanged.
- Around line 550-567: Validate and reject unsafe agent_id values before using
them in sed/registry updates: in the cmd_register flow (immediately after its
start, e.g., after the current line ~508) add a check that agent_id matches a
safe character class (e.g., only alphanumeric, underscore, hyphen) and on
failure call log_error and return 1; this prevents sed injection in the sed
invocations that update REGISTRY_FILE (the sed lines that replace or insert
"${agent_id},${role},..." should then only ever see a validated agent_id).
🧹 Nitpick comments (2)
.agent/scripts/mail-helper.sh (2)
120-133:parse_messageis defined but not used internally.This function appears to be intended for external consumers. Consider documenting this in the usage section or adding a brief comment indicating it's part of the public API for other scripts.
526-541: Locking mechanism addresses race condition concerns.The
noclobber-based lock with retry loop is a reasonable approach. However, the aggressive stale lock removal after only 1 second (5 × 0.2s) could cause issues if a legitimate holder is performing a slow operation.Consider checking lock file age or using a longer timeout before assuming staleness:
♻️ Optional: Add lock age check before removal
if [[ "$lock_acquired" != "true" ]]; then - log_warn "Could not acquire registry lock (stale lock?). Removing and retrying." - rm -f "$lock_file" + # Only remove lock if it's older than 30 seconds (likely stale from crash) + if [[ -f "$lock_file" ]]; then + local lock_age + lock_age=$(( $(date +%s) - $(stat -c %Y "$lock_file" 2>/dev/null || stat -f %m "$lock_file" 2>/dev/null || echo 0) )) + if [[ "$lock_age" -gt 30 ]]; then + log_warn "Removing stale lock (age: ${lock_age}s)" + rm -f "$lock_file" + else + log_error "Registry locked by another process" + return 1 + fi + fi (set -o noclobber; echo $$ > "$lock_file") 2>/dev/null || true fi
| function run(cmd) { | ||
| try { | ||
| return execSync(cmd, { | ||
| encoding: "utf-8", | ||
| timeout: 5000, | ||
| stdio: ["pipe", "pipe", "pipe"], | ||
| }).trim(); | ||
| } catch { | ||
| return ""; | ||
| } | ||
| } |
There was a problem hiding this comment.
Shell command injection risk when directory contains metacharacters.
While directory comes from the plugin system rather than direct user input, interpolating it into shell commands without sanitization poses a risk if the path contains shell metacharacters (;, $, backticks, etc.). Consider using cwd option for git commands instead of -C, or validate/escape the directory path.
🛠️ Safer approach using cwd option
For git commands specifically, use cwd instead of shell interpolation:
function runInDir(cmd, cwd) {
try {
return execSync(cmd, {
encoding: "utf-8",
timeout: 5000,
cwd,
stdio: ["pipe", "pipe", "pipe"],
}).trim();
} catch {
return "";
}
}Then in getGitContext:
- const branch = run(`git -C "${directory}" branch --show-current 2>/dev/null`);
+ const branch = runInDir("git branch --show-current", directory);🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis
[failure] 18-18: .agent/plugins/opencode-aidevops/index.mjs#L18
Detected calls to child_process from a function argument cmd. This could lead to a command injection if the input is user controllable.
🤖 Prompt for AI Agents
In @.agent/plugins/opencode-aidevops/index.mjs around lines 16 - 26, The current
run(cmd) executes shell commands with interpolated directories, which risks
shell injection; replace it with a new runInDir(cmd, cwd) that calls execSync
with the cwd option (and no shell interpolation) and update callers like
getGitContext to pass the directory via cwd instead of embedding it into cmd;
additionally validate/normalize the directory argument (e.g., path.resolve and
fs.stat to ensure it is an existing directory and reject or escape paths
containing shell metacharacters) and keep the same encoding/timeout/stdio
behavior and error handling pattern in the new runInDir function.
| function getRelevantMemories(directory) { | ||
| const memoryHelper = join(SCRIPTS_DIR, "memory-helper.sh"); | ||
| if (!existsSync(memoryHelper)) return ""; | ||
|
|
||
| // Get the project name from the directory for context-relevant recall | ||
| const projectName = directory.split("/").pop() || ""; | ||
|
|
||
| // Recall recent memories related to this project | ||
| const memories = run( | ||
| `bash "${memoryHelper}" recall "${projectName}" --limit 5 2>/dev/null` | ||
| ); | ||
| if (!memories) return ""; | ||
|
|
||
| return [ | ||
| "## Relevant Memories", | ||
| "Previous session learnings relevant to this project:", | ||
| memories, | ||
| ].join("\n"); | ||
| } |
There was a problem hiding this comment.
Project name extraction is fragile and poses injection risk.
directory.split("/").pop() returns empty string for trailing slashes and passes unsanitized into a shell command. If directory ends with a malicious segment, it could inject commands.
🛠️ Proposed fix with sanitization
function getRelevantMemories(directory) {
const memoryHelper = join(SCRIPTS_DIR, "memory-helper.sh");
if (!existsSync(memoryHelper)) return "";
// Get the project name from the directory for context-relevant recall
- const projectName = directory.split("/").pop() || "";
+ const projectName = (directory.replace(/\/+$/, "").split("/").pop() || "")
+ .replace(/[^a-zA-Z0-9_-]/g, "");
+ if (!projectName) return "";
// Recall recent memories related to this project
const memories = run(
`bash "${memoryHelper}" recall "${projectName}" --limit 5 2>/dev/null`
);🤖 Prompt for AI Agents
In @.agent/plugins/opencode-aidevops/index.mjs around lines 103 - 121, The
project name extraction in getRelevantMemories is fragile and unsafe: replace
directory.split("/").pop() with a robust basename extraction (use path.basename
or equivalent) to correctly handle trailing slashes and empty results, then
properly escape or avoid interpolating projectName into a shell string (use
child_process spawn/execFile or a shell-escape utility) when invoking
memoryHelper; ensure you reference getRelevantMemories, memoryHelper,
projectName, and run to locate the change, validate empty projectName before
calling recall, and pass the project name as a safe argument rather than raw
string interpolation.
| # Check if agent already registered | ||
| if grep -q "^${agent_id}," "$REGISTRY_FILE" 2>/dev/null; then | ||
| # Update last_seen and status | ||
| if command -v sed &>/dev/null; then | ||
| sed -i.bak "s|^${agent_id},.*|${agent_id},${role},${branch},${worktree},active,$(grep "^${agent_id}," "$REGISTRY_FILE" | cut -d',' -f6),${timestamp}|" "$REGISTRY_FILE" | ||
| rm -f "${REGISTRY_FILE}.bak" | ||
| fi | ||
| log_info "Updated agent: $agent_id (last_seen: $timestamp)" | ||
| else | ||
| # Add new agent before closing --> | ||
| if command -v sed &>/dev/null; then | ||
| sed -i.bak "/^-->$/i\\ | ||
| ${agent_id},${role},${branch},${worktree},active,${timestamp},${timestamp}" "$REGISTRY_FILE" | ||
| rm -f "${REGISTRY_FILE}.bak" | ||
| fi | ||
| log_success "Registered agent: $agent_id (role: $role, branch: $branch)" | ||
| fi | ||
| fi |
There was a problem hiding this comment.
Potential sed injection if agent_id contains special characters.
If agent_id contains characters like |, &, \, or /, the sed pattern could break or behave unexpectedly. Consider sanitizing or validating the agent_id format.
🛡️ Suggested validation for agent_id
Add this validation early in cmd_register (after line 508):
# Validate agent_id contains only safe characters
if [[ ! "$agent_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
log_error "Invalid agent_id: must contain only alphanumeric, underscore, or hyphen"
return 1
fi🤖 Prompt for AI Agents
In @.agent/scripts/mail-helper.sh around lines 550 - 567, Validate and reject
unsafe agent_id values before using them in sed/registry updates: in the
cmd_register flow (immediately after its start, e.g., after the current line
~508) add a check that agent_id matches a safe character class (e.g., only
alphanumeric, underscore, hyphen) and on failure call log_error and return 1;
this prevents sed injection in the sed invocations that update REGISTRY_FILE
(the sed lines that replace or insert "${agent_id},${role},..." should then only
ever see a validated agent_id).
| if [[ "$active_only" == true ]]; then | ||
| echo "Active Agents:" | ||
| grep ',active,' "$REGISTRY_FILE" 2>/dev/null | while IFS=',' read -r id role branch _worktree _status _registered last_seen; do | ||
| echo " ${CYAN}$id${NC} ($role) on $branch - last seen: $last_seen" | ||
| done | ||
| else | ||
| cat "$REGISTRY_FILE" | ||
| fi | ||
| } |
There was a problem hiding this comment.
Color codes won't render without echo -e.
Line 642 uses ${CYAN} and ${NC} variables but echo without -e flag won't interpret ANSI escape sequences. The colors will appear as literal escape characters.
🐛 Fix: Use echo -e for color output
if [[ "$active_only" == true ]]; then
echo "Active Agents:"
grep ',active,' "$REGISTRY_FILE" 2>/dev/null | while IFS=',' read -r id role branch _worktree _status _registered last_seen; do
- echo " ${CYAN}$id${NC} ($role) on $branch - last seen: $last_seen"
+ echo -e " ${CYAN}$id${NC} ($role) on $branch - last seen: $last_seen"
done
else📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if [[ "$active_only" == true ]]; then | |
| echo "Active Agents:" | |
| grep ',active,' "$REGISTRY_FILE" 2>/dev/null | while IFS=',' read -r id role branch _worktree _status _registered last_seen; do | |
| echo " ${CYAN}$id${NC} ($role) on $branch - last seen: $last_seen" | |
| done | |
| else | |
| cat "$REGISTRY_FILE" | |
| fi | |
| } | |
| if [[ "$active_only" == true ]]; then | |
| echo "Active Agents:" | |
| grep ',active,' "$REGISTRY_FILE" 2>/dev/null | while IFS=',' read -r id role branch _worktree _status _registered last_seen; do | |
| echo -e " ${CYAN}$id${NC} ($role) on $branch - last seen: $last_seen" | |
| done | |
| else | |
| cat "$REGISTRY_FILE" | |
| fi | |
| } |
🤖 Prompt for AI Agents
In @.agent/scripts/mail-helper.sh around lines 639 - 647, The agent listing
prints ANSI color variables (${CYAN}, ${NC}) using plain echo which won't
interpret escape sequences; update the print to use an escape-aware output
(e.g., replace the echo call that prints " ${CYAN}$id${NC} ($role) on $branch -
last seen: $last_seen" with an echo -e or a printf '%b' equivalent) so the
${CYAN} and ${NC} color codes render; ensure any other similar echoes in the
same block (and the initial "Active Agents:" line if intended to be colored) are
updated consistently and that the IFS/while loop remains unchanged.
- Fix capitalization: GitHub-flavored Markdown (build.txt) - Fix prompt file existence check: validate from AGENT_PROMPTS path, not hardcoded (generate-opencode-agents.sh) Note: Model identifiers (claude-sonnet-4-20250514, claude-opus-4-20250514) are correct Claude 4 series models. CodeRabbit's web search returned outdated info. subagent-index.toon count (13) matches actual rows - Augment miscounted.
|
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Jan 24 01:52:07 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
Document the new orchestration capabilities from PR #158: - TOON mailbox system for inter-agent messaging - Stateless coordinator with pulse-based task dispatch - Agent registry with role/branch/worktree tracking - Model routing tiers (haiku/sonnet/opus/flash/pro) - Compaction plugin for context preservation - Custom system prompt based on upstream OpenCode - Subagent index for fast agent discovery
|
t004.18 verified and merged. This unblocks t004.24 (Build content generation AI tools) — both blockers (t004.17, t004.18) are now resolved. t004.24 is ready for dispatch. Posted by AI Supervisor (automated reasoning cycle) |



Summary
Implements the Multi-Agent Orchestration & Token Efficiency plan (p013/t068) - enabling parallel agent coordination with minimal token overhead.
Changes
.agent/prompts/build.txtreplaces default Claude Code harness, eliminating conflicting instructions (e.g., "use Glob" vs "use git ls-files")mail-helper.sh- inter-agent messaging with per-agent inboxes, TOON format (40-60% token savings vs JSON), archive→remember→prune lifecyclesubagent-index.toonandworkflows/pre-edit.mdcoordinator-helper.sh- pulse-based orchestration (~20K tokens per pulse), convoy grouping, fswatch/polling triggersMODEL_TIERSdict mapping to actual model IDsNew Files
.agent/prompts/build.txt- Custom system prompt.agent/scripts/mail-helper.sh- Inter-agent mailbox (656 lines).agent/scripts/coordinator-helper.sh- Stateless coordinator (397 lines).agent/subagent-index.toon- Compressed agent/subagent index.agent/workflows/pre-edit.md- Detailed pre-edit workflowModified Files
.agent/AGENTS.md- Compressed (778 → 219 lines).agent/scripts/generate-opencode-agents.sh- Prompt + model routing.agent/scripts/loop-common.sh- Mailbox awarenessRemaining (future PRs)
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.