the new code architecture
The current netsky codebase is easier to explain than the earlier versions because it has a visible center. One binary starts everything, one daemon composes the long-running services, one shared client crate defines the records, and the web app reads the same database-backed state the CLI and daemon write while forwarding a small amount of user input back through the daemon.
the top level #
The workspace is split into small crates with job-shaped names, not a grab bag of modules (Cargo.toml:1-24).
members = [ "src/crates/netsky", "src/crates/netsky-agent", "src/crates/netsky-cli", "src/crates/netsky-client", "src/crates/netsky-codex", "src/crates/netsky-comms", "src/crates/netsky-daemon", "src/crates/netsky-db", "src/crates/netsky-logging", "src/crates/netsky-self", "src/crates/netsky-web", ]
That split matters because the runtime now has an obvious center of gravity. There is no mystery “core” crate carrying half the system by implication. The package names tell you what belongs where.
one binary, explicit entry paths #
src/crates/netsky/src/main.rs is thin on purpose. It checks the argv shape and dispatches to daemon internals for daemon run and daemon supervise-restart; otherwise it falls through to the CLI (src/crates/netsky/src/main.rs:4-23, src/crates/netsky/src/main.rs:34-58).
flowchart TD
binary[netsky binary]
cli[netsky-cli]
daemon[netsky-daemon]
db[(meta.db)]
web[netsky-web]
comms[netsky-comms]
agent[netsky-agent]
binary --> cli
binary --> daemon
daemon --> db
daemon --> web
daemon --> comms
daemon --> agent
cli --> db
This is a better shape than mixing always-on service code and one-shot command code in one long module tree. The entrypoint stays dumb, and the runtime split stays explicit.
the daemon is the composition root #
The most important crate is netsky-daemon.
It opens paths, creates the database connection, constructs the agent runtime, constructs the comms bus, starts the logger, starts the web server, and starts the iMessage poller (src/crates/netsky-daemon/src/lib.rs:169-199, src/crates/netsky-daemon/src/lib.rs:665-694).
The daemon is intentionally boring: it wires dependencies and leaves behavior to smaller crates.
| crate | job |
|---|---|
netsky-daemon | wire the long-running system together |
netsky-cli | operator verbs and one-shot commands |
netsky-client | shared records, paths, and vocabulary |
netsky-db | durable metadata spine |
netsky-comms | message delivery and iMessage/channel I/O |
netsky-agent | manager/worker runtime and Codex sessions |
netsky-web | read-mostly dashboard and API surface |
Supporting crates such as netsky-codex, netsky-logging, and netsky-self keep the Codex bridge, structured logging, and supervised self-update logic out of the higher-level crates (src/crates/netsky-agent/src/lib.rs:3-4, src/crates/netsky-daemon/src/lib.rs:9, src/crates/netsky-daemon/src/lib.rs:1032-1041).
The crate map is only the first half of the story. The more important flow is simple: inbound iMessage is persisted and routed by netsky-comms; netsky-agent ensures a session and executes work; netsky-db holds the resulting records; netsky-web renders that state back to operators.
shared schema first #
netsky-client is small, but it is the crate that keeps the rest honest.
It defines the shared vocabulary for actors, messages, tasks, delivery status, and system paths (src/crates/netsky-client/src/lib.rs:54-105, src/crates/netsky-client/src/lib.rs:107-158). It also defines the record structs that move between the daemon, CLI, web app, and database (src/crates/netsky-client/src/lib.rs:167-224).
This is the quiet win in the new architecture. The web app is not inventing one shape, the CLI another, and the database a third. They share the same records and vocabulary.
the database is the spine #
netsky-db is not a helper crate. It is the system spine.
On startup it creates tables for actors, sessions, tasks, task dependencies, messages, deliveries, actor inputs, notes, logs, settings, and self-update runs (src/crates/netsky-db/src/lib.rs:66-79, src/crates/netsky-db/src/lib.rs:87-210).
That schema means netsky can answer practical questions without reconstructing state from terminal output:
- who is running
- what work is open
- which message was sent where
- what note or log explains the current state
Without that schema, you are back to reconstructing state from terminals and chat logs, which is exactly the kind of drift this architecture is trying to remove.
agents and comms are separate on purpose #
netsky-agent owns manager and worker runtime behavior. It starts manager0, starts workers, records actor state, creates tasks, and submits work into Codex-backed sessions (src/crates/netsky-agent/src/lib.rs:122-179, src/crates/netsky-agent/src/lib.rs:203-260).
netsky-comms owns message movement. It writes outbound messages into the database, routes owner-bound messages to iMessage, injects actor-bound messages into channel directories, and polls inbound iMessages back into the system (src/crates/netsky-comms/src/lib.rs:86-153, src/crates/netsky-comms/src/lib.rs:155-200, src/crates/netsky-comms/src/lib.rs:202-218, src/crates/netsky-comms/src/lib.rs:220-260).
That separation is worth protecting. Running an agent and delivering a message are different concerns. They fail differently, and the code is easier to reason about when they stay separate.
the web app is read-mostly and narrow #
The web crate is simple by design. It exposes a handful of HTML routes and JSON endpoints, one POST /api/user-message handoff, and an SSE feed, then builds a dashboard snapshot by reading actors, sessions, tasks, messages, and logs from the database (src/crates/netsky-web/src/lib.rs:139-154, src/crates/netsky-web/src/lib.rs:194-223, src/crates/netsky-web/src/lib.rs:276-307).
Right now it is intentionally narrow: a small set of HTML routes, mostly read endpoints plus one user-message handoff, and a dashboard assembled from database reads.
The next pass on the web app is not more framework. It is better lineage:
- clickable cards
- filtered drill-down views
- cross-links between tasks, messages, actors, and sessions
- small stateful animations that show healthy, busy, idle, or unhealthy at a glance
why this architecture works #
The runtime is legible, the crates map to real jobs, and new features have somewhere obvious to go. The web app, CLI, comms layer, and agent runtime can change independently because they share records instead of reaching through each other.
That does not make the system finished. It makes it understandable. At this stage, that is the more valuable property: the seams are clear enough to change one part without guessing about the rest.