Skip to content
Merged

Dev #11

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
f226ea9
feat(web): contact form that hides the address and turns bots away
coder-RT Nov 1, 2026
b6ccd01
feat(api): owner-only room map, one per room, kept out of robot activity
coder-RT Nov 1, 2026
e350ef9
feat(ios): room maps on Find, a list grouped like Found, and a tab ta…
coder-RT Nov 1, 2026
409533f
fix(ios): redraw a room's map after a rescan, and on pull to refresh
coder-RT Nov 1, 2026
fd35c46
feat(pipeline): one object per thing when a walk loops back
coder-RT Nov 1, 2026
905dd5b
feat(ios): a room's earlier scans fold under its newest as history
coder-RT Nov 1, 2026
aea5b72
refactor(pipeline): name a sighting's fields instead of indexing a tuple
coder-RT Nov 1, 2026
856c921
chore: make test-all runs every suite and keeps the logs
coder-RT Nov 1, 2026
959c127
feat(api): trust levels by photos seen, "not there" is remembered, fu…
coder-RT Nov 1, 2026
3d354d0
feat(api): the robot package as one .zip, and bounded ask fields
coder-RT Nov 1, 2026
0b2bdd3
feat: a public, geometry-only export of a real scan; test-all reports…
coder-RT Nov 1, 2026
3581ce0
feat(ios): trust levels, share for robots, tap feedback everywhere, s…
coder-RT Nov 1, 2026
c827a3e
feat(web): the real scan at /homes/flat/viewer, a live hero headline,…
coder-RT Nov 1, 2026
3d7acec
feat(api): compass routes for the Watch, from where the room's scan b…
coder-RT Nov 1, 2026
16a5c4f
feat(watch): Recall on Apple Watch Ultra 4, by voice or the Digital C…
coder-RT Nov 1, 2026
b78fdfa
docs: installing and using the Watch preview
coder-RT Nov 1, 2026
5bfed41
feat(api): AR labels: everything found in a room, placed in its saved…
coder-RT Nov 1, 2026
638fdeb
feat(ios): See it in AR, a live LiDAR view while scanning, and an edg…
coder-RT Nov 1, 2026
c4a563c
feat(ios): scans that explain themselves: a plan first, a step card w…
coder-RT Nov 1, 2026
2aea82c
feat: a scan waiting with no worker says so, instead of processing fo…
coder-RT Nov 1, 2026
c1f6e44
fix(ios): quiet on open, and the speaker stops what it is reading
coder-RT Nov 1, 2026
4a66694
docs(web): the landing page's first step describes the guided scan
coder-RT Nov 1, 2026
1c404bb
fix(pipeline): empty the GPU cache only where MPS exists
coder-RT Nov 1, 2026
02c5f97
fix(watch): the Watch stops waiting when the iPhone never answers
coder-RT Nov 1, 2026
91b0964
refactor(api): routers only map; only use cases write the memory
coder-RT Nov 1, 2026
f70aeb9
test(api): Schemathesis checks every GET against the spec, and the sp…
coder-RT Nov 1, 2026
1622596
feat(api): the Verifier sits behind an ArrivalVerifier port
coder-RT Nov 1, 2026
9bf56e8
docs: CLAUDE.md matches the code: in-process Verifier, ARWorldMap res…
coder-RT Nov 1, 2026
d3ac548
fix(ios): handle the now-documented 401 in the generated client's swi…
coder-RT Nov 1, 2026
78f5ed7
feat(web): the problem question rotates, three true words link to the…
coder-RT Nov 1, 2026
f4804d8
fix(pipeline): the warm server is never open, and jobs stay inside th…
coder-RT Nov 1, 2026
08262e5
fix(api): request limits, loopback by default, and inputs that can't …
coder-RT Nov 1, 2026
dc7866c
fix(web): security headers, robots.txt, size before reading, and a li…
coder-RT Nov 1, 2026
446cc96
fix(ios): a pairing link asks first, the key stays on this Mac and th…
coder-RT Nov 1, 2026
5f8169b
feat(web): a Locked to your phone card that says what keeps the home'…
coder-RT Nov 1, 2026
33bbf61
fix(web): the question and the three words wrap on a phone at 200% text
coder-RT Nov 1, 2026
dec96cf
fix(ios): a Tailscale address stays plain http
coder-RT Nov 1, 2026
68c32cd
feat(web,docs): Recall's mark in the tab; how Meta Muse runs Recall a…
coder-RT Nov 1, 2026
9d7f01b
fix(web): Recall's own favicon.ico and Apple icon; the Muse Code sect…
coder-RT Nov 1, 2026
48c3b60
docs: a public install guide in docs/install, tested from a fresh clo…
coder-RT Nov 1, 2026
0b68e18
feat(web): the actual scan as the demo: agents, axes, view options, r…
coder-RT Nov 1, 2026
7a40f9c
fix: furniture stands on the floor in the architect view; Surface ope…
coder-RT Nov 2, 2026
210b57f
feat(web): publish the author's real living-room scan (geometry only,…
coder-RT Nov 2, 2026
d1f1007
docs: the README's install, code layout, Meta Muse, Muse Code and Bas…
coder-RT Nov 2, 2026
6f627a3
ci: install the EGL and OpenGL libraries Open3D loads on Linux
coder-RT Nov 2, 2026
efa7ec3
ci: typecheck the web app with next typegen first
coder-RT Nov 2, 2026
df6e3d7
docs,feat(web): README security and privacy and data model; Devices c…
coder-RT Nov 2, 2026
76ee21b
docs,feat(web): built first for blind and low-vision people, useful t…
coder-RT Nov 2, 2026
59fcc41
docs,feat(web): how Recall never guesses, and a FAQ that points at th…
coder-RT Nov 2, 2026
28d2809
feat(web): Architect opens from above with the walls cut at 1.5 m
coder-RT Nov 2, 2026
6845052
fix(api): guide in the frame of the scan the thing was last seen in
coder-RT Nov 2, 2026
ef57e92
fix(api): open and migrate the database once, at startup, under a lock
coder-RT Nov 2, 2026
4ff7a14
fix(ios): the keyboard hides on a tap outside the field, on Return, a…
coder-RT Nov 2, 2026
39b98e1
feat(ios): the Mac address is hidden in Settings until the eye is tapped
coder-RT Nov 2, 2026
def22b5
feat(web): a warm-yellow 'Explore a real room' button, and a way back…
coder-RT Nov 2, 2026
a15d24a
feat(ios): choose Recall's voice: automatic, female or male, premium …
coder-RT Nov 2, 2026
6ccb06e
docs: mark Muse Code as contributor on the Muse doc
coder-RT Nov 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
# Open3D's Linux wheel loads the system's EGL and OpenGL libraries even when nothing is drawn
- run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libegl1 libgl1 libgomp1
- run: uvx ruff check . && uvx ruff format --check .
- run: >-
uv run --no-project --python 3.12 --with numpy --with scipy --with shapely --with pytest --with httpx
Expand All @@ -56,6 +58,6 @@ jobs:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @recall/web exec tsc --noEmit
- run: pnpm --filter @recall/web typecheck # next typegen first: PageProps and LayoutProps are generated
- run: pnpm --filter @recall/web lint
- run: pnpm --filter @recall/web build
22 changes: 13 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Grounding rules (system prompt **and** enforced in code):
- **Guide** (above) is the lead agent and the only one on the live question path.
- **Verifier** confirms arrival from one camera frame: SAM 3.1 + geometry decide; **Muse Spark 1.1** (hosted, multimodal) is asked only as a tie-break and may only downgrade to `uncertain`, never confirm.
It is an API use case, so its memory updates (self-healing) keep the one-writer rule. Reached through the `ArrivalVerifier`
port: **in-process by default and in the live demo**; over **A2A** optional (`VERIFIER_TRANSPORT`).
port: **in-process** (`VERIFIER_TRANSPORT=inprocess`, the only transport built; A2A for the Verifier is optional and not built).
- **Home Memory agent** exposes read-only skills to external agents over **A2A**, off by default, never the user's live position.
Inbound input is structured only (one object name, validated against `resources/agents/find-object.input.schema.json`; free
text never reaches a model or a tool), then the `PromptScreener` port (**Muse Spark Open** or Glimmer, strict JSON, fails closed).
Expand All @@ -172,7 +172,9 @@ models are banned):
into 3D per frame, then **`cluster_dbscan` per label** merges the sightings, so one mug seen in 30 frames becomes one object with
one centroid and one confidence.
4. **2D geometry (Shapely):** floor outline polygon, wall segments and furniture footprints, used for `room.json` and the occupancy grid.
5. **Later scans:** global registration (FPFH + RANSAC) then ICP into the first scan's frame, with Open3D, never hand-rolled.
5. **Later scans:** the phone relocalises against the room's saved ARWorldMap (ARKit, not a model), so a rescan's poses are
already in the first scan's frame (`aligned_to`); if it doesn't relocalise, memory starts fresh and says so. Point-cloud
registration (Open3D FPFH + ICP) is **not built** (ADR-037); never hand-roll it.
Umeyama stays for ARKit ↔ MapAnything poses.

