From b3753b1d6dee5c79619776c401f8e7edac723ec9 Mon Sep 17 00:00:00 2001 From: Nitsu45 Date: Fri, 16 Oct 2026 22:24:15 +0200 Subject: [PATCH] Study focused investigation working context offline --- ...tigation-working-context-study-20261016.md | 179 ++++++ .../investigation-context-study-20261016.json | 571 ++++++++++++++++++ scripts/study_investigation_context.py | 157 +++++ tests/test_investigation_context_study.py | 63 ++ 4 files changed, 970 insertions(+) create mode 100644 docs/investigation-working-context-study-20261016.md create mode 100644 docs/measurements/investigation-context-study-20261016.json create mode 100644 scripts/study_investigation_context.py create mode 100644 tests/test_investigation_context_study.py diff --git a/docs/investigation-working-context-study-20261016.md b/docs/investigation-working-context-study-20261016.md new file mode 100644 index 0000000..0193814 --- /dev/null +++ b/docs/investigation-working-context-study-20261016.md @@ -0,0 +1,179 @@ +# Schlanker Investigation-Arbeitskontext: Offline-Studie + +## Gegenstand + +Untersucht wurden alle 17 gespeicherten Investigation-Kontexte des neuesten +erhaltenen Bug-17-Pakets (g148, historischer Prompt v11). Die bestehenden +Promptbuilder wurden zum Rekonstruieren verwendet, ohne Provideraufruf. +Caseinterpretation, Hypothesengenerierung, produktive Prompts, Controller und +Paketverträge sind unverändert. Das Werkzeug ist eine Offline-Designstudie, +kein bereits ausführbares neues Promptprotokoll. + +Werkzeug: `scripts/study_investigation_context.py --output `. +Messdaten: `measurements/investigation-context-study-20261016.json`. +Stand: nach Commit 500ae27. Kostenbaseline unverändert 7,40 EUR; keine API-Nutzung. + +## Was die Investigation gegenwärtig tatsächlich bekommt + +`analysis/investigator.py::_context` stellt eine vollständige Archivdarstellung +zusammen. `analysis/investigation_prompt.py::build_prompt` erzeugt daraus eine +kompaktere Modelldarstellung über `full_history_view` und `block_view`. +Der produktive Nachfolger v12 ergänzt die kausale Bewertung, verwendet aber +weiterhin dieselbe vollständige Historienstrategie. + +Die Nachricht enthält: + +1. Systeminstruktionen: Beweismaßstab, Confidence, nächste Aktion, Toolnutzung, + Grenzen der Interpretation, Referenzkodierung und Verhalten beim Frühabschluss. +2. Statische Fallinformationen: aufbereitete Quellen, Identitäten, Interpretation, + Tool-/Budgetregeln und Schemas. Diese liegen bei geeigneter Gemini-Konfiguration + im stabilen Fallcache und müssen nicht erneut voll berechnet werden. +3. Veränderliche Arbeitsdaten: ausgewählte Hypothese, andere Hypothesen, + Fragen und Notizhistorien, Budgetkonten, alle Rechercheergebnisse und deren + Metadaten. Das vorherige Ergebnis ist bereits als Beleg referenziert. +4. Das Antwortschema. Die Antwort bewertet das vorige Ergebnis, aktualisiert + die ausgewählte Hypothese und wählt genau eine nächste Aktion. Nicht jeder + Dispatch ist ein einzelner API-Aufruf. + +**Es fehlt ein eigener, verlässlich gepflegter kurzer Erkenntnisstand.** +Alte `assessment`-Notizen sind vielfach Arbeitsprotokolle mit nächstem Suchschritt, +keine kumulative Pro-/Contra-Zusammenfassung. Eine Confidence `supported` +verwandelt solche Notizen nicht in belastbare Evidenz. + +## Zwei gemessene Varianten + +Bytewerte messen exakt serialisierte UTF-8-Daten, keine Provider-Tokens. +Der gleiche ursprüngliche Systemtext und das gleiche Schema bleiben für diesen +Datenvergleich stehen. Vor einem Live-Einsatz müssten die Instruktionen an den +neuen Vertrag angepasst werden, insbesondere Aussagen über vollständige Notizen. + +| Variante, 17 Kontexte | Nachrichteninhalt gesamt | Live-Transport | Veränderung Live | +|---|---:|---:|---:| +| Bisherige v11-Darstellung | 1.400.843 B | 645.032 B | — | +| Verwaltungsdaten/Notizhistorie verkürzt, alle Recherchepayloads behalten | 1.233.456 B | 459.777 B | **−28,72 %** | +| Zusätzlich nur jüngstes Rechercheergebnis behalten, ausdrücklich unsicher | 1.110.549 B | 313.571 B | **−51,39 %** | + +Der stabile Präfix bleibt in allen Varianten inhaltlich gleich; seine summierte +Transportdarstellung beträgt 987.326 B. Die Live-/Präfixmessung enthält andere +Transporthüllen als die Nachrichteninhaltmessung; die Spalten nicht addieren. + +### Variante A: pragmatische Verringerung ohne Entfernen von Recherchepayloads + +- Andere Hypothesen: kurze Vorschläge, Confidence und Disposition; keine + Notizen, Fragen, Laufzähler oder internen Zustandszeiger. +- Ausgewählte Hypothese: Vorschlag bleibt, alle Fragen bleiben, Historiennotizen + werden durch die letzte Arbeitsnotiz und gegebenenfalls den letzten expliziten + kausalen Review ersetzt. Offene Fragen erscheinen zusätzlich im Arbeitsbrief. + Das ist bewusst konservativ, noch nicht auf jedes doppelte Feld optimiert. +- Budget: ausgewähltes Konto; fremde Konten und globale Buchhaltung bleiben + beim Controller. Dessen Scheduling wird nicht an das Modell verlagert. +- Evidenz: alle Beleg-IDs und Inhalte bleiben vollständig enthalten, auch aus + anderen Hypothesen. Interne Record-/Contentpfade, Hashes, Format-/Paketfelder, + bereits berechnete Novelty und interne Request-/Resultspeicherverweise werden + aus der Modelldarstellung entfernt. Query-/Result-Inhalte, Abfrageumfang, + Herkunft und gemeinsame Quell-/Metadatentabellen bleiben unverändert. +- Unbekannte Felder in erhaltenen Evidenz-Records bleiben erhalten. + +Für den letzten Aufruf mit 15 Belegen sinkt der Live-Transport von **63.206 B +auf 43.766 B**. Der Vergleich prüft für jeden Beleg, dass sein Inhalt unverändert +bleibt, und dass Quell-/Result-/Metadatentabellen unverändert bleiben. + +Diese Variante ist **nicht insgesamt verlustfrei**: alte Modellnotizen und die +Detailzustände anderer Hypothesen fehlen. Eine vergessene Zwischenfolgerung ist +weiterhin möglich, solange es keinen gepflegten kumulativen Erkenntnisstand gibt. +Die Offline-Messung belegt keine gleichbleibende Analysequalität. + +### Variante B: nur frisches Ergebnis ist kein tragfähiger Arbeitskontext + +Im letzten Aufruf fehlen dann die Inhalte von E000001 bis E000014; nur E000015 +bleibt vollständig. Diese älteren Belege enthalten gerade die entscheidenden +Informationen zu Quest-ID, QuestAccept, Core-Dispatch und AI-Erzeugung. +Alle gemeinsamen Tabellen bleiben im Größenexperiment konservativ vorhanden; +das ist also keine maximal mögliche Verkleinerung. Trotzdem verliert die LLM +den expliziten Zusammenhang der ausgelassenen Belege mit Abfrage und Ergebnis. + +**Diese Variante nicht aktivieren.** Sie illustriert die Größenordnung, wiederholt +aber ohne zuverlässiges Arbeitsgedächtnis das Problem der früheren Recall-Lösung. +Die Größenmessung ist kein Grund, alte Gegenbelege aus der Untersuchung zu drängen. + +## Die vorgeschlagene Pro-/Contra-Darstellung + +Die Idee des Nutzers ist sinnvoll. Die kurze Darstellung sollte einen +**versionsgebundenen Erkenntnisstand der Hypothese** bilden. Sie ersetzt nicht +die Existenz und Prüfbarbeit der Belege. Paketpfade braucht sie nicht; +kurze interne Beleg-IDs sollte das Modell weiterhin bekommen, um Aussagen +prüfen, zuordnen und zitieren zu können. Eine menschenlesbare Ansicht darf die +IDs ausblenden. Modellschlüsse werden ausdrücklich nicht zu unabhängigen Fakten. + +So könnte H1 als lesbares Beispiel aussehen (redaktionell erstellte Illustration, +nicht automatisch aus Confidence berechnet und nicht an eine LLM geschickt): + +> **Vermutung:** Die Questannahme startet den Escort nicht, weil der passende +> Callback fehlt oder eine veraltete Quest-ID geprüft wird. +> +> **Anlass:** Der Tester beobachtet nach der Annahme keinen Bewegungsbeginn. +> Das ist mit der Vermutung vereinbar, belegt aber diesen speziellen Defekt nicht. +> +> **Bisher dafür:** Kein konkret nachgewiesener Callback-/ID-Defekt. +> +> **Bisher dagegen:** Die gespeicherten Codebefunde zeigen eine Behandlung der +> Quest-ID 26050, einen QuestAccept-Override mit StartEvent-Aufruf, einen +> AI-Dispatch im Core und die passende AI-Erzeugung. +> +> **Unsicherheit:** Statischer Sourcecode belegt noch keine tatsächliche +> Ausführung im betroffenen Deployment. Das ist eine Lücke, kein positiver +> Beleg für einen fehlenden Callback. +> +> **Nächste Entscheidung:** Das neue Ergebnis zu den Dispatch-Bedingungen +> bewerten. Stützt es keinen Defekt des behaupteten Mechanismus, die Hypothese +> abschwächen oder verwerfen, statt sie still auf eine andere Ursache umzudeuten. + +Das ist keine vorweggenommene endgültige Diagnose und keine generelle Anweisung, +bei jedem künftigen Bug denselben Codepfad zu untersuchen. Die Inhalte sind +fallspezifisch. Das Programm darf nicht bereits vorgeben, dass H1 falsch sein muss. + +Der aktuelle v12-Review enthält bereits `support`, `against`, `effect` und +`reason`. Er ist ein guter Anschluss, aber noch kein vollständiger strukturierter +kumulativer Arbeitsbrief: Die IDs haben keine einzeln zugeordnete Kurzbegründung, +und alte v11-Läufe haben diese Struktur gar nicht. Das Offline-Werkzeug markiert +fehlende Pro-/Contra-Daten deshalb als `not_recorded_in_this_legacy_run`, statt +sie zu erfinden oder alte Arbeitsnotizen als Beweis auszugeben. + +## Konkreter nächster Implementierungsentwurf + +1. In der **ohnehin erfolgenden Investigation-Antwort** einen knappen + kumulativen Arbeitsbrief aktualisieren: stützende Befunde, Gegenbefunde, + Unsicherheiten, aktuell zu prüfende Frage. Je Befund kurze Begründung, + Geltungsbereich und echte Beleg-IDs; keine weitere Zusammenfassungs-LLM. +2. Diesen Brief als versioniertes Artefakt an die Akte anhängen. Der Controller + prüft Referenzen/Struktur, bewahrt Gegenbelege und verifiziert ausdrücklich + entfernte/ersetzte Befunde strukturell. Semantische Wahrheit kann er damit + nicht automatisch beweisen. Die finale Diagnose bleibt eine Gegenprüfung. +3. Zuerst Variante A mit diesem Arbeitsbrief produktionsreif machen. Das neue + Ergebnis, sämtliche bisherigen Recherchepayloads und ihre Reichweite bleiben + zunächst verfügbar. So prüfen wir Briefqualität und Verwaltungsreduktion, + ohne zugleich Belege zu verbergen. +4. Erst anschließend gezielte Belegauswahl erwägen: frisch/unbewertet, im Brief + referenziert, für die offene Frage relevant und Gegenbelege über Hypothesen- + grenzen hinweg. Nicht nur nach Alter oder sammelnder Hypothese filtern. + Für zusätzlich benötigte Belege braucht es eine zuverlässige lokale + Wiederbereitstellung, getrennt von kostenpflichtigen Außenweltabfragen. +5. Das Modell entscheidet über den nächsten sinnvollen Suchschritt anhand von + Frage und Befundlage. Ein kurzer Aufgabenrahmen sagt, **welche Entscheidung** + zu treffen ist; er schreibt keine vermeintlich richtige Ursache oder konkrete + Suche im Voraus fest. Erlaubte Tools/Parameter und vollständiges frisches + Ergebnis bleiben Bestandteil des Vertrags. + +Caseinterpretation und Hypothesengenerierung bleiben von diesem Entwurf +ausdrücklich ausgenommen. Keine Änderung der Systemgrenzen oder der Architekturdatei. + +## Prüfung und Grenzen + +- Offline-Neudarstellung aller 17 historischen Kontexte abgeschlossen. +- Assertions prüfen unveränderte Evidenzinhalte und gemeinsame Tabellen. +- Vier gezielte Tests: keine erfundene Pro-/Contra-Bilanz, Erhalt eines vorhandenen + kausalen Reviews, Erhalt fremder Gegenbelege/Scope ohne Mutation des Originals, + explizite Kennzeichnung fehlender Belege in der unsicheren Variante. +- Kein Tokenizer-/Provider-Benchmark, keine behauptete Euro-Ersparnis und kein + bestandener Qualitätstest. Neue Instruktionen und ein Arbeitsbrief können + zusätzliche Ausgabetokens benötigen; entscheidend ist der Gesamtaufwand. diff --git a/docs/measurements/investigation-context-study-20261016.json b/docs/measurements/investigation-context-study-20261016.json new file mode 100644 index 0000000..f88ef65 --- /dev/null +++ b/docs/measurements/investigation-context-study-20261016.json @@ -0,0 +1,571 @@ +{ + "study": "offline_investigation_context_v1", + "limitations": [ + "Not a runnable prompt protocol; production unchanged.", + "No quality test or token/cost claim; bytes are UTF-8.", + "Old note history removed in both variants; no semantic summary fabricated.", + "Fresh-only deliberately lacks evidence; not recommended for deployment.", + "Historical v11 has no structured causal review; a reliable pro/con brief needs a new persisted contract.", + "Same original system/schema for fair data-size comparison; a live protocol needs consistent revised instructions." + ], + "identity": { + "case_id": "BUG-000017", + "package_id": "trial-stable-case-cache-BUG-000017-20261016", + "package_generation": 148 + }, + "contexts": 17, + "totals": { + "baseline": { + "message_bytes": 1400843, + "live_transport_bytes": 645032, + "prefix_transport_bytes": 987326 + }, + "administrative_trim": { + "message_bytes": 1233456, + "live_transport_bytes": 459777, + "prefix_transport_bytes": 987326, + "live_reduction_percent": 28.72 + }, + "fresh_only_unsafe": { + "message_bytes": 1110549, + "live_transport_bytes": 313571, + "prefix_transport_bytes": 987326, + "live_reduction_percent": 51.39 + } + }, + "largest_evidence_example": { + "context": "analysis/investigation/0001/contexts/H1-0013.json", + "evidence_count": 15, + "selected_hypothesis": "H1", + "task_brief": { + "claim": "The CreatureScript npc_professor_phizzlethorpe in src/server/scripts/EasternKingdoms/arathi_highlands.cpp may either completely lack an OnQuestAccept hook or check for an outdated quest ID instead of 26050, resulting in no escort start routine or AI state transition being invoked when the player accepts Goggle Boggle.", + "confidence": "supported", + "causal_balance": { + "status": "not_recorded_in_this_legacy_run", + "meaning": "No support/against summary can be inferred from confidence or novelty." + }, + "last_working_note": "E000014 resolves Q0008, confirming that npc_professor_phizzlethorpe correctly overrides CreatureScript::GetAI to return a new npc_professor_phizzlethorpeAI instance and registers it via AddSC_arathi_highlands. Now checking Player.cpp around line 14157 to inspect the exact conditions guarding the dispatch of QuestAccept to the creature AI.", + "note_status": "prior_model_claim_not_independent_evidence", + "open_questions": [ + { + "id": "Q0009", + "resolved": false, + "text": "Under what exact conditions does Player.cpp invoke questGiver->ToCreature()->AI()->QuestAccept(this, quest)?" + } + ] + } + }, + "samples": [ + { + "context": "analysis/investigation/0001/contexts/H1-0001.json", + "hypothesis": "H1", + "evidence_count": 0, + "baseline": { + "message_bytes": 54135, + "live_transport_bytes": 6169, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 53354, + "live_transport_bytes": 5172, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 53476, + "live_transport_bytes": 5304, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [], + "selected_notes": 0 + }, + { + "context": "analysis/investigation/0001/contexts/H2-0001.json", + "hypothesis": "H2", + "evidence_count": 1, + "baseline": { + "message_bytes": 58772, + "live_transport_bytes": 11418, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 56657, + "live_transport_bytes": 8961, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 56179, + "live_transport_bytes": 8365, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001" + ], + "selected_notes": 0 + }, + { + "context": "analysis/investigation/0001/contexts/H3-0001.json", + "hypothesis": "H3", + "evidence_count": 2, + "baseline": { + "message_bytes": 63436, + "live_transport_bytes": 16638, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 59986, + "live_transport_bytes": 12718, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 58904, + "live_transport_bytes": 11390, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002" + ], + "selected_notes": 0 + }, + { + "context": "analysis/investigation/0001/contexts/H4-0001.json", + "hypothesis": "H4", + "evidence_count": 3, + "baseline": { + "message_bytes": 65888, + "live_transport_bytes": 19359, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 60857, + "live_transport_bytes": 13712, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 59641, + "live_transport_bytes": 12218, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003" + ], + "selected_notes": 0 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0002.json", + "hypothesis": "H1", + "evidence_count": 4, + "baseline": { + "message_bytes": 70806, + "live_transport_bytes": 25148, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 65004, + "live_transport_bytes": 18640, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 61450, + "live_transport_bytes": 14247, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000002", + "E000003", + "E000004" + ], + "selected_notes": 1 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0003.json", + "hypothesis": "H1", + "evidence_count": 5, + "baseline": { + "message_bytes": 73842, + "live_transport_bytes": 28514, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 66952, + "live_transport_bytes": 20811, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 62798, + "live_transport_bytes": 15690, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004" + ], + "selected_notes": 2 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0004.json", + "hypothesis": "H1", + "evidence_count": 6, + "baseline": { + "message_bytes": 77785, + "live_transport_bytes": 32913, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 69753, + "live_transport_bytes": 23977, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 64845, + "live_transport_bytes": 17962, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005" + ], + "selected_notes": 3 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0005.json", + "hypothesis": "H1", + "evidence_count": 7, + "baseline": { + "message_bytes": 80230, + "live_transport_bytes": 35636, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 71278, + "live_transport_bytes": 25689, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 65765, + "live_transport_bytes": 18941, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006" + ], + "selected_notes": 4 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0006.json", + "hypothesis": "H1", + "evidence_count": 8, + "baseline": { + "message_bytes": 83442, + "live_transport_bytes": 39188, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 73634, + "live_transport_bytes": 28310, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 67421, + "live_transport_bytes": 20734, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007" + ], + "selected_notes": 5 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0007.json", + "hypothesis": "H1", + "evidence_count": 9, + "baseline": { + "message_bytes": 86998, + "live_transport_bytes": 43110, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 76091, + "live_transport_bytes": 31042, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 68486, + "live_transport_bytes": 21884, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008" + ], + "selected_notes": 6 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0008.json", + "hypothesis": "H1", + "evidence_count": 10, + "baseline": { + "message_bytes": 90310, + "live_transport_bytes": 46776, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 78294, + "live_transport_bytes": 33508, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 68716, + "live_transport_bytes": 22145, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008", + "E000009" + ], + "selected_notes": 7 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0009.json", + "hypothesis": "H1", + "evidence_count": 11, + "baseline": { + "message_bytes": 93077, + "live_transport_bytes": 49876, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 79984, + "live_transport_bytes": 35440, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 68813, + "live_transport_bytes": 22280, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008", + "E000009", + "E000010" + ], + "selected_notes": 8 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0010.json", + "hypothesis": "H1", + "evidence_count": 12, + "baseline": { + "message_bytes": 96183, + "live_transport_bytes": 53309, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 81989, + "live_transport_bytes": 37681, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 70138, + "live_transport_bytes": 23713, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008", + "E000009", + "E000010", + "E000011" + ], + "selected_notes": 9 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0011.json", + "hypothesis": "H1", + "evidence_count": 13, + "baseline": { + "message_bytes": 98736, + "live_transport_bytes": 56150, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 83291, + "live_transport_bytes": 39164, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 70036, + "live_transport_bytes": 23609, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008", + "E000009", + "E000010", + "E000011", + "E000012" + ], + "selected_notes": 10 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0012.json", + "hypothesis": "H1", + "evidence_count": 14, + "baseline": { + "message_bytes": 101062, + "live_transport_bytes": 58786, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 84476, + "live_transport_bytes": 40568, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 70527, + "live_transport_bytes": 24191, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008", + "E000009", + "E000010", + "E000011", + "E000012", + "E000013" + ], + "selected_notes": 11 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0012-attempt-0001.json", + "hypothesis": "H1", + "evidence_count": 14, + "baseline": { + "message_bytes": 101106, + "live_transport_bytes": 58836, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 84520, + "live_transport_bytes": 40618, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 70571, + "live_transport_bytes": 24241, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008", + "E000009", + "E000010", + "E000011", + "E000012", + "E000013" + ], + "selected_notes": 11 + }, + { + "context": "analysis/investigation/0001/contexts/H1-0013.json", + "hypothesis": "H1", + "evidence_count": 15, + "baseline": { + "message_bytes": 105035, + "live_transport_bytes": 63206, + "prefix_transport_bytes": 58078 + }, + "administrative_trim": { + "message_bytes": 87336, + "live_transport_bytes": 43766, + "prefix_transport_bytes": 58078 + }, + "fresh_only_unsafe": { + "message_bytes": 72783, + "live_transport_bytes": 26657, + "prefix_transport_bytes": 58078 + }, + "unsafe_omitted_evidence": [ + "E000001", + "E000002", + "E000003", + "E000004", + "E000005", + "E000006", + "E000007", + "E000008", + "E000009", + "E000010", + "E000011", + "E000012", + "E000013", + "E000014" + ], + "selected_notes": 13 + } + ] +} diff --git a/scripts/study_investigation_context.py b/scripts/study_investigation_context.py new file mode 100644 index 0000000..3a9acee --- /dev/null +++ b/scripts/study_investigation_context.py @@ -0,0 +1,157 @@ +"""Offline design study, never a live prompt protocol or an LLM call. + +Keep evidence intact in the conservative variant. The fresh-only variant is an +explicitly unsafe size experiment, not a deployment recommendation. +""" +import argparse +import copy +import json +from pathlib import Path + +from viano_bug_analysis.analysis.investigation_prompt import build_prompt +from viano_bug_analysis.analysis.model_context import model_json +from viano_bug_analysis.llm.gemini import GeminiProvider +from viano_bug_analysis.package import PackageReader + + +def size(value): + return len(model_json(value).encode('utf-8')) + + +def brief(hypothesis): + notes = hypothesis.get('notes', []) + reviews = [n for n in notes if n.get('causal_review')] + review = reviews[-1].get('causal_review') if reviews else None + assessments = [n for n in notes if n.get('kind') == 'assessment'] + return { + 'claim': hypothesis['proposal']['mechanism'], + 'confidence': hypothesis['confidence'], + 'causal_balance': copy.deepcopy(review) if review else { + 'status': 'not_recorded_in_this_legacy_run', + 'meaning': 'No support/against summary can be inferred from confidence or novelty.'}, + 'last_working_note': assessments[-1]['text'] if assessments else None, + 'note_status': 'prior_model_claim_not_independent_evidence', + 'open_questions': [q for q in hypothesis.get('questions', []) if not q.get('resolved')], + } + + +def administrative_trim(document): + """Drop controller bookkeeping/history, preserve every research payload. + + Unknown fields are retained. Deliberate note-history omissions are explicit; + this is not advertised as lossless. Stable case fields remain identical. + """ + result = copy.deepcopy(document) + view = result['input'] + selected = view['selected_hypothesis'] + for hid, h in view['hypotheses'].items(): + if hid != selected: + view['hypotheses'][hid] = { + 'proposal': h['proposal'], 'confidence': h['confidence'], + 'disposition': h['disposition']} + else: + h['task_brief'] = brief(h) + # The last note is in the brief; resolved question IDs/text stay. + h['omitted_note_count'] = len(h.pop('notes', [])) + view['hypotheses'][hid].pop('last_record_ref', None) + view['hypotheses'][hid].pop('invalid_responses', None) + view['hypotheses'][hid].pop('iterations', None) + for entry in view['evidence'].values(): + entry.pop('record_ref', None) + record = entry['record'] + for key in ('format', 'format_version', 'id', 'case_id', 'package_id', + 'analysis_revision', 'content_ref', 'content_sha256', + 'novelty_score', 'duplicate_of', 'action_id', 'origin'): + record.pop(key, None) + # Scope, queries, snapshot/deployment provenance and result types remain + # in the unmodified content and shared source/result tables. + budget = view['budget'] + account = budget['accounts'][selected] + view['budget'] = {'selected_account': account} + return result + + +def fresh_only(document): + """Deliberate omission experiment: exposes why a working memory is needed.""" + result = administrative_trim(document) + view = result['input'] + previous = view.get('previous_result') + keep = {previous['evidence_id']} if previous else set() + omitted = set(view['evidence']) - keep + view['evidence'] = {eid: entry for eid, entry in view['evidence'].items() if eid in keep} + # Tables remain intact: removing referenced material would distort the size + # comparison and risk losing fresh-result scope/metadata. This is conservative. + view['omitted_evidence_ids'] = sorted(omitted) + view['omission_warning'] = 'Offline experiment: old evidence payloads unavailable; not safe to deploy.' + return result, sorted(omitted) + + +def transport(req, document): + changed = copy.deepcopy(req) + changed['messages'][1]['content'] = model_json(document) + system, prefix, live = GeminiProvider._messages(changed, stable_case_context=True) + return {'message_bytes': sum(len(m['content'].encode()) for m in changed['messages']), + 'live_transport_bytes': size(live), 'prefix_transport_bytes': size([system, prefix])} + + +def measure(path): + rows = [] + example = None + with PackageReader(path) as reader: + identity = {k: reader.manifest[k] for k in ('case_id', 'package_id', 'package_generation')} + for name in reader.files: + if not (name.startswith('analysis/investigation/') and '/contexts/' in name): + continue + context = json.loads(reader.read_bytes(name)) + if context.get('prompt_version') not in {'investigation_project_v11', 'investigation_project_v12'}: + continue + req = build_prompt(context) + doc = json.loads(req['messages'][1]['content']) + trimmed = administrative_trim(doc) + unsafe, omitted = fresh_only(doc) + assert doc['input']['evidence'].keys() == trimmed['input']['evidence'].keys() + for eid, original in doc['input']['evidence'].items(): + assert original['content'] == trimmed['input']['evidence'][eid]['content'] + for key in ('shared_sources', 'shared_results', 'shared_metadata', 'sources', 'interpretation'): + assert doc['input'].get(key) == trimmed['input'].get(key) + row = {'context':name, 'hypothesis':context['selected_hypothesis'], + 'evidence_count':len(context['evidence']), + 'baseline':transport(req, doc), 'administrative_trim':transport(req, trimmed), + 'fresh_only_unsafe':transport(req, unsafe), 'unsafe_omitted_evidence':omitted, + 'selected_notes':len(context['hypotheses'][context['selected_hypothesis']]['notes'])} + rows.append(row) + if example is None or row['evidence_count'] > example['evidence_count']: + h = trimmed['input']['hypotheses'][context['selected_hypothesis']] + example = {'context':name, 'evidence_count':row['evidence_count'], + 'selected_hypothesis':context['selected_hypothesis'], + 'task_brief':h['task_brief']} + totals = {variant:{unit:sum(r[variant][unit] for r in rows) + for unit in ('message_bytes', 'live_transport_bytes', 'prefix_transport_bytes')} + for variant in ('baseline', 'administrative_trim', 'fresh_only_unsafe')} + for variant in ('administrative_trim', 'fresh_only_unsafe'): + base = totals['baseline']['live_transport_bytes'] + totals[variant]['live_reduction_percent'] = round(100*(1-totals[variant]['live_transport_bytes']/base),2) if base else None + return {'identity':identity, 'contexts':len(rows), 'totals':totals, + 'largest_evidence_example':example, 'samples':rows} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('package', type=Path) + parser.add_argument('--output', required=True, type=Path) + args = parser.parse_args() + report = {'study':'offline_investigation_context_v1', + 'limitations':['Not a runnable prompt protocol; production unchanged.', + 'No quality test or token/cost claim; bytes are UTF-8.', + 'Old note history removed in both variants; no semantic summary fabricated.', + 'Fresh-only deliberately lacks evidence; not recommended for deployment.', + 'Historical v11 has no structured causal review; a reliable pro/con brief needs a new persisted contract.', + 'Same original system/schema for fair data-size comparison; a live protocol needs consistent revised instructions.'], + **measure(args.package)} + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2)+'\n', encoding='utf8') + print(json.dumps(report['totals'])) + + +if __name__ == '__main__': + main() diff --git a/tests/test_investigation_context_study.py b/tests/test_investigation_context_study.py new file mode 100644 index 0000000..b2b9a17 --- /dev/null +++ b/tests/test_investigation_context_study.py @@ -0,0 +1,63 @@ +"""Offline design study must not alter production inputs or invent findings.""" +import copy +import importlib.util +from pathlib import Path + +spec = importlib.util.spec_from_file_location('context_study', + Path(__file__).parents[1] / 'scripts' / 'study_investigation_context.py') +study = importlib.util.module_from_spec(spec) +spec.loader.exec_module(study) + + +def document(): + h = {'proposal':{'title':'Missing hook', 'mechanism':'Hook may be absent'}, + 'confidence':'supported', 'disposition':'active', + 'notes':[{'kind':'assessment','text':'Found a hook; inspect dispatch.'}], + 'questions':[{'id':'Q1','text':'Is it dispatched?', 'resolved':False}], + 'last_record_ref':'internal/path', 'iterations':3, 'invalid_responses':0} + content = {'items':[{'query':{'kind':'database.lookup', 'where':{'entry':5}}, + 'result':{'items':[], 'coverage':'complete'}, + 'origin':{'snapshot_id':'snapshot-1'}}]} + e = {'record_ref':'internal/record', 'record':{'id':'E000001', + 'hypothesis_id':'H2','operation':'project.query','origin':{'request_ref':'internal'}}, + 'content':content} + return {'input':{'selected_hypothesis':'H1', 'hypotheses':{'H1':h,'H2':copy.deepcopy(h)}, + 'evidence':{'E000001':e, 'E000002':copy.deepcopy(e)}, + 'previous_result':{'evidence_id':'E000002'}, + 'budget':{'accounts':{'H1':{'remaining':20},'H2':{'remaining':10}}}, + 'shared_sources':{'FILE1':{'blocks':[{'lines':['counterevidence']}]}}}} + + +def test_no_invented_pro_con_from_confidence(): + result = study.brief(document()['input']['hypotheses']['H1']) + assert result['causal_balance']['status'] == 'not_recorded_in_this_legacy_run' + assert 'support' not in result['causal_balance'] + + +def test_recorded_causal_review_retained_without_reinterpretation(): + h = document()['input']['hypotheses']['H1'] + review = {'effect':'contradicts','support':[], 'against':['E000001'], + 'reason':'Required hook exists.'} + h['notes'].append({'kind':'assessment','text':'Check deployment.', 'causal_review':review}) + assert study.brief(h)['causal_balance'] == review + + +def test_trim_preserves_all_payloads_and_cross_hypothesis_counterevidence(): + original = document() + before = copy.deepcopy(original) + result = study.administrative_trim(original) + assert original == before + assert result['input']['evidence'].keys() == original['input']['evidence'].keys() + for eid, entry in original['input']['evidence'].items(): + assert result['input']['evidence'][eid]['content'] == entry['content'] + assert result['input']['shared_sources'] == original['input']['shared_sources'] + assert result['input']['hypotheses']['H1']['omitted_note_count'] == 1 + + +def test_unsafe_experiment_explicitly_lists_omissions_keeps_fresh_payload(): + original = document() + result, omitted = study.fresh_only(original) + assert omitted == ['E000001'] + assert set(result['input']['evidence']) == {'E000002'} + assert result['input']['evidence']['E000002']['content'] == original['input']['evidence']['E000002']['content'] + assert 'not safe to deploy' in result['input']['omission_warning']