Skip to content

Start the free Asimov collector on the writer, not the handle warm-up caught - #6472

Merged
vivekchand merged 1 commit into
mainfrom
fix/asimov-collector-writer
Oct 17, 2026
Merged

vivekchand merged 1 commit into
mainfrom
fix/asimov-collector-writer

Conversation

@vivekchand

Copy link
Copy Markdown
Owner

No-PRD: fixes a shipped capability that never ran; the behaviour it restores is REQ-ASIMOV-001 in https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/requirements/a508a5c5-8821-4964-8930-3561b7d02f2b

The bug

/api/asimov/runs has always returned {"runs":[]} on this node, and the daemon log carries this on every boot since the connector shipped:

WARNING Asimov collection could not start. Other collectors will continue.

The except Exception logged no traceback, so it read as an optional collector declining rather than a failure. It is a failure. With exc_info turned on:

File "clawmetry/asimov/daemon.py", line 36, in start
  reader = AsimovLocalStore(store, initialize=True)
File "clawmetry/asimov/store.py", line 74, in __init__
  raise RuntimeError("Asimov ingestion requires the daemon writer")
ASIMOV-DIAG store=<class 'clawmetry.local_store._ProxyStore'> read_only=True

Why it happens

Ordering inside sync.py::run_daemon:

  1. _startup_store = get_store(read_only=False) — but nothing has called mark_writer_owner() yet, so for a process whose node is already registered, get_store takes the _daemon_registered() branch and returns the dashboard's read-only _ProxyStore. The log line after it still says "owned".
  2. local_server.start() — this is what calls mark_writer_owner() and opens the real writer, caching it in _store_rw.
  3. _start_asimov(_startup_store) — passes the handle from step 1.

Every other caller in the daemon calls get_store() after step 2 and gets the real writer, which is why only Asimov collection is affected. Pro's robotics ingester resolves its own store (robotics/plugin.py:43) and has been ingesting the same journals fine, which is how an Asimov run can sit in robotics_runs while asimov_runs stays empty.

The fix

daemon.daemon_writer(store) returns the handle it was given when that handle can write, and asks local_store.get_store(read_only=False) when it cannot. By step 3 that answers with the real writer. One function, no change to global store semantics, and no change for a caller that already holds a writer.

Verification

Hot-patched into site-packages and restarted the daemon on this machine:

  • /api/asimov/runs now returns the imported run: lab-asimov-1 | asimov_recording | hardware | 95 events
  • /asimov renders it: robot lab-asimov-1, status Recorded, mode WALK, 20 joint rows, zero page errors
  • no new "could not start" warning after the restart

Guards in tests/test_asimov_daemon.py: a start() handed a read-only proxy resolves and ingests; a start() handed a writer must not re-resolve (the fake get_store raises if it does). Revert-proofed: without the fix the first test fails with the same RuntimeError at store.py:74.

Found while checking that the free Asimov view still works for installs without the Physical AI workbench (#6471).

🤖 Generated with Claude Code

… caught

The daemon has never ingested an Asimov journal on an install that had
already registered a node. sync.py warms local_store.get_store up at
boot and keeps that handle, then starts local_server, which is what
calls mark_writer_owner(). Until that call, get_store answers a
registered-daemon process with the dashboard's read-only _ProxyStore, so
the handle the warm-up captured is a proxy. Every later caller in the
daemon gets the real writer from _store_rw, but the Asimov collector is
handed the stale one and refuses:

  RuntimeError: Asimov ingestion requires the daemon writer
  WARNING Asimov collection could not start. Other collectors will continue.

That warning has been in this node's log on every boot since the feature
shipped, and /api/asimov/runs has always been empty, so /asimov is a view
that can never hold anything. The warning swallowed the traceback, which
is why it read as an optional collector declining rather than a failure.

Resolve the writer at start instead of trusting the caller's handle. A
caller that already holds a writer keeps it.

Verified on this node: with the fix hot-patched into site-packages and
the daemon restarted, an imported recording now reaches asimov_runs and
/asimov renders it (lab-asimov-1, Recorded, WALK, 20 joints), with no
new "could not start" warning.

Guard: a start() handed a read-only proxy must resolve and ingest, and a
start() handed a writer must not re-resolve. Revert-proofed: without the
fix the first test raises the same RuntimeError at store.py:74.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@vivekchand
vivekchand merged commit f7a4428 into main Oct 17, 2026
50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant