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 # Communication
- roboco_channel_list, roboco_channel_history - 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 - roboco_report_blocker
# Claude Code Built-in # Claude Code Built-in
@@ -487,4 +488,8 @@ permissions:
journals_read: journals_read:
- backend cell members (be-dev-1, be-dev-2, be-qa, be-doc, be-pm) - 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) # Team Journals (Read Cell Members)
- roboco_journal_read_team, roboco_journal_scope - roboco_journal_read_team, roboco_journal_scope
- roboco_channel_list, roboco_channel_history - 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 ## Permissions
+6 -1
View File
@@ -362,7 +362,8 @@ tools:
# Communication # Communication
- roboco_channel_list, roboco_channel_history - 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 - roboco_report_blocker
# Testing Tools # Testing Tools
@@ -395,4 +396,8 @@ permissions:
- qa_pass_tasks - qa_pass_tasks
- qa_fail_tasks - qa_fail_tasks
- escalate_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 # Communication
- roboco_channel_list, roboco_channel_history - 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 - roboco_report_blocker
# Claude Code Built-in # Claude Code Built-in
@@ -397,4 +398,8 @@ permissions:
journals_read: journals_read:
- frontend cell members (fe-dev-1, fe-dev-2, fe-qa, fe-doc, fe-pm) - 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) # Team Journals (Read Cell Members)
- roboco_journal_read_team, roboco_journal_scope - roboco_journal_read_team, roboco_journal_scope
- roboco_channel_list, roboco_channel_history - 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 ## Permissions
+6 -1
View File
@@ -248,7 +248,8 @@ tools:
- roboco_journal_read_team, roboco_journal_scope - roboco_journal_read_team, roboco_journal_scope
# Communication # Communication
- roboco_channel_list, roboco_channel_history - 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 ## Permissions
@@ -276,4 +277,8 @@ permissions:
- qa_pass_tasks - qa_pass_tasks
- qa_fail_tasks - qa_fail_tasks
- escalate_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 # Communication
- roboco_channel_list, roboco_channel_history - 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 - roboco_report_blocker
# Design Tools # Design Tools
@@ -364,4 +365,8 @@ permissions:
journals_read: journals_read:
- ux_ui cell members (ux-dev, ux-qa, ux-doc, ux-pm) - 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) # Team Journals (Read Cell Members)
- roboco_journal_read_team, roboco_journal_scope - roboco_journal_read_team, roboco_journal_scope
- roboco_channel_list, roboco_channel_history - 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 ## Permissions
+6 -1
View File
@@ -246,7 +246,8 @@ tools:
- roboco_journal_read_team, roboco_journal_scope - roboco_journal_read_team, roboco_journal_scope
# Communication # Communication
- roboco_channel_list, roboco_channel_history - 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 ## Permissions
@@ -274,4 +275,8 @@ permissions:
- qa_pass_tasks - qa_pass_tasks
- qa_fail_tasks - qa_fail_tasks
- escalate_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) await self._update_task_status(task_id, TaskStatus.AWAITING_QA)
async def _mark_needs_revision(self, task_id: UUID) -> None: 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) await self._update_task_status(task_id, TaskStatus.NEEDS_REVISION)
async def _mark_awaiting_documentation(self, task_id: UUID) -> None: 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) 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: 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) 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: async def _mark_completed(self, task_id: UUID) -> None:
"""Mark task as completed.""" """Mark task as completed."""
await self._update_task_status(task_id, TaskStatus.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"} {"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) # Save plan to task via API (REQUIRED before start can be called)
plan_data = { plan_data = {
"approach": f"Implement {ctx.title}", "approach": f"Implement {ctx.title}",
"steps": [s.get("description", str(s)) for s in ctx.subtasks], "steps": [s.get("description", str(s)) for s in ctx.subtasks],
"risks": [], "risks": risks,
"estimated_sessions": 1, "estimated_sessions": estimated_sessions,
} }
await self._api_call("PATCH", f"/tasks/{ctx.task_id}", json={"plan": plan_data}) 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) "Failed to publish", path=doc_spec.path, error=str(e)
) )
# Mark docs complete - task goes to PM for final review # Use proper docs-complete endpoint (handles notes, status)
await self._mark_awaiting_pm_review(ctx.task_id) doc_summary = f"Published: {', '.join(ctx.written_docs)}"
await self._docs_complete(ctx.task_id, doc_summary)
await self.send_message( await self.send_message(
ctx.session_id, ctx.session_id,
f"TASK-{str(ctx.task_id)[:8]} documentation complete, awaiting PM review\n" 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", message_type="action",
task_id=ctx.task_id, 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, task_id=ctx.task_id,
) )
# Update task status # Use proper QA fail endpoint (handles notes, status, reassignment)
await self._mark_needs_revision(ctx.task_id) 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: else:
ctx.verdict = TestResult.PASS ctx.verdict = TestResult.PASS
@@ -396,8 +401,11 @@ PASS,All criteria verified successfully,No issues found
task_id=ctx.task_id, task_id=ctx.task_id,
) )
# Update task status # Use proper QA pass endpoint (handles notes, status)
await self._mark_awaiting_documentation(ctx.task_id) await self._qa_pass(
ctx.task_id,
f"All {len(ctx.test_cases)} tests passed. Ready for documentation.",
)
ctx.notes.append( ctx.notes.append(
f"[{datetime.now(UTC).isoformat()}] Verdict: {ctx.verdict.value.upper()}" 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. Single source of truth for agent roles, teams, and cell memberships.
All enforcement modules and MCP servers should import from here. 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 from typing import Final
@@ -98,12 +118,6 @@ PM_ROLES: Final[set[str]] = {
"ceo", "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 - who each agent escalates to
ESCALATION_CHAIN: Final[dict[str, str]] = { ESCALATION_CHAIN: Final[dict[str, str]] = {
# Developers → Cell PM # Developers → Cell PM
@@ -224,12 +238,6 @@ def can_cancel_tasks(agent_id: str) -> bool:
return role in _CANCEL_ROLES 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: def get_escalation_target(agent_id: str) -> str | None:
"""Get the escalation target for an agent.""" """Get the escalation target for an agent."""
return ESCALATION_CHAIN.get(agent_id) return ESCALATION_CHAIN.get(agent_id)
-1
View File
@@ -131,7 +131,6 @@ async def handle_task_pause(
await client.post( await client.post(
f"/tasks/{data.task_id}/checkpoint", f"/tasks/{data.task_id}/checkpoint",
json={ json={
"agent_id": agent_id,
"state_summary": data.checkpoint_summary, "state_summary": data.checkpoint_summary,
"remaining_work": data.remaining_work, "remaining_work": data.remaining_work,
"notes": data.reason, "notes": data.reason,
+1 -1
View File
@@ -142,7 +142,7 @@ async def handle_task_progress(
progress_resp = await client.post( progress_resp = await client.post(
f"/tasks/{task_id}/progress", f"/tasks/{task_id}/progress",
json={"agent_id": agent_id, "message": message, "percentage": percentage}, json={"message": message, "percentage": percentage},
) )
if not progress_resp.ok: if not progress_resp.ok: