chore(orchestration): drop XML wrappers + close remaining audit gaps
Final pass after re-audit. Three sub-specs landed:
**XML simplification** — the legacy XML envelopes were prompt-engineering
ceremony, not parser primitives (the SDK uses native tool-calling). Drop
the verbose wrappers in favor of one-liner labeled headers. Side benefit:
fixes the unescaped-content XML-injection bug the audit caught (peer
content containing ``</content>`` no longer breaks the wrapper).
- ``_format_inter_agent_message``: ``<inter_agent_message><sender>...
<content>...`` 9-line XML → ``[Message from {name} ({id}) | type=... |
priority=...]\n{content}``.
- ``_render_completion_report``: ``<agent_completion_report><agent_info>
...<results>...`` XML → human-readable structured text with section
headers and bulleted lists.
- ``inherited_context``: ``<inherited_context_from_parent>...`` →
``== Inherited context from parent (background only) ==``.
**MG1: TUI stop-agent uses graceful cancel.** ``tui.py`` was calling
``bus.cancel_descendants`` (hard, ``task.cancel()`` mid-stream) for the
stop-agent button. Switched to ``bus.cancel_descendants_graceful``, which
uses ``RunResultStreaming.cancel(mode="after_turn")`` to let each agent
finish its current turn (and save to session) before honoring the cancel.
The hard path remains in ``entry.py`` for KeyboardInterrupt where
graceful isn't possible.
**MG2: Document hook lock-free stats mutation.** Added a comment in
``hooks.on_llm_start`` explaining why ``warned_85`` / ``warned_final``
are mutated lock-free: SDK serializes ``on_llm_start`` per agent, so this
hook is the sole writer to those keys; ``record_usage`` only writes
disjoint keys (in/out/cached/calls).
**AG3: Auto-load ``coordination/root_agent`` skill for the root.**
Legacy auto-loaded the orchestration-guidance skill for root agents
only. Threaded ``is_root`` through ``render_system_prompt`` →
``_resolve_skills``; root agents now get the skill, children don't.
Skipped (per user direction): whitebox-wiki integration (CG2-4) — the
auto-injection / auto-update of the shared repo wiki was a pre-migration
feature; user opted not to restore it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from agents import RunContextWrapper, function_tool
|
||||
@@ -52,26 +53,34 @@ def _render_completion_report(
|
||||
findings: list[str],
|
||||
recommendations: list[str],
|
||||
) -> str:
|
||||
"""Render an ``<agent_completion_report>`` XML payload (legacy parity)."""
|
||||
from datetime import UTC, datetime
|
||||
from html import escape
|
||||
"""Render a child's completion report as plain structured text.
|
||||
|
||||
Goes into the parent's bus inbox; the inject filter prepends a
|
||||
``[Message from ...]`` header on top, so this body just carries the
|
||||
contents. No XML — no escaping concerns, no parser ambiguity.
|
||||
"""
|
||||
status = "SUCCESS" if success else "FAILED"
|
||||
completion_time = datetime.now(UTC).isoformat()
|
||||
findings_xml = "".join(f"<finding>{escape(f)}</finding>" for f in findings)
|
||||
recs_xml = "".join(f"<recommendation>{escape(r)}</recommendation>" for r in recommendations)
|
||||
return (
|
||||
"<agent_completion_report>\n"
|
||||
f" <agent_info><agent_name>{escape(agent_name)}</agent_name>"
|
||||
f"<agent_id>{escape(agent_id)}</agent_id>"
|
||||
f"<task>{escape(task)}</task>"
|
||||
f"<status>{status}</status>"
|
||||
f"<completion_time>{completion_time}</completion_time></agent_info>\n"
|
||||
f" <results><summary>{escape(result_summary)}</summary>"
|
||||
f"<findings>{findings_xml}</findings>"
|
||||
f"<recommendations>{recs_xml}</recommendations></results>\n"
|
||||
"</agent_completion_report>"
|
||||
)
|
||||
|
||||
lines: list[str] = [
|
||||
f"== Completion report from {agent_name} ({agent_id}) ==",
|
||||
f"Status: {status}",
|
||||
f"Time: {completion_time}",
|
||||
]
|
||||
if task:
|
||||
lines.append(f"Task: {task}")
|
||||
lines.append("")
|
||||
lines.append("Summary:")
|
||||
lines.append(result_summary or "(none)")
|
||||
if findings:
|
||||
lines.append("")
|
||||
lines.append("Findings:")
|
||||
lines.extend(f"- {f}" for f in findings)
|
||||
if recommendations:
|
||||
lines.append("")
|
||||
lines.append("Recommendations:")
|
||||
lines.extend(f"- {r}" for r in recommendations)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@function_tool(timeout=30)
|
||||
@@ -456,9 +465,9 @@ async def create_agent(
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
"<inherited_context_from_parent>\n"
|
||||
"== Inherited context from parent (background only) ==\n"
|
||||
f"{rendered}\n"
|
||||
"</inherited_context_from_parent>\n"
|
||||
"== End of inherited context ==\n"
|
||||
"Use the above as background only; do not continue the "
|
||||
"parent's work. Your task follows."
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user