Result: `room.json` = floor polygon, wall segments, furniture boxes, validated against `schemas/RoomModel.yaml`; written by
Expand Down Expand Up @@ -207,7 +209,7 @@ feeds it the ARKit intrinsics, poses and LiDAR depth. Its output is then already
- If any view has a pose, the first view must too.
- **Umeyama** similarity transform (ARKit vs MapAnything camera centres) is the **check and fallback**: it measures drift,
and aligns an images-only run if ARKit inputs are missing.
- Later scans align to the first with Open3D (global registration, then ICP).
- Later scans share the first scan's frame through ARWorldMap relocalisation (FPFH + ICP not built, ADR-037).

## 5. Scan episodes and upload

Expand Down Expand Up @@ -286,9 +288,9 @@ recall/
│ │ │ │ └── tools.py tool registry, validated against resources/tools.json
│ │ │ ├── adapters/
│ │ │ │ ├── inbound/http/ FastAPI routers: mapping only, no logic
│ │ │ │ ├── inbound/a2a/ verifier_server.py, home_memory_server.py
│ │ │ │ ├── inbound/a2a/ home_memory_server.py
│ │ │ │ └── outbound/
│ │ │ │ ├── agents/ verifier_a2a_client.py, verifier_inprocess.py
│ │ │ │ ├── agents/ verifier_inprocess.py (ArrivalVerifier)
│ │ │ │ ├── vision/ sam31_meta_api.py, spark_vision_meta_api.py (HTTP only)
│ │ │ │ ├── safety/ meta_model_screen.py (Muse Spark Open or Glimmer, strict JSON verdict)
│ │ │ │ ├── llm/ openai_compatible.py (+ baseten_spark_open, glimmer_hosted, lmstudio, meta configs)
Expand All @@ -308,7 +310,7 @@ recall/
│ └── src/recall_pipeline/
│ ├── domain/ Episode, Frame, Pose, arkit_to_opencv(), align.py (Umeyama check/fallback), room_model.py (planes, Shapely 2D geometry), merge.py (per-label clustering), blur.py
│ ├── application/ process_episode.py, ports.py (Reconstructor, Segmenter, EpisodeSource, ResultSink)
│ └── adapters/ mapanything_local.py, mapanything_remote.py, sam31_meta_api.py, sam31_modal.py (optional), open3d_geometry.py (clean-up, segment_plane, DBSCAN, FPFH+ICP), rerun_log.py, storage, api_client.py
│ └── adapters/ mapanything_local.py, mapanything_remote.py, sam31_meta_api.py, sam31_modal.py (optional), open3d_geometry.py (clean-up, segment_plane, DBSCAN), rerun_log.py, storage, api_client.py
├── deploy/
│ ├── baseten/README.md Spark Open: hosted endpoint, key only
│ ├── modal/glimmer/ Glimmer serving (TODO(verify) Modal API); fireworks/ alternative
Expand All @@ -321,7 +323,7 @@ recall/
│ ├── run.py
│ ├── verifier.yaml 20 arrival frames: 10 right spot, 10 wrong spot
│ └── results.md dated accuracy table
├── examples/a2a-client/home_robot.py external agent asking Recall over A2A (Muse Spark)
├── examples/ros2-robot/ a ROS 2 robot loading Recall's export (GET /homes/{id}/robot-package)
├── samples/replay/ recorded scan + recorded model responses for `make demo` (labelled as recorded)
└── docs/ (docs/private/ is gitignored: planning docs, ADRs, progress notes, the key list)
├── architecture.md hexagon diagram + both paths (mermaid)
Expand All @@ -346,9 +348,11 @@ recall/

