feat(prompter): make the intake actually use its task-history digest (#592)

The history-digest pipeline (PR #297) injected past-task data but nothing
told the intake agent what to do with it: prompter.md now carries an
explicit don't-re-propose section (cite duplicates by short id, reference
precedent in notes, let history inform depends_on sequencing), and
list_recent_for_project excludes cancelled tasks so dead work can't pose
as precedent.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-19 17:25:41 +02:00
committed by GitHub
co-authored by Renn F
parent 6e67c71eb8
commit 29335f4732
4 changed files with 51 additions and 2 deletions
+10
View File
@@ -33,6 +33,16 @@ Keep each unit to one concern. A unit that bundles several unrelated changes is
Before your first question, use `Read` / `Grep` / `Glob` and the read-only git verbs to learn the real surface. If the CEO says "put it on the Metrics page", open the Metrics page and see what's there. If they mention an endpoint, find it. Read targeted excerpts yourself — you have no subagents, and a broad survey is never worth stalling the interview; skim the few files the request actually names. **Ground every question and every claim in what the code actually shows** — never guess at a surface you could have read.
## Task history — don't propose what's already been done
Your context may carry a **`## Task History`** section (recent tasks for this scope, oldest first, `` `short-id` title (status, date) ``) and you always have a `search_past_tasks` tool for ad-hoc lookups mid-conversation. Check both before you draft:
- **Avoid duplicates.** If the history shows a shipped or in-flight task that already covers what the CEO is describing, say so plainly instead of quietly drafting a duplicate — name it by short id and ask whether this is genuinely new or a follow-up.
- **Cite precedent.** When a task extends or follows up on a prior one, name that prior task by short id in `notes` or `what_this_builds` (e.g. "follows up `a1b2c3d4`") so the Main PM / cell PM / dev inherit the continuity you found, not just the CEO's one-liner.
- **Sequence with judgment.** In a MegaTask batch, if the history shows this project's work consistently stages a particular way (a migration before the code that needs it, one cell's contract before the cell that consumes it), let that inform your `depends_on` — but a CEO-declared ordering still wins verbatim (see MegaTasks below).
No history section, or nothing relevant in it? Draft normally — this is informational, never a requirement to manufacture precedent that isn't there.
## Interview discipline
- Open by reflecting back, in a sentence or two, what you understand they want — so they can correct course immediately.
+4 -2
View File
@@ -7384,13 +7384,14 @@ class TaskService(BaseService):
async def list_recent_for_project(
self, project_id: UUID, limit: int = 15
) -> list[TaskTable]:
"""Recent tasks for a project, most-recently-active first.
"""Recent non-cancelled tasks for a project, most-recently-active first.
Backs the prompter's history digest: the intake agent gets a compact
chronological view of what's already been built/attempted in this repo.
"Recent" = highest of completed_at / updated_at / created_at, so a
just-touched-but-not-completed task still surfaces ahead of an old
completed one.
completed one. Cancelled tasks are excluded abandoned work is not
precedent an intake agent should treat as shipped or in-flight.
"""
activity = func.coalesce(
TaskTable.completed_at, TaskTable.updated_at, TaskTable.created_at
@@ -7398,6 +7399,7 @@ class TaskService(BaseService):
stmt = (
select(TaskTable)
.where(TaskTable.project_id == project_id)
.where(TaskTable.status != TaskStatus.CANCELLED)
.order_by(activity.desc())
.limit(limit)
)
@@ -321,6 +321,21 @@ async def test_list_recent_for_project_scoped_to_project(
assert out_of_scope.id not in ids
@pytest.mark.asyncio
async def test_list_recent_for_project_excludes_cancelled(task_setup: dict) -> None:
svc = task_setup["svc"]
db = task_setup["db"]
live = await svc.create(_req(task_setup, title="live"))
cancelled = await svc.create(_req(task_setup, title="cancelled"))
cancelled.status = TaskStatus.CANCELLED
await db.flush()
rows = await svc.list_recent_for_project(task_setup["project_id"])
ids = {t.id for t in rows}
assert live.id in ids
assert cancelled.id not in ids
# ---------------------------------------------------------------------------
# Subtask hierarchy
# ---------------------------------------------------------------------------
@@ -0,0 +1,22 @@
"""compose_prompt includes the Task history guidance section for the intake role."""
from __future__ import annotations
from roboco.agents.factories._base import compose_prompt
from roboco.models import AgentRole
def test_task_history_guidance_present_for_prompter() -> None:
prompt = compose_prompt(AgentRole.PROMPTER, None, "intake-1")
assert "## Task history" in prompt
def test_task_history_guidance_names_the_search_tool_and_duplicate_avoidance() -> None:
"""The static guidance names the ambient heading, the search tool, and the
three explicit uses the CEO asked for: avoid duplicates, cite precedent by
short id, and let history inform sequencing."""
prompt = compose_prompt(AgentRole.PROMPTER, None, "intake-1")
assert "search_past_tasks" in prompt
assert "Avoid duplicates" in prompt
assert "Cite precedent" in prompt
assert "Sequence with judgment" in prompt