Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/reference/contracts/interface-budget-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and size/count budgets.

| Surface | Owner | Consumer Action | Cold Path | Size Budget | Nested Budget | Count Budget |
| --- | --- | --- | --- | --- | --- | --- |
| `heartbeat_prompt_json` | heartbeat automation | wake and route one bounded turn | `quota should-run`, `status`, or `review-packet --handoff-only` | `json_chars <= 3600` plus `interface_budget.within_budget=true` | `nested_keys <= 40` | `top_level_keys <= 30` |
| `heartbeat_prompt_json` | heartbeat automation | wake and route one bounded turn | `quota should-run`, `status`, or `review-packet --handoff-only` | `json_chars <= 4800` plus `interface_budget.within_budget=true` | `nested_keys <= 40` | `top_level_keys <= 30` |
| `review_packet_handoff_only_json` | project-agent handoff | forward the smallest sufficient task packet | full `review-packet` or run-history artifact | `json_chars <= 3000` plus `handoff_interface_budget.within_budget=true` | `nested_keys <= 40` | `top_level_keys <= 18` |
| `quota_should_run_json` | quota guard | decide whether the selected goal may spend compute | `status`, `history`, or active state | `json_chars <= 13000` | `nested_keys <= 330` | `top_level_keys <= 52` |
| `dashboard_status_json` | operator dashboard | render first-screen operator state | `history`, run artifacts, or project-local adapter output | `json_chars <= 18500` | `nested_keys <= 260` | `top_level_keys <= 25` |
Expand All @@ -19,6 +19,20 @@ projections, repeated commands, and Markdown wrappers can make emitted output
materially larger. The emitted-output qualification matrix below measures that
separate boundary through the real CLI entry point.

The heartbeat envelope ceiling covers the unbound and representative agent/scope-bound
Codex App thin fixtures. It includes generator metadata and repeated bound commands,
not only the execution prompt. The shared host contract added static safety, repair
routing, and retry-stable Turn initialization; the scoped fixture now uses about
4,362 JSON characters. The 4,800-character ceiling leaves roughly 10% headroom for
that fixture, without relaxing the independent **2,500-character thin task body**,
4,000-character native Goal body, structural limits, or emitted CLI ceilings.
It is not a token count, execution quota, or allowance to append more instructions.
Arbitrary-length caller paths/scopes are not promised to fit this fixed fixture
envelope; their emitted output is qualified separately by the CLI matrix.
Do not remove safety or settlement semantics to fit the envelope, and do not copy
dynamic quota decisions into the static prompt. No prompt text, saved automation,
scheduler cadence, or spending policy changes with this qualification adjustment.

The quota budget includes the typed action portfolio, one shared bound CLI
route, pending-selection qualification, and hard-lane preemption evidence. The
budget retains modest headroom for those enforceable semantics; repeated action
Expand Down
16 changes: 15 additions & 1 deletion examples/control_plane/hot-path-interface-budget-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
"owner": "heartbeat automation",
"consumer": "wake and route one bounded turn",
"cold_path": "quota should-run, status, or review-packet --handoff-only",
"max_json_chars": 3_600,
# Includes generator metadata and scoped commands, not just task_body.
# The independent 2,500-character thin body cap remains unchanged.
"max_json_chars": 4_800,
"max_nested_keys": 40,
"max_top_level_keys": 30,
"budget_field": "interface_budget",
Expand Down Expand Up @@ -393,6 +395,18 @@ def main() -> int:
thin=True,
runtime_profile="codex_app_heartbeat",
)
# Real automations normally bind an agent; the unbound fixture alone
# misses repeated identity/scope arguments in the generator envelope.
for binding in (
{"agent_id": "worker-a"},
{"agent_id": "worker-a", "agent_scopes": ["implementation", "review"]},
):
assert_surface("heartbeat_prompt_json", build_heartbeat_prompt(
goal_id=GOAL_ID,
thin=True,
runtime_profile="codex_app_heartbeat",
**binding,
))

assert quota_payload["should_run"] is True, quota_payload
reset_policy = quota_payload["scheduler_hint"]["reset_policy"]
Expand Down
40 changes: 40 additions & 0 deletions tests/control_plane/test_heartbeat_prompt_support.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

import runpy
from pathlib import Path

import pytest

from loopx.control_plane.heartbeat.agent import (
Expand Down Expand Up @@ -59,6 +62,43 @@ def test_interface_budget_uses_visible_goal_mode() -> None:
assert budget["within_budget"] is True


@pytest.mark.parametrize("mode,limit", [("thin", 2500), ("visible_goal", 4000)])
def test_prompt_body_limit_remains_independent_of_json_envelope(mode: str, limit: int) -> None:
for size in (limit, limit + 1):
budget = build_interface_budget(
task_body="x" * size,
goal_id="fixture-goal",
active_state="fixture-state",
native_goal_host=mode == "visible_goal",
)
assert budget["max_chars"] == limit
assert budget["within_budget"] is (size <= limit)


def test_heartbeat_envelope_and_body_overflow_are_both_rejected() -> None:
smoke = runpy.run_path(str(
Path(__file__).resolve().parents[2]
/ "examples/control_plane/hot-path-interface-budget-smoke.py"
))
check = smoke["assert_surface"]
payload = build_heartbeat_prompt(
goal_id="interface-budget-goal", thin=True,
runtime_profile="codex_app_heartbeat", agent_id="worker-a",
agent_scopes=["implementation", "review"],
)
check("heartbeat_prompt_json", payload)
# Passing the inner body check cannot hide extra envelope metadata.
envelope = {**payload, "extra": ""}
envelope["extra"] = "x" * (4800 - smoke["json_size"](envelope))
check("heartbeat_prompt_json", envelope)
envelope["extra"] += "x"
with pytest.raises(AssertionError):
check("heartbeat_prompt_json", envelope)
# A small envelope cannot waive the independently evaluated body budget.
with pytest.raises(AssertionError):
check("heartbeat_prompt_json", {"interface_budget": {"within_budget": False}})


def test_agent_scope_normalization_dedupes_and_rejects_angle_brackets() -> None:
assert normalize_agent_scopes(["a b", "a b", "c"]) == ["a b", "c"]
with pytest.raises(ValueError, match="angle brackets"):
Expand Down
Loading