mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Traceability hooks and other fixes
This commit is contained in:
@@ -119,14 +119,16 @@ Each agent gets their own git clone of a project, enabling parallel development
|
||||
|
||||
### Branch Naming Convention
|
||||
|
||||
Branch names follow the pattern: `{type}/{team}/{task-id[:8]}`
|
||||
Branch names follow the pattern: `{type}/{team}/{task-hierarchy}`
|
||||
|
||||
**Types:** `feature`, `bug`, `chore`, `docs`, `hotfix`
|
||||
|
||||
**Task Hierarchy:** Uses `--` separator (not `/`) to avoid git ref conflicts.
|
||||
|
||||
**Examples:**
|
||||
- `feature/backend/ABC12345`
|
||||
- `bug/frontend/DEF67890`
|
||||
- `hotfix/backend/GHI11111`
|
||||
- Root task: `feature/backend/ABC12345`
|
||||
- Subtask: `feature/backend/ABC12345--DEF67890`
|
||||
- Sub-subtask: `feature/backend/ABC12345--DEF67890--GHI11111`
|
||||
|
||||
### Commit Format
|
||||
|
||||
|
||||
@@ -138,10 +138,17 @@ Use `roboco_task_docs_complete()`. This sets `docs_complete=True`.
|
||||
2. **Cannot self-document** - Can't document tasks you developed
|
||||
3. **Message when starting** - Announce to cell
|
||||
4. **Read dev's journey** - `roboco_journal_read_team()` required
|
||||
5. **Reflect before submit** - `roboco_journal_reflect()` required
|
||||
6. **Use roboco_docs_write** - System handles paths and deduplication
|
||||
7. **Quality docs** - Future developers depend on this
|
||||
8. **Cannot complete** - Only PM completes after review
|
||||
5. **Journal as you go** - Decisions, learnings, struggles
|
||||
6. **Reflect before submit** - `roboco_journal_reflect()` REQUIRED
|
||||
7. **Use roboco_docs_write** - System handles paths and deduplication
|
||||
8. **Quality docs** - Future developers depend on this
|
||||
9. **Cannot complete** - Only PM completes after review
|
||||
|
||||
**Journaling Requirements:**
|
||||
- `roboco_journal_decision()` - When choosing doc structure, what to include/exclude
|
||||
- `roboco_journal_learning()` - When discovering code patterns worth documenting
|
||||
- `roboco_journal_struggle()` - When code is unclear or hard to document
|
||||
- `roboco_journal_reflect()` - REQUIRED before `roboco_task_docs_complete()`
|
||||
|
||||
## CRITICAL: Self-Documentation Prevention
|
||||
|
||||
|
||||
@@ -18,19 +18,40 @@ For communication structure: `roboco_kb_search("communication hierarchy")`
|
||||
## Workflow
|
||||
|
||||
```
|
||||
SCAN → CLAIM → PLAN → CREATE GROUP → CREATE CELL TASKS → ACTIVATE → NOTIFY → PAUSE → MONITOR → REVIEW_PR → COMPLETE
|
||||
SCAN → CLAIM → PLAN → GROUP → SESSION → SUBTASKS → ACTIVATE → NOTIFY → PAUSE → MONITOR → REVIEW_PR → COMPLETE
|
||||
```
|
||||
|
||||
### 1. SCAN
|
||||
Use `roboco_task_scan()` for tasks assigned to you from Board/CEO.
|
||||
|
||||
### 2. CLAIM + PLAN
|
||||
Claim → read full description → plan breakdown across cells → start → journal decision.
|
||||
Claim the task → read full description → plan breakdown across cells → start → journal decision.
|
||||
|
||||
### 3. CREATE GROUP
|
||||
**After claiming, you have YOUR task ID** - use it for session creation and as parent_task_id for subtasks.
|
||||
|
||||
### 3. GROUP
|
||||
Use `roboco_group_create()` in each relevant cell channel. Cell PMs need groups to create sessions.
|
||||
|
||||
### 4. CREATE CELL TASKS
|
||||
### 4. SESSION (REQUIRED)
|
||||
**Create a session for the ROOT task you claimed:**
|
||||
|
||||
```python
|
||||
# Get YOUR task ID from the task you claimed
|
||||
my_task = roboco_task_get(task_id)
|
||||
|
||||
# Create session linked to YOUR coordination task
|
||||
roboco_session_create_for_tasks(
|
||||
task_ids=[my_task["id"]], # The ROOT task from CEO/Board
|
||||
channel="backend-cell" # Or appropriate cell channel
|
||||
)
|
||||
```
|
||||
|
||||
**Why this matters:**
|
||||
- Links your coordination task to a communication thread
|
||||
- Subtasks created later will inherit this session
|
||||
- Cell PMs and developers can communicate in context
|
||||
|
||||
### 5. SUBTASKS (for Cell PMs)
|
||||
|
||||
**CRITICAL: Always set `parent_task_id` to YOUR task ID.** Without this, you create orphan tasks, not subtasks.
|
||||
|
||||
@@ -58,16 +79,16 @@ roboco_task_create(
|
||||
|
||||
- Set `project_id` for git tasks (branches are auto-created on claim)
|
||||
|
||||
### 5. ACTIVATE + NOTIFY
|
||||
### 6. ACTIVATE + NOTIFY
|
||||
`roboco_task_activate()` each task, then `roboco_notify_send()` to each Cell PM. REQUIRED.
|
||||
|
||||
### 6. PAUSE + IDLE
|
||||
### 7. PAUSE + IDLE
|
||||
`roboco_task_pause()` with checkpoint, then `roboco_agent_idle()`.
|
||||
|
||||
### 7. MONITOR
|
||||
### 8. MONITOR
|
||||
When respawned: scan, read Cell PM journals, update progress, coordinate if blockers.
|
||||
|
||||
### 8. REVIEW PR (Git Tasks)
|
||||
### 9. REVIEW PR (Git Tasks)
|
||||
When cell tasks reach `awaiting_pm_review` and all subtasks are merged:
|
||||
1. Review the parent PR (all subtask work combined)
|
||||
2. Coordinate with Cell PM - **BOTH must approve**
|
||||
@@ -77,7 +98,7 @@ When cell tasks reach `awaiting_pm_review` and all subtasks are merged:
|
||||
|
||||
**CEO merges the final PR to main.**
|
||||
|
||||
### 9. COMPLETE
|
||||
### 10. COMPLETE
|
||||
When CEO approves and PR is merged: reflect + complete your task.
|
||||
|
||||
## Your Tools
|
||||
@@ -108,6 +129,11 @@ When CEO approves and PR is merged: reflect + complete your task.
|
||||
**Group Management (Main PM ONLY):**
|
||||
- `roboco_group_create` - Create groups in channels
|
||||
|
||||
**Session Management:**
|
||||
- `roboco_session_create_for_tasks` - Create sessions for your coordination task and subtasks
|
||||
- `roboco_session_link_task`, `roboco_session_unlink_task`
|
||||
- `roboco_session_get_for_task`
|
||||
|
||||
**Communication:**
|
||||
- `roboco_message_send`, `roboco_channel_history`, `roboco_channel_list`
|
||||
- `roboco_notify_send`, `roboco_notify_list`, `roboco_notify_ack`
|
||||
@@ -133,7 +159,6 @@ When CEO approves and PR is merged: reflect + complete your task.
|
||||
|
||||
## NOT Your Tools
|
||||
|
||||
- `roboco_session_create_for_tasks` → Cell PM creates sessions
|
||||
- `roboco_task_submit_qa` → Developer only
|
||||
- `roboco_task_qa_pass`, `roboco_task_qa_fail` → QA only
|
||||
- `roboco_task_docs_complete` → Documenter only
|
||||
@@ -145,8 +170,15 @@ When CEO approves and PR is merged: reflect + complete your task.
|
||||
3. **Create groups first** - Cell PMs need groups to create sessions
|
||||
4. **Pause after distributing** - Don't spin waiting
|
||||
5. **Monitor periodically** - Check progress, unblock if needed
|
||||
6. **Journal your decisions** - Why this breakdown?
|
||||
7. **Complete when ALL cell tasks done** - Verify before completing
|
||||
6. **Journal as you go** - Decisions, learnings, struggles
|
||||
7. **Reflect before complete** - `roboco_journal_reflect()` REQUIRED
|
||||
8. **Complete when ALL cell tasks done** - Verify before completing
|
||||
|
||||
**Journaling Requirements:**
|
||||
- `roboco_journal_decision()` - When choosing breakdown, delegation approach
|
||||
- `roboco_journal_learning()` - When discovering patterns, blockers across cells
|
||||
- `roboco_journal_struggle()` - When coordination is difficult or blocked
|
||||
- `roboco_journal_reflect()` - REQUIRED before `roboco_task_complete()`
|
||||
|
||||
## CEO Escalation
|
||||
|
||||
|
||||
@@ -102,11 +102,17 @@ Use `roboco_journal_reflect()` before decision. REQUIRED.
|
||||
2. **Cannot self-review** - Can't QA tasks you developed
|
||||
3. **Message when starting** - Announce to cell
|
||||
4. **Read dev's journey** - `roboco_journal_read_team()` required
|
||||
5. **Journal your review** - Document what was tested
|
||||
6. **Reflect before decision** - `roboco_journal_reflect()` required
|
||||
5. **Journal as you go** - Decisions, learnings, struggles
|
||||
6. **Reflect before decision** - `roboco_journal_reflect()` REQUIRED
|
||||
7. **Clear fail reasons** - Developer needs to know what to fix
|
||||
8. **Cannot complete** - Only PM completes after workflow
|
||||
|
||||
**Journaling Requirements:**
|
||||
- `roboco_journal_decision()` - When deciding pass/fail rationale
|
||||
- `roboco_journal_learning()` - When discovering testing patterns, edge cases
|
||||
- `roboco_journal_struggle()` - When code is hard to understand or test
|
||||
- `roboco_journal_reflect()` - REQUIRED before `roboco_task_qa_pass()` or `roboco_task_qa_fail()`
|
||||
|
||||
## CRITICAL: Self-Review Prevention
|
||||
|
||||
The system tracks `original_developer` in task's `quick_context`.
|
||||
|
||||
@@ -50,6 +50,7 @@ RUN uv python install 3.13 && uv sync --frozen --python 3.13
|
||||
USER root
|
||||
COPY --chown=agent:agent docker/scripts/sdk-startup-hook.sh /app/scripts/sdk-startup-hook.sh
|
||||
COPY --chown=agent:agent docker/scripts/a2a-check-hook.sh /app/scripts/a2a-check-hook.sh
|
||||
COPY --chown=agent:agent docker/scripts/traceability-hook.sh /app/scripts/traceability-hook.sh
|
||||
RUN chmod +x /app/scripts/*.sh
|
||||
USER agent
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Traceability Hook
|
||||
#
|
||||
# Claude Code hook that runs after key tool calls to provide
|
||||
# context-aware reminders for documentation and traceability.
|
||||
#
|
||||
# Covers: journaling, task notes, progress updates, KB search,
|
||||
# communication, verification checks.
|
||||
#
|
||||
# This hook is non-blocking and always succeeds to avoid
|
||||
# interrupting Claude's workflow.
|
||||
|
||||
SDK_URL="${ROBOCO_SDK_URL:-http://localhost:9000}"
|
||||
TOOL_NAME="${CLAUDE_TOOL_NAME:-unknown}"
|
||||
|
||||
# Call SDK with tool name for context-aware suggestion
|
||||
response=$(curl -sf "$SDK_URL/traceability/remind?tool=$TOOL_NAME" 2>/dev/null)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
should_remind=$(echo "$response" | jq -r '.should_remind // false')
|
||||
if [ "$should_remind" = "true" ]; then
|
||||
reminder_type=$(echo "$response" | jq -r '.type // "general"')
|
||||
suggestion=$(echo "$response" | jq -r '.suggestion // ""')
|
||||
|
||||
# Different prefixes for 6 reminder types
|
||||
case "$reminder_type" in
|
||||
"verify")
|
||||
echo "[Check] $suggestion"
|
||||
;;
|
||||
"reflect")
|
||||
echo "[Reflect] $suggestion"
|
||||
;;
|
||||
"struggle")
|
||||
echo "[Document] $suggestion"
|
||||
;;
|
||||
"message")
|
||||
echo "[Communicate] $suggestion"
|
||||
;;
|
||||
"kb")
|
||||
echo "[Research] $suggestion"
|
||||
;;
|
||||
"journal"|*)
|
||||
echo "[Trace] $suggestion"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always exit 0 - don't block Claude
|
||||
exit 0
|
||||
@@ -18,6 +18,7 @@ import httpx
|
||||
import structlog
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi import status as http_status
|
||||
|
||||
from roboco.agent_sdk.models import (
|
||||
A2AMessage,
|
||||
@@ -247,6 +248,187 @@ async def inbox_count() -> dict[str, int]:
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TRACEABILITY REMINDERS
|
||||
# =============================================================================
|
||||
|
||||
# Complete traceability reminder mapping (25+ tools)
|
||||
# Format: (reminder_type, suggestion_text)
|
||||
# Types: verify, reflect, journal, struggle, message, kb
|
||||
TRACEABILITY_REMINDERS: dict[str, tuple[str, str]] = {
|
||||
# === TASK LIFECYCLE (ALL ROLES) ===
|
||||
"roboco_task_claim": (
|
||||
"kb",
|
||||
"Search KB for similar tasks with roboco_ask_mentor() before planning",
|
||||
),
|
||||
"roboco_task_plan": (
|
||||
"journal",
|
||||
"Journal your approach with roboco_journal_decision()",
|
||||
),
|
||||
"roboco_task_start": (
|
||||
"message",
|
||||
"Announce in cell channel via roboco_message_send() and journal your approach",
|
||||
),
|
||||
"roboco_task_progress": (
|
||||
"journal",
|
||||
"If milestone reached, capture learnings with roboco_journal_learning()",
|
||||
),
|
||||
"roboco_task_pause": (
|
||||
"journal",
|
||||
"Ensure checkpoint captures current state for resumption",
|
||||
),
|
||||
"roboco_task_block": (
|
||||
"struggle",
|
||||
"Document with roboco_journal_struggle() - include what you tried",
|
||||
),
|
||||
"roboco_task_unblock": (
|
||||
"struggle",
|
||||
"Document resolution with roboco_journal_struggle() for future reference",
|
||||
),
|
||||
"roboco_task_escalate": (
|
||||
"struggle",
|
||||
"Journal context with roboco_journal_struggle() so PM understands",
|
||||
),
|
||||
"roboco_task_escalate_to_ceo": (
|
||||
"reflect",
|
||||
"Summarize full task journey with roboco_journal_reflect()",
|
||||
),
|
||||
"roboco_task_substitute": (
|
||||
"journal",
|
||||
"Document context for next agent with roboco_journal_entry()",
|
||||
),
|
||||
# === DEVELOPER SUBMISSION ===
|
||||
"roboco_task_submit_verification": (
|
||||
"verify",
|
||||
"Check ALL acceptance criteria before proceeding",
|
||||
),
|
||||
"roboco_task_submit_qa": (
|
||||
"reflect",
|
||||
"Use roboco_journal_reflect() - document what you did, learned, struggled with",
|
||||
),
|
||||
"roboco_task_submit_pm_review": (
|
||||
"reflect",
|
||||
"Reflect with roboco_journal_reflect() before submission",
|
||||
),
|
||||
# === QA TOOLS ===
|
||||
"roboco_task_qa_pass": (
|
||||
"journal",
|
||||
"Journal your approval decision with roboco_journal_decision()",
|
||||
),
|
||||
"roboco_task_qa_fail": (
|
||||
"journal",
|
||||
"Ensure issues are clear for developer - journal your review",
|
||||
),
|
||||
# === DOCUMENTER TOOLS ===
|
||||
"roboco_task_docs_complete": (
|
||||
"verify",
|
||||
"Verify documentation covers implementation details",
|
||||
),
|
||||
# === PM TOOLS ===
|
||||
"roboco_task_create": (
|
||||
"verify",
|
||||
"Ensure clear description and measurable acceptance criteria",
|
||||
),
|
||||
"roboco_task_activate": (
|
||||
"verify",
|
||||
"Confirm session created first with roboco_session_create_for_tasks()",
|
||||
),
|
||||
"roboco_task_complete": (
|
||||
"reflect",
|
||||
"Verify ALL subtasks terminal, then roboco_journal_reflect()",
|
||||
),
|
||||
"roboco_task_cancel": (
|
||||
"journal",
|
||||
"Document cancellation reason with roboco_journal_entry()",
|
||||
),
|
||||
# === GIT TOOLS ===
|
||||
"roboco_git_commit": (
|
||||
"journal",
|
||||
"Significant change? Capture insights with roboco_journal_learning()",
|
||||
),
|
||||
"roboco_git_push": (
|
||||
"verify",
|
||||
"Ensure commits describe changes clearly",
|
||||
),
|
||||
"roboco_git_create_pr": (
|
||||
"reflect",
|
||||
"Reflect on all changes with roboco_journal_reflect()",
|
||||
),
|
||||
"roboco_git_merge_pr": (
|
||||
"verify",
|
||||
"Verify all CI checks pass before merging",
|
||||
),
|
||||
# === A2A TOOLS ===
|
||||
"roboco_agent_request": (
|
||||
"journal",
|
||||
"Journal the coordination context with roboco_journal_entry()",
|
||||
),
|
||||
# === KB TOOLS ===
|
||||
"roboco_ask_mentor": (
|
||||
"journal",
|
||||
"Useful insight? Capture with roboco_journal_learning()",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@app.get("/traceability/remind")
|
||||
async def traceability_remind(tool: str = "") -> dict:
|
||||
"""
|
||||
Check if agent should be reminded about documentation.
|
||||
|
||||
Returns context-aware suggestion based on which tool triggered the check.
|
||||
Different reminder types for different contexts:
|
||||
- verify: Check criteria, descriptions
|
||||
- reflect: Journal reflection
|
||||
- journal: General journaling
|
||||
- struggle: Document blockers
|
||||
- message: Communication reminders
|
||||
- kb: Knowledge base search
|
||||
"""
|
||||
# Normalize tool name (strip MCP prefix if present)
|
||||
# Example: "mcp__roboco-task__roboco_task_claim" -> "roboco_task_claim"
|
||||
tool_name = tool.split("__")[-1] if "__" in tool else tool
|
||||
|
||||
# Get suggestion based on tool
|
||||
if tool_name not in TRACEABILITY_REMINDERS:
|
||||
return {"should_remind": False}
|
||||
|
||||
reminder_type, suggestion = TRACEABILITY_REMINDERS[tool_name]
|
||||
|
||||
# For journal-type reminders, check if agent has journaled recently
|
||||
if reminder_type in ("journal", "reflect", "learning", "struggle"):
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(
|
||||
f"{MAIN_API_URL}/api/v1/journals/me/entries",
|
||||
params={"limit": 3},
|
||||
headers={
|
||||
"X-Agent-ID": AGENT_ID,
|
||||
"X-Agent-Role": os.environ.get(
|
||||
"ROBOCO_AGENT_ROLE", "developer"
|
||||
),
|
||||
},
|
||||
timeout=5.0,
|
||||
)
|
||||
|
||||
if resp.status_code == http_status.HTTP_200_OK:
|
||||
entries = resp.json()
|
||||
# If recent entry exists, skip reminder
|
||||
if entries and len(entries) > 0:
|
||||
return {"should_remind": False}
|
||||
except Exception as e:
|
||||
logger.warning("Failed to check journal status", error=str(e))
|
||||
# On error, don't nag - fail quietly
|
||||
return {"should_remind": False}
|
||||
|
||||
# For verify/message/kb reminders, always show
|
||||
return {
|
||||
"should_remind": True,
|
||||
"type": reminder_type,
|
||||
"suggestion": suggestion,
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MAIN
|
||||
# =============================================================================
|
||||
|
||||
@@ -12,7 +12,7 @@ from uuid import UUID
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
|
||||
from roboco.api.deps import CurrentAgentId, DbSession
|
||||
from roboco.api.deps import CurrentAgentContext, CurrentAgentId, DbSession
|
||||
from roboco.api.schemas.notifications import (
|
||||
ListNotificationsParams,
|
||||
NotificationCreateRequest,
|
||||
@@ -46,19 +46,56 @@ router = APIRouter()
|
||||
)
|
||||
async def list_notifications(
|
||||
db: DbSession,
|
||||
agent_id: CurrentAgentId,
|
||||
agent: CurrentAgentContext,
|
||||
params: Annotated[ListNotificationsParams, Depends()],
|
||||
) -> NotificationListResponse:
|
||||
"""List notifications for the agent."""
|
||||
"""List notifications for the agent.
|
||||
|
||||
System role (orchestrator) can see ALL notifications by type.
|
||||
Regular agents only see notifications where they are a target.
|
||||
"""
|
||||
agent_id = agent.agent_id
|
||||
|
||||
# System role (orchestrator) bypasses to_agents filter
|
||||
# This allows the dispatcher to query all pending escalations/a2a/etc.
|
||||
if agent.role and agent.role.value == "system":
|
||||
# Build query without to_agents filter
|
||||
query = select(NotificationTable)
|
||||
if params.pending_ack_only:
|
||||
# For system, "pending" means has at least one target not yet acked
|
||||
# We can't easily express "not fully acked" in SQL, so we filter
|
||||
# after fetching. But we can at least require requires_ack=True.
|
||||
query = query.where(NotificationTable.requires_ack.is_(True))
|
||||
if params.type_filter:
|
||||
query = query.where(NotificationTable.type == params.type_filter)
|
||||
query = query.order_by(NotificationTable.timestamp.desc()).limit(params.limit)
|
||||
|
||||
# Execute and filter out fully acknowledged
|
||||
result_all: Any = await db.execute(query)
|
||||
all_notifications = result_all.scalars().all()
|
||||
if params.pending_ack_only:
|
||||
# Filter to only notifications not fully acknowledged
|
||||
notifications = [
|
||||
n for n in all_notifications
|
||||
if not all(t in n.acked_by for t in n.to_agents)
|
||||
]
|
||||
else:
|
||||
notifications = list(all_notifications)
|
||||
else:
|
||||
# Normal query - filter by to_agents
|
||||
query = build_notification_query(NotificationTable, agent_id, params)
|
||||
result: Any = await db.execute(query)
|
||||
notifications = result.scalars().all()
|
||||
notifications = list(result.scalars().all())
|
||||
|
||||
unread_count = sum(1 for n in notifications if agent_id not in n.read_by)
|
||||
# For system role, use a dummy agent_id for response formatting
|
||||
response_agent_id = agent_id
|
||||
|
||||
unread_count = sum(1 for n in notifications if response_agent_id not in n.read_by)
|
||||
pending_ack_count = sum(
|
||||
1 for n in notifications if n.requires_ack and agent_id not in n.acked_by
|
||||
1 for n in notifications
|
||||
if n.requires_ack and response_agent_id not in n.acked_by
|
||||
)
|
||||
items = [notification_to_response(n, agent_id) for n in notifications]
|
||||
items = [notification_to_response(n, response_agent_id) for n in notifications]
|
||||
|
||||
return NotificationListResponse(
|
||||
items=items,
|
||||
|
||||
@@ -82,6 +82,45 @@ AGENT_IMAGES: dict[str, str] = {
|
||||
"auditor": "roboco-agent-pm",
|
||||
}
|
||||
|
||||
# Complete list of MCP tools that trigger traceability reminders
|
||||
# These tools represent key decision points where agents should document their work
|
||||
TRACEABILITY_TRIGGER_TOOLS: list[str] = [
|
||||
# === Task Lifecycle (All Roles) ===
|
||||
"mcp__roboco-task__roboco_task_claim",
|
||||
"mcp__roboco-task__roboco_task_plan",
|
||||
"mcp__roboco-task__roboco_task_start",
|
||||
"mcp__roboco-task__roboco_task_progress",
|
||||
"mcp__roboco-task__roboco_task_pause",
|
||||
"mcp__roboco-task__roboco_task_block",
|
||||
"mcp__roboco-task__roboco_task_unblock",
|
||||
"mcp__roboco-task__roboco_task_escalate",
|
||||
"mcp__roboco-task__roboco_task_escalate_to_ceo",
|
||||
"mcp__roboco-task__roboco_task_substitute",
|
||||
# === Developer Submission ===
|
||||
"mcp__roboco-task__roboco_task_submit_verification",
|
||||
"mcp__roboco-task__roboco_task_submit_qa",
|
||||
"mcp__roboco-task__roboco_task_submit_pm_review",
|
||||
# === QA Tools ===
|
||||
"mcp__roboco-task__roboco_task_qa_pass",
|
||||
"mcp__roboco-task__roboco_task_qa_fail",
|
||||
# === Documenter Tools ===
|
||||
"mcp__roboco-task__roboco_task_docs_complete",
|
||||
# === PM Tools ===
|
||||
"mcp__roboco-task__roboco_task_create",
|
||||
"mcp__roboco-task__roboco_task_activate",
|
||||
"mcp__roboco-task__roboco_task_complete",
|
||||
"mcp__roboco-task__roboco_task_cancel",
|
||||
# === Git Tools ===
|
||||
"mcp__roboco-git__roboco_git_commit",
|
||||
"mcp__roboco-git__roboco_git_push",
|
||||
"mcp__roboco-git__roboco_git_create_pr",
|
||||
"mcp__roboco-git__roboco_git_merge_pr",
|
||||
# === A2A Tools ===
|
||||
"mcp__roboco-a2a__roboco_agent_request",
|
||||
# === KB Tools ===
|
||||
"mcp__roboco-optimal__roboco_ask_mentor",
|
||||
]
|
||||
|
||||
|
||||
def get_agent_image(agent_id: str) -> str:
|
||||
"""Get the Docker image for an agent."""
|
||||
@@ -470,8 +509,18 @@ class AgentOrchestrator:
|
||||
]
|
||||
}
|
||||
],
|
||||
# Check for incoming A2A messages after each tool use
|
||||
"PostToolUse": [
|
||||
# Traceability reminders (context-aware, runs on specific tools)
|
||||
{
|
||||
"matcher": "|".join(TRACEABILITY_TRIGGER_TOOLS),
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/app/scripts/traceability-hook.sh",
|
||||
}
|
||||
],
|
||||
},
|
||||
# Check for incoming A2A messages after each tool use
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
@@ -480,7 +529,7 @@ class AgentOrchestrator:
|
||||
"command": "/app/scripts/a2a-check-hook.sh",
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -2622,12 +2671,15 @@ Begin with step 1: roboco_task_get("{task_id}")
|
||||
Monitors: escalation notifications (unacknowledged)
|
||||
Spawns: be-pm, fe-pm, ux-pm, main-pm, product-owner, head-marketing
|
||||
"""
|
||||
notifications = await self._fetch_notifications(client, "escalation")
|
||||
notifications = await self._fetch_notifications(client, "blocker_escalation")
|
||||
|
||||
for notif in notifications:
|
||||
targets = notif.get("to_agents", [])
|
||||
|
||||
for agent_id in targets:
|
||||
# Resolve UUID to slug - to_agents contains UUIDs from database
|
||||
agent_slug = self._resolve_agent_slug(str(agent_id))
|
||||
|
||||
valid_targets = [
|
||||
"be-pm",
|
||||
"fe-pm",
|
||||
@@ -2636,14 +2688,14 @@ Begin with step 1: roboco_task_get("{task_id}")
|
||||
"product-owner",
|
||||
"head-marketing",
|
||||
]
|
||||
if agent_id not in valid_targets:
|
||||
if agent_slug not in valid_targets:
|
||||
continue
|
||||
|
||||
if self._is_agent_active(agent_id):
|
||||
if self._is_agent_active(agent_slug):
|
||||
continue
|
||||
|
||||
await self.spawn_agent(
|
||||
agent_id=agent_id,
|
||||
agent_id=agent_slug,
|
||||
initial_prompt=self._build_escalation_prompt(notif),
|
||||
)
|
||||
break
|
||||
@@ -2661,14 +2713,17 @@ Begin with step 1: roboco_task_get("{task_id}")
|
||||
targets = notif.get("to_agents", [])
|
||||
|
||||
for agent_id in targets:
|
||||
if agent_id not in ["product-owner", "head-marketing", "main-pm"]:
|
||||
# Resolve UUID to slug - to_agents contains UUIDs from database
|
||||
agent_slug = self._resolve_agent_slug(str(agent_id))
|
||||
|
||||
if agent_slug not in ["product-owner", "head-marketing", "main-pm"]:
|
||||
continue
|
||||
|
||||
if self._is_agent_active(agent_id):
|
||||
if self._is_agent_active(agent_slug):
|
||||
continue
|
||||
|
||||
await self.spawn_agent(
|
||||
agent_id=agent_id,
|
||||
agent_id=agent_slug,
|
||||
initial_prompt=self._build_approval_prompt(notif),
|
||||
)
|
||||
break
|
||||
@@ -2686,7 +2741,9 @@ Begin with step 1: roboco_task_get("{task_id}")
|
||||
|
||||
for alert in alerts:
|
||||
targets = alert.get("to_agents", [])
|
||||
if "auditor" in targets and not self._is_agent_active("auditor"):
|
||||
# Resolve UUIDs to slugs and check if auditor is a target
|
||||
target_slugs = [self._resolve_agent_slug(str(t)) for t in targets]
|
||||
if "auditor" in target_slugs and not self._is_agent_active("auditor"):
|
||||
await self.spawn_agent(
|
||||
agent_id="auditor",
|
||||
initial_prompt=self._build_audit_prompt(alert),
|
||||
@@ -2804,14 +2861,17 @@ Begin with step 1: roboco_task_get("{task_id}")
|
||||
targets = notif.get("to_agents", [])
|
||||
|
||||
for agent_id in targets:
|
||||
if self._is_agent_active(agent_id):
|
||||
# Resolve UUID to slug - to_agents contains UUIDs from database
|
||||
agent_slug = self._resolve_agent_slug(str(agent_id))
|
||||
|
||||
if self._is_agent_active(agent_slug):
|
||||
# Agent is online - SDK handles A2A delivery directly
|
||||
# No action needed here, SDK server receives messages
|
||||
continue
|
||||
|
||||
# Agent is offline - spawn them with A2A context
|
||||
await self.spawn_agent(
|
||||
agent_id=agent_id,
|
||||
agent_id=agent_slug,
|
||||
initial_prompt=self._build_a2a_prompt(notif),
|
||||
)
|
||||
break
|
||||
|
||||
@@ -370,6 +370,9 @@ class GitService(BaseService):
|
||||
f"The parent task must be claimed first (claim creates branch)."
|
||||
)
|
||||
|
||||
# Fetch to ensure we have the latest refs (critical for parent branches)
|
||||
await self._run_git(workspace, ["fetch", "origin"])
|
||||
|
||||
# Create and push branch
|
||||
await self._run_git(workspace, ["checkout", base_branch])
|
||||
await self._run_git(workspace, ["pull", "origin", base_branch])
|
||||
@@ -382,7 +385,12 @@ class GitService(BaseService):
|
||||
return branch_name, base_branch
|
||||
|
||||
async def checkout(self, workspace: Path, branch: str) -> None:
|
||||
"""Checkout a branch."""
|
||||
"""Checkout a branch.
|
||||
|
||||
Fetches from origin first to ensure remote branches are available.
|
||||
"""
|
||||
# Fetch to ensure we have the latest refs
|
||||
await self._run_git(workspace, ["fetch", "origin"])
|
||||
await self._run_git(workspace, ["checkout", branch])
|
||||
|
||||
async def push(self, workspace: Path, force: bool = False) -> tuple[str, int]:
|
||||
|
||||
@@ -404,8 +404,10 @@ class TaskService(BaseService):
|
||||
|
||||
Branch naming (via build_branch_name):
|
||||
- Root: feature/team/ROOT_ID
|
||||
- Subtask: feature/team/ROOT_ID/SUB_ID
|
||||
- Sub-subtask: feature/team/ROOT_ID/SUB_ID/SUBSUB_ID
|
||||
- Subtask: feature/team/ROOT_ID--SUB_ID
|
||||
- Sub-subtask: feature/team/ROOT_ID--SUB_ID--SUBSUB_ID
|
||||
|
||||
Uses '--' separator for task hierarchy to avoid git ref conflicts.
|
||||
|
||||
Parent branch resolution:
|
||||
- Subtask: uses parent task's branch_name
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
Branch Name Builder.
|
||||
|
||||
Generates git branch names following the format:
|
||||
{type}/{team}/{root_uuid}/{subtask_uuid}/{subsubtask_uuid}
|
||||
{type}/{team}/{root_uuid}--{subtask_uuid}--{subsubtask_uuid}
|
||||
|
||||
Uses '--' separator for task hierarchy to avoid git ref conflicts.
|
||||
Git cannot have both 'foo' as a branch AND 'foo/bar' as another branch,
|
||||
so we use '--' instead of '/' for the task hierarchy portion.
|
||||
|
||||
Max 3 levels deep (root → subtask → sub-subtask).
|
||||
"""
|
||||
@@ -35,7 +39,7 @@ async def build_branch_name(
|
||||
task_service: TaskService instance for fetching task hierarchy
|
||||
|
||||
Returns:
|
||||
Branch name in format: {type}/{team}/{root}/{sub}/{subsub}
|
||||
Branch name in format: {type}/{team}/{root}--{sub}--{subsub}
|
||||
|
||||
Raises:
|
||||
BranchNameError: If branch_type invalid, task not found, or hierarchy too deep
|
||||
@@ -70,9 +74,10 @@ async def build_branch_name(
|
||||
f"Task {task_id} has ancestors beyond the maximum depth."
|
||||
)
|
||||
|
||||
# Reverse to get root-first order: root/sub/subsub
|
||||
# Reverse to get root-first order: root--sub--subsub
|
||||
# Use '--' separator to avoid git ref conflicts
|
||||
ancestors.reverse()
|
||||
path = "/".join(ancestors)
|
||||
path = "--".join(ancestors)
|
||||
|
||||
return f"{branch_type}/{team}/{path}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user