# P6 task 09: `sift batch`, README and skill **Branch:** `p6` (`git switch p6`; `git status --short` must be empty, otherwise stop) **Commit subject:** `cli: sift batch submit|status|get|cancel; README and skill` ## Goal Agents reach the batch endpoint through the CLI, as they do `fetch` and `search`. There is no Hermes plugin. ## Files - Copy: `docs/plans/p6/_files/cmd_sift/batch_test.go` → `cmd/sift/batch_test.go` - Create: `cmd/sift/batchcmd.go` (`batchMain`, `parseBatchArgs`, one small function per verb) - Modify: `cmd/sift/main.go` (one `case "batch":` line and the usage text), `README.md`, `skill/SKILL.md`, `docs/implementer-log.md` ## What already exists - `cmd/sift`: `loadClient() (base, key string, err error)` reads `client.toml`; `searchcmd.go` is the pattern for a verb (build the request with the bearer header, decode the JSON envelope, exit non-zero on a non-2xx). Never put a key on the command line. - The endpoints (task 07): - `POST /v1/batch` `{"targets":[…]}` → `202 {"success","job_id","status","targets","reused"}`; - `GET /v1/batch/{id}` (summary; `?results=1` inlines results); - `GET /v1/batch/{id}/{idx}` (one result); - `DELETE /v1/batch/{id}`. ## Interfaces added here ```go // parseBatchArgs validates a `sift batch` command line (without "batch"). func parseBatchArgs(args []string) (verb string, rest []string, err error) ``` ``` sift batch submit # prints {"job_id", "status", "reused"} sift batch status [--results] # prints the status body (a summary unless --results) sift batch get # prints one target's result, as `sift fetch --json` would sift batch cancel # prints {"success", "status"} ``` ## Rules 1. `parseBatchArgs` accepts exactly the forms above: - `submit` with one argument (`-` means stdin); - `status` with a job id, optionally preceded by `--results`; - `get` with a job id and an `idx` that is a non-negative integer; - `cancel` with a job id. Anything else (no verb, a missing or extra argument, an unknown verb, a bad `idx`) is an error. `rest` is the arguments after the verb, unchanged. 2. `submit` reads the file and checks it locally before calling the server: it must be a JSON object with a non-empty `targets` array. It may also be a bare array of targets or of URL strings; wrap either into `{"targets": […]}`, turning each string into `{"url": s}`. 3. Every verb prints the server's JSON on stdout, and on a non-2xx prints the error envelope to stderr and exits with `exitFor(status, msg)`, as `fetch` does. 4. **`README.md`**, a short "Batch" section: - the four endpoints; - the model: serial per job, one worker by default, round-robin across jobs; - the caps (32 targets, 1 live job per caller, 8 in all) and the 256 KiB body; - idempotency: the same list from the same caller returns the same job while it is live or has results; - the lease/TTL and restart semantics; - the summary-vs-results reads; - restricted-caller redaction; - the `sift_batch_*` metrics. 5. **`skill/SKILL.md`**, a short section: - for several URLs you will read later, `sift batch submit` them once, rather than a loop of `sift fetch`; - poll with `sift batch status`, and read each result with `sift batch get `; - don't use `--results` unless you need every result at once (it can be large); - a batch is slow on purpose: for one URL you need now, use `sift fetch`. ## Steps - [ ] **1.** `git switch p6`; copy the given test. **2. See it fail.** - [ ] **3.** `batchcmd.go`. **4.** `main.go`. **5.** README and skill. - [ ] **6.** `gofmt -w`; `go test -race -count=3 ./cmd/sift/` → `ok`. **7.** `make gate`. - [ ] **8.** Row `p6/09-cli-docs`; commit. ```sh git add cmd/sift README.md skill/SKILL.md docs/implementer-log.md git commit -F .state/commit-msg.txt ``` ## Done when - The given test and every existing cmd test pass under `-race -count=3`; `make gate` ok; the given file is byte-identical.