# Public API
`kcode-k1-chat-state` owns provider-free scheduling around one boxed `Chatend`.
```rust
pub use kcode_k1_chat_chatend::{
ActionId, BoxContent, BoxId, ChatBox, DispatchCall, DispatchOutcome, TransitionError,
};
pub struct InferenceStart {
pub job: u64,
pub frontier: Option<BoxId>,
pub attempt: Arc<AtomicU8>,
}
pub enum StateError {
Transition(TransitionError),
WrongInference { expected: Option<u64>, actual: u64 },
JobIdExhausted,
NotStalled,
Busy,
}
pub struct ActorState { /* private fields */ }
impl ActorState {
pub fn new(force: bool) -> Self;
pub fn boxes(&self) -> &[ChatBox];
pub fn halted(&self) -> bool;
pub fn halt(&mut self, text: String) -> bool;
pub fn take_halt(&mut self) -> Option<String>;
pub fn restart(&mut self) -> Result<(), StateError>;
pub fn accept_system(&mut self, text: String) -> Result<(), StateError>;
pub fn accept_user(&mut self, text: String) -> Result<(), StateError>;
pub fn accept_attachment(&mut self) -> Result<(), StateError>;
pub fn accept_async_return(&mut self, action_id: ActionId, result: Result<String, String>) -> Result<(), StateError>;
pub fn force_inference(&mut self);
pub fn begin_inference(&mut self) -> Result<Option<InferenceStart>, StateError>;
pub fn append_kennedy_text(&mut self, job: u64, text: &str) -> Result<(), StateError>;
pub fn collect_provider_call(&mut self, job: u64, name: String, arguments: String) -> Result<(), StateError>;
pub fn complete_provider_output(&mut self, job: u64, action_ids: Vec<ActionId>) -> Result<Vec<DispatchCall>, StateError>;
pub fn complete_dispatch(&mut self, outcomes: Vec<DispatchOutcome>) -> Result<(), StateError>;
pub fn stall_inference(&mut self, job: u64, text: String) -> Result<(), StateError>;
pub fn quiet(&self) -> bool;
}
```
User, system, attachment, and asynchronous Ktool Return arrivals, plus `force_inference`, set the single scheduling flag. Provider-generated calls enter dispatch but do not schedule an inference. Arrivals while provider or dispatch work is active coalesce behind that barrier; once it clears, one inference drains the full ordered batch. A new inference freezes the last visible box, opens one Kennedy, and owns a fresh monotonic job and shared attempt counter. A stalled inference retains that open Kennedy and frontier; taking its halt text does not discard the stalled round, and `restart` makes that same round retryable under a fresh job. `quiet` excludes active, stalled, retryable, dispatching, forced, and arrival-scheduled work, but ignores an unresolved asynchronous action after its continuation.
All canonical ordering, ID assignment, box immutability, call collection, dispatch validation, originating-call lookup, and arrival draining remain owned by `Chatend`. State transition failures are preserved through `StateError::Transition`; wrong jobs, exhausted job IDs, a non-stalled restart, and a restart during active or dispatch work do not mutate state. Halt text is first-write until taken, and `restart` errors are `StateError::NotStalled` or `StateError::Busy` without partial mutation.
The r6 transcript strings, byte cursors, history snapshots, compaction, batches, numeric tool and worker jobs, progress text, and `ChatView` are removed. Serialization, persistence, crash recovery, replay, redelivery, migration, attachment data, hidden metadata, time notices, native receipts, provider projection, runtime, UI, and clocks are deferred. Operations are constant work apart from delegated `Chatend` transitions, retained input sizes, text append, returned dispatch vectors, and atomic allocation.