context-engineering-core 0.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. context_engine/__init__.py +15 -0
  2. context_engine/__main__.py +3 -0
  3. context_engine/_sqlite.py +19 -0
  4. context_engine/_windows_security.py +125 -0
  5. context_engine/_windows_storage.py +110 -0
  6. context_engine/adapters.py +148 -0
  7. context_engine/answers.py +90 -0
  8. context_engine/budget.py +93 -0
  9. context_engine/cli.py +222 -0
  10. context_engine/config.py +193 -0
  11. context_engine/context.py +116 -0
  12. context_engine/demo.py +98 -0
  13. context_engine/errors.py +91 -0
  14. context_engine/evaluation/__init__.py +1 -0
  15. context_engine/evaluation/commands.py +90 -0
  16. context_engine/evaluation/corpus.py +247 -0
  17. context_engine/evaluation/data/freeze.json +26 -0
  18. context_engine/evaluation/data/ground_truth.json +287 -0
  19. context_engine/evaluation/data/pin_retention.json +8 -0
  20. context_engine/evaluation/data/protocol.json +122 -0
  21. context_engine/evaluation/data/scenario.json +1226 -0
  22. context_engine/evaluation/data/summary_fixture.json +7 -0
  23. context_engine/evaluation/execution.py +538 -0
  24. context_engine/evaluation/fixtures.py +32 -0
  25. context_engine/evaluation/grading.py +62 -0
  26. context_engine/evaluation/harness.py +165 -0
  27. context_engine/evaluation/heldout/__init__.py +260 -0
  28. context_engine/evaluation/heldout/__main__.py +40 -0
  29. context_engine/evaluation/heldout/data/ground_truth.json +18 -0
  30. context_engine/evaluation/heldout/data/manifest.json +9 -0
  31. context_engine/evaluation/heldout/data/protocol.json +14 -0
  32. context_engine/evaluation/heldout/data/scenarios.json +18 -0
  33. context_engine/evaluation/heldout/data/scoring_manifest.json +4 -0
  34. context_engine/evaluation/heldout/data/scoring_protocol.json +12 -0
  35. context_engine/evaluation/heldout/scoring.py +176 -0
  36. context_engine/evaluation/protocol.py +264 -0
  37. context_engine/evaluation/reporting.py +294 -0
  38. context_engine/evaluation/results.py +331 -0
  39. context_engine/evaluation/run_commands.py +146 -0
  40. context_engine/evaluation/strategies.py +261 -0
  41. context_engine/inspection.py +266 -0
  42. context_engine/layers/__init__.py +18 -0
  43. context_engine/layers/cap.py +99 -0
  44. context_engine/layers/common.py +162 -0
  45. context_engine/layers/pin.py +68 -0
  46. context_engine/layers/retrieve.py +233 -0
  47. context_engine/layers/summarize.py +202 -0
  48. context_engine/layers/window.py +29 -0
  49. context_engine/mcp_server.py +267 -0
  50. context_engine/memory/__init__.py +5 -0
  51. context_engine/memory/codec.py +91 -0
  52. context_engine/memory/demo.py +90 -0
  53. context_engine/memory/store.py +770 -0
  54. context_engine/models.py +384 -0
  55. context_engine/pipeline.py +383 -0
  56. context_engine/providers/__init__.py +1 -0
  57. context_engine/providers/client.py +301 -0
  58. context_engine/providers/commands.py +138 -0
  59. context_engine/providers/contracts.py +324 -0
  60. context_engine/providers/credentials.py +66 -0
  61. context_engine/providers/memory.py +112 -0
  62. context_engine/providers/store.py +408 -0
  63. context_engine/providers/transport.py +195 -0
  64. context_engine/providers/windows_credentials.py +23 -0
  65. context_engine/py.typed +0 -0
  66. context_engine/service/__init__.py +1 -0
  67. context_engine/service/__main__.py +68 -0
  68. context_engine/service/app.py +404 -0
  69. context_engine/service/auth.py +132 -0
  70. context_engine/service/client.py +52 -0
  71. context_engine/service/control.py +134 -0
  72. context_engine/service/demo.py +151 -0
  73. context_engine/service/integrations.py +28 -0
  74. context_engine/service/schemas.py +59 -0
  75. context_engine/service/worker.py +59 -0
  76. context_engine/service/workers.py +156 -0
  77. context_engine/summaries.py +196 -0
  78. context_engine/tokens.py +171 -0
  79. context_engine/work.py +46 -0
  80. context_engineering_core-0.12.0.dist-info/METADATA +203 -0
  81. context_engineering_core-0.12.0.dist-info/RECORD +84 -0
  82. context_engineering_core-0.12.0.dist-info/WHEEL +4 -0
  83. context_engineering_core-0.12.0.dist-info/entry_points.txt +3 -0
  84. context_engineering_core-0.12.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,146 @@
