fix(content): keep coordination notes off dev_notes / quick_context

Two agent-authored fields were leaking non-developer content into the
human note columns the panel renders:

- apply_escalation appended '[ESCALATED] From X to Y\nReason: ...' to
  dev_notes (the developer's space). On a re-escalation loop a stuck cell
  PM grew one task's dev_notes to ~8KB across 5 escalations. It now writes
  a structured orchestration_markers['escalation'] record; the target
  still learns the reason from the escalate notification.
- approve_and_start string-packed 'approve_and_start_notes:<text>' into
  quick_context (raw key:value soup). It now writes
  orchestration_markers['approve_and_start_notes'], leaving quick_context
  for the human ResumptionNote only.

Adds typed marker accessors (get/set_escalation, get/set_approve_and_start_notes)
and refactors _record_pr_review under the complexity bound by extracting
_compose_review_body. Documents update_task_with_message as the legacy
A2A-protocol log (dev_notes is intentional there, not pollution).
This commit is contained in:
Renn F
2026-06-21 05:36:50 +02:00
parent 4c85cc6dfa
commit 3ff6967b9d
6 changed files with 98 additions and 20 deletions
@@ -91,22 +91,25 @@ async def test_reassigns_to_main_pm_and_keeps_pending(start_setup: dict) -> None
assert out is not None
assert out.assigned_to == start_setup["main_pm"].id
assert out.status == TaskStatus.PENDING
assert out.quick_context is not None
assert "approve_and_start_notes:" in out.quick_context
assert note in out.quick_context
# The CEO note is a structured marker, not raw soup in quick_context.
assert (out.orchestration_markers or {})["approve_and_start_notes"] == note
assert "approve_and_start_notes:" not in (out.quick_context or "")
@pytest.mark.asyncio
async def test_audit_note_appends_to_existing_context(start_setup: dict) -> None:
async def test_audit_note_preserves_context_and_stores_marker(
start_setup: dict,
) -> None:
task = start_setup["mk"]()
task.quick_context = "prior context"
await start_setup["db"].flush()
note = "Board signed off; ship it."
out = await start_setup["svc"].approve_and_start(task.id, note)
assert out is not None
assert out.quick_context is not None
assert "prior context" in out.quick_context
assert f"approve_and_start_notes:{note}" in out.quick_context
# An existing human ResumptionNote is preserved untouched...
assert out.quick_context == "prior context"
# ...and the CEO note lands in the markers, not appended to the context.
assert (out.orchestration_markers or {})["approve_and_start_notes"] == note
@pytest.mark.asyncio
@@ -1448,7 +1448,10 @@ async def test_apply_escalation_reassigns_and_blocks(
assert task.status == TaskStatus.BLOCKED
assert task.assigned_to == target.id
assert task.blocker_raised_by == task_setup["agent_id"]
assert "[ESCALATED]" in (task.dev_notes or "")
# The escalation is a structured marker, NOT a developer note.
assert not (task.dev_notes or "")
esc = (task.orchestration_markers or {})["escalation"]
assert esc == {"from": "dev-1", "to": "cell-pm", "reason": "external blocker"}
@pytest.mark.asyncio