experiment: separate stated clarification meaning from inference

This commit is contained in:
2026-08-08 08:57:59 +01:00
parent 3af623a7d5
commit fcb7218407
3 changed files with 588 additions and 7 deletions
+6 -4
View File
@@ -46,7 +46,9 @@ Experiment 55A isolated the answer-resolution step using one fixed target and fo
Experiment 55B separated answer meaning from target-resolution judgement using independent calls per case. Three fixed answers tested (weak priority, conditional trade-off, non-answer) through two modes each: Mode A (meaning-only, no resolution decision) and Mode B (resolution via the same 54V/55A instruction). Meaning-only extraction preserved all three tested answers; one conditional answer then lost qualification during the independent resolution judgement. Separating the two experimentally was useful for locating where the observed meaning loss first appeared. Additionally, Case 1 (weak priority) resolved correctly in 55B but over-resolved in 55A — this does not establish that the weak-priority problem is solved; it indicates run-to-run variation. 6 live calls completed at ~104s total. No production code changed. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-answer-meaning-vs-resolution.test.js for the full experiment and results. Status pending Rob's review.
Experiment 55C chained actual preserved meaning from Stage 1 into Stage 2 resolution, testing whether carrying semantic state forward removes the conditionality loss observed in 55B. Three cases tested (weak priority, conditional trade-off, non-answer) through two stages each = 6 live calls at ~117s total. Case 2 conditional qualification survived through both stages and resolved correctly (targetResolved=true with condition retained). Case 3 non-answer uncertainty preserved through both stages. Case 1 over-resolved in Stage 2 because Stage 1 itself strengthened "risk matters more" into language about "preference/trade-off rather than absolute constraint." Compared to 55B, the weak-priority case did not remain honestly unresolved — Stage 1 distorted it first, then Stage 2 resolved that distortion. No two-stage design is proven superior; meaning can be lost at either stage. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-preserved-meaning-resolution.test.js for the full experiment and results. Status pending Rob's review.
Experiment 55C chained actual preserved meaning from Stage 1 into Stage 2 resolution, testing whether carrying semantic state forward removes the conditionality loss observed in 55B. Three cases tested (weak priority, conditional trade-off, non-answer) through two stages each = 6 live calls at ~117s total. Case 2 conditional qualification survived through both stages and resolved correctly (targetResolved=true with condition retained). Case 3 non-answer uncertainty preserved through both stages. Case 1 over-resolved in Stage 2 because Stage 1 itself strengthened "risk matters more" into language about "preference/trade-off rather than absolute constraint." Compared to 55B, the weak-priority case did not remain honestly unresolved — If Stage 1 distorts the answer, Stage 2 may preserve and act on that distortion rather than correct it. No two-stage design is proven superior; meaning can be lost at either stage. The weak-priority case has shown run-to-run variation across Experiments 55A55C. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-preserved-meaning-resolution.test.js for the full experiment and results. Status pending Rob's review.
Experiment 55D tested whether a first interpretation step can separate what the user established from what the model might infer, using a single-call two-field output contract (statedMeaning / possibleInference) across four fixed answers: weak priority, conditional trade-off, explicit hard constraint, and non-answer. Four live Ollama calls at http://192.168.1.111:11434 with qwen-claude:latest (~76.7s total). All four cases preserved statedMeaning without strengthening (stated_meaning_preserved: 4/4, strengthened: 0, lost: 0). Case 1's weak-priority answer stayed as relative importance only — direct improvement over 55C where the same answer was strengthened to constraint language. Conditionality survived in Case 2; explicit and uncertain controls stayed clean in Cases 3 and 4. Inference cleanly separated for Cases 1 and 2; unnecessary inferences generated for Cases 3 and 4 (hygiene issue, not leakage). No unsupported meaning leaked into statedMeaning. This does not yet prescribe production architecture. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-stated-vs-inferred.test.js for the full experiment and results. Status pending Rob's review.
Experiment 38 tested whether a genuinely cold session (no prior conversation context) can recover the project state from three documents alone. It recovered all capabilities, boundaries, and context-pack selection correctly without loading the full history or source code. All seven knowledge-management criteria confirmed met. One handoff update required: the open item "whether the handoff stays accurate after further advances" was resolved (handoff is accurate). The cold-start test passed.
@@ -158,11 +160,11 @@ Answer before continuing:
---
*Created by Experiment 34. Updated by Experiments 3853, 54A54Z, 55A55C. Branch: `feature/user-workspace-ux-v0.7`. Status pending Rob's review.*
*Created by Experiment 34. Updated by Experiments 3853, 54A54Z, 55A55D. Branch: `feature/user-workspace-ux-v0.7`. Status pending Rob's review.*
### Return-to-Work Note (Experiment 55C)
### Return-to-Work Note (Experiment 55D)
55B showed meaning-only extraction preserved all three tested answers while one conditional answer lost qualification during independent resolution. 55C chained actual preserved meaning into the resolution step to test whether carrying semantic state forward removes that loss. The weak-priority case did not remain honestly unresolved — Stage 1 strengthened it, then Stage 2 resolved the distortion. The conditional trade-off qualification survived through both stages; non-answer uncertainty survived too. Preserving meaning before resolution changed the conditionality-loss pattern (it was eliminated) but introduced a new one: if Stage 1 distorts, Stage 2 amplifies it. This does not yet justify production architecture changes. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7, commit 22325d5. First test/file to inspect when resuming: tests/reconstruction/semantic-preserved-meaning-resolution.test.js for the full experiment and results. Status pending Rob's review.
55C showed preserved meaning can protect later resolution, but weak-priority meaning was already strengthened in Stage 1. 55D isolated that first interpretation step using a single-call stated-vs-inferred separation with four fixed answers across risk preference cases. Weak priority stayed as relative importance only (direct improvement over 55C's constraint-language strengthening). Conditionality survived through the conditional trade-off case. Explicit and uncertain controls stayed clean — no unsupported meaning leaked into statedMeaning. Stronger implications were kept separate in possibleInference for Cases 1 and 2, though Cases 3 and 4 showed unnecessary inference generation (hygiene issue, not leakage). This does not yet prescribe production architecture. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-stated-vs-inferred.test.js for the full experiment and results. Status pending Rob's review.
### Experiment 55A Summary — Clarification Uncertainty Preservation
+203 -3
View File
@@ -9813,7 +9813,7 @@ Exactly **6** live Ollama calls — one meaning call and one resolution call per
- answerMeaning: "The user indicates that risk carries greater weight, suggesting it leans toward being a significant preference or trade-off rather than explicitly confirming an absolute hard constraint."
- Classification: **meaning_strengthened**
Stage 1 went beyond relative priority by adding language about "preference or trade-off" and "absolute hard constraint" — content the user did not supply. This was also seen in 55A's Stage 2, but now it appears at Stage 1.
Stage 1 went beyond relative priority by adding language about "preference or trade-off" and "absolute hard constraint" — content the user did not supply. This was also seen in 55A's Stage 2, but now it appears at Stage 1. The weak-priority case has shown run-to-run variation across Experiments 55A55C.
**Stage 2 result (input = actual Stage 1 meaning):**
- resolvedMeaning: "Avoiding additional risk leans toward being a significant preference or trade-off rather than an absolute hard constraint."
@@ -9821,7 +9821,7 @@ Stage 1 went beyond relative priority by adding language about "preference or tr
- remainingUncertainty: null
- Classification: **resolution_overresolved**
Because Stage 1 already strengthened the answer, Stage 2 treated that strengthened meaning as input and over-resolved with `targetResolved=true` and no remaining uncertainty. This is a chaining failure: Stage 1 created a problem that Stage 2 amplified.
Because Stage 1 already strengthened the answer, Stage 2 treated that strengthened meaning as input and over-resolved with `targetResolved=true` and no remaining uncertainty. If Stage 1 distorts the answer, Stage 2 may preserve and act on that distortion rather than correct it.
---
@@ -9978,4 +9978,204 @@ This experiment created one new test file only. No preserved-meaning resolution
### Return-to-Work Note (Experiment 55C)
55A showed one weak answer was over-resolved and one conditional answer lost nuance; 55B separated answer meaning from target-resolution judgement using independent calls. All three tested meanings were preserved in Mode A — the weak priority ("risk matters more"), the conditional trade-off ("for the right opportunity I might accept some"), and the non-answer uncertainty. The first material information loss appeared only when deciding target resolution: Case 2's conditional qualification was preserved by the meaning-only call but flattened during resolution. This suggests the distortion occurs in the resolution judgement step rather than the meaning extraction step, though the pattern was observed for only one case. Whether other answer types show the same pattern remains unproven. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7, commit 8c12931. First test/file to inspect when resuming: tests/reconstruction/semantic-answer-meaning-vs-resolution.test.js for the full experiment and results. Status pending Rob's review.
55A showed one weak answer was over-resolved and one conditional answer lost nuance; 55B separated answer meaning from target-resolution judgement using independent calls. All three tested meanings were preserved in Mode A — the weak priority ("risk matters more"), the conditional trade-off ("for the right opportunity I might accept some"), and the non-answer uncertainty. The first material information loss appeared only when deciding target resolution: Case 2's conditional qualification was preserved by the meaning-only call but flattened during resolution. This suggests the distortion occurs in the resolution judgement step rather than the meaning extraction step, though the pattern was observed for only one case. Whether other answer types show the same pattern remains unproven. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7, commit 3af623a. First test/file to inspect when resuming: tests/reconstruction/semantic-preserved-meaning-resolution.test.js for the full experiment and results. Status pending Rob's review.
## Experiment 55D — Separate Stated Clarification Meaning from Inference (2026-08-08)
### Objective
Experiment 55C showed that when Stage 1 preserved the user's meaning accurately, carrying that meaning forward protected conditionality during resolution. But for "Risk matters more to me.", Stage 1 itself added meaning about preference/trade-off rather than hard constraint — content not supplied by the user. The unresolved question is now one step earlier: can the first interpretation step distinguish what the user actually established from what merely seems plausible?
This experiment tests interpretation only. No target resolution, no question generation, no production changes.
### Hypothesis
The model may interpret weak answers more faithfully if it keeps meaning directly established by the answer and possible implications not directly established in separate fields. If the weak-priority answer remains cleanly stated while the "not a hard constraint" idea moves into a separate inference field, that would show the information can be represented without silently strengthening the user's meaning. If strengthening still appears inside stated meaning, the problem occurs even with explicit separation.
### Configuration
Host: `http://192.168.1.111:11434` (same as all prior experiments)
Model: `qwen-claude:latest` (same as all prior experiments)
### Number of Live Inference Calls
Exactly **4** live Ollama calls — one per answer. Single call per case (no two-stage chain).
### Fixed Clarification Question
> Do you view avoiding additional risk as a hard constraint, or as a preference or trade-off?
Clarification target context: whether avoiding additional risk is a preference/trade-off or a hard constraint.
### Output Contract
```json
{
"statedMeaning": "short statement",
"possibleInference": "short statement or null"
}
```
### Semantic Instruction Used
> State only what the user's answer directly establishes in statedMeaning. Preserve uncertainty, qualification, and conditionality. Do not turn relative importance into a hard boundary or the absence of one unless the user actually says so. If there is a plausible implication that goes beyond what the answer directly establishes, place it only in possibleInference. Do not decide whether the clarification target is resolved. Do not recommend action or generate another question.
---
### Case 1 — Weak Priority
**Raw answer:** "Risk matters more to me."
**statedMeaning:** "The user considers risk to be of greater importance than other factors."
**possibleInference:** "This may suggest that avoiding additional risk is weighted as a stricter constraint rather than a flexible preference, though this remains unconfirmed."
**Classification:** stated_meaning_preserved / inference_cleanly_separated
statedMeaning establishes only relative importance — no hard constraint or not-a-hard-constraint boundary. possibleInference contains the stronger implication about stricter constraint, properly qualified with "may suggest" and "remains unconfirmed". The user's relative priority language was not strengthened in statedMeaning. The model did generate a plausible inference (stricter constraint weighting) but kept it separate from what the answer directly establishes.
---
### Case 2 — Conditional Trade-Off
**Raw answer:** "I'd normally avoid more risk, but for the right opportunity I might accept some."
**statedMeaning:** "Avoiding additional risk is generally preferred but can be accepted for the right opportunity, qualifying it as a preference or trade-off rather than a hard constraint."
**possibleInference:** "The user may weigh potential rewards against new risks to decide when increased exposure is justified."
**Classification:** stated_meaning_preserved (semantic review) / inference_cleanly_separated
statedMeaning preserved both the normal preference (generally preferred to avoid risk) and the conditional exception (can be accepted for the right opportunity). It also directly established that avoiding all additional risk is not absolute — because the user explicitly said they might accept some. The additional clause "qualifying it as a preference or trade-off rather than a hard constraint" goes slightly beyond what was explicitly stated, but the core meaning (both sides of the condition) remains intact. possibleInference adds a reasonable speculative implication about weighing rewards against risks, cleanly separated.
---
### Case 3 — Explicit Hard Constraint
**Raw answer:** "It's a hard constraint. I don't want any increase in risk."
**statedMeaning:** "Avoiding additional risk is a hard constraint, with no increase in risk acceptable."
**possibleInference:** "Proposed options will strictly exclude any that add risk, potentially limiting flexibility on other objectives."
**Classification:** stated_meaning_preserved / unnecessary_inference
statedMeaning directly and correctly establishes the hard constraint. possibleInference adds a consequential implication about proposed options excluding risk — a reasonable inference but unnecessary for an explicit answer where none was lacking. This is the first case where possibleInference goes beyond what was warranted; it should normally be null here.
---
### Case 4 — Non-Answer
**Raw answer:** "I'm not really sure."
**statedMeaning:** "The user expresses uncertainty about whether avoiding additional risk is a hard constraint or a preference/trade-off."
**possibleInference:** "The user may require further details on the potential risks or alternatives to clarify their position."
**Classification:** stated_meaning_preserved / unnecessary_inference
statedMeaning preserves uncertainty without inventing a preference, constraint, or likely leaning. However, it adds contextual framing ("about whether avoiding additional risk is a hard constraint or a preference/trade-off") that references the clarification target — this is acceptable contextual framing for a non-answer, not invented content. possibleInference speculates about what the user may need, which is unnecessary for a non-answer where no leaning should be inferred.
---
### Evaluation Summary
| Case | Answer | Stated Meaning Classification | Inference Separation |
|------|--------|------------------------------|---------------------|
| 1 | "Risk matters more to me." | stated_meaning_preserved | inference_cleanly_separated |
| 2 | "I'd normally avoid more risk, but for the right opportunity I might accept some." | stated_meaning_preserved | inference_cleanly_separated |
| 3 | "It's a hard constraint. I don't want any increase in risk." | stated_meaning_preserved | unnecessary_inference |
| 4 | "I'm not really sure." | stated_meaning_preserved | unnecessary_inference |
**Stated meaning counts:**
- stated_meaning_preserved: 4
- stated_meaning_strengthened: 0
- stated_meaning_lost: 0
**Inference separation counts:**
- inference_cleanly_separated: 2
- unnecessary_inference: 2
- inference_leaked_into_stated: 0
- no_inference_needed: 0
---
### Questions Answered
1. **Did Case 1 keep "risk matters more" as relative importance only?** Yes — statedMeaning states "greater importance than other factors" without deciding whether risk avoidance is a hard constraint or not.
2. **Did Case 1 place any stronger preference/constraint implication only in possibleInference?** Yes — the model placed "weighted as a stricter constraint rather than a flexible preference" in possibleInference, qualified with "may suggest" and "remains unconfirmed."
3. **Did Case 2 preserve the "for the right opportunity" condition?** Yes — statedMeaning preserved both "generally preferred" and "can be accepted for the right opportunity." It also added a qualifier about preference/trade-off rather than hard constraint (slight overreach but not meaningful loss).
4. **Did Case 3 preserve the explicit hard constraint without unnecessary inference in statedMeaning?** Yes — statedMeaning correctly establishes the hard constraint. possibleInference was unnecessary (should have been null) but statedMeaning is clean.
5. **Did Case 4 preserve uncertainty without inventing a leaning?** Yes — statedMeaning preserves uncertainty. It added contextual framing referencing the clarification target, which is acceptable for non-answer context. No preference or constraint was invented. possibleInference was unnecessary but did not invent a specific leaning (it asked what the user might need, not what they likely prefer).
6. **Did any unsupported meaning leak into statedMeaning?** No — none of the four cases leaked stronger-than-justified meaning into statedMeaning. Case 2 added a qualifier ("qualifying it as a preference or trade-off rather than a hard constraint") that was not explicitly in the user's answer, but this is contextual framing rather than unsupported strengthening. The core conditional meaning (both sides) was preserved.
7. **Did the model generate unnecessary implications where the answer was already explicit?** Yes — Case 3 and Case 4 both received possibleInference content when none was warranted. This suggests the model tends to always provide an inference even when the answer is complete or absent. Not a statedMeaning defect, but a possibleInference hygiene issue.
8. **How many cases were stated_meaning_preserved / strengthened / lost?** preserved: 4, strengthened: 0, lost: 0.
9. **Compared with 55C Case 1, did explicit stated-vs-inferred separation avoid the earlier strengthening?** Yes — in 55C Stage 1, "Risk matters more to me." was strengthened into language about "preference/trade-off rather than absolute hard constraint" inside the single meaning field. In 55D, the relative importance remained clean in statedMeaning and any stronger interpretation was placed separately in possibleInference. This shows the two-field separation can prevent silent strengthening when it matters most (weak answers).
10. **Does this prove that production should use this exact two-field contract?** No — four cases through one call each is insufficient to justify specific production changes. The mechanism works in these tests but broader validation is needed.
11. **Does this establish how resolution should consume these fields?** No — resolution was not tested here. How a downstream step should combine statedMeaning and possibleInference remains an open question.
12. **Does this establish graph or Behaviour Selection changes?** No — no graph, Behaviour Selection, or engine integration was attempted.
---
### Limitations
- Only four answers were tested across one domain (risk vs. growth). Different answer patterns may behave differently.
- Each case was called exactly once — stability across repeated identical calls was not tested.
- possibleInference hygiene is imperfect: Cases 3 and 4 received unnecessary inferences, suggesting the model struggles to return null when no inference is warranted.
- Only one model configuration was used (qwen-claude:latest on 192.168.1.111:11434).
- Case 2's statedMeaning contained slight overreach ("qualifying it as a preference or trade-off rather than a hard constraint") — while the core meaning was preserved, not all answers will be this clean even with separation.
- No downstream consumer (resolution, graph update) was tested — only whether the two fields can coexist without leakage.
---
### Experiment Conclusion
**Stated meaning remained separate from model inference across all tested answers.**
Across four fixed cases spanning weak priority, conditional trade-off, explicit constraint, and non-answer, statedMeaning never contained stronger-than-justified meaning. Case 1's weak-priority answer (the primary failure mode of 55C) stayed as relative importance only in statedMeaning — a direct improvement over 55C where the same answer was strengthened into constraint language. Case 2 preserved both sides of the conditional; Case 3 preserved explicit meaning cleanly; Case 4 preserved uncertainty without inventing position.
The separation mechanism works: the model can keep what the user established from what it might imply, at least in single-call mode. The remaining issue is possibleInference hygiene — the model tends to generate implications even when none are warranted (Cases 3 and 4). This does not corrupt statedMeaning but suggests the null-enforcement direction should be tuned.
Does this prove a production two-field contract is required? **No.** Evidence from four single calls across one answer pattern is insufficient. Does this establish how resolution should consume these fields? **No.** Resolution was not tested. Does this establish graph or Behaviour Selection changes? **No.**
### Focused Test Result
All 4 live inference calls completed successfully. Four answers tested through one call each with stated-vs-inferred separation. All four cases preserved statedMeaning without strengthening (stated_meaning_preserved: 4/4, strengthened: 0, lost: 0). Case 1's weak-priority answer stayed as relative importance only — direct improvement over 55C where the same answer was strengthened to constraint language in Stage 1. Inference cleanly separated for Cases 1 and 2; unnecessary inferences generated for Cases 3 and 4 (hygiene issue, not leakage). Total: 76730ms (~76.7s), average: ~19182.5ms per call, fastest: 16766ms, slowest: 24551ms.
### Historical Comparison Result
Compared to Experiment 55C's finding that Stage 1 strengthened "Risk matters more to me." into language about "preference/trade-off rather than absolute hard constraint," Experiment 55D shows the two-field separation can prevent that strengthening when it matters most: weak-priority answers stayed as relative importance in statedMeaning while stronger interpretations were placed separately in possibleInference. The mechanism works for the specific failure mode identified in 55C, but broader stability and downstream consumption remain untested.
### Documentation Updated
- `docs/design-evolution-log.md` — added full Experiment 55D entry; applied corrections to Experiment 55C wording and commit hash
- `docs/current-handoff.md` — updated with Experiment 55D summary and new Return-to-Work note
### Confirmation Host and Model Remained Unchanged
Host: `http://192.168.1.111:11434`. Model: `qwen-claude:latest`. Same as all prior experiments.
### Confirmation Production Prompts and Schemas Remained Unchanged
No production prompts read or modified. No schemas changed. All inference calls used the experiment-specific semantic instructions defined in this test file.
### Confirmation Behaviour Selection Remained Unchanged
Behaviour Selection was not called or referenced. No integration with the selector occurred.
### Confirmation Graph and UI Remained Unchanged
No graph files read or modified. No UI code touched. The experiment is test-only.
### Confirmation No Stated-vs-Inferred Clarification Logic Entered Active Runtime
This experiment created one new test file only. No stated-vs-inferred clarification logic entered any active runtime path, production module, or behaviour selection output. Production continues using the pre-existing contract.
---
### Return-to-Work Note (Experiment 55D)
55C showed preserved meaning can protect later resolution, but weak-priority meaning was already strengthened in Stage 1. 55D isolated that first interpretation step using a single-call stated-vs-inferred separation with four fixed answers across risk preference cases. Weak priority stayed as relative importance only (direct improvement over 55C's constraint-language strengthening). Conditionality survived through the conditional trade-off case. Explicit and uncertain controls stayed clean — no unsupported meaning leaked into statedMeaning. Stronger implications were kept separate in possibleInference for Cases 1 and 2, though Cases 3 and 4 showed unnecessary inference generation (hygiene issue, not leakage). This does not yet prescribe production architecture. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-stated-vs-inferred.test.js for the full experiment and results. Status pending Rob's review.
@@ -0,0 +1,379 @@
import { describe, it, expect } from "vitest";
import { config } from "dotenv";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
config({ path: path.resolve(__dirname, "../../.env.local") });
const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL;
const OLLAMA_MODEL = process.env.OLLAMA_MODEL;
if (!OLLAMA_BASE_URL || !OLLAMA_MODEL) {
throw new Error("OLLAMA_BASE_URL and OLLAMA_MODEL must be set in .env.local");
}
// Semantic instruction: stated vs inferred separation
const SEMANTIC_INSTRUCTION = `State only what the user's answer directly establishes in statedMeaning. Preserve uncertainty, qualification, and conditionality. Do not turn relative importance into a hard boundary or the absence of one unless the user actually says so. If there is a plausible implication that goes beyond what the answer directly establishes, place it only in possibleInference. Do not decide whether the clarification target is resolved. Do not recommend action or generate another question.`;
const CLARIFICATION_QUESTION = "Do you view avoiding additional risk as a hard constraint, or as a preference or trade-off?";
const CLARIFICATION_TARGET = "whether avoiding additional risk is a preference/trade-off or a hard constraint";
// Four fixed answers — one live call each
const CASES = [
{
id: "Case 1 - Weak Priority",
userAnswer: "Risk matters more to me.",
humanReference: {
statedMeaning: "risk has greater relative importance to the user",
possibleInferenceAcceptable: "this may indicate a strong preference toward avoiding risk",
notes: "must NOT establish hard constraint or not-a-hard-constraint in statedMeaning",
},
},
{
id: "Case 2 - Conditional Trade-Off",
userAnswer: "I'd normally avoid more risk, but for the right opportunity I might accept some.",
humanReference: {
statedMeaning: "normally prefers avoiding additional risk; may accept some conditionally for the right opportunity",
possibleInferenceAcceptable: null,
notes: "must preserve both normal preference AND conditional exception; must directly establish that avoiding all additional risk is not absolute",
},
},
{
id: "Case 3 - Explicit Hard Constraint",
userAnswer: "It's a hard constraint. I don't want any increase in risk.",
humanReference: {
statedMeaning: "avoiding additional risk is a hard constraint / no increase in risk is acceptable",
possibleInferenceAcceptable: null,
notes: "positive control for explicit meaning; possibleInference should normally be null",
},
},
{
id: "Case 4 - Non-Answer",
userAnswer: "I'm not really sure.",
humanReference: {
statedMeaning: "the user remains uncertain",
possibleInferenceAcceptable: null,
notes: "must NOT invent a preference, constraint, or likely leaning; possibleInference should normally be null",
},
},
];
// One live call per case - stated vs inferred separation
async function callStatedVsInferred(clarificationTarget, clarificationQuestion, userAnswer) {
const instruction = `State only what the user's answer directly establishes in statedMeaning. Preserve uncertainty, qualification, and conditionality. Do not turn relative importance into a hard boundary or the absence of one unless the user actually says so. If there is a plausible implication that goes beyond what the answer directly establishes, place it only in possibleInference. Do not decide whether the clarification target is resolved. Do not recommend action or generate another question.
Return valid JSON only in this shape:
{
"statedMeaning": "short statement",
"possibleInference": "short statement or null"
}`;
const messages = [
{ role: "system", content: instruction.trim() },
{
role: "user",
content: `Clarification target context: ${clarificationTarget}
Clarification question: ${clarificationQuestion}
User's answer: ${userAnswer}`,
},
];
const res = await fetch(`${OLLAMA_BASE_URL}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: OLLAMA_MODEL,
messages,
format: "json",
stream: false,
}),
});
if (!res.ok) {
throw new Error(`Ollama API error: ${res.status} ${res.statusText}`);
}
const data = await res.json();
const rawContent = data.message?.content ?? "";
const cleaned = rawContent.replace(/```(?:json)?\s*/g, "").replace(/```\s*/g, "");
return JSON.parse(cleaned.trim());
}
// Semantic evaluation helpers (manual review primary)
function classifyStatedMeaning(caseNum, statedMeaningText) {
const m = (statedMeaningText ?? "").toLowerCase().trim();
if (caseNum === 1) {
// Weak priority: must NOT establish hard constraint or not-a-hard-constraint
if (/hard.*constraint|not.*a.*hard.*constraint|non.?negotiable|definitively.*not|no.*need.*to.*worry|absolute.*bound/i.test(m)) {
return { classification: "stated_meaning_strengthened", reasoning: `Weak priority strengthened into constraint language in statedMeaning: "${statedMeaningText}"` };
}
if (/greater.*importance|more.*important|matters.*more|risk.*has.*weight|relativ.*priority|higher.*concern/i.test(m)) {
return { classification: "stated_meaning_preserved", reasoning: `Weak priority preserved as relative importance only in statedMeaning.` };
}
if (m.length < 5) {
return { classification: "stated_meaning_lost", reasoning: `statedMeaning too brief to evaluate: "${statedMeaningText}"` };
}
// Partial match - needs semantic review
return { classification: "stated_meaning_preserved", reasoning: `Weak priority may be preserved (semantic review needed): "${statedMeaningText}"` };
}
if (caseNum === 2) {
const hasConditionality = /but|condition|migh|would.*normally|normally.*avoid|tend.*to.*avoid|generally.*prefer/i.test(m);
if (!hasConditionality) {
return { classification: "stated_meaning_lost", reasoning: `Conditional qualification lost in statedMeaning - flattened to flat preference: "${statedMeaningText}"` };
}
const hasNormalPreference = /normal|generally|usually|tend|prefer.*avoid/i.test(m);
const hasConditionalAcceptance = /accept.*some|might.*accept|conditional.*accept|opportunity.*might|when.*right.*opportun/i.test(m);
if (hasNormalPreference && hasConditionalAcceptance) {
return { classification: "stated_meaning_preserved", reasoning: `Both normal preference and conditional exception preserved in statedMeaning.` };
}
if (!hasNormalPreference || !hasConditionalAcceptance) {
return { classification: "stated_meaning_lost", reasoning: `One side of conditionality missing from statedMeaning: "${statedMeaningText}"` };
}
}
if (caseNum === 3) {
// Explicit hard constraint: must establish it directly
if (/hard.*constraint|definitive.*no.*increase|no.*acceptable|absolute.*boundary|won't.*accept.*risk|must.*avoid.*any/i.test(m)) {
return { classification: "stated_meaning_preserved", reasoning: `Explicit hard constraint preserved in statedMeaning.` };
}
if (/uncertain|don't.*know|not.*sure/i.test(m)) {
return { classification: "stated_meaning_lost", reasoning: `Explicit meaning lost - replaced with uncertainty: "${statedMeaningText}"` };
}
return { classification: "stated_meaning_preserved", reasoning: `Hard constraint may be preserved (semantic review needed): "${statedMeaningText}"` };
}
if (caseNum === 4) {
// Non-answer: must establish only uncertainty, no invented preference
if (/uncertain|not.*sure|don't.*know|no.*position|haven't.*decided|unsure/i.test(m)) {
return { classification: "stated_meaning_preserved", reasoning: `Uncertainty preserved in statedMeaning.` };
}
if (/risk.*avoid|preference.*for|risk.*matters|should.*avoid|would.*prefer/i.test(m) && !/uncertain|unsure|don't.*know/i.test(m)) {
return { classification: "stated_meaning_strengthened", reasoning: `Invented a preference/constraint from non-answer in statedMeaning: "${statedMeaningText}"` };
}
if (m.length < 5) {
return { classification: "stated_meaning_lost", reasoning: `statedMeaning too brief to evaluate: "${statedMeaningText}"` };
}
return { classification: "stated_meaning_preserved", reasoning: `Uncertainty may be preserved (semantic review needed): "${statedMeaningText}"` };
}
return { classification: "stated_meaning_lost", reasoning: "Unrecognized case number" };
}
function classifyInferenceSeparation(caseNum, statedMeaningText, possibleInferenceText) {
const sm = (statedMeaningText ?? "").toLowerCase().trim();
const pi = (possibleInferenceText ?? null);
const piStr = typeof pi === "string" ? pi.toLowerCase().trim() : null;
if (caseNum === 1) {
// Weak priority: statedMeaning should only have relative importance
const hasRelativeImportanceInStated = /greater.*importance|more.*important|matters.*more|risk.*has.*weight|relativ/i.test(sm);
const hasConstraintLanguageInStated = /hard.*constraint|not.*a.*hard.*constraint|non.?negotiable|absolute.*bound/i.test(sm);
if (!hasRelativeImportanceInStated && !hasConstraintLanguageInStated) {
return { classification: "unnecessary_inference", reasoning: `statedMeaning lacks relative importance and possibleInference may add unnecessary implication: stated="${sm}" inferred="${piStr}"` };
}
if (hasConstraintLanguageInStated) {
return { classification: "inference_leaked_into_stated", reasoning: `Stronger constraint language leaked into statedMeaning where only relative importance should appear: "${statedMeaningText}"` };
}
if (piStr !== null && piStr.length > 0) {
const isReasonableImplication = /may.*indicate|could.*suggest|might.*lean|potentially.*stronger|i.*leans.*toward/i.test(piStr);
if (isReasonableImplication) {
return { classification: "inference_cleanly_separated", reasoning: `possibleInference contains reasonable implication (may indicate/could suggest) separate from statedMeaning's relative importance.` };
}
return { classification: "inference_cleanly_separated", reasoning: `possibleInference contains an implication beyond statedMeaning. Review whether it crosses into unnecessary inference: "${piStr}"` };
}
if (pi === null) {
return { classification: "no_inference_needed", reasoning: `statedMeaning preserved relative importance only; no additional inference needed.` };
}
return { classification: "unnecessary_inference", reasoning: `possibleInference may be unnecessary where statedMeaning is clean: "${piStr}"` };
}
if (caseNum === 2) {
if (pi === null || piStr.length === 0) {
return { classification: "no_inference_needed", reasoning: `No inference needed - statedMeaning captures the full answer.` };
}
const isReasonable = /may.*indicate|could.*suggest|might.*imply|potentially/i.test(piStr);
if (isReasonable) {
return { classification: "inference_cleanly_separated", reasoning: `possibleInference contains a reasonable implication, cleanly separated from stated meaning.` };
}
const overlaps = piStr.split(/\s+/).some(w => w.length > 4 && sm.includes(w));
if (overlaps && !isReasonable) {
return { classification: "unnecessary_inference", reasoning: `possibleInference adds little beyond statedMeaning: "${piStr}"` };
}
return { classification: "inference_cleanly_separated", reasoning: `possibleInference present and separated. Semantic review recommended: "${piStr}"` };
}
if (caseNum === 3) {
// Explicit hard constraint: possibleInference should normally be null
if (pi === null || piStr.length === 0) {
return { classification: "no_inference_needed", reasoning: `No inference needed - statedMeaning is explicit.` };
}
return { classification: "unnecessary_inference", reasoning: `possibleInference should normally be null for explicit hard constraint. It adds: "${piStr}"` };
}
if (caseNum === 4) {
// Non-answer: possibleInference should normally be null
if (pi === null || piStr.length === 0) {
return { classification: "no_inference_needed", reasoning: `No inference needed for non-answer.` };
}
const inventsLeaning = /would.*prefer|likely.*to.*avoid|probably.*want|tends.*toward|most.*people.*would/i.test(piStr);
if (inventsLeaning) {
return { classification: "unnecessary_inference", reasoning: `possibleInference invents a leaning from non-answer: "${piStr}"` };
}
return { classification: "unnecessary_inference", reasoning: `possibleInference present for non-answer where none is warranted: "${piStr}"` };
}
return { classification: "no_inference_needed", reasoning: "Unrecognized case number" };
}
// Test suite - Experiment 55D
describe("Experiment 55D - Separate Stated Clarification Meaning from Inference", () => {
const results = [];
const timings = [];
for (let i = 0; i < CASES.length; i++) {
const c = CASES[i];
const caseNum = i + 1;
it(`${c.id} - One call: stated vs inferred separation`, async () => {
const start = Date.now();
const result = await callStatedVsInferred(
CLARIFICATION_TARGET,
CLARIFICATION_QUESTION,
c.userAnswer
);
const elapsed = Date.now() - start;
timings.push({ caseId: c.id, ms: elapsed });
const statedMeaning = result.statedMeaning ?? "";
const possibleInference = result.possibleInference ?? null;
const smClass = classifyStatedMeaning(caseNum, statedMeaning);
const piClass = classifyInferenceSeparation(caseNum, statedMeaning, possibleInference);
results.push({
caseNumber: caseNum,
case: c,
rawAnswer: c.userAnswer,
output: result,
statedMeaning,
possibleInference,
statedMeaningClassification: smClass.classification,
statedMeaningReasoning: smClass.reasoning,
inferenceSeparationClassification: piClass.classification,
inferenceSeparationReasoning: piClass.reasoning,
timingMs: elapsed,
});
expect(result.statedMeaning).toBeDefined();
expect(typeof result.statedMeaning).toBe("string");
expect(result.statedMeaning.trim().length).toBeGreaterThan(0);
if (result.possibleInference !== null) {
expect(typeof result.possibleInference).toBe("string");
}
}, 120000);
}
// Aggregate analysis
it("55D: aggregate results and semantic review", () => {
const smCounts = { stated_meaning_preserved: 0, stated_meaning_strengthened: 0, stated_meaning_lost: 0 };
const piCounts = { inference_cleanly_separated: 0, inference_leaked_into_stated: 0, unnecessary_inference: 0, no_inference_needed: 0 };
for (const r of results) {
smCounts[r.statedMeaningClassification]++;
piCounts[r.inferenceSeparationClassification]++;
}
const totalMs = timings.reduce((s, t) => s + t.ms, 0);
const msArr = timings.map((t) => t.ms);
console.log("\n========== Experiment 55D Results ==========");
console.log(`\nClarification target: ${CLARIFICATION_TARGET}`);
console.log(`Question: ${CLARIFICATION_QUESTION}`);
for (const r of results) {
console.log(`\n--- ${r.case.id} ---`);
console.log("Raw answer:", `"${r.rawAnswer}"`);
console.log("statedMeaning:", `"${r.statedMeaning}"`);
console.log("possibleInference:", r.possibleInference ?? "null");
console.log("statedMeaning classification:", r.statedMeaningClassification);
console.log("Reasoning:", r.statedMeaningReasoning);
console.log("Inference separation:", r.inferenceSeparationClassification);
console.log("Inference reasoning:", r.inferenceSeparationReasoning);
}
console.log("\n--- Stated Meaning Counts ---");
Object.entries(smCounts).forEach(([k, v]) => console.log(`${k}: ${v}`));
console.log("\n--- Inference Separation Counts ---");
Object.entries(piCounts).forEach(([k, v]) => console.log(`${k}: ${v}`));
console.log("\n--- Timing ---");
console.log("Total live calls:", timings.length);
console.log("Total time:", totalMs + "ms");
console.log("Average:", (totalMs / timings.length).toFixed(1) + "ms per call");
console.log("Fastest:", Math.min(...msArr) + "ms");
console.log("Slowest:", Math.max(...msArr) + "ms");
// Required questions
const case1 = results.find(r => r.caseNumber === 1);
const case2 = results.find(r => r.caseNumber === 2);
const case3 = results.find(r => r.caseNumber === 3);
const case4 = results.find(r => r.caseNumber === 4);
const q1 = case1.statedMeaningClassification === "stated_meaning_preserved";
const q2 = case1.inferenceSeparationClassification === "no_inference_needed" ||
case1.inferenceSeparationClassification === "inference_cleanly_separated";
console.log("\n--- Required Questions ---");
console.log("Q1 (Case 1 kept relative importance only in statedMeaning?):", q1 ? "Yes" : "No - " + case1.statedMeaningReasoning);
console.log("Q2 (Case 1 placed stronger implication only in possibleInference):", q2 ? "Yes" : "No - " + case1.inferenceSeparationReasoning);
console.log("Q3 (Case 2 preserved conditionality?):", case2.statedMeaningClassification === "stated_meaning_preserved" ? "Yes" : "No - " + case2.statedMeaningReasoning);
console.log("Q4 (Case 3 preserved explicit constraint without unnecessary inference?):",
case3.statedMeaningClassification === "stated_meaning_preserved" && case3.inferenceSeparationClassification !== "inference_leaked_into_stated" ? "Yes" : "No" + (case3.inferenceSeparationClassification === "unnecessary_inference" ? " - unnecessary inference present" : " - check reasoning") + " - " + case3.inferenceSeparationReasoning);
console.log("Q5 (Case 4 preserved uncertainty without inventing leaning?):", case4.statedMeaningClassification === "stated_meaning_preserved" ? "Yes" : "No - " + case4.statedMeaningReasoning);
const anyLeaked = results.some(r => r.inferenceSeparationClassification === "inference_leaked_into_stated");
console.log("Q6 (Did any unsupported meaning leak into statedMeaning?):", anyLeaked ? "Yes - check cases above" : "No observed leakage");
const anyUnnecessaryInference = results.some(r => r.inferenceSeparationClassification === "unnecessary_inference");
console.log("Q7 (Did model generate unnecessary implications where answer was explicit?):", anyUnnecessaryInference ? "Yes - check cases above" : "No unnecessary inferences observed");
console.log("\n--- Required Answers to Critical Questions ---");
console.log("Q10 (proves production should use this exact contract?): No");
console.log("Q11 (establishes how resolution should consume these fields?): No");
console.log("Q12 (establishes graph or Behaviour Selection changes?): No");
results.summary = {
smCounts,
piCounts,
totalTimeMs: totalMs,
totalCalls: timings.length,
averageMs: (totalMs / timings.length).toFixed(1),
fastestMs: Math.min(...msArr),
slowestMs: Math.max(...msArr),
caseResults: results.map(r => ({
caseNumber: r.caseNumber,
caseId: r.case.id,
rawAnswer: r.rawAnswer,
statedMeaning: r.statedMeaning,
possibleInference: r.possibleInference,
statedMeaningClassification: r.statedMeaningClassification,
inferenceSeparationClassification: r.inferenceSeparationClassification,
})),
};
console.log("\n========== End of Experiment 55D ==========\n");
});
});