### Make targets

`setup dev demo live probes rerun test lint lint-imports migrate bundle-spec gen-models gen-swift-client eval`
`setup dev demo live probes rerun test test-all lint lint-imports migrate bundle-spec gen-models gen-swift-client eval`

- `demo`: replay from `samples/replay/`, no keys, output labelled as recorded. `live`: real Meta model calls with `.env`.
- `test-all`: every suite (API, pipeline, lint, imports, web, iOS unit + UI) under `caffeinate`; each run's logs and
summary go to `data/test-runs/` (gitignored) with a line in `runs.jsonl` and `history.md`. `SUITES="api pipeline"` picks some.
- `probes`: one small real call per external model or service (Meta Model API, Baseten Spark Open, Muse Voice Transcribe, SAM 3.1,
R2) that prints what worked and appends the result to `docs/verification.md`; needs `.env`; never run in CI.
- `rerun EPISODE=<path>`: replay a stored episode into the Rerun viewer (`RERUN=1`); dev only, never needed for tests or `make demo`.
Expand All @@ -368,7 +372,7 @@ recall/
| 1 Scan spike + iPhone Scan | Oct 30 to Nov 1 | Full iPhone Scan feature (plain ARKit episode capture, SpeedCoach, metrics, scan report, upload, professional UI); one room episode with ARKit inputs → plane fitting → `room.json` + 3D objects in metres | Objects within ~20 cm of reality on an ARKit-input run (**go/no-go**). Runs alongside phase 2; about 8 to 11 h of iOS work |
| 2 Memory + agent | Oct 31 to Nov 1 | Memory, spatial relations, Guide agent (Spark Open) with tools | 30-question eval runs; answers only from tool output |
| 3 Guidance | Nov 1 to 2 | Find, ask, live ARKit guidance, voice; Watch; **Verifier (in-process)** | Walk to an object with eyes closed; arrival confirmed on camera |
| 4 Proof | Nov 2 to 3 | Viewer, real output on landing page, eval results, sample scan, `samples/replay/`; **A2A servers + schema validation and Meta-model screening + `home_robot.py` demo** | `make demo` works without GPU or keys; verifier precision ≥ 0.9 on 20 frames |
| 4 Proof | Nov 2 to 3 | Viewer, real output on landing page, eval results, sample scan, `samples/replay/`; **A2A Home Memory server + schema validation and Meta-model screening + robot export demo** | `make demo` works without GPU or keys; verifier precision ≥ 0.9 on 20 frames |
| 5 Ship | Nov 3 to 4 | README ("What runs where"), 3-minute video, X post, submission, rehearsal | Submitted before Nov 4 evening Warsaw; tag `submission` |

