netsky today

Netsky is a local viable system with one root, bounded clones, a watchdog, and a CLI-owned control plane. The base prompt names the split directly: S5 policy in prompts, S4 intelligence in notes, S3 control in agent0, S3* audit in spot checks, S2 coordination in workspaces, and S1 operations in clones (src/crates/netsky-prompts/prompts/base.md:20-28).

This post is the current shape only. If you want the arc that produced it, read past, present, future system architecture.

topology #

One machine runs one constellation. agent0 is the root orchestrator. agentinfinity is the watchdog. agent1..N are bounded workers. Non-trivial edits land in fresh repo clones under workspaces/<task>/repo. The ticker is the 60 second heartbeat. Launchd at 120 seconds is the failsafe (src/crates/netsky-prompts/prompts/base.md:30-40, src/crates/netsky-prompts/prompts/base.md:83-88).

flowchart TD
    owner --> root[netsky0 / agent0]
    root --> clones[agent1..N]
    root --> watchdog[agentinfinity]
    ticker[netsky-ticker] --> watchdog
    launchd[launchd failsafe] --> watchdog
    clones --> ws[workspaces/<task>/repo]

The top-level CLI reflects that topology. netsky up starts the constellation. netsky agent starts one agent. netsky attach attaches to a tmux pane. netsky restart respawns the constellation through the watchdog path (src/crates/netsky-cli/src/cli.rs:17-40, src/crates/netsky-cli/src/cli.rs:71-87).

runtimes #

Netsky runs two operator-facing runtimes today: Claude and Codex. Neither owns a privileged role. Any agent slot (agent0, agentinfinity, or a clone) can be spawned under either runtime via --type claude|codex. The system may balance or prefer one for a given slot based on availability, cost, or effort tier - but no role is runtime-locked. The CLI exposes both in one tree through netsky up --type, netsky agent --type, and netsky ai --runtime (src/crates/netsky-cli/src/cli.rs:17-24, src/crates/netsky-cli/src/cli.rs:41-70).

runtimewhere it appearsinbound pathoutbound path
Claudeagent0, clones, watchdogMCP agent source drains inboxnetsky channel send
Codexresident clone panes and netsky ai runinbox plus direct promptoutbox forwarded into agent0 inbox

The important difference is not branding. It is control surface. Spawn creates inbox, outbox, and processed directories for Codex-backed agents (src/crates/netsky-core/src/spawn.rs:144-152). netsky channel forward-outbox normalizes replies by rewriting from to the owning agent id and filling to=agent0 when needed (src/crates/netsky-cli/src/cmd/channel.rs:142-257, src/crates/netsky-cli/src/cmd/channel.rs:1142-1168). Different runtime. Same bus.

tools a clone has #

The tool policy lives in constants, not lore. Clones get eight Claude tools. Agent0 and agentinfinity get broader surfaces. The repo denies the same dangerous harness-local verbs where they would break locality or replay (src/crates/netsky-core/src/consts.rs:134-169).

rolealloweddenied
cloneBash,Edit,Glob,Grep,Read,WebFetch,WebSearch,WriteAgent,NotebookEdit,Task
agent0shell, file, monitor, skill, and task toolsAgent,CronCreate,CronDelete,CronList,RemoteTrigger,ScheduleWakeup
agentinfinityagent0 plus WebFetch,WebSearchsame deny set

The deny comment is the real design note. Claude-side self-scheduling leaks across sessions. Scheduling belongs in netsky loop and netsky cron. Sub-clone recursion belongs in netsky clone, not in the built-in Agent tool (src/crates/netsky-core/src/consts.rs:153-167).

CLI command table #

The command tree is broad enough to run the system from one binary and narrow enough to stay legible. The current top-level families live in src/crates/netsky-cli/src/cli.rs (src/crates/netsky-cli/src/cli.rs:15-120, src/crates/netsky-cli/src/cli.rs:240-309, src/crates/netsky-cli/src/cli.rs:618-837).