1
+ """CLI composition for synthetic or explicitly authorized benchmark execution and model swap."""
2
+
3
+ import asyncio
4
+ from pathlib import Path
5
+
6
+ from ..config import TokenizerConfig
7
+ from ..errors import BenchmarkError
8
+ from ..providers.client import ProviderClient
9
+ from ..providers.contracts import Completion, GenerationConfig, PriceCard, QuotaPolicy, Usage
10
+ from ..providers.credentials import load_api_key, private_bytes
11
+ from ..providers.store import RuntimeStore
12
+ from ..tokens import TiktokenCounter
13
+ from .corpus import load_bundle, strict_json
14
+ from .execution import (
15
+ RunJournal,
16
+ execute,
17
+ execution_profile,
18
+ execution_snapshot,
19
+ load_execution,
20
+ save_execution,
21
+ )
22
+ from .protocol import load_freeze, make_manifest
23
+ from .reporting import export_report, scorecard
24
+
25
+
26
+ class OfflineBenchmarkTransport:
27
+ namespace = "c06-synthetic-unknown-v1"
28
+
29
+ async def send(self, payload, api_key, timeout):
30
+ # Deliberately no corpus, expected answer, fact ID or grading access.
31
+ return Completion(
32
+ payload["model"], "synthetic-response", "UNKNOWN", "stop", Usage(40, 5, 0, 2)
33
+ )
34
+
35
+
36
+ def add_commands(commands):
37
+ for name in ("benchmark-run", "model-swap"):
38
+ parser = commands.add_parser(name, help="Run/resume frozen benchmark; offline by default")
39
+ parser.add_argument("--run-dir", type=Path, required=True)
40
+ parser.add_argument("--mode", choices=("offline", "live"), default="offline")
41
+ parser.add_argument("--allow-live", action="store_true")
42
+ parser.add_argument("--live-config", type=Path)
43
+ parser.add_argument("--env-file", type=Path, help="Explicit private GROQ_API_KEY file")
44
+ parser.add_argument("--max-calls", type=int, default=744)
45
+ parser.add_argument("--snapshot", type=Path, required=True)
46
+ parser.add_argument("--fact", action="append")
47
+ parser.add_argument("--budget", type=int, action="append")
48
+ parser.add_argument("--variant", action="append")
49
+ if name == "benchmark-run":
50
+ parser.add_argument("--model", action="append")
51
+ parser = commands.add_parser(
52
+ "benchmark-report", help="Regenerate validated reports without inference"
53
+ )
54
+ parser.add_argument("--snapshot", type=Path, required=True)
55
+ parser.add_argument("--output-dir", type=Path, required=True)
56
+
57
+
58
+ def run_command(args):
59
+ bundle, frozen = load_bundle(), load_freeze()
60
+ if args.command == "benchmark-report":
61
+ payload = load_execution(args.snapshot, bundle, frozen)
62
+ card = scorecard(bundle, frozen, payload)
63
+ proof = export_report(args.output_dir, card)
64
+ return {"status": card["validity"]["status"], "report": proof, "inference_calls": 0}
65
+ models = bundle.get("protocol")["models"] if args.command == "model-swap" else args.model
66
+ manifest = make_manifest(
67
+ bundle, frozen, facts=args.fact, models=models, budgets=args.budget, variants=args.variant
68
+ )
69
+ if args.snapshot.exists():
70
+ raise BenchmarkError("Snapshot output exists; choose a new immutable export path")
71
+ if type(args.max_calls) is not int or not 0 <= args.max_calls <= 744:
72
+ raise BenchmarkError("Invalid execution batch size")
73
+ if args.mode == "live":
74
+ if not args.allow_live or bundle.get("protocol")["approval"] != "owner_approved":
75
+ raise BenchmarkError(
76
+ "Live execution requires explicit authorization and owner-approved registration"
77
+ )
78
+ if not args.live_config:
79
+ raise BenchmarkError("Live execution requires a private configuration and GROQ_API_KEY")
80
+ key = load_api_key(args.env_file)
81
+ try:
82
+ config = strict_json(private_bytes(args.live_config, 65536).decode(), maximum=65536)
83
+ if set(config) != {"quota", "prices", "max_run_cost_microusd", "ledger_path"}:
84
+ raise BenchmarkError("Unexpected live configuration fields")
85
+ quota = QuotaPolicy(**config["quota"])
86
+ if quota.daily_budget_microusd <= 0 and quota.billing_mode != "free_tier":
87
+ raise BenchmarkError("A positive daily spending cap is required")
88
+ prices = {
89
+ model: PriceCard(**config["prices"][model]) for model in manifest.data["models"]
90
+ }
91
+ for card in prices.values():
92
+ quota.validate_prices(card)
93
+ maximum = config["max_run_cost_microusd"]
94
+ if type(maximum) is not int or not 0 <= maximum <= 10**12:
95
+ raise BenchmarkError("Invalid run spending cap")
96
+ if (quota.billing_mode == "free_tier") != (maximum == 0):
97
+ raise BenchmarkError("Free-tier mode requires exactly zero run spending cap")
98
+ ledger_path = Path(config["ledger_path"])
99
+ if not ledger_path.is_absolute():
100
+ raise BenchmarkError(
101
+ "Live account ledger path must be absolute and shared across runs"
102
+ )
103
+ except (OSError, KeyError, TypeError, ValueError):
104
+ raise BenchmarkError("Invalid private live configuration") from None
105
+ maximum = config["max_run_cost_microusd"]
106
+ else:
107
+ if args.env_file is not None:
108
+ raise BenchmarkError("Explicit credential files are only used in live mode")
109
+ quota = QuotaPolicy(
110
+ "synthetic-benchmark", 100000000, rpm=10000, tpm=100000000, rpd=10000, tpd=100000000
111
+ )
112
+ prices = {
113
+ m: PriceCard("synthetic-v1", 1000000, 500000, 2000000) for m in manifest.data["models"]
114
+ }
115
+ ledger_path = args.run_dir / "provider.sqlite"
116
+ key, maximum = "synthetic-not-a-credential", 100000000
117
+ store = RuntimeStore(ledger_path)
118
+ clients = {
119
+ model: ProviderClient(
120
+ store=store,
121
+ quota=quota,
122
+ prices=prices[model],
123
+ api_key=key,
124
+ generation=GenerationConfig(model=model, **manifest.data["protocol"]["generation"]),
125
+ counter=TiktokenCounter(TokenizerConfig(**manifest.data["protocol"]["tokenizer"])),
126
+ transport=OfflineBenchmarkTransport() if args.mode == "offline" else None,
127
+ )
128
+ for model in manifest.data["models"]
129
+ }
130
+ profile = execution_profile(manifest, clients, mode=args.mode, max_run_cost_microusd=maximum)
131
+ journal = RunJournal(args.run_dir / "run.sqlite", manifest, profile)
132
+ result = asyncio.run(
133
+ execute(
134
+ bundle, frozen, journal, clients, max_calls=args.max_calls, allow_live=args.allow_live
135
+ )
136
+ )
137
+ payload = execution_snapshot(journal)
138
+ save_execution(args.snapshot, payload)
139
+ return {
140
+ **result,
141
+ "execution_id": journal.execution_id,
142
+ "mode": args.mode,
143
+ "real_inference_calls": 0 if args.mode == "offline" else None,
144
+ "snapshot": str(args.snapshot),
145
+ "quality_acceptance": "NOT_EVALUATED",
146
+ }
@@ -0,0 +1,261 @@
1
+ """Offline baseline comparison on the frozen 31-fact corpus. No inference is performed.
2
+
3
+ Strategies (same frozen corpus, tokenizer and per-budget allowance):
4
+
5
+ - full_history: the raw chronological request; non-fit when over the allowance.
6
+ - recent_window: WINDOW only.
7
+ - summary_window: WINDOW + a generated extractive summary of omitted turns.
8
+ - retrieval_window: WINDOW + BM25 retrieval of omitted originals.
9
+ - combined: CAP + PIN + RETRIEVE + WINDOW + generated summary (SDK defaults, which
10
+ since 0.11.0 include `recover_capped_window=True`: RETRIEVE restores original
11
+ evidence that CAP removed from WINDOW turns).
12
+ - combined_no_recover: combined with `recover_capped_window=False`, the default
13
+ before 0.11.0 and the frozen-protocol setting.
14
+
15
+ Summary and retrieval strategies receive the same optional allocation (the frozen
16
+ retrieval + summary reserves), so they compete for equal space. Ground truth is
17
+ used only after assembly, for scoring. Answer correctness needs model inference
18
+ and is reported NOT_EVALUATED here; this measures what context reaches the model.
19
+ """
20
+
21
+ import statistics
22
+ import time
23
+
24
+ from ..config import BudgetConfig, CapConfig, RetrievalConfig, TokenizerConfig
25
+ from ..layers.summarize import PrefixSummaryPolicy
26
+ from ..models import BlockKind, ChatRequest, Message, Role
27
+ from ..pipeline import AssemblyOptions, assemble_context
28
+ from ..summaries import ExtractiveSummarizer, update_summary
29
+ from ..tokens import TiktokenCounter
30
+ from .corpus import Bundle, aliases
31
+ from .grading import present
32
+ from .harness import evidence_locations
33
+
34
+ SUMMARY_POLICY_VERSION = "extractive-v1"
35
+ STRATEGIES = {
36
+ "recent_window": AssemblyOptions(cap=False, pins=False, retrieve=False, summarize=False),
37
+ "summary_window": AssemblyOptions(cap=False, pins=False, retrieve=False, summarize=True),
38
+ "retrieval_window": AssemblyOptions(cap=False, pins=False, retrieve=True, summarize=False),
39
+ "combined": AssemblyOptions(),
40
+ "combined_no_recover": AssemblyOptions(),
41
+ }
42
+ NO_RECOVER = {"combined_no_recover"}
43
+ ORDER = ("full_history", *STRATEGIES)
44
+
45
+
46
+ def _budget(protocol: dict, allowance: int, strategy: str) -> BudgetConfig:
47
+ reserves = protocol["budget"]
48
+ optional = reserves["retrieval_reserve"] + reserves["summary_reserve"]
49
+ settings = dict(reserves)
50
+ if strategy == "summary_window":
51
+ settings.update(retrieval_reserve=0, summary_reserve=optional)
52
+ elif strategy == "retrieval_window": # Same optional space, all for evidence.
53
+ settings.update(retrieval_reserve=optional, summary_reserve=0)
54
+ return BudgetConfig(
55
+ input_cap=allowance,
56
+ completion_reservation=protocol["generation"]["max_completion_tokens"],
57
+ **settings,
58
+ )
59
+
60
+
61
+ def _percentile(values: list[float], fraction: float) -> float | None:
62
+ if not values:
63
+ return None
64
+ ordered = sorted(values)
65
+ return ordered[min(len(ordered) - 1, round(fraction * (len(ordered) - 1)))]
66
+
67
+
68
+ def _assemble(job, protocol, counter, fact, allowance, strategy):
69
+ """Assemble one strategy's request; returns (result, summary source tokens)."""
70
+ history = job.arguments["history"]
71
+ budget = _budget(protocol, allowance, strategy)
72
+ options = STRATEGIES[strategy]
73
+ arguments = dict(
74
+ system=job.arguments["system"],
75
+ at=job.arguments["at"],
76
+ token_counter=counter,
77
+ cap_config=CapConfig(**protocol["cap"]),
78
+ retrieval_config=RetrievalConfig(
79
+ **protocol["retrieval"], recover_capped_window=strategy not in NO_RECOVER
80
+ ),
81
+ options=options,
82
+ )
83
+ pins = job.arguments["pinned_facts"]
84
+ result = assemble_context(history, fact["question"], pins, budget, **arguments)
85
+ summary_tokens = 0
86
+ if options.summarize:
87
+ reserve = budget.summary_reserve
88
+ snapshot = update_summary(
89
+ history,
90
+ result.diagnostics.kept_turn_ids,
91
+ ExtractiveSummarizer(max_characters=max(1, reserve * 3)),
92
+ scope=pins.scope,
93
+ policy_version=SUMMARY_POLICY_VERSION,
94
+ created_at=job.arguments["at"],
95
+ min_new_turns=1,
96
+ )
97
+ if snapshot is not None:
98
+ covered = set(snapshot.covered_turn_ids)
99
+ summary_tokens = sum(
100
+ counter.count_text(m.content)
101
+ for t in history
102
+ if t.turn_id in covered
103
+ for m in t.messages
104
+ )
105
+ result = assemble_context(
106
+ history,
107
+ fact["question"],
108
+ pins,
109
+ budget,
110
+ summary_policy=PrefixSummaryPolicy(snapshot),
111
+ **arguments,
112
+ )
113
+ return result, summary_tokens
114
+
115
+
116
+ def _probe(bundle, protocol, counter, fact, allowance, strategy) -> dict:
117
+ job = bundle.job(fact["question"])
118
+ history = job.arguments["history"]
119
+ values = aliases(fact)
120
+ raw = counter.count_request(
121
+ ChatRequest(
122
+ (
123
+ Message("raw:system", Role.SYSTEM, job.arguments["system"]),
124
+ *(m for turn in history for m in turn.messages),
125
+ Message("raw:question", Role.USER, fact["question"]),
126
+ )
127
+ )
128
+ )
129
+ base = {"fact_id": fact["fact_id"], "zone": fact["zone"], "raw_tokens": raw.estimated_tokens}
130
+ if strategy == "full_history":
131
+ fits = raw.estimated_tokens <= allowance
132
+ return {
133
+ **base,
134
+ "fits": fits,
135
+ "input_tokens": raw.estimated_tokens if fits else None,
136
+ # Oversized raw input cannot be sent, so it retains nothing usable.
137
+ "fact_present": fits,
138
+ "evidence_at_source": fits,
139
+ "fact_in_summary": False,
140
+ "retrieved_chunks": 0,
141
+ "source_chunks": 0,
142
+ "source_retrieved": False,
143
+ "summary_source_tokens": 0,
144
+ "latency_ms": 0.0,
145
+ }
146
+ started = time.perf_counter()
147
+ result, summary_tokens = _assemble(job, protocol, counter, fact, allowance, strategy)
148
+ latency = (time.perf_counter() - started) * 1000
149
+ source = fact["source_turn_id"]
150
+ chunks = [b for b in result.blocks if b.kind == BlockKind.RETRIEVED and b.content]
151
+ sources = [s for b in chunks for s in b.sources]
152
+ summary = next((b for b in result.blocks if b.kind == BlockKind.SUMMARY), None)
153
+ return {
154
+ **base,
155
+ "fits": True,
156
+ "input_tokens": result.diagnostics.estimate.estimated_tokens,
157
+ "fact_present": any(present(m.content, values) for m in result.request.messages),
158
+ "evidence_at_source": bool(evidence_locations(result, fact, history)),
159
+ "fact_in_summary": bool(summary and present(summary.content, values)),
160
+ "retrieved_chunks": len(result.diagnostics.retrieved_chunk_ids),
161
+ "source_chunks": sum(1 for s in sources if s.turn_id == source),
162
+ "source_retrieved": source in result.diagnostics.retrieved_turn_ids,
163
+ "summary_source_tokens": summary_tokens,
164
+ "latency_ms": latency,
165
+ }
166
+
167
+
168
+ def _aggregate(probes: list[dict]) -> dict:
169
+ count = len(probes)
170
+ fit = [p for p in probes if p["fits"]]
171
+ retrieving = [p for p in fit if p["retrieved_chunks"]]
172
+ reductions = [1 - p["input_tokens"] / p["raw_tokens"] for p in fit]
173
+ latencies = [p["latency_ms"] for p in fit]
174
+ by_zone: dict[str, list[int]] = {}
175
+ for p in probes:
176
+ tally = by_zone.setdefault(p["zone"], [0, 0])
177
+ tally[0] += p["evidence_at_source"]
178
+ tally[1] += 1
179
+ return {
180
+ "probes": count,
181
+ "fit": len(fit),
182
+ "fact_present": sum(p["fact_present"] for p in probes),
183
+ "evidence_at_source": sum(p["evidence_at_source"] for p in probes),
184
+ "fact_in_summary": sum(p["fact_in_summary"] for p in probes),
185
+ "evidence_by_zone": {zone: f"{hit}/{total}" for zone, (hit, total) in by_zone.items()},
186
+ "median_input_tokens": statistics.median(p["input_tokens"] for p in fit) if fit else None,
187
+ "median_raw_tokens": statistics.median(p["raw_tokens"] for p in probes),
188
+ "median_token_reduction": round(statistics.median(reductions), 4) if fit else None,
189
+ # Chunk precision: share of retrieved chunks that come from the fact's source turn.
190
+ "retrieval_precision": (
191
+ round(
192
+ statistics.mean(p["source_chunks"] / p["retrieved_chunks"] for p in retrieving), 4
193
+ )
194
+ if retrieving
195
+ else None
196
+ ),
197
+ # Turn recall: share of probes whose source turn was retrieved at all.
198
+ "retrieval_recall": (
199
+ round(sum(p["source_retrieved"] for p in fit) / len(fit), 4) if retrieving else None
200
+ ),
201
+ "summary_source_tokens_total": sum(p["summary_source_tokens"] for p in probes),
202
+ "median_latency_ms": round(statistics.median(latencies), 3) if latencies else None,
203
+ "p95_latency_ms": round(_percentile(latencies, 0.95), 3) if latencies else None,
204
+ "answer_correct": "NOT_EVALUATED",
205
+ }
206
+
207
+
208
+ def assemble_strategy(bundle: Bundle, fact: dict, allowance: int, strategy: str):
209
+ """The exact request a strategy sends for one frozen fact (for bounded live runs).
210
+
211
+ `full_history` is not assembled; it either fits raw or is a non-fit.
212
+ """
213
+ if strategy not in STRATEGIES:
214
+ raise ValueError("Unknown or non-assembled strategy")
215
+ protocol = bundle.get("protocol")
216
+ counter = TiktokenCounter(TokenizerConfig(**protocol["tokenizer"]))
217
+ job = bundle.job(fact["question"])
218
+ result, _ = _assemble(job, protocol, counter, fact, allowance, strategy)
219
+ return result, _budget(protocol, allowance, strategy)
220
+
221
+
222
+ def compare_strategies(
223
+ bundle: Bundle, budgets: tuple[int, ...] | None = None, *, include_probes: bool = False
224
+ ) -> dict:
225
+ """Run every strategy on every frozen fact at each budget. Deterministic except latency."""
226
+ protocol = bundle.get("protocol")
227
+ budgets = tuple(protocol["budgets"]) if budgets is None else tuple(budgets)
228
+ if not budgets or any(type(b) is not int or b <= 0 for b in budgets):
229
+ raise ValueError("Budgets must be positive integers")
230
+ counter = TiktokenCounter(TokenizerConfig(**protocol["tokenizer"]))
231
+ results = []
232
+ for allowance in budgets:
233
+ for strategy in ORDER:
234
+ probes = [
235
+ _probe(bundle, protocol, counter, fact, allowance, strategy)
236
+ for fact in bundle.facts
237
+ ]
238
+ entry = {"budget": allowance, "strategy": strategy, **_aggregate(probes)}
239
+ if include_probes:
240
+ entry["probe_results"] = [
241
+ {**p, "latency_ms": round(p["latency_ms"], 3)} for p in probes
242
+ ]
243
+ results.append(entry)
244
+ return {
245
+ "status": "COMPLETE",
246
+ "mode": "offline_strategy_comparison",
247
+ "inference_calls": 0,
248
+ "corpus": {"scenario_id": protocol["scenario_id"], "facts": len(bundle.facts)},
249
+ "fixture_hashes": bundle.hashes,
250
+ "summary_policy": SUMMARY_POLICY_VERSION,
251
+ "results": results,
252
+ "limitations": [
253
+ "Synthetic single-scenario corpus authored with the engine; not real-world quality.",
254
+ "Context retention only: answer correctness and faithfulness need model inference.",
255
+ "Token counts are local estimates, not provider-billed tokens.",
256
+ "Latency is local wall time and varies by machine.",
257
+ ],
258
+ }
259
+
260
+
261
+ __all__ = ["NO_RECOVER", "ORDER", "STRATEGIES", "assemble_strategy", "compare_strategies"]
@@ -0,0 +1,266 @@
1
+ """Bounded JSON input and content-opt-in offline inspection. No provider calls."""
2
+
3
+ import json
4
+ from dataclasses import dataclass, replace
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+
8
+ from .config import (
9
+ MAX_INSPECTION_BYTES,
10
+ MAX_INSPECTION_TURNS,
11
+ BudgetConfig,
12
+ CapConfig,
13
+ RetrievalConfig,
14
+ Settings,
15
+ TokenizerConfig,
16
+ )
17
+ from .errors import InspectionError
18
+ from .layers.summarize import FrozenSummaryPolicy, SummarySnapshot
19
+ from .models import KeyedPins, Message, Pin, Scope, ToolCall, ToolDefinition, Turn, canonical_json
20
+ from .pipeline import assemble_context
21
+ from .tokens import TiktokenCounter
22
+
23
+
24
+ def _object(value, allowed, required=()):
25
+ if not isinstance(value, dict) or set(value) - set(allowed) or set(required) - set(value):
26
+ raise InspectionError("Input object contains missing or unsupported fields")
27
+ return value
28
+
29
+
30
+ def _items(value):
31
+ if not isinstance(value, list):
32
+ raise InspectionError("Expected a JSON array")
33
+ return value
34
+
35
+
36
+ def _time(value):
37
+ if not isinstance(value, str):
38
+ raise InspectionError("Expected an ISO timestamp with timezone")
39
+ try:
40
+ return datetime.fromisoformat(value)
41
+ except ValueError:
42
+ raise InspectionError("Invalid ISO timestamp") from None
43
+
44
+
45
+ def _config(cls, value, default):
46
+ _object(value, cls.__dataclass_fields__)
47
+ return replace(default, **value)
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class InspectionJob:
52
+ arguments: dict
53
+ tokenizer: TokenizerConfig
54
+ synthetic: bool = False
55
+
56
+ def run(self, *, input_cap: int | None = None):
57
+ arguments = dict(self.arguments)
58
+ if input_cap is not None:
59
+ arguments["budget"] = replace(arguments["budget"], input_cap=input_cap)
60
+ return assemble_context(**arguments, token_counter=TiktokenCounter(self.tokenizer))
61
+
62
+
63
+ def parse_job(payload: dict, settings: Settings) -> InspectionJob:
64
+ _object(
65
+ payload,
66
+ {
67
+ "schema_version",
68
+ "scope",
69
+ "system",
70
+ "question",
71
+ "at",
72
+ "history",
73
+ "pins",
74
+ "tools",
75
+ "budget",
76
+ "cap",
77
+ "retrieval",
78
+ "tokenizer",
79
+ "summary",
80
+ },
81
+ {"schema_version", "scope", "system", "question", "at", "history"},
82
+ )
83
+ if type(payload["schema_version"]) is not int or payload["schema_version"] != 1:
84
+ raise InspectionError("Unsupported inspection schema version")
85
+ scope = Scope(
86
+ **_object(payload["scope"], {"tenant_id", "session_id"}, {"tenant_id", "session_id"})
87
+ )
88
+ at = _time(payload["at"])
89
+ history = []
90
+ raw_history = _items(payload["history"])
91
+ if len(raw_history) > MAX_INSPECTION_TURNS:
92
+ raise InspectionError(
93
+ "Inspection history exceeds the turn limit", maximum=MAX_INSPECTION_TURNS
94
+ )
95
+ for item in raw_history:
96
+ item = _object(
97
+ item, {"turn_id", "revision", "timestamp", "messages"}, {"turn_id", "messages"}
98
+ )
99
+ messages = []
100
+ for message in _items(item["messages"]):
101
+ message = dict(
102
+ _object(
103
+ message,
104
+ {"message_id", "role", "content", "tool_call_id", "tool_calls"},
105
+ {"message_id", "role", "content"},
106
+ )
107
+ )
108
+ calls = []
109
+ for call in _items(message.pop("tool_calls", [])):
110
+ calls.append(
111
+ ToolCall(
112
+ **_object(
113
+ call,
114
+ {"call_id", "name", "arguments_json"},
115
+ {"call_id", "name", "arguments_json"},
116
+ )
117
+ )
118
+ )
119
+ messages.append(Message(**message, tool_calls=tuple(calls)))
120
+ history.append(
121
+ Turn(
122
+ item["turn_id"],
123
+ scope,
124
+ tuple(messages),
125
+ item.get("revision", 1),
126
+ _time(item["timestamp"]) if "timestamp" in item else at,
127
+ )
128
+ )
129
+ pins = []
130
+ for pin in _items(payload.get("pins", [])):
131
+ pin = dict(
132
+ _object(
133
+ pin,
134
+ {"key", "value", "origin", "revision", "effective_at", "expires_at"},
135
+ {"key", "value", "origin"},
136
+ )
137
+ )
138
+ effective = _time(pin.pop("effective_at")) if "effective_at" in pin else at
139
+ expires = _time(pin.pop("expires_at")) if pin.get("expires_at") is not None else None
140
+ pin.pop("expires_at", None)
141
+ pins.append(Pin(scope=scope, **pin, effective_at=effective, expires_at=expires))
142
+ tools = []
143
+ for tool in _items(payload.get("tools", [])):
144
+ tool = _object(tool, {"name", "description", "parameters"}, {"name", "parameters"})
145
+ tools.append(
146
+ ToolDefinition(
147
+ tool["name"], tool.get("description", ""), canonical_json(tool["parameters"])
148
+ )
149
+ )
150
+ policy = None
151
+ if payload.get("summary") is not None:
152
+ raw = _object(
153
+ payload["summary"],
154
+ {"content", "covered_turn_ids", "policy_version", "created_at", "expires_at"},
155
+ {"content", "covered_turn_ids", "policy_version", "created_at"},
156
+ )
157
+ ids = _items(raw["covered_turn_ids"])
158
+ if any(not isinstance(identifier, str) for identifier in ids) or len(set(ids)) != len(ids):
159
+ raise InspectionError("Summary turn IDs must be unique strings")
160
+ turns = {t.turn_id: t for t in history}
161
+ if set(ids) - turns.keys():
162
+ raise InspectionError("Summary refers to missing history")
163
+ policy = FrozenSummaryPolicy(
164
+ SummarySnapshot.from_history(
165
+ scope=scope,
166
+ content=raw["content"],
167
+ history=tuple(turns[i] for i in ids),
168
+ policy_version=raw["policy_version"],
169
+ created_at=_time(raw["created_at"]),
170
+ expires_at=_time(raw["expires_at"]) if raw.get("expires_at") is not None else None,
171
+ )
172
+ )
173
+ question = payload["question"]
174
+ if not isinstance(question, str):
175
+ raise InspectionError("Question must be a string")
176
+ return InspectionJob(
177
+ {
178
+ "history": tuple(history),
179
+ "question": question,
180
+ "pinned_facts": KeyedPins(scope, tuple(pins)),
181
+ "system": payload["system"],
182
+ "at": at,
183
+ "tools": tuple(tools),
184
+ "summary_policy": policy,
185
+ "budget": _config(BudgetConfig, payload.get("budget", {}), settings.budget),
186
+ "cap_config": _config(CapConfig, payload.get("cap", {}), settings.cap),
187
+ "retrieval_config": _config(
188
+ RetrievalConfig, payload.get("retrieval", {}), settings.retrieval
189
+ ),
190
+ },
191
+ _config(TokenizerConfig, payload.get("tokenizer", {}), settings.tokenizer),
192
+ )
193
+
194
+
195
+ def load_job(path: Path, settings: Settings) -> InspectionJob:
196
+ def unique(pairs):
197
+ result = {}
198
+ for key, value in pairs:
199
+ if key in result:
200
+ raise InspectionError("Duplicate JSON keys are not allowed")
201
+ result[key] = value
202
+ return result
203
+
204
+ def reject_constant(_):
205
+ raise InspectionError("JSON numeric constants must be finite")
206
+
207
+ try:
208
+ with path.open("rb") as handle:
209
+ raw = handle.read(MAX_INSPECTION_BYTES + 1)
210
+ if len(raw) > MAX_INSPECTION_BYTES:
211
+ raise InspectionError(
212
+ "Inspection file exceeds the byte limit", maximum=MAX_INSPECTION_BYTES
213
+ )
214
+ payload = json.loads(
215
+ raw.decode("utf-8"), object_pairs_hook=unique, parse_constant=reject_constant
216
+ )
217
+ except OSError:
218
+ raise InspectionError("Cannot read inspection input") from None
219
+ except (UnicodeError, ValueError, RecursionError):
220
+ raise InspectionError("Inspection input must be valid UTF-8 JSON") from None
221
+ try:
222
+ return parse_job(payload, settings)
223
+ except (TypeError, KeyError, OverflowError, RecursionError):
224
+ raise InspectionError("Invalid inspection input structure") from None
225
+
226
+
227
+ def demo_job(settings: Settings) -> InspectionJob:
228
+ from .demo import demo_context
229
+
230
+ class DemoPolicy:
231
+ def get_summary(self, history, scope, at):
232
+ return SummarySnapshot.from_history(
233
+ scope=scope,
234
+ content="Earlier discussion concerned an incident and routine telemetry.",
235
+ history=history,
236
+ policy_version="synthetic-demo-v1",
237
+ created_at=at,
238
+ )
239
+
240
+ context, budget, at = demo_context()
241
+ return InspectionJob(
242
+ {
243
+ "history": context.original_turns,
244
+ "question": context.question,
245
+ "pinned_facts": context.pins,
246
+ "budget": budget,
247
+ "at": at,
248
+ "system": "Use the supplied evidence to answer.",
249
+ "summary_policy": DemoPolicy(),
250
+ },
251
+ settings.tokenizer,
252
+ synthetic=True,
253
+ )
254
+
255
+
256
+ def save_inspection(path: Path, result: dict) -> None:
257
+ """Explicit export never overwrites an existing file."""
258
+ try:
259
+ path.parent.mkdir(parents=True, exist_ok=True)
260
+ with path.open("x", encoding="utf-8") as handle:
261
+ json.dump(result, handle, ensure_ascii=False, indent=2)
262
+ handle.write("\n")
263
+ except FileExistsError:
264
+ raise InspectionError("Output already exists; choose a new path") from None
265
+ except (OSError, ValueError):
266
+ raise InspectionError("Cannot write inspection output") from None