Problem
Agents call grove_contribute with only summary text and skip all structured fields (artifacts, scores, relations). The frontier ranks by recency only. The DAG is flat — no edges.
Root Cause
grove_contribute accepts ALL kinds (work, review, discussion, etc.) with everything optional. LLMs only fill required fields. JSON Schema can't express "required if kind=review". So agents always take the lazy path: grove_contribute(kind="review", summary="LGTM") — skipping targetCid and scores.
grove_review exists as sugar that forces targetCid, but agents bypass it by calling grove_contribute(kind="review") directly.
You can't have a generic tool alongside specific tools. Agents will always pick the one with fewer required fields.
Fix: Replace grove_contribute with per-kind tools
Remove grove_contribute entirely. Each kind gets its own tool with strict required fields:
| Tool |
Required |
Replaces |
grove_submit_work |
summary, artifacts, agent |
grove_contribute(kind=work) |
grove_submit_review |
targetCid, summary, scores, agent |
grove_review + grove_contribute(kind=review) |
grove_discuss |
summary, agent |
grove_contribute(kind=discussion) + grove_send_message |
grove_reproduce |
targetCid, result, summary, agent |
grove_contribute(kind=reproduction) |
grove_done |
summary, agent |
grove_contribute(context.done=true) |
No generic escape hatch. No bypass. 5 tools instead of 5 overlapping ones.
Server-side validation
Each tool returns isError: true with actionable message if required fields are wrong:
VALIDATION_ERROR: Reviews must include at least one score.
Example: scores: { "correctness": { value: 0.9, direction: "maximize" } }
Zod descriptions with examples
artifacts: z.record(z.string(), z.string())
.describe("File artifacts. Map path to content. " +
"Example: {\"hello.txt\": \"Hello World\"}. " +
"Without artifacts, reviewers cannot see your files.")
Also merge
| Merge |
Into |
Why |
grove_send_message |
grove_discuss |
Both create discussion contributions. Use tags for @mentions. |
grove_bounty_claim |
grove_claim |
bounty_claim calls claim internally. Agents just call claim. |
Also remove
| Remove |
Why |
grove_contribute |
Generic escape hatch. Agents bypass structured fields. Replaced by per-kind tools above. |
grove_review |
Replaced by grove_submit_review with scores REQUIRED (not optional). |
E2E Testing Guide
How to test (MUST follow — no workarounds, no manual injection)
cd /Users/tafeng/grove/.claude/worktrees/snazzy-humming-thacker
bun run src/cli/main.ts tui
- j → "New session" → Enter
- Enter → select "review-loop"
- Enter → confirm name
- Wait for setup (~10s if Nexus running, ~60s cold start)
- Enter → continue past Agent Detect
- Type
create hello.txt → Enter → Enter (confirm spawn)
- Wait — agents take 30-60s. Do NOT inject contributions manually.
- Watch feed for:
▒ work — coder created file and called grove tool
▷ review — reviewer responded
- Loop continues until reviewer approves
grove_done signals completion
- Tab → check DAG, Detail, Frontier, Claims panels
- Ctrl+B → back to simple view
- q q → quit
What to verify
tmux testing (for CI or automated verification)
tmux new-session -d -s grove-tui -x 120 -y 40
tmux send-keys -t grove-tui "cd $(pwd) && TERM=xterm-256color bun run src/cli/main.ts tui" Enter
sleep 10
# Navigate through screens
tmux send-keys -t grove-tui j Enter # New session
sleep 2
tmux send-keys -t grove-tui Enter # review-loop
sleep 1
tmux send-keys -t grove-tui Enter # confirm name
sleep 15 # wait for setup
tmux send-keys -t grove-tui Enter # past agent detect
sleep 1
tmux send-keys -t grove-tui "create hello.txt" Enter # goal
sleep 1
tmux send-keys -t grove-tui Enter # confirm spawn
# Poll for contributions (30s poll interval, agents take 30-60s)
for i in $(seq 1 20); do
sleep 15
FEED=$(tmux capture-pane -t grove-tui -p | grep -E "▒|▷|◇")
COUNT=$(echo "$FEED" | grep -c "▒\|▷\|◇")
echo "=== $i: $COUNT contributions ==="
[ -n "$FEED" ] && echo "$FEED"
# Check for grove_done
DONE=$(cat .grove/agent-logs/reviewer-*.log 2>/dev/null | grep -c "grove_done\|DONE")
[ "$DONE" -gt 0 ] && echo "SESSION COMPLETE" && break
done
Known architecture decisions
- Local SQLite for data — MCP and HTTP server both read/write
.grove/grove.db. NOT Nexus VFS (rate limited).
- Nexus for IPC only — SSE push between agents via NexusWsBridge. Not for contribution storage.
- Shared Nexus instance — All worktrees share
~/.grove/nexus-data (same Docker stack). API key read from ~/.grove/nexus-data/.state.json.
- 30s polling baseline —
usePolledData at 30s interval. EventBus (when SSE connected) triggers instant refresh(). No "safety net" polling inside event-driven hook.
- codex MCP "Unsupported" is normal — stdio transport always shows this. Tools work fine.
- Single
grove MCP name — registered via codex mcp add grove. No per-spawn unique names (avoids 93+ stale entries).
findGroveDir prefers CWD — checks $CWD/.grove/ before walking up parent dirs.
Common pitfalls (from debugging this session)
- Don't inject contributions manually (
bun -e). The whole point is agents call grove tools themselves.
- Don't unset GROVE_NEXUS_URL to bypass Nexus. Fix the key lookup instead.
- Stale server on port 4515 — if server was spawned by a previous TUI run, it has old env vars. Kill it:
lsof -ti:4515 | xargs kill -9
- 93 stale codex MCP entries — clean with:
codex mcp list | grep grove- | awk '{print $1}' | while read n; do codex mcp remove "$n"; done
- Multiple Nexus Docker stacks — check
docker ps --filter name=nexus. Should be ONE stack. Kill extras: docker compose -p nexus-HASH down
Repro Steps (current broken state)
bun run src/cli/main.ts tui
- New session → review-loop → "create hello.txt" → spawn agents
- Watch: coder sends
grove_contribute(kind="work", summary="...") — no artifacts
- Watch: reviewer sends
grove_contribute(kind="review", summary="LGTM") — no targetCid, no scores
- Tab → Frontier: only "recency" metric
- Tab → DAG: flat list, no edges
Expected After Fix
- Coder calls
grove_submit_work(summary, artifacts={...}) — reviewer can checkout files
- Reviewer calls
grove_submit_review(targetCid, summary, scores={...}) — DAG shows edges, frontier ranks by scores
- No way to bypass required fields — tools enforce structure
Files to Change
src/mcp/tools/contributions.ts — remove grove_contribute, grove_review. Add grove_submit_work, grove_submit_review.
src/mcp/schemas.ts — update schemas with required fields
src/mcp/tools/messaging.ts — merge send_message into discuss
src/mcp/tools/done.ts — keep grove_done as-is (already has required summary + agent)
src/mcp/server.ts — update tool registration
src/tui/spawn-manager.ts — update CLAUDE.md/CODEX.md agent instructions to reference new tools
src/core/acpx-runtime.ts — update system-reminder to reference new tools
src/core/operations/contribute.ts — keep contributeOperation as internal (tools call it, agents don't)
Problem
Agents call
grove_contributewith onlysummarytext and skip all structured fields (artifacts, scores, relations). The frontier ranks by recency only. The DAG is flat — no edges.Root Cause
grove_contributeaccepts ALL kinds (work, review, discussion, etc.) with everything optional. LLMs only fillrequiredfields. JSON Schema can't express "required if kind=review". So agents always take the lazy path:grove_contribute(kind="review", summary="LGTM")— skippingtargetCidandscores.grove_reviewexists as sugar that forcestargetCid, but agents bypass it by callinggrove_contribute(kind="review")directly.You can't have a generic tool alongside specific tools. Agents will always pick the one with fewer required fields.
Fix: Replace grove_contribute with per-kind tools
Remove
grove_contributeentirely. Each kind gets its own tool with strict required fields:grove_submit_worksummary,artifacts,agentgrove_contribute(kind=work)grove_submit_reviewtargetCid,summary,scores,agentgrove_review+grove_contribute(kind=review)grove_discusssummary,agentgrove_contribute(kind=discussion)+grove_send_messagegrove_reproducetargetCid,result,summary,agentgrove_contribute(kind=reproduction)grove_donesummary,agentgrove_contribute(context.done=true)No generic escape hatch. No bypass. 5 tools instead of 5 overlapping ones.
Server-side validation
Each tool returns
isError: truewith actionable message if required fields are wrong:Zod descriptions with examples
Also merge
grove_send_messagegrove_discussgrove_bounty_claimgrove_claimAlso remove
grove_contributegrove_reviewgrove_submit_reviewwith scores REQUIRED (not optional).E2E Testing Guide
How to test (MUST follow — no workarounds, no manual injection)
cd /Users/tafeng/grove/.claude/worktrees/snazzy-humming-thacker bun run src/cli/main.ts tuicreate hello.txt→ Enter → Enter (confirm spawn)▒ work— coder created file and called grove tool▷ review— reviewer respondedgrove_donesignals completionWhat to verify
grove_doneafter approving — loop ends cleanly (no infinite ping-pong).grove/agent-logs/showgrove_submit_work (completed)andgrove_submit_review (completed)tmux testing (for CI or automated verification)
Known architecture decisions
.grove/grove.db. NOT Nexus VFS (rate limited).~/.grove/nexus-data(same Docker stack). API key read from~/.grove/nexus-data/.state.json.usePolledDataat 30s interval. EventBus (when SSE connected) triggers instantrefresh(). No "safety net" polling inside event-driven hook.groveMCP name — registered viacodex mcp add grove. No per-spawn unique names (avoids 93+ stale entries).findGroveDirprefers CWD — checks$CWD/.grove/before walking up parent dirs.Common pitfalls (from debugging this session)
bun -e). The whole point is agents call grove tools themselves.lsof -ti:4515 | xargs kill -9codex mcp list | grep grove- | awk '{print $1}' | while read n; do codex mcp remove "$n"; donedocker ps --filter name=nexus. Should be ONE stack. Kill extras:docker compose -p nexus-HASH downRepro Steps (current broken state)
bun run src/cli/main.ts tuigrove_contribute(kind="work", summary="...")— no artifactsgrove_contribute(kind="review", summary="LGTM")— no targetCid, no scoresExpected After Fix
grove_submit_work(summary, artifacts={...})— reviewer can checkout filesgrove_submit_review(targetCid, summary, scores={...})— DAG shows edges, frontier ranks by scoresFiles to Change
src/mcp/tools/contributions.ts— remove grove_contribute, grove_review. Add grove_submit_work, grove_submit_review.src/mcp/schemas.ts— update schemas with required fieldssrc/mcp/tools/messaging.ts— merge send_message into discusssrc/mcp/tools/done.ts— keep grove_done as-is (already has required summary + agent)src/mcp/server.ts— update tool registrationsrc/tui/spawn-manager.ts— update CLAUDE.md/CODEX.md agent instructions to reference new toolssrc/core/acpx-runtime.ts— update system-reminder to reference new toolssrc/core/operations/contribute.ts— keep contributeOperation as internal (tools call it, agents don't)