fix(run): S10 del 2 — post-mortem: stopp-artefakt, SDK-isolasjon, raw-JSON-direktiv
Transkript-analyse av den stoppede live-kjøringen (10 kall, 162 250 tokens, $0.331506): konfig-lekkasjen (setting_sources=None laster ALLE filsystem- settings) injiserte operatørens Claude-konfig i hvert kall — ~10-15k uncachede tokens, en påtvunget bekreftelses-preamble som gjorde ren-JSON-svar umulige, og en checker kapret av lekkede instrukser (debatt konvergerte aldri). - persist_stop_artifacts: stopp-event verbatim + usage/kost persisteres ALLTID ved BudgetExceeded (delt usage-shape med fullført-run-stien) - build_call_options: setting_sources=[] (SDK isolation mode, verifisert mot installert 0.2.110-kilde), system_prompt=None → tom system-prompt; detach- bevis via monkeypatchet query - _generation_prompt: krever ONLY the raw JSON object (fence-innpakning ga 4 fullpris parse-retries) 187/187 uten nøkkel · ruff + mypy --strict rene · tre detach-bevis RØDE → grønn Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QdSfQdND84oeq2mbjueLTS
This commit is contained in:
parent
0238507df4
commit
7637c6feae
7 changed files with 253 additions and 25 deletions
|
|
@ -1,11 +1,13 @@
|
|||
"""The Claude Agent SDK model client — RUN-PATH ONLY (honesty rule §1, §11).
|
||||
"""The Claude Agent SDK model client (honesty rule §1, §11).
|
||||
|
||||
This module is the ONE place the programme touches a real model (S10, D6). It
|
||||
is never imported by the test suite — the offline suite proves the loop with
|
||||
the scripted stand-in, and this client slots into the same ``ModelClient``
|
||||
protocol seam. Each ``complete()`` is one bounded ``query()`` call: no tools,
|
||||
one turn, a first-class USD cap (``ClaudeAgentOptions.max_budget_usd``) ON TOP
|
||||
of the §8 token/round meter that the loop already charges.
|
||||
This module is the ONE place the programme touches a real model (S10, D6).
|
||||
The network path (``query()``) is run-path-only; the offline suite proves the
|
||||
loop with the scripted stand-in, imports this module WITHOUT touching a key
|
||||
or the network, and pins the call options (``tests/test_sdk_isolation.py``).
|
||||
Each ``complete()`` is one bounded, ISOLATED ``query()`` call: no tools, one
|
||||
turn, a first-class USD cap (``ClaudeAgentOptions.max_budget_usd``) ON TOP of
|
||||
the §8 token/round meter that the loop already charges, and NO filesystem
|
||||
settings (``setting_sources=[]``).
|
||||
|
||||
Verified against claude-agent-sdk 0.2.110: ``query()`` yields
|
||||
``AssistantMessage`` (text blocks + real model id) and a closing
|
||||
|
|
@ -39,6 +41,28 @@ _USAGE_TOKEN_FIELDS = (
|
|||
)
|
||||
|
||||
|
||||
def build_call_options(model_id: str, *, max_budget_usd: float) -> ClaudeAgentOptions:
|
||||
"""One bounded, ISOLATED completion call (§8 + S10 post-mortem).
|
||||
|
||||
``setting_sources=[]`` is the SDK's documented isolation mode (verified
|
||||
against 0.2.110): the spawned CLI loads NO filesystem settings — no
|
||||
session hooks, no CLAUDE.md, no operator instructions. The default
|
||||
(``None``) loads ALL sources: in the S10 live run that injected the
|
||||
operator's config into every call (~10-15k uncached tokens each) and
|
||||
mandated a confirmation preamble that made pure-JSON replies impossible.
|
||||
``system_prompt=None`` serializes to an EMPTY system prompt, not the
|
||||
Claude Code preset.
|
||||
"""
|
||||
return ClaudeAgentOptions(
|
||||
model=model_id,
|
||||
max_turns=1, # a single completion — the agentic loop lives in loop.py, not here
|
||||
tools=[], # pure text completion: no tool surface, no silent egress
|
||||
max_budget_usd=max_budget_usd,
|
||||
setting_sources=[],
|
||||
system_prompt=None,
|
||||
)
|
||||
|
||||
|
||||
def _total_tokens(usage: dict[str, Any] | None) -> int | None:
|
||||
"""Sum the provider-reported token fields; no usage stays ``None`` (§8)."""
|
||||
if usage is None:
|
||||
|
|
@ -77,12 +101,7 @@ class SdkModelClient:
|
|||
return asyncio.run(self._complete_async(prompt, model_id))
|
||||
|
||||
async def _complete_async(self, prompt: str, model_id: str) -> ModelReply:
|
||||
options = ClaudeAgentOptions(
|
||||
model=model_id,
|
||||
max_turns=1, # a single completion — the agentic loop lives in loop.py, not here
|
||||
tools=[], # pure text completion: no tool surface, no silent egress
|
||||
max_budget_usd=self._max_budget_usd_per_call,
|
||||
)
|
||||
options = build_call_options(model_id, max_budget_usd=self._max_budget_usd_per_call)
|
||||
text_parts: list[str] = []
|
||||
reply_model: str | None = None
|
||||
usage_tokens: int | None = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue