contextgc 0.4.2__tar.gz → 0.4.4__tar.gz

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 (32) hide show
  1. {contextgc-0.4.2 → contextgc-0.4.4}/PKG-INFO +295 -50
  2. {contextgc-0.4.2 → contextgc-0.4.4}/README.md +294 -49
  3. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/schemas/coding.json +8 -7
  4. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/schemas/travel.json +4 -4
  5. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc.egg-info/PKG-INFO +295 -50
  6. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc.egg-info/SOURCES.txt +1 -0
  7. {contextgc-0.4.2 → contextgc-0.4.4}/pyproject.toml +1 -1
  8. {contextgc-0.4.2 → contextgc-0.4.4}/tests/test_benchmarks.py +227 -0
  9. contextgc-0.4.4/tests/test_readme_examples.py +123 -0
  10. {contextgc-0.4.2 → contextgc-0.4.4}/tests/test_value_shapes.py +75 -19
  11. {contextgc-0.4.2 → contextgc-0.4.4}/tests/test_write_path_hardening.py +41 -0
  12. {contextgc-0.4.2 → contextgc-0.4.4}/LICENSE +0 -0
  13. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/__init__.py +0 -0
  14. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/anchors.py +0 -0
  15. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/client.py +0 -0
  16. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/gc_engine.py +0 -0
  17. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/py.typed +0 -0
  18. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/sanitizer.py +0 -0
  19. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/schemas/logistics.json +0 -0
  20. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/schemas.py +0 -0
  21. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/state_dag.py +0 -0
  22. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/state_protocol.py +0 -0
  23. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/transcript.py +0 -0
  24. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/value_shapes.py +0 -0
  25. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc/vector_tier.py +0 -0
  26. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc.egg-info/dependency_links.txt +0 -0
  27. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc.egg-info/requires.txt +0 -0
  28. {contextgc-0.4.2 → contextgc-0.4.4}/contextgc.egg-info/top_level.txt +0 -0
  29. {contextgc-0.4.2 → contextgc-0.4.4}/setup.cfg +0 -0
  30. {contextgc-0.4.2 → contextgc-0.4.4}/tests/test_agent_loop.py +0 -0
  31. {contextgc-0.4.2 → contextgc-0.4.4}/tests/test_contextgc.py +0 -0
  32. {contextgc-0.4.2 → contextgc-0.4.4}/tests/test_state_protocol.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextgc
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Deterministic context compiler for AI agent transcripts. Retires superseded facts, compacts tool output, pins declared invariants. No dependencies, no model calls.
5
5
  Author: Jay Gopal Tripathy
6
6
  License: MIT
@@ -52,38 +52,65 @@ no network, no API key, no dependencies. Same input, same output, microseconds.
52
52
  pip install contextgc
53
53
  ```
54
54
 
55
- > **Not on PyPI yet.** This line was here before the project had ever been
56
- > published, which made it a lie in the most load-bearing spot in the README.
57
- > Publishing is wired up (`.github/workflows/release.yml`, tag `v0.4.0`) and
58
- > verifies the artifacts before uploading, but until a tag is pushed the
59
- > install has to come from the repository:
55
+ > **On PyPI** as `contextgc`, published via Trusted Publishing with no stored
56
+ > credentials. `pip install contextgc` gets you the library and nothing else —
57
+ > it has **zero runtime dependencies** by design, and it is a library, not a
58
+ > CLI: there is no `contextgc` command. The entry points are
59
+ > `compile_messages`, `load_schema`, `patch_openai` and `StateDAG`.
60
60
  >
61
- > ```bash
62
- > pip install git+https://github.com/j4yop/context-hackdevengers
63
- > ```
61
+ > If you found an earlier version of this README saying "Not on PyPI yet", it
62
+ > was true when written and then stopped being true, which is how a README
63
+ > starts lying in the most load-bearing spot it has. Every number in this file
64
+ > is reproducible from the repository; see below for how to get it.
64
65
 
65
66
  ```python
66
- from contextgc import compile_messages
67
+ from contextgc import compile_messages, load_schema
67
68
 
68
69
  messages = [
69
- {"role": "user", "content": "Deliver order ORD-1 to Tower B, Flat 402. Severe peanut allergy."},
70
- {"role": "assistant", "content": "Confirmed, routing to Tower B."},
71
- {"role": "user", "content": "The elevator is broken. Deliver to the Clubhouse desk."},
72
- {"role": "assistant", "content": "Updated, routing to Clubhouse."},
73
- {"role": "user", "content": "Actually reroute to Gate 2. Code 4921."},
74
- {"role": "assistant", "content": "Rerouted to Gate 2."},
70
+ {"role": "user", "content": "Deliver order ORD-1 to 402 Oak Street, Apt 5, Springfield, IL 62704. Pay with my credit card."},
71
+ {"role": "assistant", "content": "Confirmed: 402 Oak Street, Apt 5, on the credit card."},
72
+ {"role": "user", "content": "Actually reroute to 900 Pine Avenue, Suite 12, Chicago, IL 60601."},
73
+ {"role": "assistant", "content": "Rerouted to 900 Pine Avenue, Suite 12."},
75
74
  {"role": "user", "content": "Thanks."},
76
75
  ]
77
76
 
78
- compiled, telemetry = compile_messages(messages)
77
+ compiled, telemetry = compile_messages(messages, schema=load_schema("logistics"))
79
78
 
80
79
  telemetry["active_state_slots"]
81
- # {'destination_address': 'Gate 2', 'dietary_allergy': 'peanut'}
80
+ # {'delivery_address': '900 Pine Avenue, Suite 12', 'payment_method': 'credit card'}
82
81
 
83
82
  telemetry["retired_turn_indices"]
84
- # [1, 3] -- the two assistant turns that asserted a now-stale address
83
+ # [1, 2] -- the two turns that asserted a now-stale address
85
84
  ```
86
85
 
86
+ ```
87
+ system: [ACTIVE_AGENT_STATE]
88
+ - delivery_address = "900 Pine Avenue, Suite 12" (turn 3 [inferred])
89
+ - payment_method = "credit card" (turn 0 [inferred])
90
+ user: Deliver order ORD-1 to 402 Oak Street, Apt 5, Springfield, IL 62704. Pay with my credit card.
91
+ assistant: Rerouted to 900 Pine Avenue, Suite 12.
92
+ user: Thanks.
93
+ ```
94
+
95
+ Five turns in, three out. The address that was current for one turn is gone, and
96
+ the one that superseded it is in a header the model can read without re-reading
97
+ the transcript.
98
+
99
+ > **Two things this example has to get right, and the version that was here
100
+ > first got both wrong.** It did not pass a `schema`, and it cannot: with no
101
+ > schema the read path has no vocabulary and extracts **nothing** — that is
102
+ > deliberate, not a bug, since a built-in default domain would match prose in
103
+ > every other domain. And the content has to be something the shipped schema
104
+ > actually matches. The earlier version of this example used *"Tower B, Flat
105
+ > 402"*, *"Gate 2"* and *"the Clubhouse desk"*, and claimed an output of
106
+ > `{'destination_address': 'Gate 2', 'dietary_allergy': 'peanut'}`. Those two
107
+ > slot names exist nowhere in this project, and none of that prose matches the
108
+ > `logistics` patterns, which want a US street address (`Oak Street`, `Pine
109
+ > Avenue`) and a phrase like *"my credit card"*. Run it and you got `{}` and the
110
+ > transcript straight back. **The first example in a README is the whole
111
+ > product for someone evaluating it in thirty seconds, and it was returning
112
+ > nothing while looking like it worked.**
113
+
87
114
  Try it on your own transcript at **[context-hackdevengers.vercel.app](https://context-hackdevengers.vercel.app)** —
88
115
  paste a conversation on the left, get the compiled context back on the right.
89
116
 
@@ -223,11 +250,23 @@ last-write-wins state register only earns its keep where a conversation keeps
223
250
  changing its mind about one thing.
224
251
 
225
252
  ```bash
226
- pip install 'contextgc[bench]'
253
+ git clone https://github.com/j4yop/context-hackdevengers
254
+ cd context-hackdevengers
255
+ pip install -e '.[bench]'
227
256
  python -m benchmarks fetch # 85 MB parquet shard
228
257
  python -m benchmarks run --limit 40 --schema contextgc/schemas/coding.json
229
258
  ```
230
259
 
260
+ > **A clone is required, and `pip install 'contextgc[bench]'` is not enough.**
261
+ > The `bench` extra installs `pandas` and `pyarrow` and nothing else. The
262
+ > harness lives in `benchmarks/`, which is not in the wheel — the wheel ships
263
+ > `contextgc` only, deliberately, so the library stays dependency-free. So the
264
+ > command above reads
265
+ > `ModuleNotFoundError: No module named 'benchmarks'` from a pip install, and
266
+ > that is the correct behaviour, not a bug to report. Every figure in this
267
+ > README is reproducible from a clone; none of them is reproducible from the
268
+ > wheel, because the wheel does not contain the thing that measured them.
269
+
231
270
  ```
232
271
  CORPUS swe-agent-trajectories
233
272
  transcripts 40
@@ -407,8 +446,149 @@ because fixing the repeat bug in defect 6 changed what the compiler extracts.
407
446
  The 100% is not a claim that the tracker is perfect. It is 100% of what was
408
447
  labelled, and labelling is the bottleneck. Concretely, the limit is that **all 39
409
448
  labels are one slot**: precision is currently a statement about the
410
- `current_file` regex and nothing else. (`coding.failing_test` cannot be measured
411
- at all — it has 0 speech observations, so the read path never produces it.)
449
+ `current_file` regex and nothing else. A worksheet for the second slot is built
450
+ below.
451
+
452
+ #### The second coding slot, unreachable and then not
453
+
454
+ `coding.failing_test` matched **740 times** in the coding corpus and **not once in
455
+ speech**. Every hit sat inside a pytest result — `FAILED tests/test_x.py::test_y` —
456
+ which is machine output the read path is *forbidden* to infer from. So the slot
457
+ could never fire, could never be labelled, and had no evidence behind its
458
+ contract. It was carried in a shipping schema by a number that was large and
459
+ true and meant nothing.
460
+
461
+ The number hid this because a corpus-wide count cannot tell you where the
462
+ matches were. `test_the_one_slot_that_never_fires_from_speech_says_so` existed
463
+ precisely to catch it, and the fix was to the **pattern**, not to the
464
+ measurement. Requiring the noun phrase that actually introduces a file — *the
465
+ test file `test_dispatcher.py`*, *the tests in `test_run.py`*, *the failing test
466
+ `test_x.py`*:
467
+
468
+ | `coding.failing_test` | before | after |
469
+ |---|---|---|
470
+ | speech observations | **0** | **78** |
471
+ | distinct values | 0 | **49** |
472
+ | observations in tool output | 740 | 14 |
473
+
474
+ Across 174,815 agent turns there are 81 noun-phrase hits, 71 of them not beside a
475
+ shell command. Those are assertions, not commands — which is the distinction the
476
+ old pattern had collapsed. Nothing the read path actually used is lost: it was
477
+ already discarding the other 740.
478
+
479
+ The read path now extracts `test_dispatcher.py`, `test_cognitive_complexity.py`
480
+ and `tests/test_cognitive_complexity.py` from the first 40 transcripts, which
481
+ before produced **zero**.
482
+
483
+ Two honest costs. The derived contract is now `[...]test[...].py`, which accepts
484
+ any path containing `test` — so it also accepts `latest_file.py` and
485
+ `my_test_helper.py`, and those are recorded as residual false accepts rather than
486
+ fitted away with a lookup table. And the slot is named `failing_test` while the
487
+ prose it now matches says only *a test file*; that was true of the old pattern
488
+ too, and renaming a shipped key is a breaking change not made to improve a
489
+ sentence. **49 distinct values is thin** for precision, and the worksheet below
490
+ exists to thicken it. It is measurable now, which it was not.
491
+
492
+ #### A cheaper check that turns out not to be good enough
493
+
494
+ With the slot reachable, the obvious next move is a check that needs no human.
495
+ Every `failing_test` value the read path produces from speech can be tested for
496
+ existence: does that path appear anywhere in what the agent's own tools listed?
497
+
498
+ | slot | read from speech | names a file the tools listed | rate |
499
+ |---|---|---|---|
500
+ | `failing_test` | 78 | 78 | **100.0%** |
501
+ | `current_file` | 38,375 | 38,253 | 99.7% |
502
+
503
+ `failing_test` scores 100%. **So does `current_file`, and that is the whole
504
+ finding: the check does not discriminate.** Both slots are near-perfect at it
505
+ because both name files in a repository the agent is actively working in, so
506
+ the file existing is nearly guaranteed and near-worthless as evidence that the
507
+ read path chose *correctly*.
508
+
509
+ It does rule out one real failure — the read path is not inventing paths that
510
+ were never there — and it rules out nothing else. A `failing_test` naming the
511
+ wrong-but-real test file passes it. So it is recorded as a floor, not a
512
+ precision figure, and the human labels remain the only route to the number.
513
+
514
+ #### The worksheet was a trap, and it is disarmed
515
+
516
+ `failing_test` is now reachable, so the next step is a worksheet. The default
517
+ window returns **2 rows**:
518
+
519
+ ```
520
+ --limit 60 -> 2 rows --limit 2000 -> 21 rows
521
+ --limit 500 -> 7 rows --limit 6000 -> 44 rows
522
+ ```
523
+
524
+ Two rows read as *this slot cannot be labelled* when the shard holds 49 distinct
525
+ values. A person who believed that would drop the slot and report coding
526
+ precision as single-slot permanently — which is how it was single-slot in the
527
+ first place, and the reason the 100% said so little. So the worksheet now names
528
+ any slot short of its budget, and **refuses to guess the cause**: the window may
529
+ be too small, or the corpus may have no more, and only raising `--limit` tells
530
+ them apart. At `--limit 6000` the shortfall is real exhaustion — 44 of the 49
531
+ available values, the other 5 already labelled — which is the answer the tool
532
+ would not assert on its own.
533
+
534
+ `ws.json` is built at `--per-entity 60 --limit 6000`: 44 `failing_test` rows and
535
+ 60 `current_file`, all `unlabelled`, no suggestions attached. It is waiting on
536
+ a person. **Coding precision stays at 100% (n=36, all `current_file`) until
537
+ someone fills it in**, and that sentence is the honest state of the number, not
538
+ a rounding of it.
539
+
540
+ #### The write path, checked at last
541
+
542
+ Everything above scores what the **read path** inferred. The **write path** is the
543
+ model declaring facts, and until now nothing had ever checked whether a declared
544
+ fact was *true* — which is the gap that mattered, because a declared value wins
545
+ over the read path by design. An unchecked declaration silently overrides
546
+ everything the read path got right.
547
+
548
+ The same records settle it: a capture is keyed by transcript and turn, the corpus
549
+ has the booking record for that transcript, and the turn indices line up.
550
+
551
+ ```
552
+ python -m benchmarks.ground_truth travel --declarations captures/airline-14b.json
553
+ ```
554
+
555
+ | 14B airline capture | count |
556
+ |---|---|
557
+ | declared values | 109 |
558
+ | corroborated by the record | **70** |
559
+ | **contradicted by the record** | **12** |
560
+ | unverifiable (no record saw the outcome) | 2 |
561
+ | unchecked (slot has no record field) | 25 |
562
+
563
+ **So the write path is wrong 12 times in 82 decidable declarations — 85.4%
564
+ corroborated.** And the value gate rejects **11 of those 12**. Those it catches
565
+ are the ones the earlier capture already showed: `passenger_id = "None"`,
566
+ `flight_number = "H0MVIE"` (a reservation id declared as a flight),
567
+ `flight_date = "2024-05-07T13:04:42"` (a timestamp, not a date), `passenger_id =
568
+ "Yusuf Jackson"` (a name where an id belongs).
569
+
570
+ **The twelfth is the one worth reading twice.** `cabin_class = "basic economy"`
571
+ where the record says `economy`. It satisfies its contract, passes the gate, and
572
+ is still wrong — the exact failure this project has said all along a contract
573
+ cannot catch, now with a row in a table rather than an assurance. A contract
574
+ checks shape, never truth.
575
+
576
+ Two of the numbers above are only trustworthy because of bugs found while
577
+ producing them:
578
+
579
+ - The record was looked up by **corpus** name (`airline`) while the marker is
580
+ keyed by **schema** (`travel`), so every airline record resolved to nothing and
581
+ 109 declarations scored 0 corroborated. That reads like a finding about the
582
+ write path and was a lookup that found nothing.
583
+ - `2024-05-20` and `May 20, 2024` are the same flight date. Scoring them
584
+ differently called a *true* declaration a contradiction three times in one
585
+ conversation.
586
+
587
+ **What this does not establish.** 25 declarations sit in slots with no record
588
+ field at all — `total_baggages`, `cancellation_possible` — and nothing here can
589
+ settle them either way. And n=82 across 6 transcripts on one model is a small
590
+ base. The write path is *measured* now, which it was not, and it is still not
591
+ *proven*: one well-formed false value in twelve is the number to remember.
412
592
 
413
593
  #### The same check on the third shipped schema, and what it cannot settle
414
594
 
@@ -423,34 +603,46 @@ whole line.
423
603
 
424
604
  | | count | share |
425
605
  |---|---|---|
426
- | corroborated by the record | 509 | 86.0% |
427
- | contradicted by the record | 70 | 11.8% |
428
- | unverifiable | 13 | 2.2% |
606
+ | corroborated by the record | 560 | **97.4%** |
607
+ | **contradicted by the record** | **15** | **2.6%** |
608
+ | unverifiable | 17 | 3.0% |
429
609
 
430
610
  Three things that number needs beside it.
431
611
 
612
+ **A record has to be scored at the right point in the conversation.** This is
613
+ where most of the error was, and it was in the *measurement*, not the read path.
614
+ `payment_history` says what was already charged and `address` says where the order
615
+ is now, so a `delivery_address` slot — whose whole purpose is a *requested*
616
+ change — looked like it was being judged against a record that was behind by
617
+ definition. But the record is not behind. The agent applies the change, and the
618
+ **next** tool result shows the new state:
619
+
620
+ apigen-1751 turn 13 "760 Elm Avenue" record before: 592 Elm after: 760 Elm
621
+ apigen-1817 turn 11 "123 Oak Street" record before: 463 Main after: 123 Oak
622
+ apigen-1865 turn 12 "828 River Road" record before: 388 Spruce after: 828 River
623
+
624
+ So a value is checked against the state as of **just before** the turn, and against
625
+ every state recorded **at or after** it. That took retail contradictions from **70
626
+ to 19** — and all three cases above were in the 70, and all three had been read by
627
+ hand before the data was blamed on the read path.
628
+
629
+ **And a record that never saw the outcome is silence, not disagreement.** If nothing
630
+ is recorded at or after the turn, the order had no chance to reflect the change, so
631
+ a value matching nothing is *unverifiable*. A customer who says "charge the
632
+ difference to my credit card" and then ends the conversation leaves no trace, and
633
+ calling the read path wrong for agreeing with them measures nothing. That split
634
+ took the 19 down to 15 — and it applies to the airline corpus too, where it moved
635
+ contradictions from 5 to **2**.
636
+
432
637
  **A partial address is not a contradiction.** "464 Oak Street, Suite 664" is
433
- correct, and the record also holds "San Diego, CA 92135". 11 of the 35
434
- corroborated addresses stopped at the street line; they are counted as
435
- corroborated and the incompleteness is reported beside the rate. Demanding the
436
- full line first turned one of two address contradictions at 200 transcripts into
437
- exactly that, and reading the turn showed the address was right.
438
-
439
- **The retail record cannot settle a slot about a pending change.** `payment_history`
440
- says what was already charged; `address` says where the order is now. A
441
- `delivery_address` slot exists to capture a *requested* change, so the moment a
442
- conversation is about changing something, the record is behind by definition.
443
- Reading the contradicted turns, they are that shape — "I would like to change the
444
- shipping address to 123 Oak Street", "you can use my PayPal account for any price
445
- differences along the way" — and the read path is tracking what the customer just
446
- asked for. They stay counted as contradicted, because excluding them would flatter
447
- the number, but they are the record answering a different question rather than the
448
- tracker misreading one.
449
-
450
- **No split of those 70 is published.** An attempt to classify them by regex was
451
- wrong on turns that had already been read by hand, and a precise-looking number
452
- from an unreliable classifier is worse than none. The tool prints a hand-read
453
- sample and says so.
638
+ correct, and the record also holds "San Diego, CA 92135". 25 of the 51
639
+ corroborated addresses stopped at the street line; they count as corroborated and
640
+ the incompleteness is reported beside the rate.
641
+
642
+ The two travel contradictions that remain are the irreducible ones: a cabin class
643
+ taken off a baggage-allowance table, and one turn where the agent says Economy
644
+ while the record says `basic_economy`. The second is not a pattern bug at all — the
645
+ read path faithfully recorded what the agent said.
454
646
 
455
647
  #### A ground truth that is not a person: the booking database
456
648
 
@@ -472,9 +664,9 @@ of three buckets, and reports them apart:
472
664
 
473
665
  | | count | share |
474
666
  |---|---|---|
475
- | corroborated by the record | 557 | 90.6% |
476
- | **contradicted by the record** | **5** | **0.8%** |
477
- | unverifiable (no record for that slot) | 53 | 8.6% |
667
+ | corroborated by the record | 557 | **99.6%** |
668
+ | **contradicted by the record** | **2** | **0.4%** |
669
+ | unverifiable (no record for that slot) | 56 | 9.1% |
478
670
 
479
671
  The rate is over corroborated + contradicted. The unverifiable column is **not** a
480
672
  pass — it is extractions this check says nothing about, and folding it in would
