netsky as a software factory
Netsky’s top-level identity is a viable AI system. Its operational identity is a software factory. The re-architecture draws the line between the two and makes both legible in code.
The factory produces commits. To netsky itself via cherry-pick. To owner-owned repos via pull request. To upstream OSS via pull request from a fork. Through one dispatch contract, observable in one database, measured on one dashboard.
the five layers #
flowchart TD
factory[FACTORY layer: dashboard, cross-repo, self-improvement]
daemon[DAEMON layer: netskyd, JSON-RPC over UDS]
backend[BACKEND layer: AgentBackend trait]
data[DATA layer: meta.db as source of truth]
surface[SURFACE: bin/check, iroh, iMessage, AGENTS.md]
factory --> daemon
daemon --> backend
daemon --> data
backend --> data
data --> surface
Each layer is replaceable without touching the others. That is the point. The daemon is ignorant of which backend drives an agent. The backend is ignorant of which factory workflow dispatched the turn. The dashboard is ignorant of how rows land in the database. The database is ignorant of whether a commit ships to netsky itself or to an upstream fork.
daemon #
netskyd is one long-lived Rust process. It owns exactly four things: a sqlite database at ~/.netsky/meta.db, a registry of AgentBackend trait impls, a JSON-RPC 2.0 surface over a Unix domain socket at ~/.netsky/agentd.sock, and a notification bus derived from database triggers.
That is the complete list. Every other concern — skills, prompt shape, auth, tool registry, sandbox — belongs to a backend impl. Every observability concern is a query.
Representative RPC methods:
agent.list / agent.spawn / agent.submit / agent.status / agent.cancel / agent.kill
thread.events / bus.send / bus.subscribe
directive.ingest / directive.fulfill
db.query / health.ping / gc.sweep
self.commit_proposed / self.stage_binary / self.canary_run / self.activate / self.rollback
repo.register / dispatch.cross_repo / pr.track / pr.merged
Liveness is five states computed from database columns: alive, idle, turn_in_flight, stuck, dead. No pane hash. No process list scraping. No file mtime heuristics.
backend trait #
The daemon talks to exactly one abstraction:
#[async_trait] pub trait AgentBackend: Send + Sync { fn kind(&self) -> BackendKind; async fn start(self: Arc<Self>, cfg: Value) -> Result<Session>; } pub struct Session { pub submit: mpsc::Sender<Submission>, // daemon -> backend pub events: mpsc::Receiver<BackendEvent>, // backend -> daemon pub shutdown: oneshot::Sender<()>, } pub enum Submission { Turn{..}, Interrupt{..}, ToolResult{..} } pub enum BackendEvent { TurnStarted, Delta, ToolStart, ToolEnd, TurnEnded, Usage, BackendError }
Three submission variants. Seven event variants. Config is opaque — the backend deserializes its own config struct.
Three concrete impls ship:
ClaudeCliBackend— per-turn subprocess invokingclaude -p --output-format stream-json. Subscription auth preserved via the claude binary’s keychain (src/crates/netsky-ai/src/lib.rs:108-197refactors into this).CodexForkBackend— persistentcodexsubprocess withCODEX_CHANNEL_DIR=~/.netsky/backends/<agent>/codex-channel. The fork already implements the inbox/outbox/receipts contract (gh-org/lostmygithubaccount/codex/codex-rs/docs/channel.md). Wrapping it is roughly 150 lines.NativeBackend— stub today. Lands when a netsky-owned agent exists. Trait unchanged.
Swapping a backend is one config change:
netsky config set agent.agent3.backend codex-fork
The daemon interrupts the live turn, drops the Session, starts a new one, logs a row in backend_sessions. Turn history in agent_turns persists across the swap, linked by backend_session_id.
database as source of truth #
Every agent state change is a row before the next side effect. Turns, tool calls, bus envelopes, owner directives, token usage, gate outcomes, merged PRs — all rows (src/crates/netsky-db/README.md:27). Read surfaces are queries. Live surfaces are subscriptions on change-notification rows.
Schema additions for the re-architecture:
| table | purpose |
|---|---|
agent_processes | one row per backend invocation (pid, kind, start, exit) |
agent_turns | one row per turn (submit, accept, complete, terminal kind) |
agent_events | append-only backend event stream |
agent_mail | bus envelopes (replaces the file-drop inbox directory) |
backend_sessions | one row per backend restart |
external_repos | cross-repo dispatch targets |
external_prs | PR state machine tracked for each external repo |
pr_review_cycles | per-PR review history |
self_commits | proposed self-edits with gate status |
self_activations | one row per attempted binary swap |
self_canary_runs | per-step canary evidence |
Writer authority is the invariant: netskyd is the single writer for every new table. Readers are unrestricted via db.query. The JSONL fallback posture (src/crates/netsky-db/README.md:10) survives.
factory layer #
Three capabilities ride on top of the daemon.
Dashboard. netsky factory dashboard runs a SQL-backed control room with an assembly-line row. KPIs are defined as formulas over the database: cycle time is the median minutes from brief_sent to harvest_events.status='success' for a branch, first-pass rate is successful harvests with no failed intermediate gate divided by all harvest attempts, cost per PR is attributed token_usage.cost_usd_micros divided by landed count. Every number is auditable through netsky factory explain <metric>, which returns the formula and the SQL.
netsky factory dashboard 2026-04-21 17:08 ET
Throughput Quality Cost Flow
landed today 5 first pass 82% cost/pr $1.74 wip 7
prs/day 7d 3.4 defect rate 6% tokens 4.1M backlog p95 46h
cycle p50 41m gate red 1 owner p95 12m oldest P1 18h
Assembly line
brief -> dispatch -> workspace -> gate -> harvest -> owner ack
12 10 9 6 5 4
netsky sql is the escape hatch. DataFusion over meta.db. Arbitrary query, --json default, --chart line|bar|scatter pops a plotly URL served over tailscale. The dashboard is opinionated; netsky sql is the owner’s undo button on that opinion.
Cross-repo dispatch. Three merge styles behind one RPC. cherry-pick-local for netsky itself (agent0 cherry-picks to main, no PR, matches today’s flow in AGENTS.md conventions). pr-owned for repos cody owns (gh-org/lostmygithubaccount/*, gh-org/dkdc-*). pr-upstream for OSS contributions. Each dispatch clones fresh into workspaces/<task>/<org>/<repo>/ — gh-org/<org>/<repo>/ stays a read-only reference. A 60-second poller runs gh pr view against every open PR and writes state transitions as pr_review_cycles rows. Commit authorship is Author: Cody <cody@dkdc.dev> with Co-Authored-By: netsky <netsky0@netsky.ai> on every factory-produced commit.
Self-improvement. Three invariants make netsky-ships-to-netsky safe:
flowchart LR
edit[agent0 edits in-tree] --> check[bin/check green]
check --> stage[stage binary at ~/.netsky/bin/stage/netskyd-<sha>]
stage --> canary[4-step canary]
canary --> activate[atomic symlink swap + re-exec]
activate --> watch[60s post-activate window]
watch --> done[LKG updated]
watch -->|health fails 3x| rollback[symlink to netskyd.lkg + git revert]
The canary runs four steps before a single symlink flips: health_ping (the staged binary answers on a throwaway UDS within 5s), escalate_loopback (the staged binary sends a self-marked email and iMessage through netsky escalate — this proves the owner-comms path is intact before the swap commits), gate_replay (the staged binary runs bin/check --self-canary on an identity diff), and bus_roundtrip (the staged binary sends an envelope to itself and reads it back). Authorship is enforced at push time by bin/check-authorship — a 40-line shell script that refuses pushes where AGENT_N != 0 and the diff touches paths outside workspaces/*. The prose rule becomes code.
Full autonomy is the target. The LKG binary, the canary, and the authorship check are the guards. No owner-in-the-loop per activation.
what this is not #
Not a rewrite. Every existing path keeps working during the migration. Day one adds tables and a new CLI namespace. The file-drop bus at ~/.netsky/channels/agent/<N>/inbox/ stays readable for a release window while agent_mail takes over as source of truth.
Not a new binary distribution story. cargo install netsky still installs one binary. netskyd is a subcommand.
Not a commitment to any specific model backend. The AgentBackend trait is the knob. Today’s claude CLI and today’s codex fork are two impls. A netsky-native agent is the third, stubbed, waiting.
Not a replacement for bin/check. The gate is the gate. Every commit still passes. The self-improvement pipeline runs the gate through the staged binary precisely so a commit that breaks the gate cannot activate.
Not an excuse to break netsky.ai. The site is built from the same repo through the same zorto pipeline. This post is the source of truth for what lands next.