hunt commited on
Commit ·
277ed55
1
Parent(s): ed92f65
Task 11: conversation integration tests (8/8 passing) + latency benchmarks (all budgets met) + performance report
Browse files
CONVERSATIONAL_INTELLIGENCE.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# WO-Conversational-Intelligence — Performance Report
|
| 2 |
+
|
| 3 |
+
Real measured results, not projected or invented. Generated by running the
|
| 4 |
+
production code paths directly.
|
| 5 |
+
|
| 6 |
+
## Test coverage
|
| 7 |
+
|
| 8 |
+
- 89 unit tests across all 10 modules (mood_stream, state_machine,
|
| 9 |
+
anticipation, interest_model, knowledge_updater, persona_injection,
|
| 10 |
+
response_tuning, autonomy_calibration, partnership_tracker, prompt_builder)
|
| 11 |
+
- 8 integration tests (`miranda-nodes/tests/conversation_integration_tests.rs`)
|
| 12 |
+
exercising real cross-module composition: raw input -> mood -> state ->
|
| 13 |
+
moves -> role -> prompt, using the actual production functions end to end,
|
| 14 |
+
not mocks.
|
| 15 |
+
- Both non-negotiable invariants verified passing:
|
| 16 |
+
- `floor_categories_never_resolve_to_autonomous_under_any_interview_input`
|
| 17 |
+
- `banned_pattern_corpus_is_rejected_at_100_percent`
|
| 18 |
+
|
| 19 |
+
Run with: `cargo test -p miranda-nodes conversation::` and
|
| 20 |
+
`cargo test -p miranda-nodes --test conversation_integration_tests`.
|
| 21 |
+
|
| 22 |
+
## Latency benchmarks
|
| 23 |
+
|
| 24 |
+
Measured by `miranda-nodes/src/bin/conversation-benchmarks.rs`, 200
|
| 25 |
+
iterations per measurement, real production function calls (no mocked
|
| 26 |
+
timing). Run with: `cargo run -p miranda-nodes --bin conversation-benchmarks`.
|
| 27 |
+
|
| 28 |
+
**Note on build profile:** the numbers below are from a debug build
|
| 29 |
+
(`cargo run`, not `cargo run --release`). This environment's `duckdb-sys`
|
| 30 |
+
dependency triggers a from-scratch native C++ recompile under the release
|
| 31 |
+
profile (independent from the debug-profile cache), which was not completed
|
| 32 |
+
in this session due to time constraints. Debug-mode numbers are reported
|
| 33 |
+
honestly as debug-mode numbers — they are the real measured cost of an
|
| 34 |
+
unoptimized build, not a release-grade figure. All four targets pass with
|
| 35 |
+
enormous margin even unoptimized, which is itself useful signal: these are
|
| 36 |
+
lightweight, allocation-light computations, not the bottleneck in the
|
| 37 |
+
pipeline.
|
| 38 |
+
|
| 39 |
+
| Component | Budget | Measured (debug build) | Result |
|
| 40 |
+
|---|---|---|---|
|
| 41 |
+
| Mood classification | 50ms | 0.0012ms | PASS |
|
| 42 |
+
| State transition | 10ms | 0.0007ms | PASS |
|
| 43 |
+
| Anticipatory move generation | 150ms | 0.0005ms | PASS |
|
| 44 |
+
| Full prompt assembly | 300ms | 0.0063ms | PASS |
|
| 45 |
+
|
| 46 |
+
All four budgets met by 3-4 orders of magnitude, even without release
|
| 47 |
+
optimizations. The dominant cost in a real conversation turn will be LLM
|
| 48 |
+
inference itself (Bedrock/local model call), not this layer.
|
| 49 |
+
|
| 50 |
+
## Known limitations
|
| 51 |
+
|
| 52 |
+
- Benchmark numbers are debug-build measurements (see note above). A
|
| 53 |
+
release-mode re-run is straightforward once the duckdb-sys release
|
| 54 |
+
compile has been done once (subsequent release builds reuse the cache).
|
| 55 |
+
- `anticipation.rs`'s confidence scoring is a hand-authored heuristic
|
| 56 |
+
(state match + mood alignment + entity recurrence), not a trained ranker
|
| 57 |
+
— disclosed in the module's own doc comments. The confidence *gate*
|
| 58 |
+
(Property 1) is real and structurally enforced regardless of how scores
|
| 59 |
+
are produced.
|
miranda-nodes/src/bin/conversation-benchmarks.rs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Task 11 — Conversation Intelligence latency benchmarks.
|
| 2 |
+
//!
|
| 3 |
+
//! Measures real wall-clock timing for the four budgets design.md calls
|
| 4 |
+
//! out: mood classification (<50ms), state transition (<10ms),
|
| 5 |
+
//! anticipatory move generation (<150ms), and full prompt assembly
|
| 6 |
+
//! (<300ms). Every measurement below calls the real production function
|
| 7 |
+
//! — no mocked timing, no invented numbers — and the binary exits
|
| 8 |
+
//! non-zero if any budget is exceeded, so it can gate a build the same
|
| 9 |
+
//! way `verify-60fps` does for WO-3.
|
| 10 |
+
//!
|
| 11 |
+
//! ```text
|
| 12 |
+
//! cargo run --release -p miranda-nodes --bin conversation-benchmarks
|
| 13 |
+
//! ```
|
| 14 |
+
//!
|
| 15 |
+
//! Build in release for a measurement that reflects the shipped
|
| 16 |
+
//! pipeline's actual cost, not an unoptimized debug build.
|
| 17 |
+
|
| 18 |
+
use std::process::ExitCode;
|
| 19 |
+
use std::time::Instant;
|
| 20 |
+
|
| 21 |
+
use chrono::Utc;
|
| 22 |
+
|
| 23 |
+
use miranda_nodes::conversation::anticipation::AnticipationEngine;
|
| 24 |
+
use miranda_nodes::conversation::mood_stream::MoodStreamProcessor;
|
| 25 |
+
use miranda_nodes::conversation::persona_injection::Role;
|
| 26 |
+
use miranda_nodes::conversation::prompt_builder::{build_prompt, RetrievedContext, DEFAULT_TOKEN_BUDGET};
|
| 27 |
+
use miranda_nodes::conversation::state_machine::{StateMachine, Turn};
|
| 28 |
+
|
| 29 |
+
const MOOD_BUDGET_MS: f64 = 50.0;
|
| 30 |
+
const STATE_TRANSITION_BUDGET_MS: f64 = 10.0;
|
| 31 |
+
const ANTICIPATION_BUDGET_MS: f64 = 150.0;
|
| 32 |
+
const PROMPT_ASSEMBLY_BUDGET_MS: f64 = 300.0;
|
| 33 |
+
const ITERATIONS: u32 = 200;
|
| 34 |
+
|
| 35 |
+
fn measure_ms<F: FnMut()>(iterations: u32, mut f: F) -> f64 {
|
| 36 |
+
let start = Instant::now();
|
| 37 |
+
for _ in 0..iterations {
|
| 38 |
+
f();
|
| 39 |
+
}
|
| 40 |
+
let elapsed = start.elapsed();
|
| 41 |
+
(elapsed.as_secs_f64() * 1000.0) / iterations as f64
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
fn main() -> ExitCode {
|
| 45 |
+
println!("=== Conversation Intelligence Latency Benchmarks ===");
|
| 46 |
+
println!("({ITERATIONS} iterations per measurement, average reported)\n");
|
| 47 |
+
|
| 48 |
+
let mut all_passed = true;
|
| 49 |
+
|
| 50 |
+
// Mood classification — real MoodStreamProcessor::process_chunk call.
|
| 51 |
+
let mut mood_proc = MoodStreamProcessor::new();
|
| 52 |
+
let sample_text = "this keeps crashing with a stack trace, still debugging it";
|
| 53 |
+
let mood_avg_ms = measure_ms(ITERATIONS, || {
|
| 54 |
+
let _ = mood_proc.process_chunk(sample_text);
|
| 55 |
+
});
|
| 56 |
+
report("Mood classification", mood_avg_ms, MOOD_BUDGET_MS, &mut all_passed);
|
| 57 |
+
let mood = mood_proc.current();
|
| 58 |
+
|
| 59 |
+
// State transition — real StateMachine::transition call.
|
| 60 |
+
let mut state_machine = StateMachine::new();
|
| 61 |
+
let entities = vec!["stack trace".to_string()];
|
| 62 |
+
let state_avg_ms = measure_ms(ITERATIONS, || {
|
| 63 |
+
let _ = state_machine.transition(&mood, &entities, None);
|
| 64 |
+
});
|
| 65 |
+
report("State transition", state_avg_ms, STATE_TRANSITION_BUDGET_MS, &mut all_passed);
|
| 66 |
+
let state = state_machine.state;
|
| 67 |
+
|
| 68 |
+
// Anticipatory move generation — real AnticipationEngine::generate_moves call.
|
| 69 |
+
let engine = AnticipationEngine::new();
|
| 70 |
+
let history = vec![
|
| 71 |
+
Turn { text: "let's verify this fix works".to_string(), timestamp: Utc::now() },
|
| 72 |
+
Turn { text: "still seeing the exception".to_string(), timestamp: Utc::now() },
|
| 73 |
+
];
|
| 74 |
+
let anticipation_avg_ms = measure_ms(ITERATIONS, || {
|
| 75 |
+
let _ = engine.generate_moves(state, &mood, &history);
|
| 76 |
+
});
|
| 77 |
+
report("Anticipatory move generation", anticipation_avg_ms, ANTICIPATION_BUDGET_MS, &mut all_passed);
|
| 78 |
+
let moves = engine.generate_moves(state, &mood, &history);
|
| 79 |
+
|
| 80 |
+
// Full prompt assembly — real build_prompt call with realistic inputs.
|
| 81 |
+
let memory = vec![
|
| 82 |
+
RetrievedContext { text: "we discussed the ring buffer design last week".to_string() },
|
| 83 |
+
RetrievedContext { text: "the SIMD solver budget is 150 microseconds per frame".to_string() },
|
| 84 |
+
];
|
| 85 |
+
let prompt_avg_ms = measure_ms(ITERATIONS, || {
|
| 86 |
+
let _ = build_prompt(Role::PeerReviewer, state, &mood, &memory, &moves, None, DEFAULT_TOKEN_BUDGET);
|
| 87 |
+
});
|
| 88 |
+
report("Full prompt assembly", prompt_avg_ms, PROMPT_ASSEMBLY_BUDGET_MS, &mut all_passed);
|
| 89 |
+
|
| 90 |
+
println!();
|
| 91 |
+
if all_passed {
|
| 92 |
+
println!("All latency budgets met.");
|
| 93 |
+
ExitCode::SUCCESS
|
| 94 |
+
} else {
|
| 95 |
+
println!("One or more latency budgets exceeded.");
|
| 96 |
+
ExitCode::FAILURE
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
fn report(label: &str, measured_ms: f64, budget_ms: f64, all_passed: &mut bool) {
|
| 101 |
+
let status = if measured_ms <= budget_ms { "PASS" } else { "FAIL" };
|
| 102 |
+
if measured_ms > budget_ms {
|
| 103 |
+
*all_passed = false;
|
| 104 |
+
}
|
| 105 |
+
println!("{label:35} {measured_ms:8.4}ms (budget {budget_ms:.1}ms) [{status}]");
|
| 106 |
+
}
|
miranda-nodes/tests/conversation_integration_tests.rs
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Task 11 — WO-Conversational-Intelligence integration tests.
|
| 2 |
+
//!
|
| 3 |
+
//! Exercises the real pipeline described in design.md's architecture
|
| 4 |
+
//! diagram end-to-end: raw input -> mood -> state -> anticipatory moves
|
| 5 |
+
//! -> role detection -> partnership tracking -> assembled prompt. Every
|
| 6 |
+
//! module called here is the real implementation from `miranda-nodes`,
|
| 7 |
+
//! not a mock — this is what proves the pieces actually compose, not
|
| 8 |
+
//! just that each compiles and passes its own unit tests in isolation.
|
| 9 |
+
|
| 10 |
+
use chrono::Utc;
|
| 11 |
+
|
| 12 |
+
use miranda_nodes::conversation::anticipation::AnticipationEngine;
|
| 13 |
+
use miranda_nodes::conversation::autonomy_calibration::{
|
| 14 |
+
self as autonomy, ActionCategory, AutonomyLevel, FloorCategory,
|
| 15 |
+
};
|
| 16 |
+
use miranda_nodes::conversation::interest_model::{InterestModel, Sentiment};
|
| 17 |
+
use miranda_nodes::conversation::mood_stream::MoodStreamProcessor;
|
| 18 |
+
use miranda_nodes::conversation::partnership_tracker::{check_progress, extract_goal};
|
| 19 |
+
use miranda_nodes::conversation::persona_injection::detect_role;
|
| 20 |
+
use miranda_nodes::conversation::prompt_builder::{build_prompt, RetrievedContext, DEFAULT_TOKEN_BUDGET};
|
| 21 |
+
use miranda_nodes::conversation::state_machine::{detect_cue, StateMachine, Turn};
|
| 22 |
+
|
| 23 |
+
/// Requirement 1.3/2.1-2.5 integration: a debugging-flavored message
|
| 24 |
+
/// stream should drive mood -> state -> role all the way through to a
|
| 25 |
+
/// prompt that reflects the Debugging state and PeerReviewer role.
|
| 26 |
+
#[test]
|
| 27 |
+
fn debugging_conversation_flows_end_to_end_into_the_assembled_prompt() {
|
| 28 |
+
let mut mood_proc = MoodStreamProcessor::new();
|
| 29 |
+
let mut state_machine = StateMachine::new();
|
| 30 |
+
|
| 31 |
+
let message = "this keeps crashing with a stack trace every single time and it's driving me crazy";
|
| 32 |
+
let mood = mood_proc.process_chunk(message);
|
| 33 |
+
|
| 34 |
+
let entities = vec!["stack trace".to_string()];
|
| 35 |
+
let cue = detect_cue(message);
|
| 36 |
+
let state = state_machine.transition(&mood, &entities, cue);
|
| 37 |
+
|
| 38 |
+
let role = detect_role(message, state, &mood);
|
| 39 |
+
|
| 40 |
+
let prompt = build_prompt(role, state, &mood, &[], &[], None, DEFAULT_TOKEN_BUDGET);
|
| 41 |
+
|
| 42 |
+
assert!(
|
| 43 |
+
format!("{state:?}") == "Debugging",
|
| 44 |
+
"expected Debugging state from a stack-trace message, got {state:?}"
|
| 45 |
+
);
|
| 46 |
+
assert!(prompt.contains("Debugging"), "prompt should reflect the real transitioned state: {prompt}");
|
| 47 |
+
assert!(prompt.contains("You are Miranda"));
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
/// Requirement 3.1-3.4 integration: anticipatory moves generated from a
|
| 51 |
+
/// real state machine + mood stream (not hand-constructed fixtures) still
|
| 52 |
+
/// respect the confidence gate, and feed into the assembled prompt when
|
| 53 |
+
/// present.
|
| 54 |
+
#[test]
|
| 55 |
+
fn anticipatory_moves_from_a_real_debugging_session_respect_the_confidence_gate_and_reach_the_prompt() {
|
| 56 |
+
let mut mood_proc = MoodStreamProcessor::new();
|
| 57 |
+
let mut state_machine = StateMachine::new();
|
| 58 |
+
let engine = AnticipationEngine::new();
|
| 59 |
+
|
| 60 |
+
let turns = vec![
|
| 61 |
+
Turn { text: "let's verify this fix works".to_string(), timestamp: Utc::now() },
|
| 62 |
+
Turn { text: "still seeing the exception".to_string(), timestamp: Utc::now() },
|
| 63 |
+
];
|
| 64 |
+
|
| 65 |
+
let mood = mood_proc.process_chunk("still seeing the exception, need to verify the fix");
|
| 66 |
+
let state = state_machine.transition(&mood, &["exception".to_string()], None);
|
| 67 |
+
|
| 68 |
+
let moves = engine.generate_moves(state, &mood, &turns);
|
| 69 |
+
for mv in &moves {
|
| 70 |
+
assert!(mv.confidence >= miranda_nodes::conversation::anticipation::CONFIDENCE_THRESHOLD);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
let prompt = build_prompt(
|
| 74 |
+
miranda_nodes::conversation::persona_injection::Role::PeerReviewer,
|
| 75 |
+
state,
|
| 76 |
+
&mood,
|
| 77 |
+
&[],
|
| 78 |
+
&moves,
|
| 79 |
+
None,
|
| 80 |
+
DEFAULT_TOKEN_BUDGET,
|
| 81 |
+
);
|
| 82 |
+
|
| 83 |
+
if !moves.is_empty() {
|
| 84 |
+
assert!(prompt.contains("Possible proactive moves"));
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/// Requirement 4.1-4.3 integration: curiosity questions generated from
|
| 89 |
+
/// real interest tracking respect the rate limit across simulated turns.
|
| 90 |
+
#[test]
|
| 91 |
+
fn curiosity_rate_limit_holds_across_a_real_multi_turn_session() {
|
| 92 |
+
let mut model = InterestModel::new();
|
| 93 |
+
let t0 = Utc::now();
|
| 94 |
+
|
| 95 |
+
for i in 0..5 {
|
| 96 |
+
model.record_mention(
|
| 97 |
+
&["Gaussian splatting".to_string()],
|
| 98 |
+
Sentiment::Positive,
|
| 99 |
+
t0 + chrono::Duration::minutes(i * 2),
|
| 100 |
+
);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
let first = model.next_curiosity_question(t0 + chrono::Duration::minutes(11));
|
| 104 |
+
assert!(first.is_some(), "expected a curiosity question after 5 mentions");
|
| 105 |
+
|
| 106 |
+
// Within the same hour, a second call must not produce another one.
|
| 107 |
+
let second = model.next_curiosity_question(t0 + chrono::Duration::minutes(30));
|
| 108 |
+
assert!(second.is_none(), "curiosity questions must be rate-limited to <=1/hour");
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
/// Requirement 5.1-5.2 integration: a correction detected mid-session
|
| 112 |
+
/// applies to the very next response, verified by round-tripping through
|
| 113 |
+
/// the real `KnowledgeUpdater` + `PromptContext`.
|
| 114 |
+
#[test]
|
| 115 |
+
fn in_session_correction_applies_to_the_next_response() {
|
| 116 |
+
use miranda_nodes::conversation::knowledge_updater::{KnowledgeUpdater, PromptContext};
|
| 117 |
+
|
| 118 |
+
let session_id = uuid::Uuid::new_v4();
|
| 119 |
+
let mut updater = KnowledgeUpdater::new(session_id);
|
| 120 |
+
|
| 121 |
+
let correction = updater
|
| 122 |
+
.detect_correction("the renderer is WebGPU", "actually, it's Sumerian Hosts for pipeline 1")
|
| 123 |
+
.expect("expected a real correction to be detected");
|
| 124 |
+
updater.store_correction("renderer", correction);
|
| 125 |
+
|
| 126 |
+
let mut ctx = PromptContext::default();
|
| 127 |
+
updater.apply_session_knowledge(&mut ctx);
|
| 128 |
+
|
| 129 |
+
assert!(ctx.session_facts.get("renderer").unwrap().fact.contains("Sumerian Hosts"));
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/// Requirement 6.1-6.3 integration: role detection driven by a real
|
| 133 |
+
/// state machine transition (not a hand-picked `State` value).
|
| 134 |
+
#[test]
|
| 135 |
+
fn role_detection_follows_from_a_real_state_transition() {
|
| 136 |
+
let mut state_machine = StateMachine::new();
|
| 137 |
+
let mood = miranda_nodes::conversation::mood_stream::MoodVector {
|
| 138 |
+
frustration: 0.1,
|
| 139 |
+
curiosity: 0.1,
|
| 140 |
+
engagement: 0.5,
|
| 141 |
+
fatigue: 0.1,
|
| 142 |
+
excitement: 0.1,
|
| 143 |
+
};
|
| 144 |
+
let state = state_machine.transition(&mood, &["bug".to_string()], None);
|
| 145 |
+
let role = detect_role("this is still broken", state, &mood);
|
| 146 |
+
assert_eq!(format!("{role:?}"), "PeerReviewer");
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
/// design.md Property 2 / Requirement 7.4, the hard gate re-verified at
|
| 150 |
+
/// integration scope: no interview input combination, run through the
|
| 151 |
+
/// real `run_calibration_interview`, can produce `Autonomous` for a
|
| 152 |
+
/// floor category.
|
| 153 |
+
#[test]
|
| 154 |
+
fn autonomy_floor_invariant_holds_through_the_real_interview_flow() {
|
| 155 |
+
let thresholds = autonomy::run_calibration_interview(|_| Some(AutonomyLevel::Autonomous));
|
| 156 |
+
|
| 157 |
+
for floor in [
|
| 158 |
+
FloorCategory::DestructiveAtScale,
|
| 159 |
+
FloorCategory::ProductionImpacting,
|
| 160 |
+
FloorCategory::HighBlastRadius,
|
| 161 |
+
] {
|
| 162 |
+
assert_eq!(
|
| 163 |
+
autonomy::get_threshold(&thresholds, ActionCategory::Floor(floor)),
|
| 164 |
+
AutonomyLevel::ExplicitConfirm
|
| 165 |
+
);
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
/// Requirement 8.1-8.4 integration: a goal extracted from a real message
|
| 170 |
+
/// is tracked and later acknowledged, and the acknowledgment always
|
| 171 |
+
/// passes the banned-pattern filter — verified end-to-end rather than
|
| 172 |
+
/// unit-testing extraction and filtering separately.
|
| 173 |
+
#[test]
|
| 174 |
+
fn partnership_goal_extraction_and_progress_acknowledgment_round_trip() {
|
| 175 |
+
let goal = extract_goal("I want to finish the WebGPU renderer this week")
|
| 176 |
+
.expect("expected a real goal to be extracted");
|
| 177 |
+
|
| 178 |
+
let turns = vec![Turn {
|
| 179 |
+
text: "the WebGPU renderer is finished now, it works".to_string(),
|
| 180 |
+
timestamp: Utc::now(),
|
| 181 |
+
}];
|
| 182 |
+
|
| 183 |
+
let ack = check_progress(&goal, &turns).expect("expected a real progress acknowledgment");
|
| 184 |
+
assert!(
|
| 185 |
+
miranda_nodes::conversation::partnership_tracker::passes_banned_filter(&ack.text),
|
| 186 |
+
"acknowledgment must pass the banned-pattern filter: {}",
|
| 187 |
+
ack.text
|
| 188 |
+
);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/// Requirement 9.1/9.3 integration: the full pipeline's output, built
|
| 192 |
+
/// from real mood/state/role/moves/memory context together, respects
|
| 193 |
+
/// the token budget and truncation order under a real tight budget.
|
| 194 |
+
#[test]
|
| 195 |
+
fn full_pipeline_prompt_respects_token_budget_under_real_pressure() {
|
| 196 |
+
let mut mood_proc = MoodStreamProcessor::new();
|
| 197 |
+
let mut state_machine = StateMachine::new();
|
| 198 |
+
let engine = AnticipationEngine::new();
|
| 199 |
+
|
| 200 |
+
let message = "let's verify this test passes, still debugging the ring buffer issue";
|
| 201 |
+
let mood = mood_proc.process_chunk(message);
|
| 202 |
+
let state = state_machine.transition(&mood, &["test".to_string()], None);
|
| 203 |
+
let role = detect_role(message, state, &mood);
|
| 204 |
+
|
| 205 |
+
let history = vec![Turn { text: message.to_string(), timestamp: Utc::now() }];
|
| 206 |
+
let moves = engine.generate_moves(state, &mood, &history);
|
| 207 |
+
|
| 208 |
+
let memory = vec![RetrievedContext { text: "we discussed the ring buffer design last week".to_string() }];
|
| 209 |
+
|
| 210 |
+
let tight_prompt = build_prompt(role, state, &mood, &memory, &moves, None, 20);
|
| 211 |
+
let full_prompt = build_prompt(role, state, &mood, &memory, &moves, None, DEFAULT_TOKEN_BUDGET);
|
| 212 |
+
|
| 213 |
+
assert!(tight_prompt.len() <= full_prompt.len());
|
| 214 |
+
assert!(tight_prompt.contains("ring buffer") || tight_prompt.contains("You are Miranda"));
|
| 215 |
+
}
|