**Schedule:** phases keep their dates; we work overtime (evenings) to finish each one on time rather than cut scope.
Expand Down
4 changes: 4 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Installing Recall

The installation guide lives in [docs/install/README.md](docs/install/README.md): what Recall is, then step by step
for a person or an AI agent (the website, the no-keys demo, and the live setup with the iPhone and the Watch).
21 changes: 18 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
SHELL := /bin/bash
API := services/api
PIPE := services/pipeline
# where the API listens: the LAN by default so the phone reaches it; BIND=127.0.0.1 when a Cloudflare Tunnel is the only way in
BIND ?= 0.0.0.0
WEB := apps/web
REDOCLY := npx -y @redocly/cli@2.72.1

.PHONY: help setup dev worker robot-export regression viewer live demo record probes diagrams pair rerun test lint lint-imports migrate bundle-spec gen-models gen-swift-client eval
.PHONY: help setup dev worker robot-export regression viewer public-scan live demo record probes diagrams pair rerun test test-all lint lint-imports migrate bundle-spec gen-models gen-swift-client eval

help:
@grep -E '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) | awk -F':.*?## ' '{printf " %-18s %s\n", $$1, $$2}'
Expand All @@ -17,7 +19,7 @@ setup: ## Install Python 3.12 services (uv), the web app (pnpm) and generate the
$(MAKE) bundle-spec gen-models migrate