categorycommands
orchestrationup, down, restart, agent, codex, attach, agentinfinity, agentinit, workspace, clone
schedulingwatchdog, tick, cron, loop, launchd, quiet, nap
channelschannel, io, imessage, email, calendar, tasks, drive, iroh, setup, handoffs, escalate
AI workersai, skill
observabilitystatus, query, analytics, events, gates, doctor, morning, session, config, prompts, test, task, ingest
aliasesobserve, dev, admin

The verbs that matter most are the boring ones:

netsky clone brief briefs/review.md --workspace pr-123 --agent 7 --type codex
netsky channel send agent7 "gate is green. harvest next." --from agent0
netsky loop create 15m "check CI, then report delta only"
netsky cron add morning-brief "0 11 * * *" agent0 /morning-brief
netsky imessage send --owner "bin/check is green on branch foo"
netsky ai run --model codex-gpt-5.4 --detach "summarize this diff"

Those examples line up with the implemented interfaces. clone brief is a dedicated subcommand family (src/crates/netsky-cli/src/cli.rs:806-837). imessage send requires exactly one of --chat or --owner (src/crates/netsky-cli/src/cmd/imessage.rs:4-47). ai run supports --model, --skill, --timeout, --detach, and --cwd (src/crates/netsky-cli/src/cli.rs:321-367).

the agent bus #

The agent bus is a filesystem inbox rooted at ~/.netsky/channels/agent/agent<N>/. The base prompt says inbound envelopes live under inbox/ and netsky channel send is the outbound path (src/crates/netsky-prompts/prompts/base.md:42-49). The channel code defines the operational details: claim then emit then archive, poison quarantine for hostile input, and symlink refusal at every channel directory boundary (src/crates/netsky-cli/src/cmd/channel.rs:7-33, src/crates/netsky-cli/src/cmd/channel.rs:35-58).

~/.netsky/channels/agent/
  agent0/
    inbox/
    claimed/
    delivered/
    poison/
  agent7/
    inbox/
    outbox/
    forwarding/
    claimed/
    delivered/
    processed/
    poison/
flowchart LR
    A[producer] --> B[inbox]
    B --> C[claimed]
    C --> D[delivered]
    B --> E[poison]
    O[Codex outbox] --> F[forwarder]
    F --> B0[agent0 inbox]

Ordering across producers is only best-effort because filenames sort by wall-clock nanoseconds (src/crates/netsky-cli/src/cmd/channel.rs:26-33). That is acceptable for the current local bus. Readers that need stronger causal ordering must carry it in the envelope body.

MCP today #

MCP is narrow now. .mcp.json declares exactly four servers: agent, imessage, email, and iroh (.mcp.json:1-20). The channel module says why. The agent MCP surface is emit-only. Shell sends route through the CLI (src/crates/netsky-cli/src/cmd/channel.rs:1-18).

That is the current architectural cut. MCP handles inbound events over stdio. Mutations happen through repo-owned commands. The clone can still react to an inbound message through MCP. It just performs the side effect through netsky <verb> afterward (src/crates/netsky-core/src/consts.rs:134-169, src/crates/netsky-cli/src/cli.rs:263-286, src/crates/netsky-cli/src/cli.rs:725-837).

scheduling #

Scheduling is durable. The base prompt tells operators to use netsky loop create and netsky cron add, not harness-local wakeups (src/crates/netsky-prompts/prompts/base.md:60-68). Cron entries live in ~/.netsky/cron.toml (src/crates/netsky-core/src/paths.rs:117-119, docs/cron.md:1-24). Loop entries persist on disk and netsky loop tick writes due prompts into agent inboxes with from=agentloop (src/crates/netsky-cli/src/cmd/loop_cmd.rs:47-88, src/crates/netsky-cli/src/cmd/loop_cmd.rs:140-185).

