# RDM Kernel Protocol v1 The kernel (`rdm-kernel`) is a resident child process of the `dsh-rdm` DSH plugin. It speaks **newline-delimited JSON (NDJSON)** over its stdio pipes: one UTF-8 JSON object per line, `\n` terminated, no pretty printing. * `stdout` carries protocol messages only (responses + notifications). * `stderr` carries human-readable logs; the plugin drains it and never parses it. * The kernel exits when `stdin` reaches EOF (parent plugin gone) or on `shutdown`. * The kernel never listens on a network port. All control is parent→child stdio. This protocol is private to the plugin: it is versioned here, and no compatibility with other downloaders, their CLIs or their persisted state is promised or implemented. ## Lifecycle 1. Plugin spawns `rdm-kernel --data-dir [--log-level warn]`. 2. Kernel creates `/tasks/`, loads persisted task records, marks tasks that were `running`/`probing`/`queued` in the previous kernel generation as `interrupted` (or auto-resumes them when `autoResume` is true), then writes: ```json {"event":"ready","protocol":1,"kernel":{"version":"1.0.0","pid":1234,"dataDir":"C:\\...\\dsh-rdm"}} ``` 3. Plugin sends `initialize` (optional but recommended) with session defaults. 4. Plugin issues tool/UI requests; kernel emits throttled `task` notifications. 5. On unload the plugin kills the child (or closes stdin); kernel persists state and exits. Request envelope: ```json {"id":"","method":"start","params":{}} ``` Success response: ```json {"id":"","ok":true,"result":{}} ``` Failure response: ```json {"id":"","ok":false,"error":{"code":"not_found","message":"task t_1 does not exist"}} ``` `id` is a string chosen by the plugin; responses may arrive out of order. Requests are handled concurrently; a long `wait` does not block other methods. ## Types ### RequestSpec (input of `start`) ```jsonc { "url": "https://example.com/file.iso", // required, http/https only "dir": "C:\\Downloads", // absolute; default = initialize.defaultDir or process cwd "fileName": "file.iso", // optional override; sanitized "connections": 8, // 1..32, default initialize.connections (8) "chunkSizeBytes": 0, // 0 = auto; otherwise 262144..67108864 "headers": {"Authorization": "Bearer ..."}, // optional extra request headers "cookie": "a=1; b=2", // optional Cookie header "referer": "https://example.com/", // optional "userAgent": "custom/1.0", // optional (default "dsh-rdm/") "expectedSha256": "<64 hex>", // optional post-download verification "speedLimitBps": 1048576, // optional per-task bandwidth cap "resume": true, // default true "singleStream": false, // force exactly one rangeless GET "proxy": "http://127.0.0.1:7890", // optional HTTP proxy "insecure": false, // accept invalid TLS certs (debug only) "maxAttempts": 6 // per-chunk retry budget, default 6 } ``` `dir` is created if missing. `start` returns immediately with a `TaskView` (status usually `queued` or `probing`); the transfer runs in the background. ### TaskView ```jsonc { "id": "t_9f3a1c2d5e6b7a80", "url": "https://example.com/file.iso", "finalUrl": "https://cdn.example.com/file.iso", // null until probed "fileName": "file.iso", "dir": "C:\\Downloads", "path": "C:\\Downloads\\file.iso", // null until target resolved "status": "queued", // see Status "downloaded": 0, // bytes written to .part "total": 123456789, // null until known "speedBps": 0, // EWMA over ~2s, 0 when idle "etaSecs": null, "activeWorkers": 0, "peakWorkers": 0, "connections": 8, // planned cap "chunkSize": 4194304, // null for single-stream "resumedChunks": 0, "rangeSupported": null, // null until probed "expectedSha256": null, "sha256": null, // set on success when verified "error": null, // {"code","message"} on failure "retries": 0, "createdAt": 1762560000000, // ms epoch "startedAt": null, "finishedAt": null, "elapsedMs": 0, "resumable": false } ``` Status values: `queued`, `probing`, `running`, `paused`, `completed`, `failed`, `cancelled`, `interrupted`. Terminal: `completed`, `failed`, `cancelled`. Resumable: `paused`, `interrupted`, `cancelled` (when a valid `.part` + resume state exists), `failed` only after an explicit `retry`. ### TaskEvent ```jsonc {"seq":1,"at":1762560000000,"kind":"plan","message":null,"chunk":null,"attempt":null,"delayMs":null} ``` `kind` ∈ `info`, `probe`, `plan`, `started`, `retry`, `controller`, `paused`, `resumed`, `verify`, `completed`, `failed`, `cancelled`. Events are a bounded in-memory ring (last 200 per task) and are diagnostic only; task state is authoritative. ### Meta (result of `probe`) ```jsonc { "finalUrl": "https://cdn.example.com/file.iso", "fileName": "file.iso", "totalBytes": 123456789, // null when unknown "supportsRanges": true, "contentType": "application/octet-stream", "etag": "\"v1\"", "lastModified": "Wed, 21 Oct 2026 07:28:00 GMT", "httpVersion": "HTTP/1.1" } ``` ## Methods ### `initialize` ```jsonc {"defaultDir":"C:\\Downloads","connections":8,"chunkSizeBytes":0, "speedLimitBps":0,"maxAttempts":6,"autoResume":true,"logLevel":"warn"} ``` Result: ```jsonc {"protocol":1, "kernel":{"version":"1.0.0","pid":1234,"dataDir":"C:\\Users\\x\\.dsh\\dsh-rdm"}, "config":{"defaultDir":"C:\\Downloads","connections":8,"chunkSizeBytes":0, "speedLimitBps":0,"maxAttempts":6,"autoResume":true,"logLevel":"warn"}} ``` Defaults apply for omitted fields. `initialize` may be sent once; later calls update defaults for *new* tasks only. ### `probe` `params`: `{url, headers?, cookie?, referer?, userAgent?, proxy?, insecure?, timeoutMs?}` Result: `{"meta": Meta}`. Never downloads the body. On a server without `Accept-Ranges` the kernel issues one ranged `GET` (bytes `0-0`) to prove range support before reporting `supportsRanges`. ### `start` `params`: `RequestSpec`. Result: `{"task": TaskView}`. ### `list` `params`: `{"status"?: "running" | ["paused","failed"], "limit"?: 50}` (limit 1..200, default 50; newest first). Result: `{"tasks": [TaskView]}`. ### `get` `params`: `{"id": "t_...", "events"?: 50}` (events 0..500, default 50, oldest first). Result: `{"task": TaskView, "events": [TaskEvent]}`. ### `wait` `params`: `{"ids": ["t_..."], "timeoutMs"?: 60000}` (1..20 ids, timeout 100..300000, default 60000). Waits until **every** id is terminal or the timeout expires. Unknown ids fail with `not_found`. Result: `{"tasks": [TaskView], "timedOut": false}`. ### `control` `params`: `{"id":"t_...","action":"pause|resume|cancel|retry|remove","deletePart"?:false}` * `pause` — cooperative stop of a `queued|probing|running` task; persists resume state; status becomes `paused`. * `resume` — continues from `paused|interrupted|cancelled|failed` when a valid `.part` state exists, otherwise restarts from zero. Status returns to `queued`. * `cancel` — stops the task; keeps `.part`/state; status `cancelled`. * `retry` — discards `.part`/state and starts over from zero. * `remove` — deletes the task record; when `deletePart` is true also deletes `.part`/state, never the completed target file. Running task is cancelled first. Result: `{"task": TaskView}` (`null` for `remove`). ### `stats` Result: ```jsonc {"protocol":1,"kernel":{"version":"1.0.0","pid":1234,"uptimeMs":123456, "dataDir":"C:\\Users\\x\\.dsh\\dsh-rdm"}, "tasks":{"total":3,"queued":0,"running":1,"paused":1,"completed":1, "failed":0,"cancelled":0,"interrupted":0}, "totals":{"bytesDownloaded":987654321,"downloadsCompleted":12,"downloadsFailed":1}, "config":{...}} ``` ### `shutdown` Result `{"ok":true}`; kernel persists state and exits within 2 seconds. ## Notifications ```jsonc {"event":"ready","protocol":1,"kernel":{"version":"1.0.0","pid":1234,"dataDir":"..."}} {"event":"task","task": TaskView} ``` `task` notifications are sent on every status/error/summary transition and, while a task is `running`, at most twice per second per task (progress is also readable via `list`/`get`). Notifications may interleave with responses on stdout. ## Error codes | code | meaning | |---|---| | `invalid_request` | malformed params/JSON, bad URL, bad range | | `not_found` | unknown task id | | `conflict` | action not valid for the current status | | `http_status` | non-success HTTP status from origin (message carries status) | | `network` | DNS/connect/TLS/body failure after retries | | `remote_changed` | validators/size changed during resume | | `checksum_mismatch` | `expectedSha256` mismatch | | `io` | local filesystem failure | | `cancelled` | control-flow result reported by long operations | | `unsupported` | kernel cannot honor the request | | `internal` | unexpected kernel error | ## Guarantees / non-goals * **Multi-connection**: when the origin advertises ranges, the kernel splits the file into fixed chunks, hands them out dynamically to a worker pool (slow chunks never pin the tail), and adapts the active worker count toward the configured cap. * **Final snapshots are exact**: any terminal transition (completed, failed, cancelled) flushes a final task view; a completed task always reports `downloaded == total` and `finishedAt`/`elapsedMs` set, and pause/cancel/fail report the real byte count written so far. * **Resumable**: `.part` + `.part.json` resume state with validator checks (ETag, Last-Modified, total length); resume survives kernel restarts. * **Byte-level correctness**: every ranged response is validated against the expected Content-Range/total before writing; the final file size must equal the advertised total; `expectedSha256` is verified before renaming `.part`. * **Failure isolation**: a chunk retries with exponential backoff on transport errors, 408/429/5xx (honoring `Retry-After`) before the task fails. * Not supported (deliberately removed from the RDM core): mirrors, HTTP/2 preference for range workers, per-origin learned budgets, GUI/IPC/MCP, resolver/AI layers, legacy serialized state compatibility. ## Persistence layout ``` / tasks/.json # task record: spec + status + timestamps + result ``` Download files live wherever the task requested (`dir`), with `.part` while incomplete and `.part.json` as resume state; both are removed on successful completion.