Three improvements that turn the doc-gardener demo from "runs but stays in 'draft' forever" into a goal that properly transitions through its lifecycle and renders a completion summary on /goals/<id>. ### 1. complete_goal MCP tool (#59, #62) New tool surface: complete_goal(goal_id, status, summary, completion_message_id?) The critic calls this from inside the sandbox after it sends its FINAL: DM. Records the one-paragraph human-readable summary on the goal row plus a pointer to the message that carried the FINAL text, so the Web UI /goals/<id> page has both the verdict and a deep link to the full findings JSON. Status parameter accepts completed | stuck | cancelled. Idempotent when called with the current status. Rejects callers owned by a different human than the goal owner. Plumbing: - New migration 026_goals_completion_summary.sql adds two columns to goals: completion_summary TEXT, completion_message_id INTEGER (FK messages.id, ON DELETE SET NULL). - internal/goals/types.go: new CompletionSummary + CompletionMessageID fields on Goal struct. - internal/goals/store.go: Get/List Scan both new columns; SetCompletion(goalID, status, summary, messageID) helper that updates status+summary+message_id atomically and populates completed_at for terminal states. - internal/goals/service.go: Complete(ctx, goalID, status, summary, messageID) wraps the store method with legalTransition gating. legalTransition expanded so draft can jump straight to completed (no mandatory "active" hop required). - internal/mcp/goals_tools.go: completeGoalTool definition + handleCompleteGoal handler. Tool count 6 → 7. - internal/api/goals_handler.go: surfaces completion_summary, completion_message_id, and completed_at on both list and detail endpoints so the Svelte /goals UI can render them. ### 2. Draft → active auto-transition in propose_task_tree (#60) handleProposeTaskTree now flips the goal from draft to active at the end. Previously the coordinator would call create_goal + propose_task_tree and dispatch inspector, but the goal stayed in draft forever because nothing transitioned it. Now the mere fact of having a task tree means the goal is active. Safe: the transition is best-effort and ignores the legal-transition error when the goal is already beyond draft. ### 3. REVISE round cap (#61) Two-layer enforcement: - Server-side: examples/doc-gardener/start.sh drops max_trigger_depth from 8 to 4. Each REVISE round costs 2 hops (critic→inspector + inspector→critic), so depth=4 caps the loop at roughly 2 rounds before the reactor refuses further dispatches. - Prompt-side: inspector now includes revision_round (starting at 0, incremented when it sees a REVISE: input) in its findings JSON. Critic reads revision_round and force-FINALs when >= 1. Prompt explicitly tells the critic to call complete_goal after sending FINAL, so the goal row gets a proper completion_summary. ### 4. run_task.sh terminal-state detection Rewrote the poll loop to watch goals.status/completion_summary as the definitive "done" signal rather than parsing DM bodies. Keeps a message-based fallback for TRIVIAL/CANNOT paths that don't create a goal. Treats "Received system trigger..." and "Coalesced trigger..." as informational (they're `__coalesced__` reactor synthetic events leaking through the coordinator reply, not real user-facing output). Bare coordinator replies are terminal only when no goal was created. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
123 lines
5.3 KiB
Bash
Executable File
123 lines
5.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# run_task.sh — send a doc-verification goal DM from algis to
|
|
# doc-coordinator and wait for the FINAL: reply that flows back from
|
|
# docs-critic. The whole flow is driven by MCP tool calls inside three
|
|
# Docker-isolated agent containers — nothing here writes to the DB
|
|
# directly.
|
|
#
|
|
# Usage:
|
|
# ./run_task.sh # default doc-gardener brief
|
|
# ./run_task.sh "your custom goal here"
|
|
#
|
|
# The default brief asks the inspector to verify mcpproxy CLI flag
|
|
# documentation against the actual binary. Override with any free-form
|
|
# brief — the coordinator triages it.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
BIN="$SCRIPT_DIR/bin/synapbus"
|
|
SOCKET="$SCRIPT_DIR/data/synapbus.sock"
|
|
|
|
DEFAULT_GOAL='Verify the CLI commands listed on https://docs.mcpproxy.app/cli/command-reference still exist in the current mcpproxy binary. Install mcpproxy in the sandbox first (releases at https://github.com/smart-mcp-proxy/mcpproxy-go/releases — pick the linux-arm64 or linux-amd64 variant matching `uname -m`). For each documented command, check whether `mcpproxy --help` and `mcpproxy <command> --help` show it; flag any drift, missing commands, or doc claims that no longer match. Produce a patch suggestion list.'
|
|
|
|
GOAL="${1:-$DEFAULT_GOAL}"
|
|
|
|
say() { printf '\033[1;36m[run]\033[0m %s\n' "$*"; }
|
|
die() { printf '\033[1;31m[run][FAIL]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
[ -x "$BIN" ] || die "synapbus binary not found at $BIN — run ./start.sh first"
|
|
[ -S "$SOCKET" ] || die "admin socket missing — is synapbus running?"
|
|
|
|
cd "$SCRIPT_DIR"
|
|
DB="$SCRIPT_DIR/data/synapbus.db"
|
|
|
|
# Snapshot the current max message id so we only look at replies from
|
|
# THIS run, not stale replies left from previous invocations.
|
|
BASELINE=$(sqlite3 "$DB" "SELECT COALESCE(MAX(id), 0) FROM messages" 2>/dev/null || echo 0)
|
|
|
|
say "sending goal DM: algis → doc-coordinator (baseline msg_id=$BASELINE)"
|
|
printf '%s' "$GOAL" | "$BIN" --socket "$SOCKET" messages send \
|
|
--from algis \
|
|
--to doc-coordinator \
|
|
--priority 8 >&2
|
|
|
|
say "waiting for goal completion or FINAL:/CANNOT: reply to algis (up to 600s)..."
|
|
deadline=$(( $(date +%s) + 600 ))
|
|
last_seen_id=$BASELINE
|
|
|
|
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
# Goal completion check (definitive signal — set by complete_goal MCP).
|
|
# A goal in 'completed'/'stuck'/'cancelled' state with a
|
|
# completion_summary means the critic finalized the verdict.
|
|
COMPLETED=$(sqlite3 "$DB" "
|
|
SELECT id FROM goals
|
|
WHERE status IN ('completed','stuck','cancelled')
|
|
AND completion_summary IS NOT NULL
|
|
ORDER BY id DESC LIMIT 1
|
|
" 2>/dev/null || true)
|
|
if [ -n "$COMPLETED" ]; then
|
|
say "goal $COMPLETED reached terminal state"
|
|
GOAL_SUMMARY=$(sqlite3 "$DB" "SELECT status || ': ' || COALESCE(completion_summary,'') FROM goals WHERE id = $COMPLETED" 2>/dev/null)
|
|
say "$GOAL_SUMMARY"
|
|
echo "$COMPLETED" > "$SCRIPT_DIR/.last_goal_id"
|
|
say "goal id = $COMPLETED — render with ./report.sh"
|
|
exit 0
|
|
fi
|
|
|
|
# Message-based fallback (for TRIVIAL/CANNOT paths that skip the
|
|
# task tree and never call complete_goal).
|
|
NEW_LINES=$(sqlite3 -separator '|' "$DB" "
|
|
SELECT id, from_agent, replace(substr(body, 1, 280), char(10), ' ')
|
|
FROM messages
|
|
WHERE to_agent = 'algis'
|
|
AND from_agent != 'algis'
|
|
AND id > $last_seen_id
|
|
ORDER BY id ASC
|
|
" 2>/dev/null || true)
|
|
|
|
if [ -n "$NEW_LINES" ]; then
|
|
while IFS='|' read -r id from body; do
|
|
[ -z "$id" ] && continue
|
|
say "← [$from #$id] $body"
|
|
last_seen_id=$id
|
|
case "$body" in
|
|
DELEGATED:*|REVISING:*|Received\ system\ trigger*|Coalesced\ trigger*)
|
|
;; # informational, keep waiting
|
|
FINAL:*|CANNOT:*)
|
|
say "terminal response received"
|
|
GOAL_ID=$(sqlite3 "$DB" 'SELECT id FROM goals ORDER BY id DESC LIMIT 1' 2>/dev/null || echo)
|
|
if [ -n "$GOAL_ID" ]; then
|
|
echo "$GOAL_ID" > "$SCRIPT_DIR/.last_goal_id"
|
|
say "goal id = $GOAL_ID — render with ./report.sh"
|
|
fi
|
|
exit 0
|
|
;;
|
|
*)
|
|
# A bare reply from doc-coordinator with no status
|
|
# prefix is a TRIVIAL-triage direct answer. Count
|
|
# it as terminal only if no goal was created (i.e.
|
|
# the coordinator didn't start a pipeline).
|
|
if [ "$from" = "doc-coordinator" ]; then
|
|
HAS_GOAL=$(sqlite3 "$DB" 'SELECT COUNT(*) FROM goals' 2>/dev/null || echo 0)
|
|
if [ "$HAS_GOAL" = "0" ]; then
|
|
say "direct (trivial) response received"
|
|
exit 0
|
|
fi
|
|
# Otherwise keep waiting — the coordinator
|
|
# already delegated and will finalize via
|
|
# complete_goal once the critic runs.
|
|
fi
|
|
;;
|
|
esac
|
|
done <<EOF
|
|
$NEW_LINES
|
|
EOF
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
say "timed out waiting for terminal response"
|
|
say "check http://localhost:18089/runs and http://localhost:18089/goals"
|
|
exit 2
|