A newer version of the Streamlit SDK is available: 1.58.0
Phase 0/1/2 Integration Guide
This document explains how the three phases of the agentic quantum dot tuning system integrate together.
Architecture Overview
┌─────────────────────────────────────────────────────────────────────┐
│ EXECUTIVE AGENT (Phase 2) │
│ Main orchestration loop (Fig. 2) │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────┐ ┌─────────────┐
│ PERCEPTION │ │ PLANNING │ │ HARDWARE │
│ (Phase 1) │ │ (Phase 2) │ │ (Phase 0) │
└────────────────┘ └────────────┘ └─────────────┘
│ │ │
│ - DQCGatekeeper │ - BeliefUpdater │ - DeviceAdapter
│ - InspectionAgent │ - ActiveSensingPolicy│ - SafetyCritic
│ - EnsembleCNN │ - MultiResBO │ - HITLManager
│ - OOD Detector │ - StateMachine │ - GovernanceLogger
│ │ - TranslationAgent │
└───────────────────┴──────────────────────┴─────────────┘
│
▼
┌─────────────────────┐
│ EXPERIMENT STATE │
│ (Phase 0) │
│ Single source of │
│ truth │
└─────────────────────┘
Data Flow: One Complete Loop
1. Active Sensing (Phase 2 Planning)
ActiveSensingPolicy.select() examines ExperimentState.belief and proposes a measurement:
plan = sensing_policy.select(state.belief, v1_range, v2_range)
# → MeasurementPlan(modality=COARSE_2D, v1_range=(-0.5, 0.5), ...)
2. Translation (Phase 2 Agent)
TranslationAgent.execute() converts the plan to a DeviceAdapter call:
result = translator.execute(plan)
measurement = result.measurement # Measurement from CIMSimulatorAdapter
state.add_measurement(measurement)
3. Data Quality Check (Phase 1 Perception)
DQCGatekeeper.assess() runs physics-based quality checks:
dqc_result = dqc.assess(measurement)
state.add_dqc_result(dqc_result)
if dqc_result.quality == DQCQuality.LOW:
# STOP — do not pass to InspectionAgent
return failure_result
4. Classification (Phase 1 Perception)
InspectionAgent.inspect() runs the full perception pipeline:
if measurement.is_2d:
classification, ood_result = inspector.inspect(measurement, dqc_result)
state.add_classification(classification)
state.add_ood_result(ood_result)
InspectionAgent internally:
- Log-preprocesses the array
- Runs EnsembleCNN (5-model ensemble)
- Extracts physics features (FFT peak ratio, diagonal strength)
- Checks for physics override
- Runs OOD detector on penultimate features
- Generates structured NL summary
5. Belief Update (Phase 2 Planning)
BeliefUpdater updates the POMDP belief using CIM observation model:
belief_updater.update_from_2d(measurement, classification)
# Updates state.belief.charge_probs in-place
For line scans (bypass InspectionAgent per blueprint §5.1):
if measurement.modality == MeasurementModality.LINE_SCAN:
belief_updater.update_from_1d(measurement)
6. Bayesian Optimization (Phase 2 Planning)
MultiResBO.propose() uses GP with CIM-informed priors:
bo.update(state.bo_history)
proposal = bo.propose(current=state.current_voltage, l1_max=0.10)
# → ActionProposal(delta_v=VoltagePoint(vg1=0.05, vg2=0.02), ...)
7. Safety Critic (Phase 0 Hardware)
SafetyCritic enforces hard constraints:
proposal = safety_critic.clip(proposal, state.current_voltage)
verdict = safety_critic.verify(state.current_voltage, proposal)
if not verdict.all_passed:
state.record_safety_violation()
return failure_result
8. Risk Assessment + HITL (Phase 0 Governance)
HITLManager.compute_risk_score() aggregates 12 trigger conditions:
risk = hitl_manager.compute_risk_score(
proposal=proposal,
safety_verdict=verdict,
dqc_flag=dqc_result.quality.value,
ood_score=ood_result.score,
ensemble_disagreement=classification.ensemble_disagreement,
consecutive_backtracks=state.consecutive_backtracks,
step=state.step + 1,
)
if risk >= 0.70:
event = hitl_manager.queue_request(...)
event = hitl_manager.await_decision(event) # BLOCKS
state.add_hitl_event(event)
9. Execute Move (Phase 2 Agent + Phase 0 Hardware)
If all checks pass:
translator.execute_voltage_move(
vg1=state.current_voltage.vg1 + safe_dv.vg1,
vg2=state.current_voltage.vg2 + safe_dv.vg2,
)
state.apply_move(safe_dv)
10. Governance (Phase 0)
GovernanceLogger records every action:
decision = Decision(
run_id=state.run_id,
step=state.step,
intent="voltage_move",
stage=state.stage,
observation_summary={...},
action_summary={...},
rationale="...",
)
state.add_decision(decision)
governance_logger.log(decision)
11. State Machine (Phase 2 Planning)
StateMachine.process_result() manages stage transitions:
result = navigation_result(target_reached=True, belief_confidence=0.85)
new_stage, rationale, hitl_triggered = state_machine.process_result(result)
if new_stage != state.stage:
state.advance_stage(new_stage)
Backtracking on failure:
if retries_exhausted:
event = BacktrackEvent(from_stage=NAVIGATION, to_stage=CHARGE_ID, ...)
state.record_backtrack(event)
Key Integration Points
BeliefState (Phase 0 stub → Phase 2 implementation)
Phase 0 defined the interface:
@dataclass
class BeliefState:
charge_probs: Dict[tuple, float] = field(default_factory=dict)
uncertainty_map: Optional[Any] = None
device_params: Dict[str, float] = field(default_factory=lambda: {...})
def entropy(self) -> float: ...
def most_likely_state(self) -> Optional[tuple]: ...
def initialise_uniform(self, charge_states: Optional[List[tuple]] = None) -> None: ...
Phase 2 implemented the particle filter:
class BeliefUpdater:
def __init__(self, belief: BeliefState, ...):
self.belief = belief # Operates on Phase 0 stub directly
self._particles = _ParticleSet(...) # Internal engine
def update_from_2d(self, measurement, classification):
# Updates self.belief.charge_probs in-place
self.belief.charge_probs = self._particles.to_charge_probs()
Classification Flow (Phase 1 → Phase 2)
InspectionAgent output:
classification = Classification(
measurement_id=...,
label=ChargeLabel.DOUBLE_DOT,
confidence=0.85,
ensemble_disagreement=0.12,
features={...},
physics_override=False,
nl_summary="...",
)
Used by multiple Phase 2 components:
- BeliefUpdater: Uses
labelandconfidenceto boost particle weights;physics_override=True→ inflates uncertainty - MultiResBO: Creates BOPoints from classifications;
confidencebecomes the reward signal - StateMachine: Uses
confidenceto determine if stage success threshold is met - HITLManager: Uses
ensemble_disagreementin risk score computation
Safety Integration (Phase 0 → Phase 2)
ExecutiveAgent always calls SafetyCritic before any move:
proposal = bo.propose(...) # Phase 2 proposes
proposal = safety_critic.clip(proposal, current) # Phase 0 clips
verdict = safety_critic.verify(current, proposal) # Phase 0 verifies
if verdict.all_passed:
translator.execute_voltage_move(...) # Phase 2 executes
state.apply_move(proposal.safe_delta_v) # Phase 0 records
else:
state.record_safety_violation()
No component can bypass SafetyCritic — it's architecturally enforced.
HITL Integration (Phase 0 → Phase 2)
HITLManager is called at two points:
Risk score computation (every voltage move):
risk = hitl_manager.compute_risk_score(...) if risk >= 0.70: event = hitl_manager.queue_request(...) event = hitl_manager.await_decision(event) # BLOCKSState machine backtracking (condition 8):
if state.consecutive_backtracks >= 2: # StateMachine returns hitl_triggered=True # ExecutiveAgent queues HITL event
No timeout auto-approval — agent blocks until human decides.
Running the Integrated System
Without Trained Models (CI/Development)
# Uses InspectionAgent in stub mode (untrained ensemble, no OOD)
pytest tests/test_integration_phase012.py -v
# Run benchmark without checkpoints
python experiments/benchmark_phase2.py --fast --skip-missing-checkpoints
With Trained Models (Full System)
# 1. Train Phase 1 models
python experiments/train_phase1.py --out experiments/checkpoints/phase1
# 2. Run benchmark
python experiments/benchmark_phase2.py --n-trials 100 --budget 2048
Manual Inspection
from qdot.core.state import ExperimentState
from qdot.simulator.cim import CIMSimulatorAdapter
from qdot.agent.executive import ExecutiveAgent
from qdot.perception.inspector import InspectionAgent
from qdot.core.hitl import HITLManager, HITLOutcome
# Setup
state = ExperimentState.new(device_id="manual_test")
adapter = CIMSimulatorAdapter(device_id="manual_test", seed=42)
inspector = InspectionAgent(ensemble=None, ood_detector=None) # Or load trained
hitl = HITLManager()
hitl.set_test_mode(auto_outcome=HITLOutcome.APPROVED) # No blocking for demo
# Create agent
agent = ExecutiveAgent(
state=state,
adapter=adapter,
inspection_agent=inspector,
hitl_manager=hitl,
max_steps=50,
measurement_budget=2048,
)
# Run
summary = agent.run()
print(f"Success: {summary['success']}")
print(f"Measurements: {summary['total_measurements']}")
print(f"Reduction: {summary['measurement_reduction']:.1%}")
File Organization
qdot/
├── core/ # Phase 0 — Foundation
│ ├── types.py # All canonical types
│ ├── state.py # ExperimentState + BeliefState stub
│ ├── governance.py # GovernanceLogger
│ └── hitl.py # HITLManager
│
├── hardware/ # Phase 0 — Device interface
│ ├── adapter.py # DeviceAdapter ABC
│ └── safety.py # SafetyCritic
│
├── simulator/ # Phase 0 — CIM simulator
│ └── cim.py # ConstantInteractionDevice + CIMSimulatorAdapter
│
├── perception/ # Phase 1 — Perception pipeline
│ ├── dqc.py # DQCGatekeeper
│ ├── inspector.py # InspectionAgent
│ ├── classifier.py # EnsembleCNN
│ ├── features.py # Physics validators
│ ├── ood.py # MahalanobisOOD
│ └── dataset.py # CIMDataset for training
│
├── planning/ # Phase 2 — POMDP + BO
│ ├── belief.py # BeliefUpdater + CIMObservationModel
│ ├── sensing.py # ActiveSensingPolicy
│ ├── bayesian_opt.py # MultiResBO + GaussianProcess
│ └── state_machine.py # StateMachine (5 stages)
│
└── agent/ # Phase 2 — Main loop
├── executive.py # ExecutiveAgent (orchestrator)
└── translator.py # TranslationAgent (NL → API)
Testing Strategy
Unit Tests (per module)
tests/test_types.py— Phase 0 typestests/test_state.py— ExperimentState, BeliefState stubtests/test_safety.py— SafetyCritic + fuzz test (5000 iterations)tests/test_simulator.py— CIM physicstests/test_perception.py— DQC, CNN, OOD, InspectionAgenttests/test_planning.py— Belief, sensing, BO, state machinetests/test_agent.py— TranslationAgent, ExecutiveAgent
Integration Tests
tests/test_integration_phase012.py— Full pipeline smoke tests
Benchmarks
experiments/train_phase1.py --fast— Phase 1 smoke test (CI)experiments/benchmark_phase2.py --fast— Phase 2 quick eval (10 trials)experiments/benchmark_phase2.py --n-trials 100— Full benchmark
Next Steps (Phase 3)
LLM Integration (IBM Granite 3-8B-Instruct)
- Replace template rationales with LLM calls
- ONE call per stage transition + ONE per HITL trigger
- Budget: ~200 tokens per call
Disorder Learning
- OOD flag → fit device-specific disorder map
- Inject into CIMObservationModel.device
- Close sim-to-real gap
Meta-Learning
- Learn CIM parameter priors from device family
- Fast adaptation to new devices
Hardware Validation
- Integrate real Si/SiGe adapter
- Run on QFlow hardware testbed
- Validate ≥50% reduction on real devices
Troubleshooting
"No tests collected" in test_agent.py
Cause: File didn't exist or pytest can't find it.
Fix: Ensure tests/test_agent.py is in the repo root tests/ directory.
"ModuleNotFoundError: No module named 'qdot.planning'"
Cause: Phase 2 modules not in Python path.
Fix: Run pip install -e . from repo root to install in editable mode.
InspectionAgent predictions are random
Cause: Running without trained checkpoints (ensemble=None).
Fix: Either (a) train models with experiments/train_phase1.py, or (b) use --skip-missing-checkpoints flag for testing without models.
HITL blocks forever
Cause: HITLManager not in test mode.
Fix: Call hitl_manager.set_test_mode(auto_outcome=HITLOutcome.APPROVED) for non-blocking testing.
SafetyCritic rejects all moves
Cause: Voltage bounds or l1_max cap too tight.
Fix: Check state.voltage_bounds and state.step_caps["l1_max"] are reasonable for your device.
Phase 0/1/2 integration complete. All components tested individually and end-to-end. Ready for Phase 3.