209 lines
11 KiB
Markdown
209 lines
11 KiB
Markdown
# Phase 1 actor prototype
|
|
|
|
Start the interactive, in-memory runtime session with:
|
|
|
|
```sh
|
|
mise run run
|
|
```
|
|
|
|
The executable loads [counter.rn](../runa-std/actors/counter.rn) as the default
|
|
behavior. Commands include `spawn`, `send`, `tick`, `inspect`, `actors`,
|
|
`events`, `stop`, `restart`, `pause`, `resume`, `cancel`, `behaviors`,
|
|
`publish`, `history`, `rollback`, and `install`; enter `help` to see usage.
|
|
`publish` reads a Rune source file and takes `new` for a first revision or the
|
|
expected current revision for an update. `rollback` requires the expected
|
|
current revision and a target historical revision. `install` selects a named
|
|
behavior's current implementation and fails if the actor is not at an idle
|
|
handler boundary.
|
|
`spawn` accepts an optional behavior name and otherwise uses `counter`. All
|
|
commands operate on the same process-local runtime and code store. 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 <count> [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, caches compiled
|
|
artifacts, and provides an in-memory named behavior registry with revisions.
|
|
- `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.
|
|
|
|
`CodeStore::publish_behavior` compiles source before appending a behavior
|
|
revision. Creation expects no existing revision; updates require the caller's
|
|
expected current revision, so stale edits fail without changing the behavior
|
|
reference. Failed compilation also leaves the reference and history unchanged
|
|
(the exact source remains available for diagnostics). Rollback points the
|
|
behavior at an earlier immutable implementation and appends a new monotonically
|
|
increasing revision; it does not rewrite history. Behavior names are
|
|
case-sensitive paths limited to ASCII letters, digits, `_`, `-`, `.`, and `/`,
|
|
with empty, `.` and `..` path components rejected.
|
|
|
|
The runtime's `install` operation switches an actor's implementation only
|
|
when it is idle, has no active invocation, and has no reserved input. Queued
|
|
messages may remain; they will use the newly installed implementation. Active
|
|
or awaiting handlers keep their original implementation, and installation
|
|
attempts during execution fail explicitly so the caller can retry at the next
|
|
handler boundary. `install` preserves the current `i64` state unchanged.
|
|
|
|
Runtime callers that need a state transformation can use
|
|
`install_with_migration`, which receives the committed state and computes its
|
|
replacement before changing either state or implementation. The operation
|
|
requires the same idle safe point; if the migration returns an error, neither
|
|
value changes and no installation event is emitted. A successful changed
|
|
state emits `StateMigrated` as well as `BehaviorInstalled`. This prototype
|
|
accepts a host-provided migration closure only: it does not infer schema
|
|
compatibility or execute migration code from Rune source. Its state contract
|
|
is still `i64`, so structured schema/version migration remains future work.
|
|
|
|
`pause` prevents an actor from being scheduled on subsequent worker ticks and
|
|
retains its active invocation, reserved message, and prior `Running`, `Awaiting`,
|
|
or `Idle` status. `resume` restores that status; an awaiting invocation resumes
|
|
only if its future has been woken. These operations take effect between calls
|
|
to `tick` and cannot interrupt a currently executing Rune slice or native
|
|
callback. They provide cooperative scheduling control, not preemption.
|
|
|
|
Actor snapshots include basic execution telemetry while an invocation is
|
|
active: completed VM instruction-step count, Rune instruction pointer, and
|
|
call-frame depth. Paused invocations retain the same telemetry. These values
|
|
are execution-local diagnostics only; they do not map to source locations or
|
|
expose locals, and disappear when the invocation completes or is cancelled.
|
|
|
|
`cancel` is distinct from `stop`: it drops an actor's active invocation, moves
|
|
its reserved message to the front of the mailbox, and returns the actor to
|
|
`Idle`. The input is therefore eligible for retry on the next tick, ahead of
|
|
messages that were already queued. Committed state is unchanged and no
|
|
transition effects are applied. Cancellation is also available for paused
|
|
invocations. It does not undo external side effects already performed by
|
|
native callbacks, so retries may repeat such effects.
|
|
|
|
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` |
|
|
| Pause between ticks | Last committed state retained | Retained | None while paused | `Paused` |
|
|
| Resume a paused invocation | Retained until invocation completes | Retained | None until completion | Prior runnable/awaiting status |
|
|
| Cancel an invocation | Last committed state retained | Moved to queue front | None from incomplete transition | `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 failed and cancelled reservations retry from the
|
|
last committed state, that stop/restart preserves reserved-first FIFO order,
|
|
and that cancellation followed by installation retries queued input in order
|
|
with the new implementation. Tests also cover 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 behavior registry or mailboxes,
|
|
transactional outbox, supervisors, API server, GUI, or automatic image
|
|
bootstrap. The behavior registry and implementation installation are
|
|
process-local; installation requires an idle handler boundary and does not
|
|
upgrade active execution. 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.
|