netsky cron tick stamps envelopes with from=agentcron and kind=cron (src/crates/netsky-cli/src/cmd/cron.rs:13-15, src/crates/netsky-cli/src/cmd/cron.rs:183-208). The ticker loop runs watchdog, cron, and loop ticks every interval (src/crates/netsky-cli/src/cmd/tick.rs:133-163).

The practical rule is simple:

  • use loop for one-shot or self-paced follow-up
  • use cron for recurring clock schedules
  • let the ticker deliver both through the same bus

That scheduling choice cleans up two different kinds of drift. The first is operator drift. Everyone uses the same verbs. The second is runtime drift. Claude and Codex do not get separate scheduling stories. The durable files and the ticker own that contract (src/crates/netsky-core/src/consts.rs:153-167, src/crates/netsky-cli/src/cmd/tick.rs:133-163).

testing and gates #

./bin/check is the gate. The script is short enough to read in one screen. It runs repo checks, builds a fresh release binary, front-loads that binary onto PATH, then runs unit and integration suites against the working tree binary instead of a stale install (bin/check:1-38).

The pre-push hook installs as the canonical guard before non-website pushes (.githooks/pre-push:1-97, bin/setup:12-27). Website-only pushes take the website/bin/build path instead of bin/check (.githooks/pre-push:73-83). The documented bypass is SKIP_PREPUSH_CHECK=1 with a required SKIP_PREPUSH_REASON, and each bypass appends an audit line under ~/.netsky/state/prepush-bypass.jsonl (.githooks/pre-push:6-35).

The test layout is split by isolation target. Rust unit tests live next to code. tests/unit/ covers fast hermetic shell behavior. tests/integration/ covers multi-command flows without live resident agents. tests/agent/ is the slow real-agent tier (tests/README.md:3-27).

Representative integration tests exercise the actual system boundaries. test-clone-tool-deny-hook.sh proves the clone deny hook blocks disallowed tools (tests/integration/test-clone-tool-deny-hook.sh:40-70). test-mcp-emit-only.sh verifies that the four MCP sources expose no mutation tools (tests/integration/test-mcp-emit-only.sh:55-101). test-clone-brief-codex-delivery.sh proves that a Codex clone brief lands in the resident tmux pane (tests/integration/test-clone-brief-codex-delivery.sh:71-104).

observability #

~/.netsky/meta.db is the durable observability store. The stack is Turso SQLite with WAL on the write side and DataFusion over Arrow snapshots on the read side (src/crates/netsky-db/README.md:5-10). The operator surfaces are netsky query and scripts/meta-db.py (src/crates/netsky-db/README.md:21-25, src/crates/netsky-prompts/prompts/base.md:17-18).

The current schema records the interesting surfaces: messages, CLI invocations, crashes, ticks, workspaces, sessions, clone dispatches, harvest events, communication events, MCP tool calls, git operations, owner directives, token usage, watchdog events, source errors, iroh events, source cursors, and per-source event delivery logs (src/crates/netsky-db/README.md:27-47).

If you want the short version, the database answers four questions:

  • what happened
  • who did it
  • which runtime did it use
  • did the watchdog have to intervene

That is enough to turn a live constellation into a replayable one.

It also means netsky can keep its debugging posture local. netsky query is enough for ad hoc SQL over the current store, and the watchdog keeps a JSONL trail even before meta.db writes land (src/crates/netsky-db/README.md:23-25, src/crates/netsky-db/README.md:82-117). The system does not need external telemetry infrastructure before it can explain itself.

what’s next #

The next shape is cross-machine. The base prompt already defines netsky0 as the owner-facing root constellation and netsky1..N as sibling constellations that relay through it over iroh (src/crates/netsky-prompts/prompts/base.md:40-49). The transport part is grounded. The larger orchestration story is still proving itself.

That is the right kind of incompleteness. Today netsky already has one root, one bus, one mutation surface, durable scheduling, a real gate, and a real observability store. The future keeps those constraints and pushes them farther.