Skip to content

Revision · Project 4 — askr

Project 4 built askr, an AI CLI that calls the Claude Messages API, streams the answer back word by word, and can retrieve from your own notes first. It’s the capstone of Phase 1, where ownership, errors, traits, async, and HTTP all get used at once. Here’s what to carry forward.

  • Trait-first design — every network call hides behind a trait (LlmClient, Embedder), not a concrete type, so tests run with no key and no network, the binary falls back to a mock, and the real client is just one more impl.
  • The Messages API shape — calling Claude means getting the request/response JSON exactly right with reqwest + serde, and pulling secrets from the environment, never the source.
  • A hermetic mock — a MockLlm implementor makes the whole suite offline and fast, which is the payoff of depending on the trait rather than the HTTP client.
  • Streaming without corruption — Server-Sent Events arrive in byte-sized pieces that can split a character in half, so you parse the stream deliberately and drive output through a sink callback for low perceived latency.
  • RAG in principle — before asking, the program chunks your files, embeds them, retrieves the most relevant passages, and stuffs them into the prompt, so the model answers from your text.
  • Vector search by hand — turning text into geometry, a hand-written cosine-similarity hot loop, and an in-memory vector store with top-k, tested with mock embeddings so no real embedder is needed.
  • Production hardening — read config at the edges and fail loudly, collapse errors to one type with a clean exit, split a library from the binary, add GitHub Actions CI, and profile before optimizing.

An AI CLI is mostly untrusted, asynchronous bytes: a stream that can stall, truncate, or split a character. Rust won’t let you pretend those bytes are a clean String, so every await is a point the borrow checker has already proven your data safe to hold and every fallible step returns a Result you must handle. This closes Phase 1 and bridges into Phase 2, where the question turns inward: building systems where nobody has to be trusted.