Guidance for anyone contributing to this repo, human or agent. Claude Code reads this through CLAUDE.md.
Goldfish is a voice recorder with no radio. Each voice sets how much of its words are kept, the record forgets on a schedule, and the device answers with light, never sound.
v01/;protocol/. Goldfish is one profile of it.BUILD-LOG.md, with the Start here section at the top.BACKLOG.md, with priorities and definitions of done.03-architecture/decisions/ (ADRs).BUILD-LOG.md (Start here, the last two phases, the open items) and the three highest-numbered ADRs in 03-architecture/decisions/.~/.venvs/goldfish-cad/bin/python v01/validate.py # every check passes: the last line reads N/N passed
The venv: uv venv --python 3.12 ~/.venvs/goldfish-cad && uv pip install --python ~/.venvs/goldfish-cad/bin/python build123d trimesh numpy scipy pytest rtree manifold3d networkx.| Path | What | Rebuilt by |
|---|---|---|
v01/cad/pebble_spec.json |
The shape and the switches that drive the CAD | edited by hand |
v01/cad/pebble_layout.py |
Packer: places every part in the cavity | imported |
v01/cad/pebble_enclosure.py |
Shells, eye, band, caps, cradle → out_pebble/ (STL, STEP, layout.json) |
v01/build.py; from scratch ≈ 3 min, resumed from a checkpoint ≈ 15–90 s |
v01/cad/stages.py |
Checkpoints the enclosure build after each tick("…") section, in ~/.cache/goldfish-cad/ |
used by the enclosure |
v01/cad/stamps.py |
Records what each step read (out_pebble/stamps.json); lists the files each step depends on |
used by every step |
v01/cad/buildability.py |
Walks the build as a builder would: BOM against the model, fastener counts, shells closing straight, driver access to every screw at its step, nut slots, board parts under bosses and heads, cable lengths, print bed and supports → out_pebble/buildability.json |
run by validate.py ("Buildable kit") |
v01/cad/section.py |
Cuts the printed meshes with a plane → out_pebble/sections/*.svg and .png (posts, icon, eye, or --x/--y/--z) |
by hand, to look at a change |
v01/cad/board_pads.py |
Every part's pads (silkscreen name, position, source) and the frame each board is drawn in (ADR-0021) | imported by the router, the checks and, through wires.json, the site |
v01/cad/wire_routes.py |
Routes every wire in wiring.md (66 at the last run; the count is summary.wires in wires.json; 31 runs, pad to pad) → out_pebble/wires.json and the cut list in wiring.md |
v01/build.py (≈ 45 s) |
v01/cad/make_web.py |
Web meshes, print copies on the bed, print.json |
after the enclosure |
v01/cad/review_geometry.py |
Geometric checks (45 at the last run) → out_pebble/review.json |
run by validate.py |
v01/sim/power_sim.py + design.json |
Electrical simulation (battery, charger, heat, rails) | run by validate.py |
v01/firmware/goldfish/ + js/goldfish.js |
Device logic in Python, mirrored in JS for the site | tests in v01/firmware/tests |
v01/firmware/scenarios/meeting.json + goldfish/meeting.py |
The site's meeting script and the runner that plays it through the real daemon on the sim board (ADR-0019) | read by site/build.py and the flow test |
v01/firmware/tools/flow_check.py |
The firmware flow test: the meeting replayed, checked from the SPI bytes, motor pin and tag | run by validate.py ("Firmware flow") and tests/test_flow.py |
protocol/ |
RP spec, Python and JS reference implementations, vectors | conformance in validate.py |
v01/bom.csv, wiring.md, assembly.md, note-format.md |
Build docs | edited by hand (print rows and the cut list are generated) |
site/workbench.src.html |
The workbench page (/workbench/ on the live site) |
python3 site/build.py (local) and --web (deploy folder site/web/) |
site/landing.src.html + site/landing/ |
The landing page (the live site's root), its photos, 3D-model data and 404 page (ADR-0022). Copy follows 01-concept/landing/voice-guide.md |
python3 site/build.py --web; the model data with site/landing_model.py after a CAD change |
After a CAD change, run one command:
~/.venvs/goldfish-cad/bin/python v01/build.py # reruns only the stale steps, then validate.py
~/.venvs/goldfish-cad/bin/python v01/build.py --site # … and the site (local and deploy folder)
~/.venvs/goldfish-cad/bin/python v01/build.py --fresh # every step from scratch, no checkpoints: before a release or a commit of print files
pebble_enclosure.py → wire_routes.py → make_web.py → validate.py (→ site/build.py and --web).stages.py splits main() at its tick("…") calls and keeps a checkpoint after each section. A run starts from the first section whose code changed: an edit near the end rebuilds in about a minute.main(), or the spec, pebble.py, pebble_layout.py or stages.py, restarts from the top.keep-out, named <what> of <owner>. The packer, the wire router, the checks and the site treat it like any part. No consumer needs feature-specific code.python v01/cad/section.py posts (or icon, eye, --y …) draws a section of the printed meshes with the boxes it crosses.Truth and checks
Python ↔ JS parity
goldfish/*.py for the device and js/goldfish.js for the site. validate.py compares ring frames, seat arcs and eye-press sequences bit for bit.pmod in goldfish.js).goldfish.js is its instant and offline preview and the Device tab's engine. A change to the meeting's lines goes in scenarios/meeting.json, never in the page, and the "Firmware flow" check replays it.CAD (build123d / OpenCASCADE)
fuse(..., tol=0.01) for unions on spline faces.dir=(0, 0, 1); their normals can point down.Pebble.inset_of). Outward offsets stay straight: slope-aware outward lofts broke booleans and turned the band inside-out.Parts and sourcing
Decisions and docs
03-architecture/decisions/ (next number, never renumber). Quote the request that caused it verbatim.BACKLOG.md (IDs never reused) and add a phase to BUILD-LOG.md when a round of work ends: what was asked (verbatim), done, found or fixed, the commit.Writing: plain, concrete English. Short sentences, numbers with units. No hype.
Licences: code Apache-2.0 (SPDX header on every .py and .js), hardware files CERN-OHL-P-2.0, docs CC BY 4.0 (see LICENSE).
Secrets and publishing
~/.herenow/credentials. Never print it or commit it, or .herenow/state.json.publish.sh site/web --slug astral-aura-h93b → npc.here.now/demo-goldfish: the landing page at /, the workbench at /workbench/) and making the GitHub repo public are the owner's calls. Ask first.These come from the ADRs. Don't break them without a new ADR.
v01/note-format.md), written to the tag when the meeting ends.# AGENTS.md: working on Goldfish
Guidance for anyone contributing to this repo, human or agent. Claude Code reads this through `CLAUDE.md`.
## What this is
Goldfish is a voice recorder with no radio. Each voice sets how much of its words are kept, the record forgets on a schedule, and the device answers with light, never sound.
- **Two products live here:**
- the **v01 kit**: a printed pebble around a Radxa ZERO 3E, in `v01/`;
- the **Resolution Protocol**: a device-independent draft standard, in `protocol/`. Goldfish is one profile of it.
- **History and current state:** `BUILD-LOG.md`, with the **Start here** section at the top.
- **What's open:** `BACKLOG.md`, with priorities and definitions of done.
- **Why things are the way they are:** `03-architecture/decisions/` (ADRs).
## Before you change anything
1. Read `BUILD-LOG.md` (Start here, the last two phases, the open items) and the three highest-numbered ADRs in `03-architecture/decisions/`.
2. Run the checks and confirm they pass before you touch anything:
```sh
~/.venvs/goldfish-cad/bin/python v01/validate.py # every check passes: the last line reads N/N passed
```
The venv: `uv venv --python 3.12 ~/.venvs/goldfish-cad && uv pip install --python ~/.venvs/goldfish-cad/bin/python build123d trimesh numpy scipy pytest rtree manifold3d networkx`.
## Where things are
| Path | What | Rebuilt by |
|---|---|---|
| `v01/cad/pebble_spec.json` | The shape and the switches that drive the CAD | edited by hand |
| `v01/cad/pebble_layout.py` | Packer: places every part in the cavity | imported |
| `v01/cad/pebble_enclosure.py` | Shells, eye, band, caps, cradle → `out_pebble/` (STL, STEP, `layout.json`) | `v01/build.py`; from scratch ≈ 3 min, resumed from a checkpoint ≈ 15–90 s |
| `v01/cad/stages.py` | Checkpoints the enclosure build after each `tick("…")` section, in `~/.cache/goldfish-cad/` | used by the enclosure |
| `v01/cad/stamps.py` | Records what each step read (`out_pebble/stamps.json`); lists the files each step depends on | used by every step |
| `v01/cad/buildability.py` | Walks the build as a builder would: BOM against the model, fastener counts, shells closing straight, driver access to every screw at its step, nut slots, board parts under bosses and heads, cable lengths, print bed and supports → `out_pebble/buildability.json` | run by `validate.py` ("Buildable kit") |
| `v01/cad/section.py` | Cuts the printed meshes with a plane → `out_pebble/sections/*.svg` and `.png` (`posts`, `icon`, `eye`, or `--x/--y/--z`) | by hand, to look at a change |
| `v01/cad/board_pads.py` | Every part's pads (silkscreen name, position, source) and the frame each board is drawn in (ADR-0021) | imported by the router, the checks and, through `wires.json`, the site |
| `v01/cad/wire_routes.py` | Routes every wire in `wiring.md` (66 at the last run; the count is `summary.wires` in `wires.json`; 31 runs, pad to pad) → `out_pebble/wires.json` and the cut list in `wiring.md` | `v01/build.py` (≈ 45 s) |
| `v01/cad/make_web.py` | Web meshes, print copies on the bed, `print.json` | after the enclosure |
| `v01/cad/review_geometry.py` | Geometric checks (45 at the last run) → `out_pebble/review.json` | run by `validate.py` |
| `v01/sim/power_sim.py` + `design.json` | Electrical simulation (battery, charger, heat, rails) | run by `validate.py` |
| `v01/firmware/goldfish/` + `js/goldfish.js` | Device logic in Python, mirrored in JS for the site | tests in `v01/firmware/tests` |
| `v01/firmware/scenarios/meeting.json` + `goldfish/meeting.py` | The site's meeting script and the runner that plays it through the real daemon on the sim board (ADR-0019) | read by `site/build.py` and the flow test |
| `v01/firmware/tools/flow_check.py` | The firmware flow test: the meeting replayed, checked from the SPI bytes, motor pin and tag | run by `validate.py` ("Firmware flow") and `tests/test_flow.py` |
| `protocol/` | RP spec, Python and JS reference implementations, vectors | conformance in `validate.py` |
| `v01/bom.csv`, `wiring.md`, `assembly.md`, `note-format.md` | Build docs | edited by hand (print rows and the cut list are generated) |
| `site/workbench.src.html` | The workbench page (`/workbench/` on the live site) | `python3 site/build.py` (local) and `--web` (deploy folder `site/web/`) |
| `site/landing.src.html` + `site/landing/` | The landing page (the live site's root), its photos, 3D-model data and 404 page (ADR-0022). Copy follows `01-concept/landing/voice-guide.md` | `python3 site/build.py --web`; the model data with `site/landing_model.py` after a CAD change |
**After a CAD change, run one command:**
```sh
~/.venvs/goldfish-cad/bin/python v01/build.py # reruns only the stale steps, then validate.py
~/.venvs/goldfish-cad/bin/python v01/build.py --site # … and the site (local and deploy folder)
~/.venvs/goldfish-cad/bin/python v01/build.py --fresh # every step from scratch, no checkpoints: before a release or a commit of print files
```
- **Steps, in order:** `pebble_enclosure.py` → `wire_routes.py` → `make_web.py` → `validate.py` (→ `site/build.py` and `--web`).
- **Stale steps fail the build.** Each step stamps the files it read. If one has changed since, the "Generated files current" check fails and names it.
- **The enclosure resumes.** `stages.py` splits `main()` at its `tick("…")` calls and keeps a checkpoint after each section. A run starts from the first section whose code changed: an edit near the end rebuilds in about a minute.
- Changing anything outside `main()`, or the spec, `pebble.py`, `pebble_layout.py` or `stages.py`, restarts from the top.
- Put a new feature in its own section, after the sections it depends on.
- **Keep-outs are layout boxes.** Space that must stay empty (a post's flared root, the icon's light path) is a box with role `keep-out`, named `<what> of <owner>`. The packer, the wire router, the checks and the site treat it like any part. No consumer needs feature-specific code.
- **Look at the change:** `python v01/cad/section.py posts` (or `icon`, `eye`, `--y …`) draws a section of the printed meshes with the boxes it crosses.
## Rules
**Truth and checks**
- Never tune a check to make it pass. If a limit was arbitrary, say so in the check's text and in the log, and point to the real test (see the drop-energy line, B-43).
- A result is "done" only when it has been run. For anything visual, look at the site in a browser too.
- New failure modes get a check. Examples: posts clear of the eye, shells don't overlap, every wire routes.
**Python ↔ JS parity**
- The firmware logic exists twice: `goldfish/*.py` for the device and `js/goldfish.js` for the site. `validate.py` compares ring frames, seat arcs and eye-press sequences bit for bit.
- Change both, or the light-ring parity check fails.
- Floats must match: copy Python's modulo exactly (`pmod` in `goldfish.js`).
- The Meeting tab runs the Python itself in Pyodide (ADR-0019); `goldfish.js` is its instant and offline preview and the Device tab's engine. A change to the meeting's lines goes in `scenarios/meeting.json`, never in the page, and the "Firmware flow" check replays it.
**CAD (build123d / OpenCASCADE)**
- Booleans can fail **silently**. After each one, check that the volume moved the right way and that the result is one solid. Use `fuse(..., tol=0.01)` for unions on spline faces.
- Extrude spline faces with `dir=(0, 0, 1)`; their normals can point down.
- The cavity is a slope-aware **inward** offset (`Pebble.inset_of`). **Outward** offsets stay straight: slope-aware outward lofts broke booleans and turned the band inside-out.
- Assert a printed part's volume is positive before export.
**Parts and sourcing**
- Every BOM row needs an exact product link that someone actually opened, plus an EU link where one exists, with stock status and a date.
- If a page can't be checked, write "unverified" rather than guess.
- A part is a candidate until its datasheet is checked and an ADR or the BOM notes accept it.
**Decisions and docs**
- A design change that alters behaviour, the shape or the parts gets an ADR in `03-architecture/decisions/` (next number, never renumber). Quote the request that caused it verbatim.
- Update `BACKLOG.md` (IDs never reused) and add a phase to `BUILD-LOG.md` when a round of work ends: what was asked (verbatim), done, found or fixed, the commit.
- Quote sources; don't paraphrase them. Model-written text in the product is labelled machine-generated.
**Writing:** plain, concrete English. Short sentences, numbers with units. No hype.
**Licences:** code Apache-2.0 (SPDX header on every `.py` and `.js`), hardware files CERN-OHL-P-2.0, docs CC BY 4.0 (see `LICENSE`).
**Secrets and publishing**
- The here.now key lives in `~/.herenow/credentials`. Never print it or commit it, or `.herenow/state.json`.
- Deploying (`publish.sh site/web --slug astral-aura-h93b` → npc.here.now/demo-goldfish: the landing page at `/`, the workbench at `/workbench/`) and making the GitHub repo public are the owner's calls. Ask first.
- Commits end with the co-author line your tool requires.
## Product rules that code must keep
These come from the ADRs. Don't break them without a new ADR.
- **No radio.** The only data path out is the passive NFC tag, read by a phone touching the tail.
- **Eye presses.** A press **only ever takes the room off the record**. A double press while off resumes, provisionally, with a 30 s objection window (RP §6.3). Holding the eye shows the battery.
- **The eye light** is powered by the microphone rail, so firmware can't light it falsely.
- **Lowering beats raising.** Nobody raises anyone else's level, and uncertainty falls toward keeping less (RP §6).
- **Phone transfer needs no button.** The note is Markdown (`v01/note-format.md`), written to the tag when the meeting ends.
- **Each person can see their own lights.** Arcs face the speaker's seat, as mirrored pairs while there are two mics.