Skip to content

Add layered channel architecture with Akka.Streams session API#14

Merged
Aaronontheweb merged 2 commits into
devfrom
feature/channel-stream-api
Feb 23, 2026
Merged

Add layered channel architecture with Akka.Streams session API#14
Aaronontheweb merged 2 commits into
devfrom
feature/channel-stream-api

Conversation

@Aaronontheweb
Copy link
Copy Markdown
Collaborator

Summary

  • Netclaw.Actors now exposes SessionPipeline (DI service) that creates MaterializedSession instances with typed Akka.Streams: Sink<ChannelInput> for input and Source<SessionOutput> for output. All actor internals (JoinSession, subscriber refs, message routing) are hidden behind the stream API.
  • New Netclaw.Channels project contains IChannel interface, ChannelHealth, and ChannelHealthStatus (moved from Netclaw.Actors.Channels), establishing the layered split between general-purpose LLM runtime and Netclaw-specific channel abstractions.
  • ChannelInput supports multi-modal content via IReadOnlyList<AIContent> from Microsoft.Extensions.AI.Abstractions.
  • ConsoleChannel and HeadlessChannel rewritten to use stream API — no more subscriber actors, channels compose Akka.Streams directly.
  • SessionJoined now extends SessionOutput for proper stream typing.
  • MessageSource added to SendUserMessage ([ProtoIgnore]) for ACL/audit metadata.
  • Implementation plan updated; gateway architecture research notes added.

Test plan

  • dotnet build Netclaw.slnx — 0 warnings, 0 errors
  • dotnet test Netclaw.slnx — 110 passed, 0 failed
  • Headless mode verified with real Ollama LLM (qwen3:30b)
  • Interactive console session verified by user (multi-turn tool use with error recovery)
  • dotnet slopwatch analyze — 0 issues

Split the channel abstraction into a layered architecture:

- Netclaw.Actors exposes SessionPipeline (DI service) that creates
  MaterializedSession instances with typed Akka.Streams:
  - Sink<ChannelInput> for input (channel connects its own Source)
  - Source<SessionOutput> for output (channel connects its own Sink)
  - SharedKillSwitch for coordinated teardown via IAsyncDisposable

- New Netclaw.Channels project contains IChannel interface, ChannelHealth,
  and ChannelHealthStatus (moved from Netclaw.Actors.Channels)

- ChannelInput supports multi-modal content via IReadOnlyList<AIContent>
  from Microsoft.Extensions.AI.Abstractions

- ConsoleChannel and HeadlessChannel rewritten to use stream API -
  no more subscriber actors, channels compose streams directly

- SessionJoined now extends SessionOutput for proper stream typing

- MessageSource added to SendUserMessage ([ProtoIgnore]) for ACL/audit
- Mark shell, file, and source-generated tool schema tasks as complete
- Defer web search/fetch tools (not needed for minimal viable concept)
- Add agent gateway architecture research notes
@Aaronontheweb Aaronontheweb merged commit 2518527 into dev Feb 23, 2026
5 of 6 checks passed
Aaronontheweb added a commit that referenced this pull request Feb 24, 2026
Update PRDs (001, 004, 009), SPEC-011, and IMPLEMENTATION_PLAN to reflect
the OpenClaw-pattern architecture: persistent daemon (Netclaw.Daemon) with
thin CLI/TUI clients (Netclaw.Cli) connecting via SignalR.

Key changes:
- Two binaries: daemon owns Akka/persistence/tools, CLI is lightweight
- TUI chat becomes pure thin client rendering SessionOutput over SignalR
- CLI commands categorized as offline vs daemon-required
- Daemon management commands: start/stop/status/install/uninstall
- systemd user service registration (no sudo)
- New implementation tasks 1.26-1.31 for the split
- Tasks 1.10-1.12 marked as done (PRs #14, #16, #17)
Aaronontheweb added a commit that referenced this pull request Feb 24, 2026
* Revise architecture to daemon + thin client split

Update PRDs (001, 004, 009), SPEC-011, and IMPLEMENTATION_PLAN to reflect
the OpenClaw-pattern architecture: persistent daemon (Netclaw.Daemon) with
thin CLI/TUI clients (Netclaw.Cli) connecting via SignalR.

Key changes:
- Two binaries: daemon owns Akka/persistence/tools, CLI is lightweight
- TUI chat becomes pure thin client rendering SessionOutput over SignalR
- CLI commands categorized as offline vs daemon-required
- Daemon management commands: start/stop/status/install/uninstall
- systemd user service registration (no sudo)
- New implementation tasks 1.26-1.31 for the split
- Tasks 1.10-1.12 marked as done (PRs #14, #16, #17)

* Split Netclaw.App into Netclaw.Daemon and Netclaw.Cli

Structural split following the daemon + thin client pattern:

- Netclaw.Daemon (netclawd): Web SDK, actor system, SignalR hub,
  config watcher, health endpoint
- Netclaw.Cli (netclaw): CLI routing (chat, -p, init, doctor,
  daemon stub, config stub), TUI, headless channel

Shared types moved to library projects:
- Config POCOs (ProviderEntry, ModelSelection, ModelReference) →
  Netclaw.Configuration
- SessionOutputDto → Netclaw.Actors.Protocol

CLI temporarily keeps full Akka stack in-process with transitional
copies of ChatClientFactory/NetclawChatClientProvider. Task 1.28
refactors to SignalR client and removes Akka dependencies.

* Wire SessionHub to SessionPipeline via SessionRegistry

Replace the stub SessionHub with a functional implementation that
creates sessions, accepts messages, and streams output back to
SignalR callers. SessionRegistry (singleton) owns session state and
uses IHubContext to push output from Akka.Streams callbacks without
requiring a hub instance.

* Add daemon management commands (start, stop, status, install, uninstall)

DaemonManager handles the full lifecycle: binary discovery, detached
process spawning with PID file tracking, graceful SIGTERM shutdown on
Linux/macOS, and systemd user service registration. Windows service
support tracked in #21.

* Fix review issues and update README for daemon architecture

Review fixes:
- CLI port conflict: use port 0 (random) for transitional in-process mode
- SessionHub: call base.OnDisconnectedAsync for framework cleanup
- SessionRegistry: log output delivery errors instead of swallowing,
  guard against duplicate CreateSession per connection, check OfferAsync
  result for queue failures
- DaemonManager: don't redirect stdio (prevents SIGPIPE / pipe deadlock),
  validate process name before SIGTERM to prevent PID recycling kills,
  check kill() return value, proper DateTimeOffset for uptime calc
- Eliminate duplicate NetclawPaths instances in both Program.cs files

README updated to reflect daemon + thin client architecture with CLI
reference documentation.

* Harden daemon session ownership and PID handling

Enforce connection-scoped SignalR sessions with typed IDs, preserve sessions for reconnect, and tighten daemon PID/process validation for safer lifecycle commands. Add targeted tests and daemon PID file ownership to keep status/stop behavior reliable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant