# Services: what runs, what restarts it, who may say (design, 2026-10-14)

A design, not yet built. It settles how sysk starts, watches and controls long-lived programs, the
job Solaris gives SMF (`svcs`, `svcadm`). It sits beside [authority.md](authority.md): controlling a
service is the `operator` right designed there.

## What sysk has today

- **Fixed tasks.** Every service (the UART and disk drivers, `fsd`, the terminal, the shell) is a
  task clause in the manifest, started at boot, wired and checked before anything runs.
- **Named services.** `fsd` declares `(serves files)`; a job binds it at run time (sysk 031).
- **Jobs.** Tasks started at run time in a pool of slots, each job a closed world (sysk 017).
- **Fail-stop.** Any fault in a task stops the machine (exit 3, `trap.S`). For proven code that is
  the honest policy: a fault is a broken assumption, and nothing after it can be trusted.

## The design

```
  manifest
    (task fsd ... (svc (restart 0)))            fixed: a task, started at boot
    (svc netd (prog net) (restart 3 60000))     managed: a catalogue program, run as a job
    (svc rsh  (prog shell) (per net/udp/23))    managed, one job per connection (inetd)
        |
        v
  svcd: the service manager, a small task holding the jobs right for services
        |   starts managed services, watches every service, restarts within its limit,
        |   serves the tree below
        v
  /svc/
     fsd/   status   up, since 00:00:02, restarts 0
            ctl      start | stop | restart        (operator only)
            log      what it wrote                 (follow /svc/fsd/log)
     netd/  status   down: net-drv failed 3 times in 60 s
     ...
```

1. **Two kinds of service, one tree.**

   | | Fixed | Managed |
   | --- | --- | --- |
   | What | drivers, `fsd`, the terminal, the console shell | the net server, a shell per connection, later programs from disk |
   | Declared | the task's clause in the manifest | an `svc` clause naming a catalogue program |
   | Runs as | a task, from boot | a job, in the pool |
   | Started | at boot | at boot, on first bind, or per connection |
   | On a fault | restarted in place, if its clause allows | the whole job restarted as a unit |

   A job is already what SMF's process contracts are: every task it started belongs to it, so a
   fault anywhere in it is the service's fault. *From SMF (contracts, `inetd` as a delegated
   restarter), launchd and Fuchsia (start on first use).*

2. **No dependency graph for order.** Every channel exists from boot, and a task that needs another
   waits on the channel; the deadlock rule proves the waiting cannot cycle. Start order never
   matters, so there is nothing to declare. *systemd's best idea, socket activation, is how sysk
   already works.* Dependencies matter only for failure, and there they are the channels
   themselves (3).

3. **A channel whose other end failed ends, with a reason.** This is the one kernel change, and the
   heart of restart. Restarting a task is easy; its peers are the problem: messages in flight are
   lost, and a client's proven protocol state (counts, stream ends) no longer matches the restarted
   server. So when a task fails, every channel end it held ends, and the peer's next `recv` or
   `send` says `(failed svc)` instead of a message. Streams already end; this adds why. A client is
   proven to handle it, as it handles an end, and a restarted service is met on fresh channels.
   *From Hubris (task generations: a client of a restarted server gets an error, never a stale
   conversation) and MINIX 3 (drivers restarted under their clients).*

4. **Restart is granted, not assumed.** Fail-stop stays the default: a service restarts only if its
   clause says how often, `(restart N T)`, at most N times in T ms. Past that it is **down**,
   with the reason, until a person acts: SMF's maintenance state, Erlang's restart intensity. A
   proven service gets `(restart 0)`: its fault is a bug to find, not hide. A driver whose device
   may wedge, or a program from disk, gets more.

5. **svcd, a small task.** It holds the jobs right for services (the console shell keeps its own
   for what a person runs: the shell should not hold the jobs it runs for others), starts and
   restarts managed services, is told by the kernel when a fixed task fails, and serves
   `/svc`. It is proven like every server (authority.md, rule 4), and it is the only task that
   may restart another.

6. **`svcs` and `svcadm` are files.**

   ```
   / % ls /svc                      svcs
   / % cat /svc/netd/status         svcs -x netd
   / % follow /svc/netd/log         its log, as it grows
   / % !echo restart > /svc/netd/ctl   svcadm restart netd: operator, through the trusted path
   ```

   `status` says the state (`up`, `starting`, `down`), since when, how many restarts, and for
   `down` why, in the words the job check already uses for its reasons. `ctl` takes `start`,
   `stop`, `restart`: control, so only `operator` can write it (authority.md, part 6). Reading is
   `observer`'s.

7. **Configuration is arguments and files.** A program's arguments, with the manifest's defaults
   (sysk 036), and files it is lent. No repository beside them. *SMF's `svc.configd` solved a
   problem sysk does not have: its configuration is already declared and checked.*

## Names

Short names, as Plan 9 and the owner prefer: `/svc`, `ctl`, `log`, `status`. `/svc` is at the top,
not under `/sys`: every top-level directory is one server, and `/sys` is the kernel's, while `svcd` is
a task (directions.md, "The file tree"). The kernel's tree
follows: `/sys/task` and `/sys/chan` since sysk 043 (they were `/sys/tasks` and `/sys/channels`). A
service and a task are two views: `/sys/task/fsd` is the scheduler's (running, waiting on what),
`/svc/fsd` the lifecycle's (up, restarts, why). A managed service's status names its tasks.

## Prior art

| From | Taken | Left |
| --- | --- | --- |
| Solaris SMF | maintenance state; contracts (as jobs); `svcs -x`'s reasons; `inetd` as a restarter; control as an authorization | XML manifests, the configuration repository, dependency declarations |
| systemd | socket activation (sysk's channels already) | scope creep; units for everything |
| launchd, Fuchsia | start on first use | |
| runit, s6 | one small supervisor | |
| Erlang/OTP | restart intensity; a supervisor that gives up and says so | restart as the default ("let it crash"): proven code should not crash |
| MINIX 3 | drivers restarted under their clients | |
| Hubris | a client of a restarted server gets an error, not a stale conversation | |
| Plan 9 | services as files | no manager at all |

## Pushed back

**Do not build it before something needs it.** Every task today is proven, and fail-stop is right
for them. Three things change that, and all arrive with networking: a device that can wedge
(virtio-net), work started on demand (a shell per connection), and later code that is not proven
(programs from disk). So: designed now, built with networking, and the first managed service is
the network shell, one job per connection.

## Order of work

| Step | What | With |
| --- | --- | --- |
| 1 | Channels end with `(failed ...)` when a peer fails; clients proven to handle it | the kernel, sysp proofs of every client |
| 2 | `svcd`, `/svc` with `status` and `log`; `svc` clauses in the manifest; fixed services listed | wire.rkt |
| 3 | Managed services: started as jobs, restarted within `(restart N T)` | the net server |
| 4 | `per`: a job per connection | the network shell |
| 5 | `ctl`, behind `operator` | authority.md, step 5 |
