# Phase 1 actor prototype Start the interactive, in-memory runtime session with: ```sh cargo run -p runa ``` The executable loads [counter.rn](../runa-std/actors/counter.rn) as the default behavior. Commands include `spawn`, `send`, `tick`, `inspect`, `actors`, `events`, `stop`, and `restart`; enter `help` to see usage. All commands operate on the same process-local runtime. This CLI is for local development, not a versioned external API. Example: ```text runa> spawn 0 spawned ActorId(1) runa> spawn 10 spawned ActorId(2) runa> send 1 1 2 message queued runa> tick 100 runa> inspect 1 runa> inspect 2 ``` The sender commits state 1 and sends a structured message; the receiver commits state 15. `tick [instruction-budget]` explicitly advances the scheduler. The default per-actor slice budget is 32 instructions. ## Current contracts - `runa-actor` owns actor, mailbox and implementation IDs, structured messages, snapshots, statuses and lifecycle events. - `runa-core` owns stable source and implementation identity types. - `runa-code` preserves original source, derives identities, and caches compiled artifacts for the process lifetime. - `runa-rune` owns compilation and typed bounded invocation execution. - `runa-runtime` owns implementation registration, queues, private committed state, scheduling, message effects and lifecycle coordination. - `runa` composes the experiment and demonstration. An implementation is published once under an ID. Actors reference that exact implementation. Implementation IDs use source content and the compile fingerprint; actor and mailbox IDs remain process-local counters. Actor IDs and incarnation counters use checked increments. The source identity hashes the exact original UTF-8 bytes with the versioned `runa-source:v1` domain. Whitespace/comment canonicalization is intentionally not attempted; textually different source has a different identity. The implementation identity includes the source ID, pinned Rune version, compiler settings/features, registered native-module manifest, runtime ABI version, and Cargo target triple. The native-module manifest and runtime ABI identifier must be updated when those compile inputs change. The cache is in-memory only. Invalid source is retained in the current `CodeStore` for diagnostics, but does not produce a compiled artifact. Cache identity is not a claim that Rune bytecode is portable or safe to persist. `CodeStore::compile_with_module` requires an explicit stable module identity; callers must change it when registered native functions or their contracts change. The CLI currently creates a fresh `CodeStore` when it starts. Prototype state is an `i64`; messages contain integer `kind` and `value` fields. Rune handlers have this deliberately restricted interface: ```text handle(state, kind, value) -> (new_state, [(target_actor_id, kind, value), ...]) ``` The returned effect vector describes sends; handlers cannot access the runtime or directly mutate committed state. General schema/versioned values and capability-bearing contexts are future work. ## Scheduling and lifecycle `tick(nonzero_budget)` gives each runnable actor one bounded slice in ID order. FIFO queues are bounded, including the reserved message in capacity accounting. A queued message is reserved when its invocation starts. Subsequent messages stay queued, including while the actor awaits. A wake marks an awaiting actor runnable; the caller still needs to drive ticks. Successful handlers validate every send target and its aggregate queue capacity before updating state, acknowledging the reserved message, and enqueueing effects. Invalid effects fail the actor without partial sends or a state commit. This is a **logical, single-worker in-memory atomicity guarantee**: observers using the runtime see either the transition before commit or after commit, not an intermediate state/effect set. It is not crash-safe or durable. External side effects performed by native callbacks are outside this boundary and cannot be rolled back. Runtime errors retain the last committed state and reserved message, set an observable failed status, and emit a failure event. The failed message is not silently acknowledged or automatically retried. External sends return errors for unknown, unavailable, or full actors. Stopping drops the invocation, retaining committed state, mailbox and reserved message. Restarting a stopped or failed actor preserves its actor ID, mailbox ID, implementation ID, committed state, and queued-message order; it increments its incarnation and puts the reserved message at the front of the queue. Restart is explicit. Repeated failure of the same reserved message can therefore block later messages until an operator changes the implementation or otherwise resolves it; there is no automatic poison-message policy or supervision. ### Transition and retry guarantees | Situation | State | Reserved input | Effects | Status | | --- | --- | --- | --- | --- | | Handler returns a valid transition | New state commits | Acknowledged | All validated sends enqueue | `Idle` | | Rune execution or transition decoding fails | Last committed state retained | Retained | None from the returned transition | `Failed` | | Any target is unknown, stopped, failed, or over capacity | Last committed state retained | Retained | No sends from the transition enqueue | `Failed` | | Stop during an invocation | Last committed state retained | Retained | None from the incomplete transition | `Stopped` | | Restart after stop or failure | Retained until retried | Moved to queue front | None until retry completes | `Idle` | Mailbox capacity counts queued plus reserved messages. Before applying a transition, the runtime validates all effects, including aggregate sends to a single target. For a self-send, the completing input is acknowledged as part of the same transition, freeing its reserved slot before the new effects are enqueued. An invalid target or over-capacity effect rejects the entire returned transition. These are process-local guarantees only. A host crash loses state, mailboxes, reservations and events. External/native callback effects may already have occurred even when the Rune invocation later fails or is stopped. Lifecycle tests verify that a failed reservation retries from the last committed state, that stop/restart preserves reserved-first FIFO order, and that a running actor's queued messages are not processed until the reservation finishes. Transition tests cover malformed results, runtime failures, invalid targets, aggregate capacity, and successful message effects. ## Limits This is not yet a full image: no persistent mailboxes, transactional outbox, supervisors, hot upgrades, API server, GUI, or automatic image bootstrap. The example default script is bundled through `include_str!`. Messages are not accepted by stopped/failed actors. Native functions must be nonblocking. Dropping a future proves cleanup only for the controlled test operation, not rollback of arbitrary external effects. Instruction slices cannot interrupt a native callback. The [execution decision](decisions/0001-worker-local-execution.md) records the scheduling trade-offs. Heap profiling and source-level debugging remain open.