# Pricklypear agent instructions The image is the API. Eval Lisp on the running habitat; do not invent REST endpoints for app features (todos, notes, bible, …). ## Origin Base URL (this host): https://pricklypear.rocks Machine contract: GET https://pricklypear.rocks/.well-known/agent.json ## Auth Scheme: HTTP Basic against the image credentials table (same username/password as https://pricklypear.rocks/login). How to get credentials: ask the human once. Store them in a local secrets file (mode 600). This document never contains passwords. Create users at /admin/users after bootstrap login. Notes: - Use the image password, not nginx/edge htpasswd. - No OAuth/JWT in v1. - Browser clients may use cookie pp_session after POST /login; agents usually use Basic auth. ## Eval API POST https://pricklypear.rocks/api/eval Content-Type: application/json Accept: application/json Authorization: Basic Request body: {"expr":"(+ 1 2)"} Response: {"ok": true, "value": "3"} {"ok": false, "error": "unbound: foo"} Unauthenticated calls are rejected (401). Errors are plain strings (arity mismatch, unbound, no active request). ## First calls - `(whoami)` - `(current-branch)` - `(list-packages)` - `(load-library "notes")` - `(notes/doc/get "agents")` - `(load-library "todo")` - `(describe "todo")` - `(apropos-docs "todo/item")` - `(function-doc "todo/item/add")` - `(load-library "bible")` - `(bible/chapter "John" 3)` ## Two kinds of functions 1. DATA / builders — take explicit arguments; safe from /api/eval: (bible/chapter "John" 3) (bible/chapter-html "John" 3 ()) (todo/list) after (load-library "todo") 2. HTTP HANDLERS — read (request-method)/(request-param ...); from /api/eval they return: no active request Examples: bible/chapter-reader, todo/page, notes/page, */page. That message is expected. Call data functions, or hit the route in a browser / with a real HTTP request. ## Selective discovery (REPL) Do not dump (list-bindings) into the model context (hundreds of names). Expand one package or name at a time: (list-packages) (load-library "todo") (describe "todo") ; package summary + shallow exports (package-exports "todo") ; shallow pkg/local names (package-bindings "todo") ; ALL names under todo/ (any depth) (apropos-docs "todo/item") ; ((name doc) …) (function-doc "todo/item/add") (function-source "todo/item/add") (function-ast "todo/item/add") (read-string "(lambda (x) x)") ; parse LLM Lisp text to data (package-exports "std") ; frozen kernel/std surface Human browser: https://pricklypear.rocks/ref (packages) or https://pricklypear.rocks/ref?q=todo ## How to build a REPL client The image is the API. Your client only needs: 1. Base URL (this host) 2. HTTP Basic username/password (from a human; never in this file) 3. POST /api/eval with JSON body {"expr":"(whoami)"} 4. Parse JSON {ok, value|error} curl: ``` curl -sS -u USER:PASS -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{"expr":"(whoami)"}' https://pricklypear.rocks/api/eval ``` Python: ``` import base64, json, urllib.request base = "https://pricklypear.rocks" user, password = "USER", "PASS" expr = "(whoami)" req = urllib.request.Request( base.rstrip("/") + "/api/eval", data=json.dumps({"expr": expr}).encode(), method="POST", headers={ "Content-Type": "application/json", "Accept": "application/json", "Authorization": "Basic " + base64.b64encode( f"{user}:{password}".encode()).decode("ascii"), }, ) print(urllib.request.urlopen(req).read().decode()) ``` Local laptop: OCaml pp-eval CLI (see repo skill pricklypear-eval) or scripts/pp-eval.sh after sourcing ~/.config/pricklypear/env. ## Symbols vs strings (code-as-data) Quoted bare names are symbols, not strings: (symbol? 'lambda) => #t (string? "lambda") => #t (symbol=? (car (read-string "(lambda (x) x)")) 'lambda) => #t Walk trees with symbol=?, not string-eq on car. (string-eq (symbol->string (car form)) "lambda") is OK if needed. CRUD still accepts 'type-name (symbols coerced at the DB boundary). ## Application libraries (load-library) - std, ui, layout, welcome — shell / HTML helpers - todo, notes, outline, plan, contacts, bookmarks, chat - calendar, flashcards, bible, dabar, weather, signup, ai Cold image after restart: (load-library "NAME") before calling. ## Live agents note (habitat AGENTS.md) After (load-library "notes"), read: (notes/doc/get "agents") Browser: https://pricklypear.rocks/notes?slug=agents This note is the living agent playbook for this habitat. Agents SHOULD update it (notes/doc/upsert) when they learn durable conventions, new libraries, or wiki workflow changes — same as editing AGENTS.md in a git repo, but durable in Postgres on the current branch. Also see (notes/doc/get "wiki-schema") for the wiki LLM preamble. ## Notes / wiki (image is the API) (load-library "notes") (notes/doc/list) (notes/doc/get "pricklypear") (roam/backlinks "pricklypear") (wiki/ingest-from-source "…raw source text…") (wiki/query "…") (wiki/lint) Outline nodes support op=eval (Lisp with self/doc/slug bound). Wiki links in bodies: [[slug]] or [[slug|label]]. ## Media upload (images / PDF) Do NOT push multi-KiB binaries through POST /api/eval — the eval body cap is 64 KiB. Use the dedicated media endpoints instead. ### Write (auth required — HTTP Basic or session) POST https://pricklypear.rocks/api/media Max body: 5 MiB. Allowed Content-Types: image/png image/jpeg image/gif image/webp image/svg+xml application/pdf Body modes: 1. Raw binary — set Content-Type to the media type; optional header X-Filename: original-name.png 2. JSON — Content-Type: application/json {"content_type":"image/png","data_b64":"…", "filename":"shot.png"} // filename optional 3. Multipart — Content-Type: multipart/form-data field name=file (or name=media) with filename + part Content-Type Success (201): {"ok":true,"id":"","url":"/media/", "content_type":"image/png","byte_size":N,"sha256":"…"} ### Read / list / delete GET https://pricklypear.rocks/media/ → raw bytes (auth required) GET https://pricklypear.rocks/api/media → JSON meta list (no bytes) DELETE https://pricklypear.rocks/api/media/ → author or admin GET https://pricklypear.rocks/media → browser gallery + upload form POST https://pricklypear.rocks/media/upload → browser multipart form ### Client helper # same env as pp_eval: PP_URL PP_USER PP_PASS python3 scripts/pp_media.py put ./shot.png python3 scripts/pp_media.py get -o out.png python3 scripts/pp_media.py list python3 scripts/pp_media.py delete ### curl (raw PNG) curl -sS -u USER:PASS -X POST https://pricklypear.rocks/api/media \ -H 'Content-Type: image/png' -H 'X-Filename: shot.png' \ --data-binary @shot.png ### curl (multipart) curl -sS -u USER:PASS -F 'file=@shot.png;type=image/png' \ https://pricklypear.rocks/api/media ### Lisp (meta only — not bulk upload) (media-meta "") ; JSON meta or () (media-list) ; your recent meta (media-url "") ; "/media/" (media-delete "") (media-put "image/png" b64 [filename]) ; ≤256 KiB only Embed in a note node body (wiki syntax): [[media:]] [[media:|alt text]] Renders when the note is viewed (auth required to fetch the image). Ownership is the uploading author. Reference media from notes/HTML with the returned /media/ URL (auth required to fetch). ## Rules 1. One Lisp expression per POST /api/eval unless you own multi-step state yourself. 2. Load libraries before calling package functions: (load-library "todo"). 3. Prefer a personal branch before defining things: (checkout "USER/personal"). 4. Two function kinds: DATA builders take explicit args and work from /api/eval (e.g. bible/chapter, bible/chapter-html with book/chapter); HTTP HANDLERS (*/page, */reader) use request-method/request-param and return "no active request" outside a browser hit — that is expected, not a crash. 5. Do not invent app-specific REST APIs — call Lisp instead. 6. Never put passwords in code you write back to the image. 7. Unauthenticated /api/eval is rejected; /agent (HTML portal) is private. 8. Dotted rest params (a . b) are NOT supported; use fixed-arity params. 9. File product issues as notes in the inbox when you cannot fix OCaml; do not invent REST for filing. 10. After (load-library "notes"), read the live agents note: (notes/doc/get "agents"). It is the habitat AGENTS.md — edit it with notes/doc/upsert when agent guidance should change. 11. Discover selectively — do not dump all bindings into context: (list-packages), (describe "pkg"), (package-bindings "pkg"), (package-exports "pkg"), (apropos-docs "pat"), (function-doc name). 12. Build a REPL client from this document: HTTP Basic + POST /api/eval JSON {"expr":"…"}. Prefer that over inventing REST. curl and Python examples are below; local hosts may use the pp-eval CLI. 13. Knowledge wiki (Karpathy loop): (wiki/ingest-from-source …), (wiki/query …), (wiki/lint); wiki links are [[slug]] on outline bodies. 14. Binary media (images/PDF): do NOT push multi-KiB bytes through /api/eval (64 KiB cap). Use POST /api/media (Basic auth; raw body, JSON data_b64, or multipart field name=file; up to 5 MiB) or scripts/pp_media.py. Browser UI: GET /media + POST /media/upload. Serve via GET /media/. Meta via (media-meta id) / (media-list); tiny fixtures only via (media-put …). ## curl example curl -sS -u USER:PASS -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{"expr":"(whoami)"}' https://pricklypear.rocks/api/eval BusyBox wget often lacks --user/--password; prefer curl or python. ## python example ```python import base64, json, urllib.request base = "https://pricklypear.rocks" user, password = "USER", "PASS" expr = "(whoami)" req = urllib.request.Request( base.rstrip("/") + "/api/eval", data=json.dumps({"expr": expr}).encode(), method="POST", headers={ "Content-Type": "application/json", "Accept": "application/json", "Authorization": "Basic " + base64.b64encode( f"{user}:{password}".encode()).decode("ascii"), }, ) print(urllib.request.urlopen(req).read().decode()) ``` ## Filing issues Prefer notes inbox (notes/doc/ensure-inbox + outline nodes) over inventing bug-tracker REST. Keep OCaml-kernel bugs precise with repro expressions. Durable agent guidance belongs in the live agents note (slug agents), not only in git AGENTS.md. ## Related public surfaces - Welcome: https://pricklypear.rocks/welcome - Source browser: https://pricklypear.rocks/code - Book PDF: https://pricklypear.rocks/book.pdf - Health: https://pricklypear.rocks/health - Interactive REPL UI (auth required): https://pricklypear.rocks/repl - Private agent portal (auth required): https://pricklypear.rocks/agent - Media gallery / upload (auth required): https://pricklypear.rocks/media ## What not to do - Do not open or rely on the raw TCP REPL port from the internet. - Do not invent per-app REST endpoints. - Do not put secrets into (define …) forms or public routes. - Do not treat "no active request" as a host crash — call a data fn. - Do not base64 multi-MiB images into /api/eval — use POST /api/media.