PicoActor.Abs
2026.1.0
See the version list below for details.
dotnet add package PicoActor.Abs --version 2026.1.0
NuGet\Install-Package PicoActor.Abs -Version 2026.1.0
<PackageReference Include="PicoActor.Abs" Version="2026.1.0" />
<PackageVersion Include="PicoActor.Abs" Version="2026.1.0" />
<PackageReference Include="PicoActor.Abs" />
paket add PicoActor.Abs --version 2026.1.0
#r "nuget: PicoActor.Abs, 2026.1.0"
#:package PicoActor.Abs@2026.1.0
#addin nuget:?package=PicoActor.Abs&version=2026.1.0
#tool nuget:?package=PicoActor.Abs&version=2026.1.0
PicoActor
AOT-compatible, in-memory Actor framework with Event Sourcing for .NET. Lightweight, zero-reflection, designed for agentic AI systems and workflow orchestration. Runs under NativeAOT and trimming.
English | 简体中文 | 日本語 | Español | Português | 繁體中文 | 한국어 | Français | Deutsch | Русский
Computational Model
┌─────────────────────────────────────────────────┐
│ ActorSystem │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Counter │ │ Agent │ │ Session │ ... │
│ │ (Actor) │ │ (Actor) │ │ (Actor) │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ IEventStore │ │
│ │ AppendAsync / LoadAsync │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Each actor owns a mailbox (in-memory Channel<Envelope>), a UUID v7
identity, and a single-threaded consumption loop. Commands are delivered
via Send (fire-and-forget) or AskAsync (request-reply).
Event-Sourced actors follow Persist-then-Mutate:
OnMessageAsync → RaiseEvent → Persist to IEventStore → Mutate state.
State is only mutated after successful persistence — the in-memory state
is always consistent with the event stream.
Why PicoActor
| Concern | Existing Options | PicoActor |
|---|---|---|
| AOT / Trimming | ❌ Akka.NET, Proto.Actor, Orleans all require reflection | ✅ Full NativeAOT support |
| Event Sourcing | ❌ Proto.Actor, Orleans lack built-in ES | ✅ Persist-then-Mutate, automatic rollback |
| Dependency Size | ❌ Akka.NET (8+ packages), Orleans (10+ packages) | ✅ 2 packages, zero dependency beyond Channels |
| DI Integration | ❌ Tied to Microsoft.Extensions.DI | ✅ Native PicoDI, zero-reflection resolution |
| netstandard2.0 | ⚠️ Akka.NET / Proto.Actor only | ✅ Abstractions target netstandard2.0 |
| Learning Curve | ❌ Steep — supervision trees, clustering, remoting | ✅ Minimal — actors + events + mailbox |
Quick Start
dotnet add package PicoActor
using PicoActor;
using PicoActor.Abs;
// 1. Setup
var store = new InMemoryEventStore();
var system = new ActorSystem(store);
// 2. Register actor factories
system.Register<Counter>(
createFactory: cmd => cmd switch
{
CreateCounter c => new Counter(c),
_ => throw new InvalidOperationException()
},
rebuildFactory: () => new Counter()
);
// 3. Create, message, stop, rebuild
var counter = await system.CreateAsync<Counter>(new CreateCounter(42));
system.Send(counter.Id, new Increment(5));
var value = await system.AskAsync<int>(counter.Id, new GetValue());
await system.StopAsync(counter.Id);
var rebuilt = await system.GetAsync<Counter>(counter.Id);
Module Details
PicoActor.Abs — Core Abstractions
Targets netstandard2.0 for maximum compatibility. Contains all
interfaces and base classes.
| Type | Role |
|---|---|
IActor |
Base interface — provides Id (UUID v7) |
IActorSystem |
Runtime contract — Register, CreateAsync, GetAsync, Send, AskAsync, StopAsync |
ICommand |
Marker interface for commands |
IDomainEvent |
Marker interface for domain events |
IEventSourcedActor |
Optional interface — Version, ReplayEvents, CommitEvents |
IEventStore |
Persistence contract — AppendAsync (optimistic concurrency), LoadAsync |
ICancelable |
Optional — CancelCurrentTurn for long-running operations |
Actor |
Abstract base — mailbox, consumption loop, SignalReady, StopAsync |
EventSourcedActor |
ES base — RaiseEvent, Mutate, Persist-then-Mutate pipeline |
Envelope |
Internal — wraps ICommand with optional TaskCompletionSource |
ActorOutputEvent |
Outbound notification — Type, Data, optional ToolCallId/ToolName/TurnId |
ConcurrencyException |
Thrown by IEventStore on version mismatch |
PicoActor — Runtime
Targets net10.0, AOT-compatible.
| Type | Role |
|---|---|
ActorSystem |
Default IActorSystem — ConcurrentDictionary registry, factory registration, message routing, CancelTurn |
InMemoryEventStore |
Lock-free in-memory store — ConcurrentDictionary-backed |
ActorConfig |
Configuration POCO — bind from PicoCfg |
ActorSystemOptions |
Options with optional IEventStore and ILogger |
PicoActorDiExtensions |
AddPicoActor() extension method for PicoDI |
Actor (Non-ES)
Inherit from Actor for pure in-memory operational actors.
public sealed class EchoActor : Actor
{
protected override ValueTask<object?> OnMessageAsync(ICommand command)
=> new ValueTask<object?>(command);
}
Event-Sourced Actor
Inherit from EventSourcedActor. Override OnMessageAsync to call
RaiseEvent, and Mutate to apply state changes.
public sealed class Counter : EventSourcedActor
{
private int _value;
public Counter(CreateCounter cmd) : base(cmd) { }
public Counter() { }
protected override ValueTask<object?> OnMessageAsync(ICommand command)
{
switch (command)
{
case Increment i:
RaiseEvent(new CounterIncremented(i.Delta));
break;
case GetValue:
return new ValueTask<object?>(_value);
}
return default;
}
protected override void Mutate(IDomainEvent @event)
{
switch (@event)
{
case CounterIncremented e: _value += e.Delta; break;
}
}
}
Persist-then-Mutate Pipeline
OnMessageAsync → RaiseEvent (record only, no state change)
→ AppendAsync (persist to IEventStore)
→ Mutate (apply events to in-memory state)
→ Reply to caller (only after success)
If AppendAsync fails, uncommitted events are discarded and Version
is rolled back. The actor is not poisoned — the next message
processes cleanly.
Messaging: Ask vs Send
| Pattern | Method | Semantics |
|---|---|---|
| Request-Reply | AskAsync<TResult>(id, command) |
Returns result after message processing |
| Fire-and-Forget | Send(id, command) |
No reply; exceptions routed to UnhandledErrorHandler |
OutputChannel
Actors broadcast ActorOutputEvent messages to external subscribers:
counter.OutputWriter = channel.Writer;
// Inside OnMessageAsync:
WriteOutput("Incremented", data: "Delta=5");
CancelTurn
Cancel long-running operations without stopping the actor:
public sealed class MyActor : Actor, ICancelable
{
private CancellationTokenSource? _currentTurnCts;
public void CancelCurrentTurn() => _currentTurnCts?.Cancel();
protected override async ValueTask<object?> OnMessageAsync(ICommand command)
{
_currentTurnCts = CancellationTokenSource.CreateLinkedTokenSource(StopToken);
// ... long-running work with _currentTurnCts.Token
}
}
system.CancelTurn(actor.Id);
Spawn
Actors create child actors via System reference:
protected override async ValueTask<object?> OnMessageAsync(ICommand command)
{
if (command is SpawnChild s)
{
var child = await System!.CreateAsync<Worker>(s.Cmd);
return child.Id;
}
return default;
}
Module Integration
PicoDI
var container = new SvcContainer();
container.AddPicoActor(); // Registers IActorSystem + InMemoryEventStore
// Custom event store
var store = new InMemoryEventStore();
container.AddPicoActor(store);
// From PicoCfg
var cfg = CfgBind.Bind<ActorConfig>(configuration, "Actor");
container.AddPicoActor(cfg);
IActorSystem is a Singleton. If ILoggerFactory is registered,
a logger is automatically injected.
Custom IEventStore
public sealed class PostgresEventStore : IEventStore
{
public ValueTask<ulong> AppendAsync(Guid actorId, ulong expectedVersion,
IReadOnlyList<IDomainEvent> events) { /* INSERT with concurrency check */ }
public ValueTask<IReadOnlyList<IDomainEvent>> LoadAsync(Guid actorId)
{ /* SELECT ordered by version */ }
}
Design Philosophy (克制 / 专注 / 优雅 / 高效)
| Principle | In Practice |
|---|---|
| 克制 (Restraint) | No distributed consensus, no supervision trees — just actors and events. |
| 专注 (Focus) | Single-threaded per-actor. One message at a time. |
| 优雅 (Elegance) | Persist-then-Mutate: state changes only after persistence. Rollback is automatic. |
| 高效 (Efficiency) | AOT-compatible, zero reflection, netstandard2.0 abstractions. |
Use Cases
- Agentic AI systems — each AI agent is an actor with conversation state
- Workflow orchestration — actors model long-running business processes
- Game server state — event-sourced actors for player/game state
- IoT device state — in-memory actors with periodic snapshotting
Packages
| Package | Target | Description |
|---|---|---|
| PicoActor.Abs | netstandard2.0 |
Core abstractions: IActor, IActorSystem, ICommand, IDomainEvent, IEventStore, Actor, EventSourcedActor |
| PicoActor | net10.0 |
Runtime: ActorSystem, InMemoryEventStore, PicoDI integration |
Comparison
| Feature | PicoActor | Akka.NET | Proto.Actor | Orleans |
|---|---|---|---|---|
| In-memory only | ✅ | ✅ | ✅ | ❌ |
| AOT / Trimming | ✅ | ❌ | ❌ | ❌ |
| Event Sourcing | ✅ | ✅ | ❌ | ❌ |
| netstandard2.0 abstractions | ✅ | ✅ | ✅ | ❌ |
| PicoDI integration | ✅ | ❌ | ❌ | ❌ |
| Persist-then-Mutate | ✅ | ❌ | ❌ | ❌ |
| Distributed / Clustering | ❌ | ✅ | ✅ | ✅ |
| Single-threaded per actor | ✅ | ✅ | ✅ | ❌ |
| Packages | 2 | 8+ | 3+ | 10+ |
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.9)
- System.Threading.Channels (>= 10.0.9)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on PicoActor.Abs:
| Package | Downloads |
|---|---|
|
PicoActor
In-memory Actor runtime for PicoNode. Provides ActorSystem (IActorSystem implementation), InMemoryEventStore, and PicoDI integration. |
GitHub repositories
This package is not used by any popular GitHub repositories.