pub struct RunState { /* private fields */ }Expand description
Durable state derived by replaying one run’s ordered event ledger.
The state machine performs no IO. Hosts inspect Self::pending_command for
requested effects and Self::pending_compaction_turn for the narrow
post-compaction context gate, then apply the resulting recorded event.
A HarnessEvent::ModelFailed restores the identical pending model command
without advancing its step.
§Examples
A tool turn advances only after the proposed call is validated, approved, executed, and recorded:
use platonic_core::*;
use serde_json::json;
let run_id = RunId::new("run-1")?;
let turn_id = TurnId::new("turn-1")?;
let call_id = ToolCallId::new("call-1")?;
let tool = ToolName::new("file.write")?;
let input = json!({"path": "note.txt", "content": "done"});
let proposal = ToolProposal {
tool: tool.clone(),
input: input.clone(),
};
let call = ToolCall {
id: call_id.clone(),
tool,
effect: EffectClass::WorkspaceWrite,
input,
};
let events = vec![
HarnessEvent::RunStarted {
run_id: run_id.clone(),
agent_id: AgentId::new("agent-1")?,
},
HarnessEvent::ContextBuilt {
run_id: run_id.clone(),
turn_id: turn_id.clone(),
context: ContextPack { token_budget: 10, fragments: vec![] },
},
HarnessEvent::ModelRequested {
run_id: run_id.clone(),
turn_id: turn_id.clone(),
step: 0,
model: ModelName::new("model-1")?,
},
HarnessEvent::ModelResponded {
run_id: run_id.clone(),
turn_id: turn_id.clone(),
step: 0,
output: Message {
role: MessageRole::Assistant,
content: "I will write the file.".into(),
},
proposed_calls: vec![proposal],
served_model: None,
usage: Some(ModelUsage { input_tokens: 3, output_tokens: 5 }),
},
HarnessEvent::ToolCallProposed {
run_id: run_id.clone(),
turn_id,
call: call.clone(),
},
HarnessEvent::PolicyEvaluated {
run_id: run_id.clone(),
call_id: call_id.clone(),
decision: PolicyDecision::RequireApproval {
reason: "workspace write".into(),
},
},
HarnessEvent::ApprovalGranted {
run_id: run_id.clone(),
call_id: call_id.clone(),
actor_id: ActorId::new("human-1")?,
},
HarnessEvent::ToolStarted {
run_id: run_id.clone(),
call_id: call_id.clone(),
},
HarnessEvent::ToolFinished {
run_id: run_id.clone(),
result: ToolResult {
call_id,
summary: "wrote note.txt".into(),
data: json!({}),
artifacts: vec![],
visibility: ResultVisibility::Both,
},
},
HarnessEvent::RunFinished { run_id },
];
let mut state = RunState::new();
for (seq, event) in events.into_iter().enumerate() {
state.apply(&RecordedEvent {
seq: seq as u64,
occurred_at_ms: 0,
event,
})?;
if seq == 5 {
assert!(matches!(state.pending_command(), Some(RunCommand::AwaitApproval { .. })));
}
if seq == 6 {
assert!(matches!(state.pending_command(), Some(RunCommand::ExecuteTool { .. })));
}
}
assert_eq!(state.phase(), &RunPhase::Finished);Implementations§
Source§impl RunState
impl RunState
Sourcepub fn run_id(&self) -> Option<&RunId>
pub fn run_id(&self) -> Option<&RunId>
Returns the bound run id, or None before run_started is applied.
Sourcepub fn phase(&self) -> &RunPhase
pub fn phase(&self) -> &RunPhase
Returns the phase derived from all successfully applied events.
Sourcepub fn pending_compaction_turn(&self) -> Option<&TurnId>
pub fn pending_compaction_turn(&self) -> Option<&TurnId>
Returns the turn whose compacted context must be built next.
After an accepted HarnessEvent::ContextCompacted, Self::phase
intentionally remains the surrounding stable phase and
Self::pending_command returns None. While this returns Some, only
a matching HarnessEvent::ContextBuilt or terminal
HarnessEvent::RunFailed can be accepted. Either accepted event clears
the pending turn.
Sourcepub fn pending_command(&self) -> Option<RunCommand>
pub fn pending_command(&self) -> Option<RunCommand>
Derives the pending host IO command without mutating run state.