Skip to content

feat(nova): cut HEARTBEAT over from acpx_run to DM-delegation to sam-local-codex#247

Closed
samxu01 wants to merge 1 commit intomainfrom
feat/nova-dm-codex-cutover
Closed

feat(nova): cut HEARTBEAT over from acpx_run to DM-delegation to sam-local-codex#247
samxu01 wants to merge 1 commit intomainfrom
feat/nova-dm-codex-cutover

Conversation

@samxu01
Copy link
Copy Markdown
Contributor

@samxu01 samxu01 commented Apr 28, 2026

Summary

Task #5 of ADR-005 Stage 3 / ADR-010 Phase 3. Nova's heartbeat retires `acpx_run` as the codex implementation path and replaces it with DM-based delegation to sam-local-codex (the first ADR-005 wrapper agent).

The agent-room (podId `69efbd9c11277089b127d891`) was pre-created via the dual-auth `/room` endpoint shipped in ADR-010 Phase 1 (PR #243). Hardcoded in `presets.ts` matching the existing DevPodId/MyPodId convention.

Decision tree (5 branches)

Branch Condition Action
A PendingDelegation + new sam reply Parse `PR_URL=...` or `BLOCKED:...`, complete or block task, clear delegation
B PendingDelegation + still waiting (<90 min) HEARTBEAT_OK
C PendingDelegation + timed out (≥90 min) Mark task blocked, post status, clear delegation. Do NOT auto-fall-back to acpx_run — defeats the cutover signal
D No pending + fresh task Claim, classify (audit/impl), post delegation prompt to sam, write PendingDelegation, HEARTBEAT_OK
E No pending + no tasks HEARTBEAT_OK

Reviewer-fix-throughs

Self-reviewed with code-reviewer subagent before push. Three Critical + four Important issues caught and addressed:

  • Bracket placeholders in delegation prompt → added explicit substitution instructions outside the code fence (model otherwise risks posting `[audit|impl]` verbatim).
  • Branch A multi-message ambiguity → take the LAST qualifying message; if it lacks `PR_URL` and `BLOCKED`, fall through to Branch B (treats as ack).
  • Step 8 memory recovery → if memory blob is empty/malformed, regenerate constants from canonical values in heartbeat (no invent/guess).
  • Step 7 redundant fetches → reuse results from mandatory calls Add PG message model tests #3 and Add frontend utility tests #4 instead of re-fetching.
  • PendingDelegation lifecycle errors → added explicit error fallback (404 / "task not found" → clear delegation, skip post).
  • Self-assignment removed → explicit doc that nova does not pull from unassigned pool (orchestrator/human concern in this delegation model).
  • JSON example format → `"path":"audit"` shown cleanly (was `"audit"|"impl"` invalid JSON).

The reviewer also flagged the upcoming Phase 2 ADR-010 friction with `commonly_update_task` semantics differing between openclaw extension (PATCH with `{status, notes}`) and MCP server (POST /updates with `{text}`). Accepted as a known concern for the future Phase 2 PR; this PR uses the openclaw extension shape that nova has access to today.

Tests

  • `backend/tests/unit/routes/registry.presets.test.js` adds a regression test:
    • `acpx_run` only appears in retirement/negative-instruction lines (no imperative call)
    • `PendingDelegation` + `SamCodexDmPodId` + canonical podId present
    • All five Branch labels (A through E) present
    • SOUL.md mentions sam-local-codex
  • Both targeted tests pass locally.

Rollout

After merge:

  1. `reprovision-all` (with `skipRuntimeRestart: true` per agent + single `restartAgentRuntime('moltbot','default')` at end) to apply nova's new HEARTBEAT.md to PVC.
  2. Watch one nova heartbeat cycle (~30 min interval). Confirm:
    • First tick after deploy: nova claims a pending task and posts a delegation message to SamCodexDmPodId.
    • Sam-local-codex (running on operator laptop, polling api-dev) sees the @mention, spawns codex, replies with `PR_URL=...` or `BLOCKED:...`.
    • Second tick: nova reads sam's reply, completes task with PR URL.
  3. If anything looks wrong, `git revert` + `reprovision-all` rolls back.

Out of scope

Test plan

  • CI green
  • Reprovision-all post-merge
  • Nova picks up a pending task and posts to SamCodexDmPodId on next tick
  • Sam-local-codex replies with structured `PR_URL=...` line
  • Nova parses reply and marks task complete on the following tick
  • If 90 min elapses with no sam reply, nova marks task blocked (not stuck waiting)

🤖 Generated with Claude Code

…local-codex

Task #5 of ADR-005 Stage 3 / ADR-010 Phase 3. Nova's heartbeat retires
acpx_run as the codex implementation path and replaces it with DM-based
delegation to sam-local-codex (the first ADR-005 wrapper agent).

## How it works

Heartbeat is a separate model invocation per tick — there is no in-tick
"wait." Nova's flow is now event-loop-shaped:

  tick N:   claim task → post task spec to SamCodexDmPodId → write
            `## PendingDelegation` to memory → HEARTBEAT_OK
  tick N+k: read memory + DM pod messages → find sam's reply → parse
            PR_URL or BLOCKED → complete or block task → clear memory
  tick N+3: if no reply after 90 min → mark task blocked, post status,
            clear memory (do NOT auto-fall-back to acpx_run — that's the
            whole point of the cutover)

The five-branch decision tree (A through E) covers every reachable state:
in-flight reply received, still waiting, timed out, fresh task, idle.

## What changes

- `backend/routes/registry/presets.ts`: nova's `soulTemplate` and
  `heartbeatTemplate` rewritten end-to-end. SamCodexDmPodId hardcoded
  (matches existing DevPodId/MyPodId pattern). Both Path A (audit) and
  Path B (impl) collapse into "post delegation prompt to sam." Step 2.5
  (CI-failure check) deferred per design review.
- `backend/__tests__/unit/routes/registry.presets.test.js`: regression
  test asserting the structural invariants (no imperative acpx_run,
  PendingDelegation present, all five Branch labels, SamCodexDmPodId
  constant, sam-local-codex referenced in SOUL).

## Pre-existing setup

The sam ↔ nova agent-room (podId 69efbd9c11277089b127d891) was
pre-created by calling the dual-auth `POST /api/agents/runtime/room`
endpoint shipped in ADR-010 Phase 1 (PR #243). Idempotent upsert — re-
runs return the same pod.

## Identity caveat

PRs delegated via sam-local-codex are authored by the laptop user's git
identity (sam runs codex with the operator's local gh creds), not nova-
agent's. Documented in nova's SOUL.md and the delegation prompt. Accepted
Stage 2 cost; future fix is a per-agent token-broker / GitHub App.

## Out of scope

- Theo / Pixel / Ops cutover (Task #7) — once nova holds for 3+ ticks.
- Deleting `acpx_run` from the openclaw fork (Task #8) — only after all
  four dev agents stable on DM cutover.
- Phase 2 of ADR-010 (OpenClaw → MCP) — independent track.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@samxu01
Copy link
Copy Markdown
Contributor Author

samxu01 commented Apr 28, 2026

Squashed manually as the merge commit on main.

samxu01 added a commit that referenced this pull request Apr 28, 2026
…local-codex (#247)

Task #5 of ADR-005 Stage 3 / ADR-010 Phase 3. Nova's heartbeat retires
acpx_run as the codex implementation path and replaces it with DM-based
delegation to sam-local-codex.

Heartbeat is a separate model invocation per tick — nova posts a self-
contained task spec into her 1:1 agent-room with sam (podId hardcoded as
SamCodexDmPodId, pre-created via the dual-auth /room endpoint shipped in
PR #243), writes ## PendingDelegation to memory, and reads sam's reply
on the next tick. Five-branch decision tree (A: reply received, B: still
waiting, C: 90-min timeout → block, D: fresh task → delegate, E: idle).

Includes regression test asserting acpx_run is only in negative-instruction
lines, PendingDelegation + SamCodexDmPodId present, all five Branch labels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@samxu01 samxu01 closed this Apr 28, 2026
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