Retire the old build spec, and point every reference at the architecture (M0.1)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m22s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m22s
Development Plan M0.1. Every section reference in code, tests, specs/, the example config, the tuning guide, and the CLAUDE.md and README files now cites docs/Architecture.md (P, L, D, O, X, Q) instead of the old spec's A-H, and docs/Architecture-Build-Spec.md is deleted (git keeps it). The plan's old-to-new table goes with it. Mostly mechanical, with a per-site choice where one old section split: A4 became L2 where it meant stop/abandon/the loop and L3 where it meant approvals; A10 became L6 in the context-budget code and L7 elsewhere; B2 became D4 for outbound Gitea and publishing and D5 for review intake; 'A3 step N' became the matching step of L8's boot sequence. Comments describing behaviour the architecture drops (planning always escalating, thresholds tuned at an old build step) now describe the current code and point at the Development Plan. docs/Architecture.md gains one line the rewrite missed: intake outages delay but never fail work (old B2), now in L5, where the poller and Matrix code cite it. Comments and docs only; no behaviour change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
890708594b
commit
1ad0b2147d
73 files changed
+259
-1786
No files matched your search
@@ -1,11 +1,11 @@
|
||||
"""LLM access (spec A5, A11).
|
||||
"""LLM access (architecture L9).
|
||||
|
||||
One client interface, present from the first real LLM call. All traffic goes
|
||||
to the LiteLLM proxy — the VM's single LLM destination; the agent holds one
|
||||
LiteLLM token and no vendor credentials. The base URL and token come from the
|
||||
agent's config (nisse_dev.config), nothing hardcoded.
|
||||
|
||||
Model identity is expressed as roles (A11), resolved to concrete model strings
|
||||
Model identity is expressed as roles (L9), resolved to concrete model strings
|
||||
from config; the resolved mapping is pinned at task start so a mid-task
|
||||
config change cannot split a task across two planners.
|
||||
"""
|
||||
@@ -34,7 +34,7 @@ class LLMError(RuntimeError):
|
||||
|
||||
# A rate limit is a transient condition, not a bad request. Losing a
|
||||
# ten-turn delegation to one wastes far more tokens than waiting out the
|
||||
# window, so retry once before surfacing it (A13's caps still bound the
|
||||
# window, so retry once before surfacing it (L9's caps still bound the
|
||||
# total).
|
||||
RATE_LIMIT_STATUS = 429
|
||||
RATE_LIMIT_RETRY_S = 20.0
|
||||
@@ -71,7 +71,7 @@ class BudgetExhausted(UpstreamUnavailable):
|
||||
Distinct from the rest of its family because it will not clear on its
|
||||
own within the hour: an exhausted daily budget takes external models away
|
||||
until it resets, so retrying wastes time and the operator needs telling.
|
||||
A5 keeps the budget itself at the proxy; recognising its refusal is ours.
|
||||
L9 keeps the budget itself at the proxy; recognising its refusal is ours.
|
||||
"""
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class RoleMap:
|
||||
return str(getattr(self, role))
|
||||
|
||||
def pinned(self) -> dict[str, str]:
|
||||
"""The full resolved mapping, to record at task start (A11)."""
|
||||
"""The full resolved mapping, to record at task start (L9)."""
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ class LiteLLMClient:
|
||||
self._renderer = OpenAIChatRenderer()
|
||||
self._sleep = sleep
|
||||
self._max_tokens = max_tokens or DEFAULT_MAX_OUTPUT_TOKENS
|
||||
# Set after each call so a caller can meter itself (A13 caps);
|
||||
# Set after each call so a caller can meter itself (L9 caps);
|
||||
# a side channel rather than a return value, so the LLMClient
|
||||
# protocol stays the same for every implementation.
|
||||
self.last_usage: dict[str, int] = {}
|
||||
@@ -211,13 +211,13 @@ class LiteLLMClient:
|
||||
out: list[CanonicalEvent] = []
|
||||
if message.get("content"):
|
||||
out.append(AssistantText(text=message["content"], model=produced_by))
|
||||
# Provider tool-call IDs are mapped at the boundary (A10): each call
|
||||
# Provider tool-call IDs are mapped at the boundary (L7): each call
|
||||
# gets a locally-minted canonical ID; the provider's is dropped, since
|
||||
# every future request is rendered wholly from the canonical log.
|
||||
for call in message.get("tool_calls") or []:
|
||||
raw = call["function"]["arguments"] or "{}"
|
||||
# Weak models emit truncated or non-JSON argument strings; that is
|
||||
# a bad model turn, not a client crash (A15).
|
||||
# a bad model turn, not a client crash (L9).
|
||||
try:
|
||||
arguments = json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
@@ -244,10 +244,10 @@ class LiteLLMClient:
|
||||
|
||||
|
||||
class AnthropicPassthroughClient:
|
||||
"""LiteLLM's Anthropic passthrough (A5). Same interface as LiteLLMClient,
|
||||
"""LiteLLM's Anthropic passthrough (L9). Same interface as LiteLLMClient,
|
||||
so a caller chooses a route rather than a different kind of client.
|
||||
|
||||
Delegations take this path because fidelity matters there (A13);
|
||||
Delegations take this path because fidelity matters there (L9);
|
||||
ask_expert and fallbacks use the normalized route, where it does not.
|
||||
"""
|
||||
|
||||
@@ -340,7 +340,7 @@ class AnthropicPassthroughClient:
|
||||
out.append(AssistantText(text=block["text"], model=produced_by))
|
||||
elif kind == "tool_use":
|
||||
# Provider ids are mapped at the boundary and never used as a
|
||||
# join key (A10); the canonical log owns identity.
|
||||
# join key (L7); the canonical log owns identity.
|
||||
out.append(
|
||||
ToolCall(
|
||||
call_id=mint_call_id(),
|
||||
@@ -349,7 +349,7 @@ class AnthropicPassthroughClient:
|
||||
)
|
||||
)
|
||||
# Vendor-opaque reasoning blocks (thinking, redacted_thinking) are
|
||||
# deliberately dropped for now: A10 requires storing them tagged
|
||||
# deliberately dropped for now: L7 requires storing them tagged
|
||||
# with the producing model and never replaying them outside the
|
||||
# session that made them, and the canonical schema has no event
|
||||
# for that yet. Nothing requests extended thinking, so none arrive.
|
||||
@@ -378,7 +378,7 @@ def _post_with_rate_limit_retry(
|
||||
raise AssertionError("unreachable")
|
||||
|
||||
|
||||
# Model families we have a native renderer adapter for. A13: delegations take
|
||||
# Model families we have a native renderer adapter for. L9: delegations take
|
||||
# the vendor's passthrough route because a normalising middlebox is lossy
|
||||
# exactly where a long-running delegation lives.
|
||||
def _response_cost(response: httpx.Response) -> float:
|
||||
|
||||
Reference in new issue
Block a user