dev: ## Run the API on :8000 (reachable on the LAN), the scan worker and the web app on :3000
( cd $(API) && uv run recall-api ) & $(MAKE) worker & ( cd $(WEB) && pnpm dev ) ; wait
( cd $(API) && RECALL_BIND_HOST=$(BIND) uv run recall-api ) & $(MAKE) worker & ( cd $(WEB) && pnpm dev ) ; wait

worker: ## The scan job worker (Huey on SQLite) and the warm pipeline (MapAnything kept loaded, freed after 10 min idle)
( cd $(PIPE) && uv run recall-pipeline serve ) & ( cd $(API) && uv run huey_consumer recall_api.infra.jobs.huey -w 1 -k thread ) ; wait
Expand All @@ -31,8 +33,18 @@ robot-export: ## One room's memory for a robot (Nav2 map, things, furniture): ma
regression: ## Re-run every processed scan now (no posting; cached SAM answers) and compare with the last run
cd $(API) && uv run python -c "from recall_api.infra import jobs, regression; print('\n'.join(regression.run(jobs.settings, jobs.run_pipeline)))"

public-scan: ## The real scan for the public site, geometry only (no photo data): make public-scan [HOME_ID=flat] -> apps/web/public/real-scan/<home>/ (review before committing)
@H="$(or $(HOME_ID),flat)"; \
case "$$H" in *[!a-z0-9-]*|"") echo "HOME_ID: lower-case letters, digits and hyphens only."; exit 2;; esac; \
ID=$$(sqlite3 data/recall.db "SELECT id FROM scans WHERE home_id = '$$H' AND status = 'processed' ORDER BY created_at DESC LIMIT 1" 2>/dev/null); \
E=$$(ls -d data/blobs/scans/*_$$ID/episode 2>/dev/null | head -1); \
if [ -z "$$ID" ] || [ ! -f "$$E/home_colors.npy" ]; then echo "No processed scan of '$$H' yet."; exit 1; fi; \
cd $(PIPE)/scripts && uv run --project .. python export_viewer_scan.py --public "$(CURDIR)/$$E" "$(CURDIR)/$(WEB)/public/real-scan/$$H" && \
echo "Review apps/web/public/real-scan/$$H/ (no photos, nothing personal) before committing it."

viewer: ## Export a home's newest processed scan for /homes/<home>/viewer: make viewer [HOME_ID=flat] (gitignored output)
@H="$(or $(HOME_ID),flat)"; \
case "$$H" in *[!a-z0-9-]*|"") echo "HOME_ID: lower-case letters, digits and hyphens only."; exit 2;; esac; \
ID=$$(sqlite3 data/recall.db "SELECT id FROM scans WHERE home_id = '$$H' AND status = 'processed' ORDER BY created_at DESC LIMIT 1" 2>/dev/null); \
E=$$(ls -d data/blobs/scans/*_$$ID/episode 2>/dev/null | head -1); \
if [ -z "$$ID" ] || [ ! -f "$$E/home_colors.npy" ]; then \
Expand All @@ -41,7 +53,7 @@ viewer: ## Export a home's newest processed scan for /homes/<home>/viewer: make
echo "Open http://localhost:3000/homes/$$H/viewer"

live: ## The API with real Meta model calls (keys from .env), and the scan worker
( cd $(API) && uv run recall-api ) & $(MAKE) worker ; wait
( cd $(API) && RECALL_BIND_HOST=$(BIND) uv run recall-api ) & $(MAKE) worker ; wait

demo: ## No keys: the API on a memory built from a recorded real scan; the Guide's replies are replayed (labelled recorded)
cd $(API) && uv run python -m recall_api.infra.demo serve $(if $(PORT),--port $(PORT))
Expand All @@ -68,6 +80,9 @@ test: ## Unit, integration and contract tests for both services
cd $(API) && uv run pytest
cd $(PIPE) && uv run pytest

test-all: ## Every suite (API, pipeline, lint, imports, web, iOS unit + UI), logs kept in data/test-runs/ (gitignored): make test-all [SUITES="api pipeline"]
python3 scripts/test_all.py

lint: ## ruff for both services, ESLint for the web app
cd $(API) && uv run ruff check . && uv run ruff format --check .
cd $(PIPE) && uv run ruff check . && uv run ruff format --check .
Expand Down
Loading
Loading