# Public API
`ActorState` is the opaque synchronous owner of chat text, delta boundaries, jobs, accepted update identities, attempts, batching, compaction, force, and halt state.
## Methods
- `ActorState::new(primary:String, force:bool)->Self` creates state with the supplied primary and force flag, an initial sent boundary of zero, and no pending text, history, jobs, active operation, batch, or halt.
- `ActorState::view(&self)->ChatView` clones primary, pending, and history and returns actions in increasing job-ID order. Inference actions load their current attempt atomically with relaxed ordering; other actions are cloned.
- `ActorState::halted(&self)->bool` reports whether halt text is installed.
- `ActorState::halt(&mut self,text:String)->bool` installs only the first halt and reports whether installation occurred.
- `ActorState::take_halt(&mut self)->Option<String>` removes and returns the installed halt text.
- `ActorState::append_pending(&mut self,text:String)` concatenates the exact text, including an empty string, without a separator.
- `ActorState::restart(&mut self)->Result<(),ChatError>` returns `NotStalled` without a halt and `Busy` while a job or batch cursor is live. Otherwise it appends pending to primary, clears pending, resets the sent boundary, forces inference, and clears the halt.
- `ActorState::begin_inference(&mut self)->Option<(u64,String,Arc<AtomicU8>)>` returns `None` while halted, while inference, compaction, or a batch is active, or when neither forced nor pending. Otherwise it moves pending into primary, clears force, allocates an attempt initialized to one and an ordered inference job, and returns the job, exact `primary[sent..]` delta, and shared attempt.
- `ActorState::finish_inference(&mut self,job:u64)->bool` clears and removes only the matching active inference and reports success.
- `ActorState::commit_output(&mut self,text:&str)` appends exact output to primary and advances the sent boundary past it, excluding that output from the next delta.
- `ActorState::force_inference(&mut self)` sets the force flag.
- `ActorState::begin_tool(&mut self,name:String)->u64` allocates an ordered tool action.
- `ActorState::begin_worker(&mut self,llm:String)->u64` allocates an ordered worker action.
- `ActorState::begin_compaction(&mut self)->(u64,String)` allocates an ordered compaction action, marks it active, and returns its job and a clone of primary.
- `ActorState::reject_compaction_batch(&mut self)` inserts `compaction must be the sole call` at pending byte zero and forces inference.
- `ActorState::begin_batch(&mut self)` starts the tool-reply insertion cursor at byte zero.
- `ActorState::apply_tool_replies(&mut self,entries:Vec<(usize,u64,String,bool)>,finished:bool)` does nothing without a batch. During a batch it sorts by call index, concatenates exact reply text, removes only entries marked complete, inserts at the cursor, forces inference, and either advances the cursor by inserted byte length or clears it when finished.
- `ActorState::complete_action(&mut self,job:u64,text:String)` removes a live action and only then appends exact text to pending and forces inference; stale completion is ignored.
- `ActorState::apply_append_update(&mut self,job:u64,identity:u64,text:String)` accepts only a live job and an identity not previously accepted for that job, then appends exact text and forces inference.
- `ActorState::accept_activity_update(&mut self,job:u64,identity:u64)->bool` records and accepts only a fresh identity for a live job, without changing text or force.
- `ActorState::complete_compaction(&mut self,job:u64,frozen:String,result:Result<String,String>)` ignores a non-active ID. A matching success records frozen in history, replaces primary, appends and clears pending, resets the sent boundary, and forces. A matching failure appends the error and pending to the old primary, preserves history and the sent boundary, and forces, so the next delta is exactly error plus pending.
- `ActorState::quiet(&self)->bool` requires no jobs, pending text, force, or batch cursor. Halt state is intentionally independent.
## Accepted domain and ordering
Job IDs are `u64`, start at one, increase monotonically, and panic clearly only if exhausted. Tool, worker, inference, and compaction results and updates must be routed with IDs returned by their matching begin methods. Callers begin at most one compaction, use `commit_output` for the current inference, complete it with `finish_inference`, and bracket immediate tool replies with one `begin_batch` and a final `finished=true` application. Batch entries use their original call indices and tool job IDs.
The batch cursor starts at zero and advances only by byte lengths of strings previously inserted there. Consequently it is always a UTF-8 boundary even when pending contains multibyte text; ordinary pending additions append after it. Stale completion and compaction IDs and duplicate or stale update identities have no effect. Restart returns its `ChatError` without partial mutation.
## Cost and concurrency
Views and compaction snapshots clone retained text. Job insertion, removal, and ordered traversal cost `O(log J)`, `O(log J)`, and `O(J)` around cloning; update identity acceptance is expected amortized `O(1)` and retains one integer per accepted identity until its job ends. Appends are amortized linear in added bytes, batch sorting is `O(E log E)`, and insertion or prepending is linear in shifted pending bytes. Memory is proportional to retained text, history, live jobs, and accepted identities.
The caller provides exclusive `&mut ActorState` access. The only concurrently observable value is each inference attempt through its `Arc<AtomicU8>` using relaxed operations. Operations create no locks, waits, tasks, channels, callbacks, I/O, or external side effects.