@@ -646,6 +838,51 @@ to infer from tool output is a large part of why — it holds no value on the
646
838
  consecutive agent actions, which is a definition chosen here and not given by
647
839
  the data.
648
840
 
841
+ #### The one staleness test that needs no edit signal
842
+
843
+ The reason above is that a rate needs a ground truth for *when the file changed*.
844
+ But there is a weaker question with a free answer, and it is the one that
845
+ actually matters: **does the read path's value match the agent's most recent file
846
+ statement?** If it does, the tracker is not stale, whatever the agent was doing
847
+ in between. The vendored coding transcript answers it outright — 18 distinct
848
+ file statements, in order:
849
+
850
+ ```
851
+ memset.py -> reproduce.py -> memset.py -> lexicon/lexicon/cli.py
852
+ ```
853
+
854
+ The read path reproduces that sequence exactly, including the round trip: the
855
+ agent creates `reproduce.py`, works in it, then goes back to `cli.py`, and the
856
+ read path follows it out of `reproduce.py` and back. Its final value equals the
857
+ agent's final statement, so **the read path is not stale, and it is a faithful
858
+ last-statement tracker.** 81% of its matches come from the agent's speech and
859
+ 19% from tool output; it takes the final one either way.
860
+
861
+ Two things this does *not* license. It does not produce a staleness **rate**,
862
+ because a rate needs the edit signal the corpus lacks. And it does not validate
863
+ the confounded 44.9%: one trajectory agreeing with itself is not a measurement.
864
+
865
+ The turn-by-turn version **is** available in the 14B capture, which records
866
+ `declared` on every turn — and it is still not measurable, for a reason worth
867
+ writing down. The capture has 6 transcripts; the vendored agent transcript is
868
+ **one** file. Aligning each transcript's model declarations to a matching agent
869
+ prefix requires slicing that one file six ways, which invents six trajectories
870
+ out of one. I did it, got 8 apparent disagreements, and discarded all 8: they
871
+ were artifacts of the slicing, and reporting them would have been the exact
872
+ failure this section is about. **A measurement that needs data the corpus does
873
+ not have is not a weak measurement, it is a fabricated one.**
874
+
875
+ One oddity to leave alone on purpose. Half the values are doubled
876
+ (`lexicon/lexicon/cli.py`, where the first `lexicon` is the conda prefix and the
877
+ second is the repo). That is the **agent's own text**, faithfully captured, and
878
+ normalising it would be fitting the tool to a transcript's confusion. Recorded
879
+ here so it is not later "fixed".
880
+
881
+ **Where this leaves the dimension.** Not unmeasured any more — measured, once,
882
+ and negative: the tracker does not go stale on the trajectory available. Still
883
+ no rate, and still n=1 trajectory. That is a better answer than the one it
884
+ replaces, and it is still not a headline.
885
+
649
886
  **The two "confirmed errors" this project carried were mislabelled.** Both were
650
887
  recorded as `current_file` extractions judged incorrect because the turn's earlier
651
888
  prose discussed a different file. Re-reading the turns against the doctrine the
@@ -1082,10 +1319,18 @@ measurement for a guess:
1082
1319
  ```json
1083
1320
  "values": {
1084
1321
  "current_file": "(?:[\\w][\\w./-]*\\.(?:py|js|ts|tsx|jsx|go|rs|java|rb|cpp|hpp))",
1085
- "failing_test": "(?:(?<![A-Za-z0-9])test_[\\w./\\[\\]-]+)"
1322
+ "failing_test": "(?:[\\w./-]*test[\\w./-]*\\.py)"
1086
1323
  }
1087
1324
  ```
1088
1325
 
1326
+ The second line is worth pausing on, because it is a contract that accepts
1327
+ `latest_file.py` — `la`+`test` — and `my_test_helper.py`. The `test_` anchoring
1328
+ this slot used to have is gone, because the pattern that reaches it from speech
1329
+ no longer requires a `test_` prefix. Both junk cases are recorded as residual
1330
+ false accepts. The trade is deliberate: a contract that rejects 2 of 49 real
1331
+ values and is unreachable buys less than one that accepts everything the read
1332
+ path can reach and admits its 2 false accepts in the open.
1333
+
1089
1334
  Deriving them mattered more than the hand-written versions looked. The old
1090
1335
  `logistics.delivery_address` was `[\w\s.,'/-]+` and `payment_method` was
1091
1336
  `[\w ]+`, so on a 48-value set of wrong-shaped declarations — `None`, error