diff --git a/docs/reference/protocols/task-graph-projection-v0.md b/docs/reference/protocols/task-graph-projection-v0.md index 25772b0f..ea365a84 100644 --- a/docs/reference/protocols/task-graph-projection-v0.md +++ b/docs/reference/protocols/task-graph-projection-v0.md @@ -15,7 +15,9 @@ The source of truth remains: - run-history evidence and blocker writebacks. The projection may appear under `attention_queue.items[].task_graph_projection` -in `loopx --format json status`. Full +in `loopx --format json status --include-task-graph`. Default status output +keeps this object on the cold path so the dashboard hot path remains within its +interface budget. Full `loopx --format json review-packet --goal-id ` output may include the same object for operator review. The handoff-only review-packet surface should stay compact and omit the graph unless a future interface budget diff --git a/docs/status-data-contract.md b/docs/status-data-contract.md index 4214d968..2820f011 100644 --- a/docs/status-data-contract.md +++ b/docs/status-data-contract.md @@ -49,11 +49,14 @@ card or timeline, but project truth still comes from the registry, active state, quota guard, and append-only run history. Rows may also include an optional `task_graph_projection` object with -`schema_version=task_graph_projection_v0`. This is a compact graph-shaped view -over existing todos, gates, leases, run ids, and event-ledger state. It is -read-only and exists only to show dependency, validation, repair, and handoff -relationships that are hard to scan in a flat todo list. It must not introduce a -second scheduler, graph write API, hidden lease store, or alternate task truth. +`schema_version=task_graph_projection_v0`. The default `status` hot path omits +this graph to preserve the dashboard interface budget; callers that need the +graph can request it with `loopx --format json status --include-task-graph`, or +use a full review packet. This is a compact graph-shaped view over existing +todos, gates, leases, run ids, and event-ledger state. It is read-only and +exists only to show dependency, validation, repair, and handoff relationships +that are hard to scan in a flat todo list. It must not introduce a second +scheduler, graph write API, hidden lease store, or alternate task truth. The protocol is defined in [`docs/reference/protocols/task-graph-projection-v0.md`](reference/protocols/task-graph-projection-v0.md); consumers should ignore the field when absent. diff --git a/examples/hot-path-interface-budget-smoke.py b/examples/hot-path-interface-budget-smoke.py index 077c6ef8..d5399674 100644 --- a/examples/hot-path-interface-budget-smoke.py +++ b/examples/hot-path-interface-budget-smoke.py @@ -279,6 +279,9 @@ def main() -> int: scan_roots=[project], limit=5, ) + status_items = status_payload["attention_queue"]["items"] + assert status_items, status_payload + assert "task_graph_projection" not in status_items[0], status_items[0] quota_payload = build_quota_should_run(status_payload, goal_id=GOAL_ID) review_packet = build_review_packet(status_payload, goal_id=GOAL_ID, action_kind="codex") handoff_payload = review_packet_handoff_only_payload(review_packet) diff --git a/loopx/cli_commands/status.py b/loopx/cli_commands/status.py index 49650306..b5af23d1 100644 --- a/loopx/cli_commands/status.py +++ b/loopx/cli_commands/status.py @@ -63,6 +63,15 @@ def register_status_commands( "to matching status queue items." ), ) + status_parser.add_argument( + "--include-task-graph", + action="store_true", + help=( + "Include the optional task_graph_projection_v0 on status items. " + "Default status output keeps this graph on the cold path to stay " + "inside the dashboard hot-path budget." + ), + ) diagnose_parser = subparsers.add_parser( "diagnose", @@ -208,6 +217,7 @@ def handle_status_command( runtime_root_override=runtime_root_arg, scan_roots=_scan_roots(args), limit=max(0, args.limit), + include_task_graph=args.include_task_graph, ) if args.agent_id: attach_agent_lane_next_actions(payload, agent_id=args.agent_id) @@ -357,6 +367,7 @@ def handle_review_packet_command( runtime_root_override=runtime_root_arg, scan_roots=_scan_roots(args), limit=max(0, args.limit), + include_task_graph=not args.handoff_only, ) payload = build_review_packet( status_payload, diff --git a/loopx/status.py b/loopx/status.py index e7a1736a..175f685f 100644 --- a/loopx/status.py +++ b/loopx/status.py @@ -8698,6 +8698,7 @@ def build_attention_queue( history: dict[str, Any], global_registry: dict[str, Any], runtime_root: Path | None = None, + include_task_graph: bool = False, ) -> dict[str, Any]: health_items: list[dict[str, Any]] = [] history_items: list[dict[str, Any]] = [] @@ -8885,13 +8886,14 @@ def build_attention_queue( interface_budget_cadence=interface_budget_cadence, ) normalize_monitor_quiet_attention_display(item) - task_graph_projection = build_task_graph_projection( - item, - goal=goal, - goal_latest_runs=goal_latest_runs, - ) - if task_graph_projection: - item["task_graph_projection"] = task_graph_projection + if include_task_graph: + task_graph_projection = build_task_graph_projection( + item, + goal=goal, + goal_latest_runs=goal_latest_runs, + ) + if task_graph_projection: + item["task_graph_projection"] = task_graph_projection attach_goal_channel_projection( item, goal=goal, @@ -9771,6 +9773,7 @@ def collect_status( runtime_root_override: str | None, scan_roots: list[Path], limit: int, + include_task_graph: bool = False, ) -> dict[str, Any]: display_limit = max(0, limit) control_plane_limit = max(display_limit, STATUS_CONTROL_PLANE_CONTEXT_LIMIT) @@ -9800,6 +9803,7 @@ def collect_status( history=history, global_registry=global_registry, runtime_root=runtime_root, + include_task_graph=include_task_graph, ) run_history = build_run_history(history, display_limit=display_limit) event_ledger_summary = build_event_ledger_summary(history)