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
2 changes: 2 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ uv run python e2e_account_isolation.py \
loop/judge 一样计入同一个 USD 1 周期,不新增周期、不追加第二条账本。任一未知用量或被
拒绝的预留会立即中断当前腿并跳过其余腿(记为 `not_run`),不自动重试;artifact 保留
每条腿的绑定身份、purpose、前后账本快照、逐调用 receipt 与真实 answer/tool trace。
模型协议错误另保留 `protocol_error` 形状诊断(固定错误标签、内容长度与结束原因),
不保存模型原文;该字段不改变 `INCOMPLETE` 判定、费用记录或剩余攻击腿的跳过行为。

`services/agent/src/ulticode_client.py` provides the Java LearningPlan HTTP wire contract
(`save_learning_plan`, `get_learning_plan`, `get_learning_plan_by_key`) and a session-bound
Expand Down
4 changes: 2 additions & 2 deletions services/agent/data/repository_corpus_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
},
{
"doc_id": "repository-development",
"version": "sha256-1b5bcaa942bff557c70c49a7eaa5f6d6437e7bf98ab6b4b2e64797828664ba32",
"chunk_id": "repository-development:sha256-1b5bcaa942bff557c70c49a7eaa5f6d6437e7bf98ab6b4b2e64797828664ba32:1",
"version": "sha256-58c66a3e9a4229509940fbe16bf8c8c9a6b821070a87208367c260ce84d30245",
"chunk_id": "repository-development:sha256-58c66a3e9a4229509940fbe16bf8c8c9a6b821070a87208367c260ce84d30245:1",
"source_path": "docs/DEVELOPMENT.md",
"access_scope": "repository-public",
"sample_kind": "real",
Expand Down
6 changes: 5 additions & 1 deletion services/agent/e2e_account_isolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@

from agent_loop import run_tool_loop
from boundary_evaluation import SEARCH_EVIDENCE_SPEC, _usd_from_micro, search_evidence_tool
from deepseek_model import DeepseekModel, ModelBudgetExceeded, model_label
from deepseek_model import DeepseekModel, ModelBudgetExceeded, ModelProtocolError, model_label
from e2e_citation_support_model import (
_assert_artifact_directory,
_claim_verdict_file,
Expand Down Expand Up @@ -1801,6 +1801,7 @@ async def _run_agent_scenario(
)
specs["search_evidence"] = SEARCH_EVIDENCE_SPEC
interrupted: str | None = None
protocol_error: str | None = None
snapshot_error: str | None = None
rounds = 0
answer = ""
Expand Down Expand Up @@ -1865,6 +1866,8 @@ async def _run_agent_scenario(
rounds, answer = loop.rounds, loop.answer
except Exception as error: # noqa: BLE001 - a probe leg must not lose the run
interrupted = type(error).__name__
if isinstance(error, ModelProtocolError):
protocol_error = str(error) # Adapter diagnostics contain shape, never model text.
finally:
usage = [entry for entry in model.usage if isinstance(entry, dict)]
response_models = sorted(
Expand Down Expand Up @@ -1933,6 +1936,7 @@ async def _run_agent_scenario(
"scenario": scenario.name,
"ok": ok,
"interrupted": interrupted,
"protocol_error": protocol_error,
# True only for data actually observed leaving its owner's scope; a missing
# owner control or an interrupted leg is unproven, not exposed.
"exposure": exposure,
Expand Down
19 changes: 19 additions & 0 deletions services/agent/tests/test_e2e_account_isolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3979,6 +3979,25 @@ def _known_answer(text="synthetic answer"):
return (json.dumps({"answer": text}), True)


@pytest.mark.parametrize("content,detail", [
("private-response-canary", "model decision was not valid JSON"),
(json.dumps({"answer": "private-response-canary", "extra": True}),
"model decision schema was malformed"),
])
def test_real_probe_retains_safe_protocol_failure_detail(monkeypatch, tmp_path, content, detail):
(verdict, evidence), budget, requests = _run_real_bound_probe(
monkeypatch, tmp_path, [(content, True)],
)
first = evidence["scenarios"][0]
assert first["interrupted"] == "ModelProtocolError"
assert first["protocol_error"].startswith(detail)
assert "private-response-canary" not in json.dumps(evidence)
assert verdict == "incomplete" and first["ok"] is False
assert len(requests) == 1 and evidence["model_calls"] == 1
assert [row["interrupted"] for row in evidence["scenarios"]][1:] == ["not_run", "not_run"]
assert budget.snapshot()["unknown_usage_attempts"] == 0


def test_real_probe_unknown_toolcall_stops_round_and_remaining_legs(monkeypatch, tmp_path):
(verdict, evidence), budget, requests = _run_real_bound_probe(
monkeypatch,
Expand Down
Loading