Changes Made

roboco/agents/base.py - Added proper workflow methods:
  - _qa_pass(task_id, qa_notes) - Calls /pass-qa endpoint
  - _qa_fail(task_id, qa_notes, issues) - Calls /fail-qa endpoint
  - _docs_complete(task_id, doc_notes?) - Calls /docs-complete endpoint
  - Marked old _mark_needs_revision, _mark_awaiting_documentation, _mark_awaiting_pm_review as DEPRECATED

  roboco/agents/qa.py - Fixed verdict phase:
  - Now uses _qa_pass() instead of _mark_awaiting_documentation()
  - Now uses _qa_fail() instead of _mark_needs_revision()
  - Properly passes QA notes and issue lists

  roboco/agents/documenter.py - Fixed submit phase:
  - Now uses _docs_complete() instead of _mark_awaiting_pm_review()
  - Properly passes documentation summary as notes
This commit is contained in:
Renn F
2025-12-24 23:34:42 +01:00
parent dd1b3394a7
commit 15f5be3f8b
16 changed files with 149 additions and 35 deletions
+6 -1
View File
@@ -451,7 +451,8 @@ tools:
# Communication
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
- roboco_report_blocker
# Claude Code Built-in
@@ -487,4 +488,8 @@ permissions:
journals_read:
- backend cell members (be-dev-1, be-dev-2, be-qa, be-doc, be-pm)
# Enforced Constraints (code enforces these rules)
task_visibility: team_only # You only see tasks assigned to your team
self_review: blocked # You cannot QA or document your own work
```
+2 -1
View File
@@ -230,7 +230,8 @@ tools:
# Team Journals (Read Cell Members)
- roboco_journal_read_team, roboco_journal_scope
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
```
## Permissions
+6 -1
View File
@@ -362,7 +362,8 @@ tools:
# Communication
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
- roboco_report_blocker
# Testing Tools
@@ -395,4 +396,8 @@ permissions:
- qa_pass_tasks
- qa_fail_tasks
- escalate_tasks
# Enforced Constraints (code enforces these rules)
task_visibility: team_only # You only see tasks for your team
self_review: blocked # You cannot QA tasks where you were the original developer
```
+6 -1
View File
@@ -361,7 +361,8 @@ tools:
# Communication
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
- roboco_report_blocker
# Claude Code Built-in
@@ -397,4 +398,8 @@ permissions:
journals_read:
- frontend cell members (fe-dev-1, fe-dev-2, fe-qa, fe-doc, fe-pm)
# Enforced Constraints (code enforces these rules)
task_visibility: team_only # You only see tasks assigned to your team
self_review: blocked # You cannot QA or document your own work
```
+2 -1
View File
@@ -228,7 +228,8 @@ tools:
# Team Journals (Read Cell Members)
- roboco_journal_read_team, roboco_journal_scope
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
```
## Permissions
+6 -1
View File
@@ -248,7 +248,8 @@ tools:
- roboco_journal_read_team, roboco_journal_scope
# Communication
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
```
## Permissions
@@ -276,4 +277,8 @@ permissions:
- qa_pass_tasks
- qa_fail_tasks
- escalate_tasks
# Enforced Constraints (code enforces these rules)
task_visibility: team_only # You only see tasks for your team
self_review: blocked # You cannot QA tasks where you were the original developer
```
+6 -1
View File
@@ -331,7 +331,8 @@ tools:
# Communication
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
- roboco_report_blocker
# Design Tools
@@ -364,4 +365,8 @@ permissions:
journals_read:
- ux_ui cell members (ux-dev, ux-qa, ux-doc, ux-pm)
# Enforced Constraints (code enforces these rules)
task_visibility: team_only # You only see tasks assigned to your team
self_review: blocked # You cannot QA or document your own work
```
+2 -1
View File
@@ -228,7 +228,8 @@ tools:
# Team Journals (Read Cell Members)
- roboco_journal_read_team, roboco_journal_scope
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
```
## Permissions
+6 -1
View File
@@ -246,7 +246,8 @@ tools:
- roboco_journal_read_team, roboco_journal_scope
# Communication
- roboco_channel_list, roboco_channel_history
- roboco_message_send, roboco_ask_question
- roboco_message_send, roboco_message_get, roboco_ask_question
- roboco_session_history_for_task # Get discussion history for your task
```
## Permissions
@@ -274,4 +275,8 @@ permissions:
- qa_pass_tasks
- qa_fail_tasks
- escalate_tasks
# Enforced Constraints (code enforces these rules)
task_visibility: team_only # You only see tasks for your team
self_review: blocked # You cannot QA tasks where you were the original developer
```
+52 -3
View File
@@ -734,17 +734,66 @@ class Agent(ABC):
await self._update_task_status(task_id, TaskStatus.AWAITING_QA)
async def _mark_needs_revision(self, task_id: UUID) -> None:
"""Mark task as needing revision (QA failed)."""
"""Mark task as needing revision (QA failed).
DEPRECATED: Use _qa_fail() instead which calls the proper endpoint.
"""
await self._update_task_status(task_id, TaskStatus.NEEDS_REVISION)
async def _mark_awaiting_documentation(self, task_id: UUID) -> None:
"""Mark task as awaiting documentation."""
"""Mark task as awaiting documentation.
DEPRECATED: Use _qa_pass() instead which calls the proper endpoint.
"""
await self._update_task_status(task_id, TaskStatus.AWAITING_DOCUMENTATION)
async def _qa_pass(self, task_id: UUID, qa_notes: str) -> None:
"""Pass QA review - calls proper /pass-qa endpoint.
This handles full workflow: saves notes, updates status, creates audit.
"""
await self._api_call(
"POST",
f"/tasks/{task_id}/pass-qa",
json={"notes": qa_notes},
)
self.log.info("QA passed", task_id=str(task_id))
async def _qa_fail(
self, task_id: UUID, qa_notes: str, issues: list[str]
) -> None:
"""Fail QA review - calls proper /fail-qa endpoint.
This handles full workflow: saves notes, updates status, reassigns to dev.
"""
full_notes = f"{qa_notes}\n\nIssues:\n" + "\n".join(f"- {i}" for i in issues)
await self._api_call(
"POST",
f"/tasks/{task_id}/fail-qa",
json={"notes": full_notes},
)
self.log.info("QA failed", task_id=str(task_id), issues_count=len(issues))
async def _mark_awaiting_pm_review(self, task_id: UUID) -> None:
"""Mark task as awaiting PM review."""
"""Mark task as awaiting PM review.
DEPRECATED: Use _docs_complete() instead which calls the proper endpoint.
"""
await self._update_task_status(task_id, TaskStatus.AWAITING_PM_REVIEW)
async def _docs_complete(self, task_id: UUID, doc_notes: str | None = None) -> None:
"""Mark documentation as complete - calls proper /docs-complete endpoint.
This handles full workflow: saves notes, updates status.
"""
payload = {"notes": doc_notes} if doc_notes else {}
await self._api_call(
"POST",
f"/tasks/{task_id}/docs-complete",
json=payload,
)
self.log.info("Documentation complete", task_id=str(task_id))
async def _mark_completed(self, task_id: UUID) -> None:
"""Mark task as completed."""
await self._update_task_status(task_id, TaskStatus.COMPLETED)
+18 -2
View File
@@ -312,12 +312,28 @@ Add unit tests,tests/test_main.py,small
{"description": response, "files": [], "complexity": "medium"}
]
# Analyze risks based on subtask complexity
risks = []
complex_subtasks = [
s for s in ctx.subtasks if s.get("complexity") == "high"
]
if complex_subtasks:
risks.append(
f"{len(complex_subtasks)} high-complexity subtasks may need extra time"
)
max_subtasks_per_phase = 5
if len(ctx.subtasks) > max_subtasks_per_phase:
risks.append("Large number of subtasks - may need to split into phases")
# Estimate sessions based on subtask count and complexity
estimated_sessions = max(1, len(ctx.subtasks) // 3 + len(complex_subtasks))
# Save plan to task via API (REQUIRED before start can be called)
plan_data = {
"approach": f"Implement {ctx.title}",
"steps": [s.get("description", str(s)) for s in ctx.subtasks],
"risks": [],
"estimated_sessions": 1,
"risks": risks,
"estimated_sessions": estimated_sessions,
}
await self._api_call("PATCH", f"/tasks/{ctx.task_id}", json={"plan": plan_data})
+4 -3
View File
@@ -437,13 +437,14 @@ good,complete,clear,helpful,None
"Failed to publish", path=doc_spec.path, error=str(e)
)
# Mark docs complete - task goes to PM for final review
await self._mark_awaiting_pm_review(ctx.task_id)
# Use proper docs-complete endpoint (handles notes, status)
doc_summary = f"Published: {', '.join(ctx.written_docs)}"
await self._docs_complete(ctx.task_id, doc_summary)
await self.send_message(
ctx.session_id,
f"TASK-{str(ctx.task_id)[:8]} documentation complete, awaiting PM review\n"
f"Published: {', '.join(ctx.written_docs)}",
f"{doc_summary}",
message_type="action",
task_id=ctx.task_id,
)
+12 -4
View File
@@ -381,8 +381,13 @@ PASS,All criteria verified successfully,No issues found
task_id=ctx.task_id,
)
# Update task status
await self._mark_needs_revision(ctx.task_id)
# Use proper QA fail endpoint (handles notes, status, reassignment)
issue_list = [t.actual or t.name for t in failed_tests]
await self._qa_fail(
ctx.task_id,
"Found issues that need fixing before approval.",
issue_list,
)
else:
ctx.verdict = TestResult.PASS
@@ -396,8 +401,11 @@ PASS,All criteria verified successfully,No issues found
task_id=ctx.task_id,
)
# Update task status
await self._mark_awaiting_documentation(ctx.task_id)
# Use proper QA pass endpoint (handles notes, status)
await self._qa_pass(
ctx.task_id,
f"All {len(ctx.test_cases)} tests passed. Ready for documentation.",
)
ctx.notes.append(
f"[{datetime.now(UTC).isoformat()}] Verdict: {ctx.verdict.value.upper()}"
+20 -12
View File
@@ -3,6 +3,26 @@ Agent Configuration
Single source of truth for agent roles, teams, and cell memberships.
All enforcement modules and MCP servers should import from here.
PERMISSION ARCHITECTURE
-----------------------
The system uses TWO complementary permission layers:
1. MCP Layer (this module - agents_config.py):
- Controls which MCP tools agents can access
- Uses role-based helper functions: can_create_tasks(), can_cancel_tasks(), etc.
- Works with agent slugs/UUIDs directly
- Determines tool visibility at MCP server registration time
2. API Layer (roboco/services/permissions.py):
- Controls fine-grained API endpoint access
- Uses TaskAction enum and PermissionService class
- Works with AgentContext and Team enums
- Validates at request time with team-scoped checks
Both layers derive from the same source data (role definitions here) but serve
different purposes. MCP is coarse-grained (tool-level), API is fine-grained
(action + team context).
"""
from typing import Final
@@ -98,12 +118,6 @@ PM_ROLES: Final[set[str]] = {
"ceo",
}
# Developer-only tools (PMs, QA, Documenters cannot use these)
DEVELOPER_ONLY_TOOLS: Final[frozenset[str]] = frozenset({
"roboco_task_submit_verification",
"roboco_task_submit_qa",
})
# Escalation chain - who each agent escalates to
ESCALATION_CHAIN: Final[dict[str, str]] = {
# Developers → Cell PM
@@ -224,12 +238,6 @@ def can_cancel_tasks(agent_id: str) -> bool:
return role in _CANCEL_ROLES
def can_submit_for_qa(agent_id: str) -> bool:
"""Check if agent can submit work for QA (developers only)."""
role = get_agent_role(agent_id)
return role == "developer"
def get_escalation_target(agent_id: str) -> str | None:
"""Get the escalation target for an agent."""
return ESCALATION_CHAIN.get(agent_id)
-1
View File
@@ -131,7 +131,6 @@ async def handle_task_pause(
await client.post(
f"/tasks/{data.task_id}/checkpoint",
json={
"agent_id": agent_id,
"state_summary": data.checkpoint_summary,
"remaining_work": data.remaining_work,
"notes": data.reason,
+1 -1
View File
@@ -142,7 +142,7 @@ async def handle_task_progress(
progress_resp = await client.post(
f"/tasks/{task_id}/progress",
json={"agent_id": agent_id, "message": message, "percentage": percentage},
json={"message": message, "percentage": percentage},
)
if not progress_resp.ok: