Wave 2 features: A2A live view (CEO chime-in + reply budget) and prompter memory (#297)

* feat(a2a): live view — watch fleet conversations, CEO chime-in, reply budget

A2A_MESSAGE_SENT published from A2AService.send (excerpt-capped) and
fanned through the existing /ws/system bridge; CEO-only admin REST for
conversations/messages + a reply route on the publish-bearing send path;
panel /a2a page with live transcript and a composer gated on task-linked
conversations. The matrix gains its one asymmetric rule: CEO may message
anyone, nobody may target the CEO — and agent replies inside a
CEO-opened conversation are hard-budgeted to one per CEO message
(per conversation, per agent), rejected with wait-don't-retry guidance.
Built subagent-driven (Sonnet 5), reviewed; v1 seams documented in the
map delta.

* feat(prompter): intake remembers the task history

Intake spawns now carry a per-project chronological digest of recent
tasks (capped: 15 lines/project, 4000 chars total — ~300-1000 tokens)
merged into the ambient layer, and the interviewer gets a bounded
search_past_tasks tool (one shared implementation behind the grok MCP
tool and the Claude SDK in-process tool) to check precedent
mid-conversation. Informational memory only — the sequencing analyzer
keeps ownership of ordering. Built subagent-driven (Sonnet 5), reviewed;
pre-existing conventions-ambient MegaTask-scope gap flagged, untouched.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-03 00:07:55 +02:00
committed by GitHub
co-authored by Renn F
parent 48f2944086
commit da563487b8
39 changed files with 3808 additions and 27 deletions
+85 -2
View File
@@ -8,8 +8,9 @@ existing v1-flow integration tests.
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from uuid import uuid4
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING, Any, cast
from uuid import UUID, uuid4
import pytest
import pytest_asyncio
@@ -237,6 +238,88 @@ async def test_get_active_count_for_agent(task_setup: dict) -> None:
assert isinstance(count, int)
# ---------------------------------------------------------------------------
# list_recent_for_project — the prompter's history digest source
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_list_recent_for_project_orders_most_recent_activity_first(
task_setup: dict,
) -> None:
svc = task_setup["svc"]
db = task_setup["db"]
now = datetime.now(UTC)
oldest = await svc.create(_req(task_setup, title="oldest"))
middle = await svc.create(_req(task_setup, title="middle"))
newest = await svc.create(_req(task_setup, title="newest"))
# Distinct activity dates: oldest only has created_at far in the past;
# middle was touched (updated_at) more recently; newest actually completed
# (completed_at wins over updated_at/created_at).
oldest.created_at = now - timedelta(days=10)
oldest.updated_at = None
middle.created_at = now - timedelta(days=9)
middle.updated_at = now - timedelta(days=5)
newest.created_at = now - timedelta(days=8)
newest.updated_at = now - timedelta(days=7)
newest.completed_at = now - timedelta(days=1)
await db.flush()
rows = await svc.list_recent_for_project(task_setup["project_id"])
ids = [t.id for t in rows]
assert ids.index(newest.id) < ids.index(middle.id) < ids.index(oldest.id)
@pytest.mark.asyncio
async def test_list_recent_for_project_respects_limit(task_setup: dict) -> None:
svc = task_setup["svc"]
db = task_setup["db"]
now = datetime.now(UTC)
tasks = [await svc.create(_req(task_setup, title=f"t{i}")) for i in range(3)]
for i, t in enumerate(tasks):
t.created_at = now - timedelta(days=10 - i) # t0 oldest, t2 newest
t.updated_at = None
await db.flush()
query_limit = 2
rows = await svc.list_recent_for_project(
task_setup["project_id"], limit=query_limit
)
assert len(rows) == query_limit
ids = [t.id for t in rows]
assert ids == [tasks[2].id, tasks[1].id]
@pytest.mark.asyncio
async def test_list_recent_for_project_scoped_to_project(
task_setup: dict, db_session: AsyncSession
) -> None:
svc = task_setup["svc"]
in_scope = await svc.create(_req(task_setup, title="in-scope"))
other_project = ProjectTable(
id=uuid4(),
name="Other-Proj",
slug=f"other-proj-{uuid4().hex[:8]}",
git_url="https://example.com/other.git",
assigned_cell=Team.BACKEND,
created_by=task_setup["agent_id"],
)
db_session.add(other_project)
await db_session.flush()
other_req = _req(task_setup, title="other-project-task")
other_req.project_id = cast("UUID", other_project.id)
out_of_scope = await svc.create(other_req)
rows = await svc.list_recent_for_project(task_setup["project_id"])
ids = {t.id for t in rows}
assert in_scope.id in ids
assert out_of_scope.id not in ids
# ---------------------------------------------------------------------------
# Subtask hierarchy
# ---------------------------------------------------------------------------