Dogfood round 1: propose_work_items gate, team-tie survives turn saves, board wakes render as cards

Leads lose propose_plan (trait-derived exclusion — plan mode is meaningless without execution tools) and gain propose_work_items: mode-independent decomposition whose approval creates the items.
Team field moves off the turn-save upsert to a dedicated setter (workers detached from their lead after one turn); board deliveries carry a MessageSource sidecar; test-worker prefers project-local tool installs.
This commit is contained in:
Rohit C Prasad
2026-08-16 15:43:06 -07:00
committed by Rohit P
parent 13f9c0b6c0
commit 844a6510a2
17 changed files with 508 additions and 25 deletions
+33 -1
View File
@@ -1872,6 +1872,37 @@ def create_app(manager: SessionManager) -> FastAPI:
enable_chat=bool(_args.get("enable_chat", False)),
)
async def items_approver(_args: dict, tool_call_id=None) -> dict:
# The decomposition gate. TEAMS-flavored sibling of plan_approver:
# park a durable Inbox item, wait, and on approval create the items.
items = _args.get("items") or []
body = "\n".join(
f"- {i.get('title', '?')} — Done when: {i.get('criteria', '?')}"
for i in items
if isinstance(i, dict)
)
item = manager.inbox.add_plan(
session_id,
"Approve the proposed work items?",
body=body,
inbox=_route(),
visibility=_visibility(),
tool_call_id=tool_call_id,
)
if item.state == "pending":
manager.persist_session(session_id)
if item.visibility == VIS_INBOX:
await _mirror(item)
resp = _parse_json(await manager.inbox.wait(item.id))
if not resp.get("approved"):
return {
"approved": False,
"feedback": resp.get("feedback") or "the user declined the split",
}
return manager.board_create_items(
session_id, [i for i in items if isinstance(i, dict)]
)
async def _apply_model(model: Optional[str]) -> None:
# Mid-session rebind is allowed (roadmap item 3, supersedes the 2026-07-04
# lock): history is canonical and providers convert per call. A real switch
@@ -1912,6 +1943,7 @@ def create_app(manager: SessionManager) -> FastAPI:
question_asker=question_asker,
tool_requester=tool_requester,
team_approver=team_approver,
items_approver=items_approver,
)
if engine is None:
await ws.send_json(
@@ -2062,7 +2094,7 @@ def create_app(manager: SessionManager) -> FastAPI:
}
)
)
elif kind == "team_response":
elif kind in ("team_response", "items_response"):
_resolve_pending(
json.dumps(
{