Capability architecture¶
Spendguard governs and records model use across metered APIs and subscription CLI lanes, routes work, measures quality and economics, and reconciles captured activity against available provider evidence. Read this map before designing a feature. Each entry below is source-read and built-and-verified unless marked PARTIAL, STUB, or VISION; verification here means implementation inspection, not a live-provider integration test. PARTIAL names a missing guarantee or wiring limit, not an absent module. Paths are relative to this repository; entry-point citations are navigable source locations. Billed API dollars, flat subscription fees, and remote compute are real costs; subscription usage's estimated value is a separate axis and must never be added to them.
Routing and contracts¶
Provider registration contract¶
adapters.register_provider(name, base_url, key_env, prefixes, kind="openai") adds an endpoint/key/prefix specification; kind selects the OpenAI-compatible or Anthropic SDK path. For example, an application can call adapters.register_provider(provider_name, base_url=endpoint, key_env=credential_env, prefixes=model_prefixes) using its configuration. provider_for accepts explicit provider:model IDs; otherwise the longest matching registered prefix wins, and unknown providers raise (src/spendguard/generate/execution/providers.py:32, src/spendguard/generate/execution/providers.py:32). Registration supplies routing metadata, not a new enforcement implementation: the selected SDK surface still needs the installed gate described below.
| Module | Status and capability | Primary entry points |
|---|---|---|
| adapters | BUILT — Common API/lane routing, pins, best-value selection, schema capability, generation controls, input fitting, retry provenance and governed recovery. Images force API execution; eligible labelled metered text calls enter storm routing. Published output ceilings generally replace caller token caps; a soft deadline records telemetry rather than cancelling work. | call (src/spendguard/generate/execution/adapters.py:353); json_schema_request (src/spendguard/generate/execution/schema.py:65); schema_capability (src/spendguard/generate/execution/schema.py:135); structured_reply (src/spendguard/generate/execution/schema.py:232) |
| vendor_call | PARTIAL — Typed facade, contracts, deadlines, transient retries, fan-out and model-serving discovery. A timed-out worker can finish and bill later; fan cost checks do not reserve all in-flight cost. consensus counts successful replies, not semantic agreement. |
call (src/spendguard/generate/execution/vendor_call.py:220); fan_out (src/spendguard/generate/execution/vendor_call.py:1145); first_ok (src/spendguard/generate/execution/vendor_call.py:1187); consensus (src/spendguard/generate/execution/vendor_call.py:1250); JobLock (src/spendguard/generate/execution/vendor_call.py:513) |
| output_contract | BUILT — Contract hashing, JSON parsing, supported schema-subset validation, item IDs and complete envelope/arity checks. This is not a full JSON Schema implementation; callable hashes exclude closure contents. | describe (src/spendguard/generate/work_integrity/output_contract.py:71); check_item (src/spendguard/generate/work_integrity/output_contract.py:346); check_envelope (src/spendguard/generate/work_integrity/output_contract.py:392) |
| best_value | BUILT — Pin-aware model/effort selection from fingerprinted measured quality frontiers, with an explicitly distinguished cold prior. | select_model_effort (src/spendguard/manage/routing/best_value.py:147) |
| intent_model_prior | BUILT — Cached agentic ranking of the whole priced catalog for an intent; supplies cold-start priors, not measured quality evidence. | rank_models_for_intent (src/spendguard/analyze/learning/intent_model_prior.py:142) |
| requirement_judge | BUILT — Whole prompt/output requirement judging, cheap judge followed by stronger adjudication on uncertainty; both failures remain unknown. | requirements_for (src/spendguard/analyze/measurement/requirement_judge.py:86); judge_requirements (src/spendguard/analyze/measurement/requirement_judge.py:121) |
| llm_files | BUILT — Whole decoded-text attachments with hashes, line counts and reconstruction checks; invalid UTF-8 is replacement-decoded rather than byte-preserved. | attach_whole (src/spendguard/generate/work_integrity/llm_files.py:43); attach_many (src/spendguard/generate/work_integrity/llm_files.py:81) |
| source_compact | BUILT — AST/token-aware removal of source prose with preservation checks and original-source fallback. | compact (src/spendguard/analyze/assurance/source_compact.py:92) |
Prospective coalescing, durable queues and admission¶
Adapter queue registration also tracks foreground calls; it does not mean every adapter call executes through the queue drainer.
These are different existing mechanisms, not names for one queue. storm_coalescer is the prospective accumulation queue: it waits for arrivals before choosing realtime versus batch. lane_queue persists work and leases ownership across attempts and processes. dispatch and xp_rate_window admit calls against concurrency and RPM/TPM capacity. whole_job plans an already-known set of jobs across a horizon. queue_planner forecasts an existing backlog; it does not collect future arrivals.
| Module | Status and capability | Primary entry points |
|---|---|---|
| storm_coalescer | PARTIAL — In-memory Future-based prospective queue. Cohorts flush at realtime budget, maximum wait, or idle gap; rolling reservations and pacing determine realtime versus Batch execution. Failed/missing keyed items alone retry; a batch submission failure is not bisected into repeated purchases. Bounded close warns about abandoned work. Coalescing is deterministic and has no per-call deadline/SLA flush. | StormCoalescer (src/spendguard/manage/token_operations/storm_coalescer.py:76) |
| storm_route | BUILT — Default-on registry connecting eligible adapter calls to shared coalescers. Registry keys include vendor/model/intent/reasoning/system hash, not horizon; unknown rates decline coalescing. Pre-enqueue failure can fall through; post-enqueue ownership is authoritative to avoid rebuying. Busy registry capacity is soft and Future waiting has no separate timeout. | coalescing_enabled (src/spendguard/manage/token_operations/storm_route.py:40); get_coalescer (src/spendguard/manage/token_operations/storm_route.py:128); route (src/spendguard/manage/token_operations/storm_route.py:160) |
| storm_submit | BUILT — Explicit storm submission plus default governed realtime and OpenAI/Anthropic batch executors, keyed collection and bounded polling. Optional durable batch executor adopts handles through the tracker; it is not the default and does not carry system/reasoning parameters. | submit_storm (src/spendguard/manage/token_operations/storm_submit.py:206); governed_realtime_executor (src/spendguard/manage/token_operations/storm_submit.py:47); default_batch_executor (src/spendguard/manage/token_operations/storm_submit.py:79); durable_batch_executor (src/spendguard/manage/token_operations/storm_submit.py:155) |
| whole_job | PARTIAL — Shared-horizon, urgency-aware whole-job planning and execution, priced budget checks, checkpointed realtime bulk delegation and persisted batch handles. Batch leg is OpenAI only; submission failure/unsupported vendors fall back to realtime with reporting. Per-intent grouping takes the first nonempty system/schema rather than partitioning heterogeneous contracts. | plan_jobs (src/spendguard/manage/routing/whole_job.py:218); run_jobs (src/spendguard/manage/routing/whole_job.py:258); load_pending (src/spendguard/manage/routing/whole_job.py:376); collect_jobs (src/spendguard/manage/routing/whole_job.py:441) |
| route_horizon | BUILT — Common urgency and horizon allocation, urgent-first realtime capacity, batch recommendation and batch chunk sizing. Plans known work rather than predicting future arrivals. | plan_horizon (src/spendguard/manage/routing/route_horizon.py:69); should_batch_fan (src/spendguard/manage/routing/route_horizon.py:180); chunk_for_batch (src/spendguard/manage/routing/route_horizon.py:204) |
| queue_planner | BUILT — Read-only backlog/rate forecasting, offload plans and batch chunk calculation; used by drainer and MCP planning. Unknown throughput remains a risk rather than invented capacity. | forecast (src/spendguard/manage/token_operations/queue_planner.py:35); offload_plan (src/spendguard/manage/token_operations/queue_planner.py:169); plan_loop (src/spendguard/manage/token_operations/queue_planner.py:270) |
| dispatch | BUILT — Global/provider/lane concurrency, cross-process slots, RPM/TPM admission, learned limits, Retry-After and connection-window AIMD. Metered admission precedes slot acquisition; oversized token requests can incur debt instead of deadlocking. Infrastructure failure can visibly run ungoverned. Adapter management defaults on; vendor facade does not enable shedding. | Governor (src/spendguard/manage/token_operations/dispatch.py:738); admit (src/spendguard/manage/token_operations/dispatch.py:1038); holding (src/spendguard/manage/token_operations/dispatch.py:1003); try_admit (src/spendguard/manage/token_operations/dispatch.py:1262); shrink_connection_window (src/spendguard/manage/token_operations/dispatch.py:296); grow_connection_window (src/spendguard/manage/token_operations/dispatch.py:336) |
| xp_rate_window | PARTIAL — SQLite cross-process second/minute request/token reservations and deadlines; oversized requests run alone. Some SQLite infrastructure errors fail open. This is admission accounting, not durable job ownership. | reserve (src/spendguard/manage/token_operations/xp_rate_window.py:110); limits_for (src/spendguard/manage/token_operations/xp_rate_window.py:46); RateDeadline (src/spendguard/manage/token_operations/xp_rate_window.py:29) |
| lane_queue | PARTIAL — Separate SQLite durable lease queue: fingerprint deduplication, completed-result reuse, priority/deadline/retry/lease expiry, batch ownership and adoption, pause/recovery, archival purge and migration. Saturation parks without burning attempts. Predictive batch offload defaults off. Stored result JSON is capped at 4,000 bytes and can become unparsable, yielding an empty result. | enqueue (src/spendguard/manage/token_operations/lane_queue.py:379); lease (src/spendguard/manage/token_operations/lane_queue.py:541); settle (src/spendguard/manage/token_operations/lane_queue.py:616); submit (src/spendguard/manage/token_operations/lane_queue.py:1021); drain (src/spendguard/manage/token_operations/lane_queue.py:1603); mark_batched (src/spendguard/manage/token_operations/lane_queue.py:757); collect_batched (src/spendguard/manage/token_operations/lane_queue.py:795) |
| work_integrity | BUILT — Exact multiset partition proof: expected work appears once, without missing, duplicate or unexpected IDs. Not a scheduler. | WorkIntegrityProof (src/spendguard/generate/work_integrity/work_integrity.py:17) |
| item_ledger | BUILT — Caller-owned JSONL item settlement, attempts, reopening and completeness assertions. Flushes without fsync; malformed records are ignored. | ItemLedger (src/spendguard/generate/work_integrity/item_ledger.py:79) |
| job_fingerprint | PARTIAL — Persisted plan/prompt/model fingerprints and resume drift refusal; agentic method-change decisions choose reuse/revalidate/rebuy. A revalidate decision stamps state but does not execute a validation sample itself. | compute_job_fingerprint (src/spendguard/generate/work_integrity/job_fingerprint.py:76); guard_resume (src/spendguard/generate/work_integrity/job_fingerprint.py:141) |
Lane identity, readiness and recovery¶
| Module | Status and capability | Primary entry points |
|---|---|---|
| lane_registry | BUILT — Canonical lane specs and provider mapping for Claude Code, Codex, Gemini, Z.ai coding and Kimi coding. Adding a lane requires a registry specification and an executor, not just config. | all_lanes (src/spendguard/store/reference_data/lane_registry.py:81); lane_spec (src/spendguard/store/reference_data/lane_registry.py:86); provider_lane_map (src/spendguard/store/reference_data/lane_registry.py:91) |
| lane_catalog | BUILT — Tier aliases, lane catalog and candidate arms, plus fallback auditing. Candidate-arm construction alone does not enforce pricing. | catalog (src/spendguard/store/reference_data/lane_catalog.py:121); arms (src/spendguard/store/reference_data/lane_catalog.py:135); audit_lane_fallback (src/spendguard/store/reference_data/lane_catalog.py:149) |
| lanes | BUILT — Own-CLI auth status, probe persistence, cached headroom, lane status and configuration commands. Installed binary is not proof of authentication; unknown is not READY. | lane_auth_status (src/spendguard/platform/lanes.py:103); lanes_status (src/spendguard/platform/lanes.py:139); probe (src/spendguard/platform/lanes.py:180); lane_headroom (src/spendguard/platform/lanes.py:248) |
| lane_quota | BUILT — TTL/reset-aware cached usage and bucket headroom. | cached_usage (src/spendguard/analyze/treasury/lane_quota.py:24); bucket_headroom (src/spendguard/analyze/treasury/lane_quota.py:47) |
| lane_quota_cost | PARTIAL — Measured per-call quota cost, overage avoidance and plan-fraction preflight. Unknown quota can skip checks, so this is not universal fail-closed quota enforcement. | estimate_lane_quota (src/spendguard/analyze/treasury/lane_quota_cost.py:173); quota_preflight (src/spendguard/analyze/treasury/lane_quota_cost.py:205); enforce_single_call_plan_fraction (src/spendguard/analyze/treasury/lane_quota_cost.py:250) |
| lane_servability | BUILT — Measured lane/model readiness, served-model shifts, refresh on rejection and compatible substitutions; protects caller pins. Distinguishes declared models from observed service. | lane_readiness (src/spendguard/analyze/measurement/lane_servability.py:60); detect_served_shift (src/spendguard/analyze/measurement/lane_servability.py:164); lane_served_substitute (src/spendguard/analyze/measurement/lane_servability.py:217); refresh_lane_servability (src/spendguard/analyze/measurement/lane_servability.py:347) |
| lane_eligibility | BUILT — Cached agentic intent eligibility classification; estimate/read-only operation leaves unjudged intents explicit. | classify_intent (src/spendguard/analyze/measurement/lane_eligibility.py:54); lane_eligible_report (src/spendguard/analyze/measurement/lane_eligibility.py:75) |
| lane_recovery | BUILT — Typed recovery proof, bounded free retry and authorization before paid fallback. Existing failure-shape interpretation uses error-text rules. | LaneRecoveryProof (src/spendguard/manage/token_operations/lane_recovery.py:49); LaneRecoveryProof.authorize_metered_recovery (src/spendguard/manage/token_operations/lane_recovery.py:69); retry_free_lane (src/spendguard/manage/token_operations/lane_recovery.py:88) |
| plan_admission | PARTIAL — Cached quota/overage risk, health/auth/cooldown-aware alternatives and confirmed substitutes. Actual cap refusal does not invent an alternate; unknown auth is not always rejected, and an auth-over-quota setting is unused. | lane_risk (src/spendguard/manage/admission/plan_admission.py:46); decide (src/spendguard/manage/admission/plan_admission.py:140); ready_meta_model (src/spendguard/manage/admission/plan_admission.py:198) |
| overage | BUILT — Cached observed invoice/window overage status and CLI display; not a fresh provider oracle. | current_overage_status (src/spendguard/analyze/treasury/overage.py:99); overage_cli (src/spendguard/analyze/treasury/overage.py:154) |
| resource_state | BUILT — Persisted reasoned cooldowns and expiring prompt-character ceilings, retaining proven-good watermarks with distinct lane/model keys. Header-mentioned output-ceiling axis is not implemented. | set_cooldown (src/spendguard/manage/token_operations/resource_state.py:94); set_size_ceiling (src/spendguard/manage/token_operations/resource_state.py:149); note_proven_good (src/spendguard/manage/token_operations/resource_state.py:172) |
Lane balancing, bandits and economics¶
| Module | Status and capability | Primary entry points |
|---|---|---|
| lane_balance | PARTIAL — Confirmed substitutes, load balancing, prompt adaptations, optional bandit routing and checkpointed bulk delegation. Bulk supports keyed results, schemas, governor capacity, pins, vision API calls, free-lane hedging and batch-on-miss. Caps are checked between chunks; in-flight chunks can overshoot. Empty confined arms widen defaults; estimate deduplicates identical inputs while fresh bulk execution does not. Checkpoint keys omit schema/reasoning/images. | delegate (src/spendguard/manage/routing/lane_balance.py:218); bulk_delegate (src/spendguard/manage/routing/lane_balance.py:474); estimate_fan (src/spendguard/manage/routing/lane_balance.py:1359); route_decision (src/spendguard/manage/routing/lane_balance.py:1572); confirm_substitute (src/spendguard/manage/routing/lane_balance.py:207); adapt_system (src/spendguard/manage/routing/lane_balance.py:1756) |
| lane_bandit | BUILT — Decayed trial/win learning, exploration and idle/headroom-aware arm selection; whole-output bakeoff judges preserve unknown failures rather than false ties. Its judge-cost estimate is not an upper bound for arbitrary whole evidence. | choose_arm (src/spendguard/analyze/learning/lane_bandit.py:173); run_bakeoff (src/spendguard/analyze/learning/lane_bandit.py:315); bandit_call (src/spendguard/analyze/learning/lane_bandit.py:387) |
| lane_economics | BUILT — Observed quota/token deltas, measured capacity, pacing, plan economics and prompt-budget reservations; unknown capacity remains unknown. | record_samples (src/spendguard/analyze/treasury/lane_economics.py:66); measured_lane_caps (src/spendguard/analyze/treasury/lane_economics.py:141); economics (src/spendguard/analyze/treasury/lane_economics.py:180); prompt_economics (src/spendguard/analyze/treasury/lane_economics.py:416) |
| lane_value | BUILT — Priced ledger-derived subscription-value stamps and daily sync, excluding transcript-mined duplicate sources. Unknowns and private ownership remain explicit; read failure is not proof of zero activity. | stamp_from_ledger (src/spendguard/analyze/treasury/lane_value.py:65); day_totals (src/spendguard/analyze/treasury/lane_value.py:150); sync (src/spendguard/analyze/treasury/lane_value.py:190) |
| route_utility | BUILT — Lane and metered rankings using headroom/reset/urgency/pacing/quota efficiency, prepaid balance and prices; default breach policy refuses silent downgrades. | rank_lanes (src/spendguard/manage/routing/route_utility.py:107); rank_metered (src/spendguard/manage/routing/route_utility.py:236); breach_decision (src/spendguard/manage/routing/route_utility.py:299); rank_for_tier (src/spendguard/manage/routing/route_utility.py:348) |
| tier_config | BUILT — Tier configuration/pricing validation and optional pinned reachability probes without fallback. | tier_config_report (src/spendguard/manage/routing/tier_config.py:56); reachability_probe (src/spendguard/manage/routing/tier_config.py:96) |
| route_economics | BUILT — Route cost, lane marginal economics, reservation/amortization and batch overflow reporting, with unknown costs explicit. | route_cost (src/spendguard/manage/routing/route_economics.py:94); route_report (src/spendguard/manage/routing/route_economics.py:211) |
| route_realized | BUILT — Read-only realized routing history and served-model drift summaries. | route_realized_report (src/spendguard/analyze/measurement/route_realized.py:17); routing_summary (src/spendguard/analyze/measurement/route_realized.py:33) |
CLI executors and warm brokers¶
Codex Phase-1 admission hardening is implemented: bounded max_waiters, checkout-inclusive timeout, recycle_on_timeout, and retirement that retains/quarantines slots until safe. Cold fallbacks honor capacity refusal. The broker's socket-failure fallback to a local pool means host-wide coordination is not an unconditional guarantee.
| Module | Status and capability | Primary entry points |
|---|---|---|
| warm_stdio_daemon | BUILT — Shared serialized startup/handshake, request waiters, reader lifecycle, idle cleanup, terminate/wait/kill/reap and retired-instance protection. Admission policy belongs to specialized daemons. | _WarmStdioJsonRpcDaemon (src/spendguard/manage/token_operations/warm_stdio_daemon.py:195) |
| codex_daemon | BUILT — Persistent Codex app-server stdio workers, per-prompt thread isolation, optional worker homes, exclusive pool checkout and bounded admission/recycling. | _CodexDaemon (src/spendguard/manage/token_operations/codex_daemon.py:150); _CodexDaemonPool (src/spendguard/manage/token_operations/codex_daemon.py:434) |
| codex_daemon_broker | PARTIAL — Unix-socket broker shares the pool across local clients; connection failure can use a local pool rather than preserving a single host-wide cap. | _CodexDaemonBroker (src/spendguard/manage/token_operations/codex_daemon_broker.py:139) |
| codex_exec | BUILT — Auth/usage and warm or cold Codex execution, credential scrubbing, safety modes and shell-snapshot GC. CLI has no forwarded max-token flag; warm usage may use character estimates. | auth_status (src/spendguard/generate/execution/codex_exec.py:319); usage (src/spendguard/generate/execution/codex_exec.py:660); run_prompt (src/spendguard/generate/execution/codex_exec.py:693) |
| subscription_exec | PARTIAL — Claude CLI execution with neutral cwd, sanitized environment and usage accounting. Accepted reasoning/max-token arguments are not forwarded. | auth_status (src/spendguard/generate/execution/subscription_exec.py:68); run_prompt (src/spendguard/generate/execution/subscription_exec.py:188) |
| antigravity_exec | PARTIAL — Gemini agy JSON execution and cached quota/reset handling. Binary availability does not establish auth; reasoning/max-token controls are not enforced. |
usage (src/spendguard/generate/execution/antigravity_exec.py:228); run_prompt (src/spendguard/generate/execution/antigravity_exec.py:276) |
| kimi_exec | PARTIAL — Default warm ACP execution with cold CLI handling for known pinned aliases. Reasoning/max-token arguments are ignored; usage can be estimated or unknown. | usage (src/spendguard/generate/execution/kimi_exec.py:43); run_prompt (src/spendguard/generate/execution/kimi_exec.py:220) |
| kimi_daemon | BUILT — Warm multiplexed ACP with an isolated session per prompt, declined authentication requests, explicit tool failures and transient cold fallback. | _KimiAcpDaemon (src/spendguard/manage/token_operations/kimi_daemon.py:37) |
| zai_exec | BUILT — Coding-plan HTTP executor with owned accounting, thinking-aware unsupported-option recovery and bounded rate-limit retries; quota can remain unknown. | usage (src/spendguard/generate/execution/zai_exec.py:47); run_prompt (src/spendguard/generate/execution/zai_exec.py:101) |
| delegate_router | BUILT — Whole-task/file classification into one-shot, agentic or needs-Codex work; selects ready/authenticated external CLI lanes, supports estimate/execute and does not fail open into unavailable paid work. | delegation_lanes_ready (src/spendguard/manage/token_operations/delegate_router.py:202); classify_task (src/spendguard/manage/token_operations/delegate_router.py:271); delegate_task (src/spendguard/manage/token_operations/delegate_router.py:696); delegate_cli (src/spendguard/manage/token_operations/delegate_router.py:754) |
Batch submission, collection and bulk safety¶
submit and callio are provider Batch API legs, not prospective queues. They are called by storm executors, OpenAI whole-job execution, the durable tracker/offload path, bulk delegation's batch-on-miss path, and experiment/history workflows. Anthropic support exists in submit/collect but is not wired uniformly into every higher-level caller.
Explicit submission contract¶
guarded_submit estimates first, refuses a count above request_cap (default 25,000), enforces cap_dollars, and refuses a projection above expected_cost * overrun_tolerance; the default tolerance is 1.2 (20% over expected; src/spendguard/generate/execution/submit.py:112). Its estimate-only path (submit=False) checks and writes an audit without submitting; normal submission also applies resilience checks and a live first-request preflight, which can itself bill. Accepted-batch booking is deferred until provider acceptance (src/spendguard/generate/execution/submit.py:355). Use separate estimate and approval stages before execution.
Pass a measured per-request avg_out_tokens to override output projection; a ceiling is a worst-case send limit, not evidence of typical output. The estimate reports both output and token-count bases, so inspect them before approving. Without tiktoken, the local counter warns about its len(text)/4 approximation and recommends installing tiktoken in the gated venv before trusting a batch estimate (src/spendguard/generate/execution/submit.py:79). Provider-aware counting also exposes proxy/normalization bases: calibrate provider factors where appropriate rather than treating a character estimate as exact (src/spendguard/generate/execution/submit.py:95, src/spendguard/analyze/cost_estimation/provider_tokens.py:183, src/spendguard/analyze/cost_estimation/content_tokens.py:317). This does not imply that every estimator uses max_tokens or that every estimate is conservative.
| Module | Status and capability | Primary entry points |
|---|---|---|
| submit | BUILT — OpenAI JSONL and Anthropic message batch construction, estimates, unique IDs, schema/sharding checks, guarded submission and accepted-batch booking. Submission normally includes a small live preflight; adapter ceilings can replace caller max-token values. | guarded_submit (src/spendguard/generate/execution/submit.py:355); submit_chat_tasks (src/spendguard/generate/execution/submit.py:522); submit_message_batch (src/spendguard/generate/execution/submit.py:655) |
| callio | PARTIAL — Captured prompt/output samples, provider-history recovery, batch status and keyed OpenAI/Anthropic collection. Live oversized prompts are explicitly skipped; historical snippets may lack replayable system/schema evidence. batch_status is OpenAI-only. |
record_io_sample (src/spendguard/store/call_corpus/callio.py:150); capture_live (src/spendguard/store/call_corpus/callio.py:222); batch_status (src/spendguard/store/call_corpus/callio.py:538); collect_chat_tasks (src/spendguard/store/call_corpus/callio.py:673); collect_message_batch (src/spendguard/store/call_corpus/callio.py:745) |
| batch_tracker | PARTIAL — Durable pre-submit manifests, lease IDs, orphan adoption and terminal completeness proofs, preventing accepted batch rebuy. Provider acceptance and local persistence are not atomic. Polling calls the OpenAI-only status helper even for Anthropic, leaving that path erroneous until expiry. | register_batch (src/spendguard/generate/execution/batch_tracker.py:82); submit_offload (src/spendguard/generate/execution/batch_tracker.py:358); poll (src/spendguard/generate/execution/batch_tracker.py:544); assert_batch_terminal_manifest (src/spendguard/generate/execution/batch_tracker.py:610) |
| openai_batch_cli | BUILT — Guarded JSONL submission with estimate/cap/dry-run and output download with overwrite protection. Failed terminal batches return before downloading error files. | submit_batch_jsonl (src/spendguard/generate/execution/openai_batch_cli.py:28); fetch_batch_output (src/spendguard/generate/execution/openai_batch_cli.py:93) |
| bulkgate | PARTIAL — Fresh estimate/test/eval evidence tied to contract and data signatures, realtime checks, latency/output learning and runaway checks. Default mode warns rather than blocks. Evaluation clips evidence to its first 12,000 characters with notice, so is not whole-evidence judging. | check_bulk (src/spendguard/manage/admission/bulkgate.py:349); estimate_job (src/spendguard/manage/admission/bulkgate.py:986); test_job (src/spendguard/manage/admission/bulkgate.py:992); eval_job (src/spendguard/manage/admission/bulkgate.py:1063); gated_batch (src/spendguard/manage/admission/bulkgate.py:1143) |
| bulk_resilience | BUILT — Structural gate requires large lane/provider submissions to be chunked and checkpointed; unknown shape refuses unless explicitly forced. Provider requests, rather than packed logical units, determine the door count. | require_resilient (src/spendguard/generate/work_integrity/bulk_resilience.py:26) |
| comprehend | BUILT — Corpus resolution, whole contiguous chunks, separate estimates and checkpointed bulk comprehension; unread inputs are named rather than silently cut. | estimate_corpus (src/spendguard/manage/token_operations/comprehend.py:112); comprehend_corpus (src/spendguard/manage/token_operations/comprehend.py:229) |
Config, installation and gate¶
config.py is the runtime resolver; config_schema.py describes settings for surfaces/onboarding. _cfg_get reads literal section/key pairs. Environment override support is individually implemented, not a universal dotted-key convention. In particular advisor numeric accessors are config-only and preserve zero; schema metadata alone does not prove an environment override is wired. Pool settings and selected other controls have explicit environment handling.
Enforcement order and cap scopes¶
For a batch estimate, _decide_and_account first routes a spendguard:* intent to the meta cage and returns. Ordinary work then passes batch-1 discipline and bulk-evidence checks, cross-process daily/monthly checks, the per-batch cap on the realistic projection, and a separate worst-case ceiling check, before recording. Submitters can defer recording until provider acceptance; otherwise the final step books a provisional estimate and an optional call-corpus row (src/spendguard/manage/admission/gate.py:787, src/spendguard/manage/admission/gate.py:787). Estimation is not a guarantee of known final batch cost.
| Control | Default and scope | Configuration / backend |
|---|---|---|
| Per-batch | $75 per submission | GATE_CAP → caps.per_batch; config.cap (src/spendguard/store/configuration/config_store.py:517) |
| Realtime cumulative | $50 per process | GATE_RT_BUDGET → caps.realtime; in-memory running total (src/spendguard/store/configuration/config_store.py:525) |
| Daily/monthly class caps | Off unless configured; llm, compute, total |
GATE_LLM_DAILY, GATE_LLM_MONTHLY, GATE_COMPUTE_DAILY, GATE_COMPUTE_MONTHLY, GATE_TOTAL_DAILY, GATE_TOTAL_MONTHLY; class_cap also reads flat/nested config and server policy (src/spendguard/store/configuration/config_store.py:550) |
| Meta | $2/day for the governor's own model use | GATE_META_BUDGET → caps.meta; persisted meta ledger accounting (src/spendguard/store/configuration/config_store.py:582) |
The gate's daily/monthly check and workload ledger recording require budget.backend=sqlite (src/spendguard/manage/admission/gate.py:612, src/spendguard/manage/admission/gate.py:651). budget.exceeded checks the resource class's daily then monthly sub-caps before total daily then monthly caps. Its local total currently sees the LLM ledger; combined LLM/compute reporting is composed elsewhere, so this is not a transactional combined-resource hard ceiling (src/spendguard/manage/admission/budget.py:848). Compute controls are alert/soft checks and never kill running jobs (src/spendguard/manage/admission/budget.py:848).
Over-cap per-batch and daily/monthly work can prompt on a TTY; affirmative approval proceeds, while noninteractive work refuses unless deliberately authorized with GATE_ALLOW=1 or appropriately raised caps. That override does not establish that every other independent bulk/intent control is waived (src/spendguard/manage/admission/gate.py:512, src/spendguard/manage/admission/gate.py:612). Realtime prechecks test estimated spend before execution, then account actual returned usage; their one-time _rt_bypass permits the rest of the process's realtime budget only, without skipping daily/monthly checks (src/spendguard/manage/admission/gate.py:1091, src/spendguard/manage/admission/gate.py:1477). Anthropic cache reads are added back into input before pricing, with cache-creation tokens passed separately (src/spendguard/manage/admission/gate.py:1369).
Kill switch and recovery¶
GATE_DISABLE=1 or the persistent $SPENDGUARD_HOME/disabled flag disables enforcement; the default flag location is ~/.spendguard/disabled (src/spendguard/store/configuration/config_store.py:26, src/spendguard/store/configuration/config_store.py:897). spendguard off writes the flag; spendguard on removes it. Clear the environment override too when recovering (src/spendguard/manage/admission/gate.py:2580). The generated startup hook checks both switches before importing spendguard, providing an escape hatch even if package import is broken; both the site/user hook and .pth bootstrap contain that check (src/spendguard/platform/setup.py:217, src/spendguard/platform/setup.py:234). Re-enabling the switches still requires installation in the interpreter actually used; the inventory's require() and SDK-coverage caveats apply.
Registering an SDK gate¶
spendguard.register(module_path, class_name, method, gate_fn, is_async=False) appends a surface; the next spendguard.install() patches it alongside built-ins. For example, spendguard.register(sdk_module, sdk_class, sdk_method, check_request) followed by spendguard.install() uses application-supplied configuration and a callback. gate_fn(kw, args) returns to allow or raises SpendGateRefused (including its subclasses) to stop; an estimator can explicitly reuse _decide_and_account with the pipeline's estimate shape (provider, model, requests, in_tok, out_tok, cost, and any additional estimate basis fields). Registration itself does not invent estimation or post-call actual-usage accounting (src/spendguard/interface/crossllm.py:242, src/spendguard/interface/crossllm.py:242).
_guard propagates deliberate refusal but warns and permits on other callback errors. install is idempotent at patched methods and contains per-surface installation failures; its optional cap argument sets GATE_CAP for this process (src/spendguard/manage/admission/gate.py:2312, src/spendguard/manage/admission/gate.py:2342). Extension authors inherit this fail-open boundary and must not mistake successful registration for comprehensive fail-closed SDK coverage.
| Module | Status and capability | Primary entry points |
|---|---|---|
| config | BUILT — Key provenance/shadowing, CLI resolution, mtime-cached config, atomic state updates, policy/local/org cap resolution, scoped intent authorizations, queue paths, lane environment scrubbing and worker settings. Owns counted/memory-budgeted thread-local ledger connections, fresh transaction handling, rollback/reset and separate maintenance connections. | load_key_files (src/spendguard/store/configuration/provider_keys.py:48); resolve_cli (src/spendguard/store/configuration/cli_resolution.py:12); advisor_num (src/spendguard/store/configuration/config_store.py:445); codex_daemon_pool_settings (src/spendguard/store/configuration/config_store.py:366); open_ledger_connection (src/spendguard/store/configuration/ledger_connection.py:462); pooled_ledger_conn (src/spendguard/store/configuration/ledger_connection.py:688); ledger_op (src/spendguard/store/configuration/ledger_connection.py:798) |
| config_schema | BUILT — Declarative settings catalog and sections used by configuration surfaces. Not all declared override metadata is implemented by runtime accessors. | sections (src/spendguard/store/configuration/config_schema.py:890) |
| keys_audit | BUILT — Key-source and shadowing audit with masked suffixes and optional strict duplicate reporting. | cmd (src/spendguard/analyze/assurance/keys_audit.py:42) |
| gate | PARTIAL — SDK interception for OpenAI/Anthropic realtime, Responses, embeddings, batches and streaming, plus budgets, explicit refusals, external-cost gate and accepted-batch booking. Incidental errors can fail open. require() proves at least one patch when SDKs are present, not every surface; no installed SDK passes vacuously. Unknown prices are loudly recorded as unpriced zero, not invented. Abandoned streams and fine-tuning training cost are not fully captured. |
install (src/spendguard/manage/admission/gate.py:2342); require (src/spendguard/manage/admission/gate.py:2485); SpendGateRefused (src/spendguard/manage/admission/spend_refusal_error.py:15); MeteredCallRefused (src/spendguard/manage/admission/spend_refusal_error.py:27); record_accepted_batch_estimate (src/spendguard/manage/admission/gate.py:800); gate_external (src/spendguard/manage/admission/gate.py:684) |
| setup | BUILT — Managed repo rules, venv/user startup hooks, interpreter coverage, skill installation and schema-driven setup. Startup bootstrap catches installation failure, making explicit require() important. Skill installation copies instructions rather than all supporting assets. |
install_rule (src/spendguard/platform/setup.py:363); install_hook (src/spendguard/platform/setup.py:742); install_skills (src/spendguard/platform/setup.py:866); cmd_init (src/spendguard/platform/setup.py:1379) |
| runner | PARTIAL — Scoped bootstrap directory and child environment for executing commands with startup injection. Bootstrap catches errors; explicit require remains necessary for fail-closed callers. | child_env (src/spendguard/platform/runner.py:69); main (src/spendguard/platform/runner.py:92) |
| coverage | BUILT — Startup-hook coverage audit across configured interpreters; structural coverage is not runtime enforcement proof. | audit_gating_coverage (src/spendguard/analyze/assurance/coverage.py:117); gaps (src/spendguard/analyze/assurance/coverage.py:136) |
| weekly_cap_guard | PARTIAL — PreToolUse protection for recognized raw Codex shell execution and Agent/Task invocations, using cached weekly headroom. Unknown cap blocks absent override; internal errors fail open. | weekly_cap_guard_pretooluse (src/spendguard/manage/admission/weekly_cap_guard.py:120); install_weekly_cap_guard (src/spendguard/manage/admission/weekly_cap_guard.py:195) |
| agent_spawn_gate | PARTIAL — Whole-task cached agentic near-cap spawn judgment, denial and delegation guidance; visible fail-open on internal failure. | evaluate_pretooluse (src/spendguard/manage/admission/agent_spawn_gate.py:77); install_agent_gate (src/spendguard/manage/admission/agent_spawn_gate.py:159) |
Canonical money ledger and capture¶
Storage, identity and isolation¶
SPENDGUARD_HOME selects state storage, defaulting to ~/.spendguard; operational config.json and credential keys.env are separate files (src/spendguard/store/configuration/provider_keys.py:-18, src/spendguard/store/configuration/provider_keys.py:-7). Email/server credentials use dedicated email.json/saas.json, gitignored configuration files or environment variables rather than tracked project config (src/spendguard/store/configuration/ledger_connection.py:821, src/spendguard/store/configuration/ledger_connection.py:865). The canonical money ledger coexists with call, I/O and learning tables; the queue has its own database, as described below. This is not a claim that every subsystem shares one database.
Charge project resolution is SPENDGUARD_PROJECT → repository .spendguard.json SaaS project → git-root basename → cwd basename. Conversation identity checks SPENDGUARD_CONV → SPENDGUARD_CHAT → CLAUDE_SESSION_ID → process fallback (src/spendguard/manage/admission/budget.py:74, src/spendguard/manage/admission/budget.py:106). Contributor resolution uses explicit configuration, then git email, then a persisted anonymous machine identity (usr_<hex>); anonymous identity is attribution fallback, not proof of a resolved human/org owner (src/spendguard/egress/saas.py:201, src/spendguard/store/configuration/ledger_connection.py:892).
Ledger connections use WAL and thread-local pooling. Cross-connection work must commit in phases to avoid holding one writer while another connection attempts to write the same file; history reconstruction documents this ordering explicitly (src/spendguard/store/configuration/ledger_connection.py:642, src/spendguard/store/configuration/ledger_connection.py:694, src/spendguard/analyze/learning/history.py:149). These phases are not an atomic multi-module transaction. Daily/monthly caps remain check-then-record: concurrent processes can each pass before another records, allowing overshoot. High-volume realtime usage also incurs per-call ledger writes and shared SQLite writer contention; pooling reduces connection overhead but does not remove these limits (src/spendguard/manage/admission/gate.py:612, src/spendguard/manage/admission/gate.py:787, src/spendguard/manage/admission/gate.py:1477).
| Module | Status and capability | Primary entry points |
|---|---|---|
| ledger | BUILT — Canonical v6 event ledger, decimal-text money, separate cost axes, UTC event timestamps and configured reporting timezone, repaired indexes, deduplication, adjustments/reversals, period locks and hash-chained audit. Compatibility totals can include multiple axes; bulk retag uses summary audit rather than per-row lock checks. | SpendLedger (src/spendguard/store/money_ledger/ledger.py:519); live_dedup_key (src/spendguard/store/money_ledger/ledger.py:492) |
| budget | BUILT — Charge recording into canonical events, snapshots, reconciliation corrections, intent/job/provider/day dimensions, unpriced records/repricing and separate meta/external spend. Write failures produce loud dead letters. Batch-estimate reconciliation matches provider/model/intent, not a unique batch ID. | record_charge (src/spendguard/manage/admission/budget.py:153); snapshot (src/spendguard/manage/admission/budget.py:215); spent_since (src/spendguard/manage/admission/budget.py:498); record_unpriced (src/spendguard/manage/admission/budget_unpriced.py:9); record_external_cost (src/spendguard/manage/admission/budget.py:651) |
| ledger_read | BUILT — Fresh read-only committed reads, allowlisted SQL/report access, wall-clock budgets and explicit oversize failures rather than clipped rows. | read_ledger_sql (src/spendguard/store/spend_search/ledger_read.py:779); read_ledger_rows (src/spendguard/store/spend_search/ledger_read.py:951); read_routing_history_report (src/spendguard/store/spend_search/ledger_read.py:1335) |
| ledger_sync | PARTIAL — Capture completeness, batch true-down and realtime reconstruction/sync. Realtime evidence is cached/forensic with representative project attribution, not comprehensive provider billing truth. Some fetch failures lack full completeness flags; correction clearing can precede failed-provider skips. | audit_completeness (src/spendguard/analyze/reconciliation/ledger_sync.py:98); true_down (src/spendguard/analyze/reconciliation/ledger_sync.py:140); reconcile_realtime (src/spendguard/analyze/reconciliation/ledger_sync.py:505); sync (src/spendguard/analyze/reconciliation/ledger_sync.py:725) |
| http_capture | PARTIAL — Best-effort httpx/requests/urllib response capture with SDK duplicate suppression. Capture only, no pre-spend enforcement; streams and some oversized/unknown-length bodies are skipped. | install (src/spendguard/generate/spend_capture/http_capture.py:329) |
| calls | BUILT — Physical-call and retry-root metadata, actual executor/model/effort, contract feedback, quality measurements and routing summaries. Metadata can survive prompt logging being disabled; some recording errors are swallowed. | context (src/spendguard/store/call_corpus/calls.py:283); record_call (src/spendguard/store/call_corpus/calls.py:692); feedback (src/spendguard/store/call_corpus/calls.py:866); cost_summary (src/spendguard/store/call_corpus/calls.py:1007) |
| guard | BUILT — Savings and routing-decision accounting, distinguishing measured savings from counterfactual distribution; prevents duplicate plan-reserved source counting. Savings are not billed money. | record_saving (src/spendguard/analyze/measurement/guard.py:139); record_decision (src/spendguard/analyze/measurement/guard.py:281); savings_crosscheck (src/spendguard/analyze/measurement/guard.py:228) |
| migrate_charges | BUILT — Idempotent decimal-conservation cutover from legacy charges, with snapshot rather than destructive table removal. | to_spend_events (src/spendguard/store/money_ledger/migrate_charges.py:22); run_cutover (src/spendguard/store/money_ledger/migrate_charges.py:112) |
| otel_ingest | PARTIAL — Stable trace/span deduplicated exported-cost or priced-token ingestion. Cache token metadata affects price but is not fully forwarded; candidate counts can include deduplicated rows. | span_to_charge (src/spendguard/generate/spend_capture/otel_ingest.py:112); ingest (src/spendguard/generate/spend_capture/otel_ingest.py:150) |
| focus_export | PARTIAL — FOCUS-shaped CSV/JSON and reconciliation export. Subscription-covered rows lack numeric EffectiveCost/allocated flat plan fee. | focus_row (src/spendguard/egress/focus_export.py:62); export_rows (src/spendguard/egress/focus_export.py:109) |
Ledger storage maintenance is separate from charge semantics: migrate_from_ledger (src/spendguard/manage/token_operations/lane_queue.py:324) copies live queue rows into the queue database and drops the old money-ledger queue table. Freed pages become reusable; the live file need not shrink until a later VACUUM. It does not perform that VACUUM itself. optimize_queue_db (src/spendguard/manage/token_operations/lane_queue.py:368) refreshes query-planner statistics after drains rather than on every operation.
Prices, catalogs and model parameters¶
Price resolution and cache semantics¶
Lowest to highest precedence is built-in _FALLBACK → $SPENDGUARD_HOME/litellm_prices.json → model_catalog.as_price_table() rates → shipped src/spendguard/prices.json → $SPENDGUARD_HOME/prices.json → $SPENDGUARD_HOME/prices.yaml → the file selected by SPENDGUARD_PRICES. The home defaults to ~/.spendguard; later layers merge per-model fields over earlier values (src/spendguard/store/reference_data/pricing.py:22, src/spendguard/store/reference_data/pricing.py:41, src/spendguard/store/reference_data/pricing.py:78). Supply provider/model rates in these data layers instead of embedding prices in application code; catalog capabilities and price provenance remain separate concerns.
_cost clamps cached input into [0, in_tok]. Realtime prices uncached input and cache reads separately, falling back to the full input rate when a cache-read price is absent. Batch uses explicit batch input/output rates and batch_cached_in when provided; without that cache-specific batch rate, cached input receives the ordinary batch-input rate. There is no universal stacking of cache and 50% batch discounts (src/spendguard/store/reference_data/pricing.py:532). Anthropic usage must be normalized because its ordinary input_tokens excludes cache-read/cache-creation tokens, while OpenAI's input count includes cached tokens; the gate adds cache reads before costing and passes cache creation separately (src/spendguard/manage/admission/gate.py:1369).
cross_check_openrouter(run=False) fetches public prices and performs exact-ID comparisons for free, returning unresolved identity and judgment work explicitly. Its default is a zero-spend, read-only drift check, not a semantic all-model price certification. run=True enables agentic identity and price-gap judgments and may spend; it does not silently rewrite the table (src/spendguard/store/reference_data/pricing.py:429).
| Module | Status and capability | Primary entry points |
|---|---|---|
| pricing | PARTIAL — Layered curated/user/synced pricing, provenance, normalization, token/unit/batch cost, catalog/live/learned ceilings and freshness checks. Unknown cost produces zero plus explicit unpriced tracking. Automatic semantic catalog mapping is a STUB returning None. | price (src/spendguard/store/reference_data/pricing.py:469); cost_or_unpriced (src/spendguard/store/reference_data/pricing.py:576); estimate (src/spendguard/store/reference_data/pricing.py:629); max_output_tokens (src/spendguard/store/reference_data/pricing.py:228); verify_pricing (src/spendguard/store/reference_data/pricing.py:634) |
| model_catalog | BUILT — Curated floor plus overlays, provider/pricing/rate/vision/embedding/capability queries and validation. Unknown capabilities are tri-state; row as-of dates differ from fetch timestamps. | model_record (src/spendguard/store/reference_data/model_catalog.py:202); rate_limit_for (src/spendguard/store/reference_data/model_catalog.py:316); model_capability (src/spendguard/store/reference_data/model_catalog.py:554); validate_catalog (src/spendguard/store/reference_data/model_catalog.py:614) |
| models | PARTIAL — Shared generation/reasoning parameter authority, effort observations and confirmed error healing. Persisted facts can be applied before verification; family-pattern seeds are existing implementation, not measured proof. | apply_call_params (src/spendguard/store/reference_data/models.py:192); resolve_effort (src/spendguard/store/reference_data/models.py:486); record_effort (src/spendguard/store/reference_data/models.py:296); heal_reasoning (src/spendguard/store/reference_data/models.py:417); confirm_reasoning (src/spendguard/store/reference_data/models.py:475) |
| catalog | BUILT — Live model-ID discovery, measured lane serving overlays, ceilings and stale refresh. Live membership does not establish price/capability; TTL is not strict usage refusal. | pull_live_catalog (src/spendguard/store/reference_data/catalog.py:32); merge_lane_observation (src/spendguard/store/reference_data/catalog.py:143); served (src/spendguard/store/reference_data/catalog.py:172); refresh_catalog_if_stale (src/spendguard/store/reference_data/catalog.py:225) |
| sync | BUILT — Validated atomic pricing refresh from LiteLLM with curated overrides taking precedence; does not author model capabilities. | sync (src/spendguard/store/reference_data/sync.py:174); refresh_if_stale (src/spendguard/store/reference_data/sync.py:208) |
| sync_capabilities | BUILT — Explicit SaaS/GitHub/LiteLLM capability/limit refresh with provenance, as-of dates and completeness audit. | audit_catalog_completeness (src/spendguard/store/reference_data/sync_capabilities.py:154); sync_capabilities (src/spendguard/store/reference_data/sync_capabilities.py:188) |
| refresh | PARTIAL — Optional paid web-assisted pricing lookup and explicit write mode; rejects truncated lookup replies. Direct SDK use relies on the installed gate rather than a local explicit require. | diff (src/spendguard/store/reference_data/refresh.py:68); main (src/spendguard/store/reference_data/refresh.py:111) |
| expected_output | BUILT — Class/model measured p90 and declared/seed/catalog expected-output estimates, with unknown basis explicit. Estimates are not automatically wire limits. | expect (src/spendguard/analyze/cost_estimation/expected_output.py:85); batch_output_estimate (src/spendguard/analyze/cost_estimation/expected_output.py:178) |
| provider_tokens | BUILT — Token estimates using encoding/factors with explicit proxy fallback, agentic factor calibration and confidence/provenance. Process factor cache reload is explicit. | count_text (src/spendguard/analyze/cost_estimation/provider_tokens.py:183); calibrate (src/spendguard/analyze/cost_estimation/provider_tokens.py:309); reload_factors (src/spendguard/analyze/cost_estimation/provider_tokens.py:151) |
| content_tokens | BUILT — Text/media token accounting and image dimensions with provider-specific formulas and named fallback assumptions. | count_tokens (src/spendguard/analyze/cost_estimation/content_tokens.py:317); count_detail (src/spendguard/analyze/cost_estimation/content_tokens.py:325); image_tokens (src/spendguard/analyze/cost_estimation/content_tokens.py:238) |
| model_preflight | BUILT — Per-spec served/priced checks and cached agentic stale-ID correction, returning every input status. Priced unchecked IDs remain usable; corrections are reported rather than applied. | preflight_models (src/spendguard/manage/admission/model_preflight.py:90); configured_specs (src/spendguard/manage/admission/model_preflight.py:116) |
| reasoning_equivalence | BUILT — Bidirectional same-provider lane/metered identity-and-effort mapping. Lesser reasoning needs affirmative judge/sample evidence; availability distinguishes yes/unverified/no. Candidate generation exists here, not bakeoff execution. | record_equivalence (src/spendguard/store/reference_data/reasoning_equivalence.py:134); resolve_metered (src/spendguard/store/reference_data/reasoning_equivalence.py:199); resolve_lane (src/spendguard/store/reference_data/reasoning_equivalence.py:273) |
Measurement, estimates and experiments¶
| Module | Status and capability | Primary entry points |
|---|---|---|
| measurement | BUILT — Separate sample/rubric/instrument/judge/candidate provenance and readings, reconstruction and comparisons. Rerun pins the first judge rather than replaying an entire multi-judge mixture. | record_reading (src/spendguard/analyze/measurement/measurement.py:142); compare (src/spendguard/analyze/measurement/measurement.py:251); rerun (src/spendguard/analyze/measurement/measurement.py:308) |
| estimate | BUILT — Fit measured sample distributions and project workload costs. | fit_from_sample (src/spendguard/analyze/cost_estimation/estimate.py:35); project (src/spendguard/analyze/cost_estimation/estimate.py:70) |
| realized | BUILT — Before/after adoption measurement and positive-increment savings sync; negative results remain visible. | measure (src/spendguard/analyze/measurement/realized.py:23); sync_to_guarded (src/spendguard/analyze/measurement/realized.py:66) |
| experiment | PARTIAL — Stored-I/O A/B pilot/equivalence experiments and guarded OpenAI/Anthropic batch promotion. Realtime exceptions can be silently skipped; effort variants are internal rather than a public axis. | experiment (src/spendguard/analyze/measurement/experiment.py:122); promote (src/spendguard/analyze/measurement/experiment.py:374) |
| equivalence | BUILT — Structural/scalar/text comparisons and optional embedding, rubric or custom grading. | structural (src/spendguard/analyze/measurement/equivalence.py:65); grade (src/spendguard/analyze/measurement/equivalence.py:123) |
| effort_titration | BUILT — Pilot/chunk/checkpoint effort experiments; only confident outcomes update learned effort, tentative outcomes remain receipts. | titrate (src/spendguard/analyze/measurement/effort_titration.py:345) |
| bakeoff | BUILT — Model-by-effort trials with actual served models, estimated caps, quality/requirements judges and replay checks. | bakeoff (src/spendguard/analyze/measurement/bakeoff.py:200) |
| calibrate | BUILT — Prediction/actual pairing, held-out backtests, quantile/hierarchical calibration and optional shared exchange. Does not automatically ship new gate policy. | predict_cost (src/spendguard/analyze/cost_estimation/calibrate.py:357); record_prediction (src/spendguard/analyze/cost_estimation/calibrate.py:439); backtest (src/spendguard/analyze/cost_estimation/calibrate.py:509) |
| stability | PARTIAL — Repeated-label consistency measurement including errors; not semantic answer quality. CLI has no separate mandatory estimate stage. | measure_stability (src/spendguard/analyze/measurement/stability.py:23) |
| estimate_divergence | BUILT — Agentic quote-versus-actual grounding adjudication; failed/unknown judgment refuses enforcement approval. | adjudicate_grounding (src/spendguard/analyze/cost_estimation/estimate_divergence.py:81); enforce (src/spendguard/analyze/cost_estimation/estimate_divergence.py:111) |
| estimate_literals | BUILT — AST literal-cost sites with whole-enclosing-context adjudication and cached verdicts. | literal_sites (src/spendguard/analyze/assurance/estimate_literals.py:45); adjudicate_literal (src/spendguard/analyze/assurance/estimate_literals.py:115) |
| token_caps | BUILT — AST output-cap site discovery and whole-context adjudication; missing certification is not approval. | sites (src/spendguard/analyze/assurance/token_caps.py:100); adjudicate_cap (src/spendguard/analyze/assurance/token_caps.py:202) |
| verdict_ledger | BUILT — Durable adjudication registry and stale/unjudged/failed/cleared comparison. | load_verdicts (src/spendguard/analyze/assurance/verdict_ledger.py:27); compare_to_verdicts (src/spendguard/analyze/assurance/verdict_ledger.py:44) |
| cachetest | BUILT — Estimate-first OpenAI/Anthropic cold/write/warm cache experiment and break-even, preserving partial/unknown warm results. Intent suffixes are capped at 400 characters; direct SDK calls rely on installed gate. | cache_test (src/spendguard/analyze/measurement/cachetest.py:100) |
Learning and optimization¶
Connected feedback loop¶
Opt-in physical-call capture and provider/history I/O recovery feed the call corpus; history mining, reconstruction and practice review create conditional insights. brief/advise and model recommendations consume that evidence; experiment tests cost and equivalence, and promote executes the selected path while retaining outputs for subsequent learning. Validation revisits insights as evidence changes; the gate enforces execution, reconciliation identifies capture gaps, and reporting surfaces results (src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374, src/spendguard/analyze/measurement/experiment.py:374). This is a connected workflow, with the evidence-clipping, heuristic validation and partial-execution limits in the inventory below; a recommendation alone is not experimental proof. See learning-advisor.md, MONEY-FLOW.md, DATA_PLANE.md, and ROADMAP.md.
The governor governs its own spend¶
Internal calls labelled through calls.context(intent="spendguard:…") are recognized by the spendguard: prefix. Batch _meta_gate and realtime precheck/accounting route them to the separate daily meta cap and kind='meta' accounting, rather than workload caps. budget.record_meta tags project llm-spendguard in the canonical ledger: “separate meta ledger” means a separate accounting axis, not a separate database (src/spendguard/manage/admission/budget.py:646, src/spendguard/manage/admission/budget.py:646, src/spendguard/manage/admission/budget.py:646, src/spendguard/manage/admission/budget.py:646). GATE_ALLOW is an explicit meta-cap override too. This contract depends on labelling; it does not prove every internal call site is labelled.
Advisor corpus reads exclude spendguard:% intents so the governor's own analysis does not train its workload recommendations (src/spendguard/store/spend_search/ledger_read.py:1022). Advisor and judge models are configurable and must have pricing for reliable meta estimates. validate_advisor checks the configured advisor.model and advisor.judge_model against pricing and returns problems; that validator is not by itself proof every entry point refuses an unpriced model (src/spendguard/store/configuration/ledger_connection.py:302).
| Module | Status and capability | Primary entry points |
|---|---|---|
| learn | BUILT — Conditional insight lifecycle and temporal graph in SQLite. | add_insight (src/spendguard/analyze/learning/learn.py:101); update_insight (src/spendguard/analyze/learning/learn.py:160); add_node (src/spendguard/analyze/learning/learn.py:182); add_edge (src/spendguard/analyze/learning/learn.py:192) |
| advise | BUILT — Confidence-weighted quality/cost recommendation, including zero-valued measurements, amortization and as-of evidence. | evidence (src/spendguard/analyze/learning/advise.py:75); ranked (src/spendguard/analyze/learning/advise.py:106); advise (src/spendguard/analyze/learning/advise.py:195) |
| advisor | PARTIAL — Replay reconstruction, aggregate mining and model recommendations from measured frontiers/cold priors. Reconstruction judges are realtime despite older batch prose; broad exception handling can swallow deliberate stops. Mining selects bounded aggregate insights. | reconstruct (src/spendguard/store/call_corpus/routing_audit.py:221); mine (src/spendguard/analyze/learning/advisor.py:171); recommend_models (src/spendguard/analyze/learning/advisor.py:291) |
| validate | PARTIAL — Mechanical model-name and aggregate-cost support/contradiction/decay checks. Semantic validation of freeform insights remains unimplemented. | validate (src/spendguard/analyze/learning/validate.py:141) |
| review | PARTIAL — High-cost insight review/freshness. Evidence bundling clips prompt/output/notes; freshness can advance on failed review. | review (src/spendguard/analyze/measurement/review.py:105); auto_fresh (src/spendguard/analyze/measurement/review.py:177) |
| bootstrap | PARTIAL — History and optional estimate-caged mining/review/reconstruction orchestration. Stage failures print and skip, so final readiness is not proof all stages succeeded. | bootstrap (src/spendguard/analyze/learning/bootstrap.py:25) |
| backfill | PARTIAL — OpenAI completed/cancelled and Anthropic history backfill with intent linkage/dedup. Later model groups in a multi-model Anthropic batch can be skipped. | backfill (src/spendguard/generate/spend_capture/backfill.py:76) |
| history | PARTIAL — Artifact/git history linkage and optional paid agentic relevance mining. Execution can spend on classification even without applying results. | reconstruct_intents (src/spendguard/analyze/learning/history.py:127); enrich_graph (src/spendguard/analyze/learning/history.py:179); mine_git (src/spendguard/analyze/learning/history.py:226) |
| brief | PARTIAL — Structured historical brief and cheap non-denied recommendations; deterministic overlap matching does not establish measured quality. Optional ready-lane refinement. | brief (src/spendguard/analyze/learning/brief.py:81) |
| prompts | BUILT — Default zero-spend structural prompt-waste lint and candidate cheaper routes; optional agentic packability assessment recommends rather than applies. | lint (src/spendguard/analyze/measurement/prompts.py:91) |
| cacheaudit | BUILT — Shared-prefix savings, script system-prompt candidates and realized cached-input-rate audit. Samples at most 30 prompts per intent/provider/model with heuristic candidate filtering; not exhaustive semantic analysis. | audit_cache_opportunities (src/spendguard/analyze/measurement/cacheaudit.py:158) |
| semcache | PARTIAL — Opt-in exact/embedding-similarity cache and JSONL dedup/population; no TTL or universal call interception. JSONL keys omit much of a full request. | get_cached (src/spendguard/manage/admission/semcache.py:150); cached_call (src/spendguard/manage/admission/semcache.py:209); dedup_jsonl (src/spendguard/manage/admission/semcache.py:259) |
| share | BUILT — Opt-in deidentified insight generalization/export/import; imported abstractions remain untrusted priors. | generalize_insight (src/spendguard/egress/share.py:34); cmd_export (src/spendguard/egress/share.py:85); cmd_import (src/spendguard/egress/share.py:122) |
Diagnostics and reliability¶
| Module | Status and capability | Primary entry points |
|---|---|---|
| reliability | PARTIAL — Estimate-first probes, cached health/auth/model shifts, remediation and alerts. Some failures become down-state observations; remediation consumes shortened errors. | sweep_estimate (src/spendguard/analyze/measurement/reliability.py:99); sweep (src/spendguard/analyze/measurement/reliability.py:171); remediate (src/spendguard/analyze/measurement/reliability.py:296); health_reds (src/spendguard/analyze/measurement/reliability.py:599) |
| balances | BUILT — Provider balance fetching and whole-response agentic extraction, hash cache and declared-balance-minus-ledger fallback; unknown remains unknown. | vendor_balance (src/spendguard/analyze/treasury/balances.py:206); refresh_balances (src/spendguard/analyze/treasury/balances.py:242); all_balances (src/spendguard/analyze/treasury/balances.py:288) |
| anomaly | BUILT — Median/MAD daily-spend anomaly heuristic, not semantic cause attribution. | robust_z (src/spendguard/analyze/reconciliation/anomaly.py:20); flag_today (src/spendguard/analyze/reconciliation/anomaly.py:40) |
| cost_integrity | BUILT — Impossible cost/token bucket ceiling audit; a result below the ceiling is not proof of correct pricing. | audit_metered_cost_vs_tokens (src/spendguard/analyze/reconciliation/cost_integrity.py:122) |
| metadata_audit | BUILT — Cached metadata age/breadth and measured-output-versus-published-limit audit, with unknown limits separate. Optional sync refreshes before inspection. | backbone_health (src/spendguard/analyze/assurance/metadata_audit.py:111) |
| audit | PARTIAL — AST gating/known-model audit and optional estimated whole-file deep chunks. Unread files can yield no findings, so absence alone is not complete coverage proof. | deep (src/spendguard/analyze/assurance/audit.py:91); main (src/spendguard/analyze/assurance/audit.py:163) |
| verify | BUILT — Structural verification, optional live probes and economics convergence; not an overall requirement proof or served-identity guarantee. | verify_system (src/spendguard/analyze/assurance/verify.py:69) |
| signal | BUILT — Scrubbed cost/quality/waste signals and scoped SaaS upload; cancelled billed batches appear separately. Heuristic recommendations and whole-batch allocation to multiple intent/model groups mean totals are not necessarily additive. | build_signals (src/spendguard/egress/signal.py:12); push_signal (src/spendguard/egress/signal.py:126) |
Conversation imports and attribution¶
| Module | Status and capability | Primary entry points |
|---|---|---|
| claudecode | PARTIAL — Incremental transcript usage, classification, invoice/overage ingestion, window attribution and context trajectories. Covered chat has billed zero and separate estimated value; inferred overage is provisional until invoices replace it. Story classification uses clipped excerpts. | ingest_events (src/spendguard/generate/spend_capture/claudecode.py:179); ingest_invoices (src/spendguard/generate/spend_capture/claudecode.py:665); attribute_overage (src/spendguard/generate/spend_capture/claudecode.py:754); sync (src/spendguard/generate/spend_capture/claudecode.py:1480); context_trajectory (src/spendguard/generate/spend_capture/claudecode.py:873) |
| codex | PARTIAL — Codex usage import, project classification, daily value and sync; final session model can misprice mixed-model usage. | update (src/spendguard/generate/spend_capture/codex.py:133); classify (src/spendguard/generate/spend_capture/codex.py:179); sync (src/spendguard/generate/spend_capture/codex.py:237) |
| chat | PARTIAL — Browser-cookie/internal-API chat import, estimated text/image value, classification and consented sync/loop. Estimates are proxies; classifications use snippets, and watermark advancement after failures can omit retries. | update (src/spendguard/generate/spend_capture/chat.py:386); classify (src/spendguard/generate/spend_capture/chat.py:992); sync (src/spendguard/generate/spend_capture/chat.py:590); loop (src/spendguard/generate/spend_capture/chat.py:1070) |
| attribution | BUILT — Shared taxonomy/team mapping and chunked agentic item classification with estimates; successful partial replies survive while omissions remain failures. | taxonomy (src/spendguard/analyze/reconciliation/attribution.py:39); classify_items (src/spendguard/analyze/reconciliation/attribution.py:79) |
| conv | PARTIAL — Canonical org/team/project resolution, human precedence, batch links, session chunks, segments and realtime/GPU attribution. Whole contiguous session chunks coexist with legacy clipped classifiers/index/synthesis. Segmentation uses every user record rather than an ask classifier; realtime tallies are explicit lower bounds. | resolve (src/spendguard/analyze/learning/conv.py:659); session_chunks (src/spendguard/analyze/learning/conv.py:900); segments (src/spendguard/analyze/learning/conv.py:365); attribute_segments (src/spendguard/analyze/learning/conv.py:468); realtime_token_tally (src/spendguard/analyze/learning/conv.py:981) |
| tag | PARTIAL — Deterministic retag and explicit project moves. Residual LLM retag has an estimate surface but no execution implementation. | retag_deterministic (src/spendguard/analyze/reconciliation/tag.py:47); move_project (src/spendguard/analyze/reconciliation/tag.py:69); estimate_llm_retag (src/spendguard/analyze/reconciliation/tag.py:102) |
| workdone | PARTIAL — Git/intent work summaries and rollups; optional summarization uses bounded intent/commit excerpts rather than whole evidence. | build_workdone (src/spendguard/analyze/reconciliation/workdone.py:76); summarize (src/spendguard/analyze/reconciliation/workdone.py:179) |
Residency, compaction and receipts¶
The residency axis and PostToolUse nudge are implemented, despite older repository instructions describing them as future work. They estimate attribution of observed context cost; they do not recover exact historical compaction boundaries.
| Module | Status and capability | Primary entry points |
|---|---|---|
| residency_gate | BUILT — Size/session-length-triggered, whole-tool-result, cached ready-lane agentic delegability judgment and cost warning. PostToolUse warns only; it cannot remove already-resident evidence and fails open. | evaluate_posttooluse (src/spendguard/manage/admission/residency_gate.py:175); install_residency_gate (src/spendguard/manage/admission/residency_gate.py:262) |
| compaction | PARTIAL — Preservation guidance, precompact/session-start records, digest and measured compaction ratios. Agentic advice uses bounded tails/snippets rather than whole session evidence. | agentic_advise (src/spendguard/generate/spend_capture/compaction.py:166); record_precompact (src/spendguard/generate/spend_capture/compaction.py:242); preservation_digest (src/spendguard/generate/spend_capture/compaction.py:259); record_sessionstart (src/spendguard/generate/spend_capture/compaction.py:271) |
| receipt | PARTIAL — Separate real-cost and subscription-value axes, savings, health, queue/risk/residency and unpriced warnings, terminal renderers and installation. Project filtering scopes API/value but flat fees, remote and external costs can remain global; default CLI is a global table plus value tree, not complete proportional per-repo allocation. Assumed fees are labelled. | tally (src/spendguard/analyze/spend_reporting/receipt_tally.py:631); render_tally (src/spendguard/analyze/spend_reporting/receipt_render.py:151); render_tree (src/spendguard/analyze/spend_reporting/receipt_render.py:434); cli (src/spendguard/analyze/spend_reporting/receipt_cli.py:171); install_cli (src/spendguard/analyze/spend_reporting/receipt_cli.py:416) |
Residency decomposition and subagent counterfactual calculations live in claudecode.py: residency_decomposition (src/spendguard/generate/spend_capture/claudecode.py:1041), residency_counterfactual_costs (src/spendguard/generate/spend_capture/claudecode.py:1028) and residency_summary_lines (src/spendguard/generate/spend_capture/claudecode.py:1198). These feed receipt/diagnostic surfaces; there is no separate missing residency subsystem to invent.
Reconciliation, truth and close¶
Fetch → transform → load¶
The data-plane design keeps external fetches and output/DB/HTTP loads around a directly testable transformation core. report.build_rows is the pure roll-up reference (src/spendguard/analyze/spend_reporting/report.py:154; offline tests: tests/test_report_build.py:1). Other named offline-tested transforms include chat _value_breakdown, _digest_conv, _day_rows, transcript _row_cost/_project_of, and conversation _dedup_top (src/spendguard/analyze/learning/conv.py:1156, src/spendguard/analyze/learning/conv.py:1156, src/spendguard/analyze/learning/conv.py:1156, src/spendguard/analyze/learning/conv.py:1156, src/spendguard/analyze/learning/conv.py:1156, src/spendguard/analyze/learning/conv.py:1156; tests/test_chat_value.py:1, tests/test_transcript_pure.py:1). Their existing attribution/heuristic limits remain as recorded above; the removed conv._project_of is not part of this contract.
SaaS build_rollup_rows and build_guarded_rows shape plain data separately from the I/O shell _rollup_rows, but now print exclusion diagnostics, so they are not strictly pure/no-print transforms (src/spendguard/egress/saas.py:484, src/spendguard/egress/saas.py:484, src/spendguard/egress/saas.py:484). Reconciliation follows a compatible map/reduce pattern: Source adapters produce comparable records and portfolio reduction computes residuals (src/spendguard/analyze/reconciliation/reconcile.py:106, src/spendguard/analyze/reconciliation/reconcile.py:106).
CI isolates offline correctness tests from provider I/O and configures two coverage floors: 40% whole-package and 78% for its explicitly listed money-critical modules. These are configured requirements, not a claim of currently measured achieved coverage; no historical achieved percentage is restored (.github/workflows/ci.yml:37, .github/workflows/ci.yml:47, .github/workflows/ci.yml:48). See DATA_PLANE.md for seams and MONEY-FLOW.md for conservation/accounting boundaries.
| Module | Status and capability | Primary entry points |
|---|---|---|
| reconcile | PARTIAL — Source protocol and unified truth/captured/residual report, with unknown/completeness explicit; not every cost category has a truth adapter. | Source (src/spendguard/analyze/reconciliation/reconcile.py:106); all_sources (src/spendguard/analyze/reconciliation/reconcile.py:123); completeness (src/spendguard/analyze/reconciliation/reconcile.py:162); run (src/spendguard/analyze/reconciliation/reconcile.py:228) |
| reconcile_openai | BUILT — Completed/cancelled batch billing retrieval for reconciliation. | fetch_batches (src/spendguard/analyze/reconciliation/reconcile_openai.py:96) |
| reconcile_anthropic | BUILT — Cached ended-period Anthropic cost retrieval with malformed-row skipping. | refresh_cache (src/spendguard/analyze/reconciliation/reconcile_anthropic.py:100); cost_by_day (src/spendguard/analyze/reconciliation/reconcile_anthropic.py:154) |
| realtime_oracle | BUILT — Admin hourly usage and project/day attribution; requires provider evidence and access. | openai_hourly (src/spendguard/analyze/reconciliation/realtime_oracle.py:45); anthropic_hourly (src/spendguard/analyze/reconciliation/realtime_oracle.py:67); by_project_day (src/spendguard/analyze/reconciliation/realtime_oracle.py:132) |
| reconcile_calls | BUILT — Dry-run-first stable recording-gap repair. | recording_gaps (src/spendguard/analyze/reconciliation/reconcile_calls.py:52); reconcile (src/spendguard/analyze/reconciliation/reconcile_calls.py:76) |
| truth | PARTIAL — Owner-scoped provider/GPU daily truth upload respecting privacy; client implementation does not verify remote server behavior. | rows (src/spendguard/analyze/reconciliation/truth.py:19); push_truth (src/spendguard/analyze/reconciliation/truth.py:37) |
| trust | BUILT — Comparability-aware local/provider/server trust report; unknown is not clean and mirrored/meta rows are excluded appropriately. | provider_truth (src/spendguard/analyze/reconciliation/trust.py:40); trust_report (src/spendguard/analyze/reconciliation/trust.py:86) |
| close | BUILT — Monthly truth/captured close, unusable-source gaps and CSV; empirical current-month run-rate requires observed days. | build_close (src/spendguard/analyze/spend_reporting/close.py:24); to_csv (src/spendguard/analyze/spend_reporting/close.py:75) |
| report | PARTIAL — Daily/weekly/monthly billing/usage report with unpriced visibility. Failed admin retrieval can leave partial totals; automatic review freshness can invoke LLM work despite older zero-call prose. | build_rows (src/spendguard/analyze/spend_reporting/report.py:154); generate_report (src/spendguard/analyze/spend_reporting/report.py:297) |
Remote compute and provider extension¶
| Module | Status and capability | Primary entry points |
|---|---|---|
| gpu_port | BUILT — Provider contract, runtime/day slicing, bill-or-rate accounting and source registry; unknown prices remain explicit. Label mapping uses existing substring rules. | GPUProvider (src/spendguard/generate/spend_capture/gpu_port.py:33); ProviderGPUSource (src/spendguard/generate/spend_capture/gpu_port.py:219); register_source (src/spendguard/generate/spend_capture/gpu_port.py:254); sources (src/spendguard/generate/spend_capture/gpu_port.py:284) |
| resources | PARTIAL — Vast inventory/history, disappeared-instance recovery, GPU attribution, alert-only compute caps and ledger sync. Forensic remote reconstruction is bounded/heuristic, not complete truth; account top-ups proxy consumed compute and residuals remain unattributed. Never kills jobs. | instances (src/spendguard/generate/spend_capture/resources.py:74); gpu_rows (src/spendguard/generate/spend_capture/resources.py:155); compute_exceeded (src/spendguard/generate/spend_capture/resources.py:261); reconstruct_remote_llm (src/spendguard/generate/spend_capture/resources.py:589); sync (src/spendguard/generate/spend_capture/resources.py:890) |
| remote | BUILT — SSH startup enforcement verification and remote ledger sync; unknown is not verified. Single-target errors can still exit successfully. | onstart_snippet (src/spendguard/platform/remote.py:70); verify_remote_enforcement (src/spendguard/platform/remote.py:89); sync (src/spendguard/platform/remote.py:136); sync_all (src/spendguard/platform/remote.py:175) |
| lambda_adapter | PARTIAL — Lambda inventory provider; untimed records and no account billing oracle. | LambdaProvider (src/spendguard/generate/spend_capture/lambda_adapter.py:43); source (src/spendguard/generate/spend_capture/lambda_adapter.py:75) |
| runpod_adapter | PARTIAL — RunPod inventory/rates; stopped/untimed gaps, no account truth, API failure can return empty inventory. | RunPodProvider (src/spendguard/generate/spend_capture/runpod_adapter.py:51); source (src/spendguard/generate/spend_capture/runpod_adapter.py:100) |
| modal_adapter | PARTIAL — Modal SDK daily billed source; no SDK is unconfigured and account truth can be unknown. | ModalProvider (src/spendguard/generate/spend_capture/modal_adapter.py:34); source (src/spendguard/generate/spend_capture/modal_adapter.py:105) |
| provider_plugins | BUILT — Entry-point plugin loading, success-once registration, retry after failures and contained plugin exceptions/SystemExit. | load_plugins (src/spendguard/generate/execution/provider_plugins.py:19); loaded (src/spendguard/generate/execution/provider_plugins.py:55) |
| provider_kit | BUILT — Offline conformance checks for registration, pricing, idempotence and failure containment. | run_conformance (src/spendguard/generate/execution/provider_kit.py:22); assert_conformance (src/spendguard/generate/execution/provider_kit.py:84) |
| litellm_adapter | PARTIAL — Success-callback accounting, skipping already-gated SDK paths; capture only, not pre-spend enforcement. | record_litellm (src/spendguard/generate/spend_capture/litellm_adapter.py:55); install (src/spendguard/generate/spend_capture/litellm_adapter.py:81) |
| bedrock_adapter | PARTIAL — boto3 response capture; streams are not consumed and unknowns remain unpriced. No admission gate. | install (src/spendguard/generate/spend_capture/bedrock_adapter.py:73) |
| vertex_adapter | PARTIAL — Sync/async generation and embedding capture; no streaming capture or pre-spend enforcement, with best-effort failure handling. | install (src/spendguard/generate/spend_capture/vertex_adapter.py:98) |
Organization sync, discovery and operations¶
Standalone client and server contract¶
Local gating, pricing, ledger, routing and local analysis require no team server; provider execution and fresh remote evidence still require their own connectivity. The opt-in SaaS seam uses a versioned /v1 HTTP contract and a Bearer key; the server maps identity to its hierarchy rather than requiring callers to attach a trusted team/org identity to every request (src/spendguard/egress/saas.py:136, src/spendguard/egress/saas.py:284). visibility=private is the default. Normal upload paths return without pushing in that mode; enabled roll-ups select aggregate fields rather than prompts, outputs or keys. Connection does not authorize unresolved org ownership, and additional trust/privacy caveats remain in the inventory (src/spendguard/store/configuration/ledger_connection.py:882, src/spendguard/egress/saas.py:484, src/spendguard/egress/saas.py:527).
pull_policy caches server caps in local config. Advisory caps are displayed suggestions and do not replace local limits; enforced caps resolve to the server ceiling or the smaller local cap, so local config can tighten but cannot loosen the effective policy (src/spendguard/egress/saas.py:787, src/spendguard/egress/saas.py:787). This resolution contract does not erase the gate's explicit override and fail-open boundaries. Sync reports client-computed ledger/status, truth, decisions and savings; estimated subscription value and guarded savings remain separate from real-cost totals.
Aggregate rows carry deterministic uids; saas crosscheck compares local and server rows and reports matching, local-only, server-only and drift states. It excludes estimated-value rows and retains provider/GPU verification gaps rather than treating missing evidence as clean (src/spendguard/store/configuration/config_store.py:999). Server implementation and byte-for-byte compatibility cannot be independently established from this client tree.
The documented external catalog flow is publication of this repository's model_catalog.json → server ingestion into a global curated catalog → anonymous /api/public/catalog and /catalog, plus authenticated /v1/models?curated=1 overlays. Source-owned as_of dates are distinct from ingestion timestamps (DATA_PLANE.md, docs/DATA_PLANE.md:149). The client pull persists overlays above the shipped floor, preserves row dates and leaves existing data intact when unavailable (src/spendguard/egress/saas.py:177, src/spendguard/store/reference_data/model_catalog.py:180). The server/publication legs are contract documentation, not locally verified server capabilities.
Event consumers and observability¶
spendguard.on_event(callback) registers callback(event: dict) and is also a decorator. The payload includes kind, provider/model, cost, decision and available in_tok/out_tok/cached_in_tok; envelope preserves it and fills v, type, unique id, UTC ts, and, when attribution resolves, conv_id/project (src/spendguard/analyze/cost_estimation/estimate.py:70, src/spendguard/analyze/cost_estimation/estimate.py:70). For example, spendguard.on_event(event_sink) connects an application's existing sink. Callbacks run inline: keep them fast; swallowed exceptions do not make a slow callback asynchronous (src/spendguard/analyze/cost_estimation/estimate.py:70).
Set emit.webhook or SPENDGUARD_WEBHOOK for JSON POSTs, and emit.otel or SPENDGUARD_OTEL for OpenTelemetry. These environment controls override config and accept explicit off values (src/spendguard/egress/emit.py:38). Webhook/OTel delivery uses a bounded daemon-thread queue and drops events when flooded; it is best-effort telemetry, not a durable audit transport (src/spendguard/egress/emit.py:69, src/spendguard/egress/emit.py:81, src/spendguard/egress/emit.py:174). OTel uses the application's global meter/tracer and exporter: counters are spendguard.cost_usd (USD) and gen_ai.client.token.usage (token), token type is input/output, and spans carry gen_ai.system, gen_ai.request.model, gen_ai.operation.name, available gen_ai.usage.*_tokens, and spendguard.decision (src/spendguard/egress/emit.py:107, src/spendguard/egress/emit.py:118).
| Module | Status and capability | Primary entry points |
|---|---|---|
| saas | PARTIAL — Aggregate client linking, owned scopes, privacy, catalog/calibration/insight/work/status sync, taxonomy, commands and advisory/enforced policy retrieval. Server implementation is outside this tree. GPU sync precedes trust alarm; alarm blocks later ledger upload but unknown/error can continue. Unresolved org ownership uploads nothing. | link (src/spendguard/egress/saas.py:284); push_rollup (src/spendguard/egress/saas.py:527); pull_policy (src/spendguard/egress/saas.py:787); run_commands (src/spendguard/egress/saas.py:819); sync (src/spendguard/egress/saas.py:895) |
| sources | BUILT — Transcript/plugin/interpreter discovery and receipt source-family classification without exposing secrets. | register_transcript_source (src/spendguard/generate/spend_capture/sources.py:64); discover_sources (src/spendguard/generate/spend_capture/sources.py:180) |
| scan | BUILT — Local source inventory, zero LLM/network. | collect (src/spendguard/generate/spend_capture/scan.py:24); render (src/spendguard/generate/spend_capture/scan.py:54) |
| schedule | BUILT — Idempotent OS scheduler installation/removal; GPU stamping precedes upload. | install_schedule (src/spendguard/platform/schedule.py:26) |
| fleet | BUILT — Loaded-SHA consumer registration, dead-PID pruning, supervised stale restarts and install refresh. Unsupervised consumers are warned rather than killed. | register_consumer (src/spendguard/platform/fleet.py:472); running_consumers (src/spendguard/platform/fleet.py:563); restart_stale (src/spendguard/platform/fleet.py:657); refresh_stale_installs (src/spendguard/platform/fleet.py:866) |
| release | BUILT — Loaded-SHA/green-pointer lifecycle and promotion checks. Promotion consumes supplied gate evidence; it does not run the suite itself. CLI deployment orchestrates validation separately. | served_sha (src/spendguard/platform/release.py:70); should_respawn (src/spendguard/platform/release.py:136); promote_release (src/spendguard/platform/release.py:163) |
| notify | BUILT — Configured Resend/SMTP notification transport. | is_configured (src/spendguard/egress/notify.py:70); send_email (src/spendguard/egress/notify.py:82) |
| emit | BUILT — Best-effort event callbacks and bounded asynchronous webhook/OTel emission. | on_event (src/spendguard/egress/emit.py:32); emit (src/spendguard/egress/emit.py:174) |
| deid | PARTIAL — Regex redaction floor plus optional Presidio, explicit degraded/off modes; not a complete PII guarantee. | redact (src/spendguard/egress/deid.py:184) |
Public surfaces and entry modules¶
| Module | Status and capability | Primary entry points |
|---|---|---|
| cli | BUILT — Lazy console dispatch including ask/delegate, lane/config/gate, ledger/reconcile/receipt, diagnostics, experiments, corpus work and operational commands. Whole-job aliases are submit-jobs/run-jobs/whole-job; queue planning is read-only. Deployment can run validation/promote/restart but was not invoked for this inventory. | main (src/spendguard/interface/cli.py:371); help_text (src/spendguard/interface/cli.py:89); ledger_read_cli (src/spendguard/interface/cli.py:95) |
| mcp_server | PARTIAL — Stdio JSON-RPC and threaded HTTP MCP, client registration and 23 tools. Some tools can spend without mandatory budget staging; vision cache keys use path strings rather than image bytes and can become stale after file replacement. | handle (src/spendguard/interface/mcp_server.py:783); serve_stdio (src/spendguard/interface/mcp_server.py:909); serve_http (src/spendguard/interface/mcp_server.py:1102); register_client (src/spendguard/interface/mcp_server.py:960) |
| crossllm | PARTIAL — Stable ask/vision facade, budgetless estimate, priced-model preflight, vendor fan/first execution and durable vision delegation. Multiple models from the same vendor can collide in vendor-keyed result maps. | ask (src/spendguard/interface/crossllm.py:276); ask_vision (src/spendguard/interface/crossllm.py:346) |
| serve | BUILT — HTTP ask/embed/metadata/health surface with body limits, refusal responses and token requirement off loopback. serve requires enforcement; constructing a server alone does not. |
make_server (src/spendguard/interface/serve.py:119); serve (src/spendguard/interface/serve.py:130) |
| compare | PARTIAL — Immediate paid comparison command with no separate local estimate stage. | main (src/spendguard/analyze/measurement/compare.py:16) |
| cascade | PARTIAL — Cheap-to-strong verification ladder. Default verification is nonempty/JSON syntax, not semantic correctness; final output can still fail verification. | cascade (src/spendguard/manage/routing/cascade.py:40); default_verify (src/spendguard/manage/routing/cascade.py:24) |
| escalation_ladder | PARTIAL — Relative escalation and confirmed zero-billable cancellation helpers; not wired as an active soft-deadline observer. | relative_escalation_gate (src/spendguard/manage/admission/escalation_ladder.py:15); cancel_on_zero_billable (src/spendguard/manage/admission/escalation_ladder.py:46) |
| init | BUILT — Lazy package API, optional adapter installers and automatic gate bootstrap; bootstrap can fail open unless required mode is set. Cost function is exported as estimate_cost to avoid shadowing estimate module. | install_litellm (src/spendguard/__init__.py:232); which_package (src/spendguard/__init__.py:277) |
| main | BUILT — python -m spendguard forwards to CLI main; no independent function definition. |
src/spendguard/main.py:1 |
| _version | BUILT — Package version constant source. | src/spendguard/platform/_version.py:1 |
| cli_arguments | BUILT — Shared fixed-format option-value parsing helper. | cli_option_value_after (src/spendguard/interface/cli_arguments.py:4) |
| ui | BUILT — Shared estimate-only banner. | estimate_only (src/spendguard/interface/ui.py:11) |
MCP tool names (all prefixed spendguard_): ledger_read, delegate, version, health, savings, route_cost, route_realized, route_horizon, plan_queue, run_jobs, advise, models, recommend, bakeoff, stability, vision, spend_overview, overage_status, top_conversations, conversation_cost, compaction_candidates, dispatch_state, config. run_jobs plans without its execution budget; delegate supports estimate; bakeoff/vision estimate absent a budget. Health can probe/remediate, recommend can invoke meta work, and stability can run paid trials.
Vision and embeddings are adapter capabilities: vision (src/spendguard/generate/execution/adapters.py:1124), embed (src/spendguard/generate/execution/embeddings.py:97), embed_batch (src/spendguard/generate/execution/embeddings.py:322), embed_compare (src/spendguard/generate/execution/embeddings.py:371). Embedding work supports chunk/checkpoint recovery and named oversize-item gaps. Native embedding Batch is OpenAI-only; Gemini's native batch leg is unwired. Embedding comparison measures vector similarity/correlation rather than retrieval quality.
The console entry declared by pyproject.toml is spendguard = spendguard.cli:main; there is no separate repository-root Python CLI/MCP implementation. The package has required dependencies, including psutil (and conditional tomli); it is not dependency-free.
Non-Python package artifacts and coverage boundary¶
All 173 Python modules under src/spendguard/ are catalogued above, including implementation helpers and entry modules. The five other non-cache package files were also inspected:
src/spendguard/model_catalog.json: curated runtime model records; packaged catalog SSOT.src/spendguard/prices.json: generated pricing projection, packaged for runtime use.src/spendguard/model_catalog.json~: backup snapshot; not runtime SSOT or packaged data.src/spendguard/README.md: historical module map; some counts/descriptions are stale, so source takes precedence.src/spendguard/py.typed: empty packaged typing marker.
Known gaps / design in flight¶
- Prospective coalescing already exists. Its missing pieces are agentic coalescing policy and per-call deadline/SLA flush, not the accumulation queue itself.
- Durable queues, admission windows and whole-job planning are separate built mechanisms. Queue result truncation and mixed-contract whole-job grouping need explicit design attention.
- Anthropic submit/collect exists, but whole-job batch execution is OpenAI-only and tracker polling is not provider-correct throughout. Optional durable storm execution is not default wiring.
- Codex pool admission hardening exists; broker failure/local fallback weakens a strict host-wide-cap guarantee.
- Gate enforcement is not universal fail-closed coverage. Raw HTTP, LiteLLM, Bedrock and Vertex capture must not be mistaken for pre-spend admission; abandoned streams and fine-tuning cost have gaps.
- Several judgment paths still clip evidence or use mechanical meaning proxies, as identified in their entries. This map records existing behavior rather than asserting compliance with repository rules.
- Bulk/checkpoint budgeting and schema/image/reasoning identity have the limitations listed above; timeout does not imply cancellation or zero bill.
- Semantic pricing-name mapping is a stub; semantic freeform insight validation and residual LLM retag execution remain absent. Soft-deadline cancellation/escalation helpers are not integrated into an active observer.
- Residency measurement and live warning are built. Exact historical context attribution, complete project-proportional receipt allocation, provider truth for every category and independently verified server behavior are not established by this tree.