Files
sift/docs/plans/p6/08-cli-docs.md
T
kyleandClaude Opus 5.5 218eaa8b53 P6 plan, owner review folded in: caller travels with the job, 1–2 round-robin workers, busy retry, summary reads, local-only tests, 8 tasks
Kyle: "Fold them into the plan." What changed and why is in docs/P6.md §14. In short:
- FetchFunc gets the *auth.Caller (the given api test could not compile, and a fetch with no
  caller would bypass per-caller gates, the pool limit, the cache class and redaction);
- store reads never bump the lease (task 01's rule contradicted its own test); the API Touches;
- 1-2 workers round-robin over live jobs, so a batch holds at most 2 of the 8 global slots;
  busy targets retry in the runner (bounded); batch PDFs use their own pool name; 210 s deadline;
- the api tests no longer touch the network or race the cancel; private names via fake DNS;
- failed/cancelled jobs are not reused; GET is a summary plus GET …/{idx} and ?results=1;
- the 256 KiB body cap moves into ServeHTTP; a fixed metrics label for /v1/batch/{id};
- guard.PreCheck (given verbatim) replaces guard.ParseURL at submit;
- 5 tasks became 8 (refactor, wiring, endpoints and serve/config split), each restating the
  APIs it uses; every given test compiles against a panic-only skeleton and fails on it.

P6 starts after P5c: task 04 moves v1fetch, which P5b changes. Recheck the api given tests
against post-P5c master before the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:55:50 -07:00

3.9 KiB

P6 task 08: 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: _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 06):
    • 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

// parseBatchArgs validates a `sift batch` command line (without "batch").
func parseBatchArgs(args []string) (verb string, rest []string, err error)
sift batch submit <targets.json|->     # prints {"job_id", "status", "reused"}
sift batch status [--results] <job_id> # prints the status body (a summary unless --results)
sift batch get <job_id> <idx>          # prints one target's result, as `sift fetch --json` would
sift batch cancel <job_id>             # 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 <job> <idx>;
    • 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/08-cli-docs; commit.
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.