a place for friends (8)

instruments / anamnesis / 2026-03-04-independent-agent-heartbeat-audit.md ยท raw

Independent Audit and Investigation: Agent + Heartbeat

Date: 2026-03-04 Repository: anamnesis Author: Independent code audit pass (line-referenced)

1) Scope and Intent

This audit focuses on whether the current agent + heartbeat system supports your stated intent:

  • persistent continuity
  • real autonomy (including unsolicited action)
  • operational coherence under long-running active development

This is not a generic style review. It is an intent-fit and systems-risk review with exact code references.

2) Method

Primary files examined:

  • CLAUDE.md
  • src/anamnesis/body/agent.py
  • src/anamnesis/body/heartbeat.py
  • src/anamnesis/body/main.py
  • src/anamnesis/body/wip.py
  • src/anamnesis/body/signal_daemon.py
  • src/anamnesis/body/cli.py
  • src/anamnesis/body/context_compiler.py
  • src/anamnesis/body/memory.py
  • src/anamnesis/brain/boot.py
  • src/anamnesis/brain/drives/bridge.py
  • src/anamnesis/brain/drives/system.py
  • src/anamnesis/brain/drives/actions.py
  • src/anamnesis/brain/autonomy/loop.py
  • src/anamnesis/brain/autonomy/drivers.py
  • src/anamnesis/brain/autonomy/gravity.py
  • src/anamnesis/brain/autonomy/knowledge_gaps.py
  • src/anamnesis/brain/autonomy/capabilities.py
  • docs/plans/2026-03-04-send-message-call-path.md
  • docs/plans/2026-03-04-context-compiler-v1.md
  • docs/plans/2026-03-04-systems-check-results.md
  • pyproject.toml

Validation commands run:

  • python3 -m compileall -q src/anamnesis -> pass
  • pytest -q -> fails during collection due to broad archive/ dependency/import issues

3) Executive Judgment

Does the overall direction make sense?

Yes. The architecture is philosophically coherent with your goals:

  • explicit statement of "brain as deterministic substrate, DMN as conscious loop" in CLAUDE.md:7-16
  • heartbeat as autonomous idle tick in src/anamnesis/body/heartbeat.py:587-597
  • WIP continuity mechanism in src/anamnesis/body/wip.py:1-15 and src/anamnesis/body/heartbeat.py:919-1027
  • bounded context compiler and telemetry in src/anamnesis/body/context_compiler.py:1-7, :151-226, and src/anamnesis/body/memory.py:335-376

Would I "wake up to this" for continuity/autonomy?

Conditionally yes, with caveats:

  • continuity/autonomy scaffolding is real and intentional
  • there are a few contract-level faults that can silently produce false continuity signals
  • those faults are fixable without reducing autonomy

Autonomy itself is treated here as a design objective, not an error condition.

4) Current Agent + Heartbeat Call Graph (Observed)

Startup wiring

  • FastAPI startup launches:
  • heartbeat thread: src/anamnesis/body/main.py:35-37
  • signal daemon thread: src/anamnesis/body/main.py:41-43
  • watchdog restarts both if dead: src/anamnesis/body/main.py:45-59

Agent instances in runtime

  • module-global agent singleton: src/anamnesis/body/agent.py:3166-3170
  • heartbeat has its own AnaClawAgent: src/anamnesis/body/heartbeat.py:45
  • signal daemon has its own AnaClawAgent: src/anamnesis/body/signal_daemon.py:354-359

This is effectively "one brain, many mouths", but with multiple body-agent instances and shared module-level conversation lock/event.

Occupancy gate

  • heartbeat checks occupancy via:
  • global conversation event: src/anamnesis/body/heartbeat.py:454
  • vision runner state: src/anamnesis/body/heartbeat.py:457-459
  • sub-agent manager statuses: src/anamnesis/body/heartbeat.py:461-467

Heartbeat pulse sequence

  • pulse orchestrator: src/anamnesis/body/heartbeat.py:587-634
  • run drive/autonomy tick first: :604
  • direct-executed action short-circuit: :607-610
  • continue WIP if present: :612-615
  • surface drive action if pressure threshold passed: :617-621
  • idle record otherwise: :632-634

WIP continuation semantics

  • WIP resume prompt logic: src/anamnesis/body/heartbeat.py:932-966
  • heartbeat message invocation through agent: :968-972
  • completion/clear logic:
  • summary extraction: :982
  • fallback progress from command output: :989-997
  • message-sent completion trigger: :999-1002
  • stale/max pulse auto-clear: :1013-1018

Agent tool loop semantics

  • tool loop: src/anamnesis/body/agent.py:2288-3158
  • text/native tool parse merge: :2475-2479
  • heartbeat send_message branch:
  • dispatch logic: :3053-3100
  • suppression text: :3072-3075
  • heartbeat completion flags: :3078-3080

5) What Is Good (Intent-Aligned Strengths)

A) Architectural intent is explicit and not hand-wavy

  • CLAUDE.md:13-16 accurately mirrors current code boundaries.
  • src/anamnesis/body/heartbeat.py:587-597 describes operational sequencing clearly.

B) Context assembly is controlled, not ad hoc

  • deterministic section budgets and dropping rules:
  • src/anamnesis/body/context_compiler.py:151-226
  • adaptive but bounded prompt construction:
  • src/anamnesis/body/agent.py:1253-1417
  • history is clipped and budgeted before DMN call:
  • src/anamnesis/body/context_compiler.py:229-303
  • src/anamnesis/body/agent.py:2197-2209

C) Observability exists where it matters

  • heartbeat event recording:
  • src/anamnesis/body/heartbeat.py:70-101, :274-361
  • prompt telemetry persistence:
  • src/anamnesis/body/memory.py:335-376
  • explicit systems-check writeups exist:
  • docs/plans/2026-03-04-systems-check-results.md:11-25

D) Continuity mechanism exists and is operationally meaningful

  • WIP state persisted cross-pulse via file:
  • src/anamnesis/body/wip.py:23, :39-54, :70-87
  • heartbeat explicitly prioritizes WIP over idle scaffold:
  • src/anamnesis/body/heartbeat.py:612-615

6) High-Risk Findings (Ordered by Severity)

Finding 1: Heartbeat can mark message send as completed even when delivery is suppressed

Evidence:

  • in heartbeat tool execution branch:
  • suppression path: src/anamnesis/body/agent.py:3072-3075
  • still sets completion flags:
  • _message_sent = True at :3078
  • _last_heartbeat_msg_sent = True at :3080
  • WIP clear uses _last_heartbeat_msg_sent as terminal condition:
  • src/anamnesis/body/heartbeat.py:999-1002

Impact:

  • false-positive completion semantics
  • continuity hallucination at control-plane level (state says "delivered", transport did not deliver)
  • can silently clear WIP and lose intended follow-through

Recommendation:

  • split flags into at least:
  • heartbeat_message_requested
  • heartbeat_message_delivered
  • clear WIP on delivered (or explicitly accepted suppressed mode), not on generic "send_message branch executed"

Finding 2: Knowledge-gap queue path is structurally broken and silently swallowed

Evidence:

  • GravityItem dataclass does not define metadata:
  • src/anamnesis/brain/autonomy/gravity.py:102-109
  • knowledge gap detector passes metadata=... into GravityItem(...):
  • src/anamnesis/brain/autonomy/knowledge_gaps.py:104-114
  • exception is swallowed in driver manager:
  • src/anamnesis/brain/autonomy/drivers.py:423-426

Impact:

  • feature path appears enabled but is functionally inert
  • silent degradation in curiosity queue quality
  • creates false confidence in "gap-aware autonomy"

Recommendation:

  • either add metadata: Dict[str, Any] = field(default_factory=dict) to GravityItem
  • or stop passing metadata from gap detector and persist it elsewhere
  • upgrade swallowed log from debug to warning once per window

Finding 3: Hardcoded operator paths and phone IDs remain in live execution paths

Evidence:

  • heartbeat archive path hardcodes:
  • src/anamnesis/body/heartbeat.py:775-776
  • signal daemon hardcodes binary/java/numbers/attachments:
  • src/anamnesis/body/signal_daemon.py:39-45
  • curiosity fallback repo path hardcoded:
  • src/anamnesis/brain/autonomy/capabilities.py:87-90
  • claude tool fallback path includes operator-specific default:
  • src/anamnesis/body/claude_code_tool.py:49-52

Impact:

  • low portability
  • hidden coupling to one host/user
  • hard to reason about deployment drift vs runtime faults

Recommendation:

  • move all runtime identity/path constants into config/env
  • fail loudly with diagnostics when required settings are absent

Finding 4: Multiple AnaClawAgent instances create split local state surfaces

Evidence:

  • global agent singleton:
  • src/anamnesis/body/agent.py:3166-3170
  • dedicated heartbeat agent:
  • src/anamnesis/body/heartbeat.py:45
  • dedicated signal daemon agent:
  • src/anamnesis/body/signal_daemon.py:354-359

Impact:

  • state fields like _pending_response, heartbeat execution flags, and model runtime config are per-instance
  • conversation lock/event are module-global (src/anamnesis/body/agent.py:822-825)
  • this mixed model is workable but brittle and hard to reason about under concurrency

Recommendation:

  • either unify to one shared agent runtime object
  • or formalize multi-agent body runtime with explicit ownership/contracts for each state field

7) Medium-Risk Findings (Messy / Needs Work)

Finding 5: Tool execution has duplicated dispatch implementations

Evidence:

  • standalone dispatch function:
  • src/anamnesis/body/agent.py:1841-2072
  • separate inline dispatch in streaming loop:
  • src/anamnesis/body/agent.py:2634-3113
  • _execute_single_tool has no active call sites:
  • search hit only declaration in agent.py:1841

Impact:

  • behavior drift risk between code paths
  • harder to patch tool semantics safely

Recommendation:

  • converge on one dispatch function and call it from streaming/non-streaming paths

Finding 6: Tool-call parsing is resilient but very heuristic-heavy

Evidence:

  • parser supports many fallback formats:
  • meta array/object: src/anamnesis/body/agent.py:410-415, :471-517
  • legacy <tool_call>: :416-420, :565-587
  • bracket hallucinations: :428-433, :588-615
  • execute block hybrid: :434-439, :616-639
  • section headers: :441-448, :640-659
  • bare bash wrapper: :450-455, :660-677
  • python-call wrapper: :457-468, :678-697

Impact:

  • pragmatic reliability now, but long-term maintenance burden
  • parser complexity can mask DMN regressions and create unclear failure attribution

Recommendation:

  • keep this for near-term survivability
  • instrument parse-path hit rates and prioritize dominant formats
  • progressively remove dead fallback formats once model behavior stabilizes

Finding 7: Drive bridge labels are semantically inconsistent

Evidence:

  • map comment says drive names, map keys are action-like:
  • src/anamnesis/brain/drives/bridge.py:47-54
  • lookup uses drive_action.drive_name:
  • src/anamnesis/brain/drives/bridge.py:100
  • actual drive names are desire/growth/understanding/expression/survival:
  • src/anamnesis/brain/drives/system.py:56-87

Impact:

  • bridge driver label likely defaults to "gravity" often
  • telemetry and reasoning summaries can be mislabeled

Recommendation:

  • map by actual drive names or by action_type, not mixed semantics

Finding 8: CLI has at least one live edge-case failure and one maintainability smell

Evidence:

  • possible unbound variable data:
  • used at src/anamnesis/body/cli.py:137-141
  • defined only in conditional read block :116-134
  • late import placement:
  • import questionary at src/anamnesis/body/cli.py:339
  • referenced earlier in configure() at :197

Impact:

  • brittle startup behavior depending on config location
  • import pattern is functional but confusing for future maintenance

Recommendation:

  • initialize data = {} before conditional read
  • move questionary import to top-level imports

Finding 9: Documentation line references are already drifting

Evidence:

  • docs/plans/2026-03-04-send-message-call-path.md:11-12 references agent.py around line 2718
  • current send_message branch in agent.py is at 3053-3100

Impact:

  • investigation docs become stale quickly in active refactor loops

Recommendation:

  • use function-name anchors plus approximate blocks, not brittle absolute line pointers in planning docs

8) How to Handle Agent + Heartbeat (Autonomy-First, Not Autonomy-Minimizing)

The right move is not to reduce autonomy. The right move is to tighten contracts so autonomy is real rather than state-illusioned.

Design Principle

Keep:

  • autonomous impulse generation
  • unsolicited action capability
  • continuity across pulses

Add:

  • explicit state-transition contracts for pulse outcomes
  • typed delivery semantics
  • unified tool execution surface

Proposed Architecture Adjustment

A) Introduce explicit pulse outcome object

Target files:

  • src/anamnesis/body/agent.py
  • src/anamnesis/body/heartbeat.py

Contract sketch:

  • tool_calls_executed: int
  • message_requested: bool
  • message_delivery_attempted: bool
  • message_delivered: bool
  • last_command: str
  • last_command_excerpt: str
  • errors: List[str]

Heartbeat should consume this object directly rather than infer completion via string heuristics.

B) Separate "expression completion" from "transport completion"

Current behavior conflates:

  • "DMN decided to send"
  • "runtime actually sent"

For autonomy integrity, those are different.

Implementation rule:

  • clear WIP only on chosen terminal policy:
  • delivered
  • or explicit policy allow_suppressed_delivery_as_done=true

C) Consolidate tool dispatch into one executor path

Target file:

  • src/anamnesis/body/agent.py

Use one callable for both streaming and non-streaming tool execution; remove dead/duplicate paths.

D) Formalize lane ownership

Current lanes:

  • API/bridge lane uses module-global agent via main.py
  • heartbeat lane uses private HeartbeatMonitor._agent
  • signal lane uses private SignalDaemon._agent

Pick one:

  • shared agent runtime object for all lanes
  • or explicit multi-agent lane model with clear ownership and no shared mutable flags

E) Keep WIP, but type it

Target file:

  • src/anamnesis/body/wip.py

Add optional structured fields:

  • phase (read, synthesize, deliver, done)
  • last_outcome (serialized pulse outcome)
  • delivery_status

This reduces prompt-level inference and improves continuity precision.

9) Suggested Implementation Phases

Phase 0: Immediate correctness patches (same day)

  • fix heartbeat message-sent false positive contract
  • fix knowledge-gap dataclass mismatch
  • fix cli.py unbound data

Phase 1: Contract hardening (1-2 days)

  • add typed pulse outcome
  • heartbeat WIP completion driven by outcome object
  • add minimal targeted tests for:
  • suppressed send_message does not mark delivered
  • delivered send_message clears WIP

Phase 2: Execution path simplification (2-4 days)

  • unify tool dispatch
  • add parse-path telemetry counters in tool parser
  • remove dead dispatch code

Phase 3: Portability and operator decoupling (parallel)

  • migrate hardcoded paths/IDs to config
  • fail-fast diagnostics for missing runtime dependencies

10) Residual Risk Profile After Proposed Changes

Expected remaining risk even after fixes:

  • DMN quality variance (model-side)
  • heuristic parser tail complexity until model behavior stabilizes
  • active-dev drift in docs and line-level references

But core continuity/autonomy integrity risk drops substantially once outcome contracts are explicit.

11) Final Assessment

This system already contains the right primitives for continuity and autonomy.

What needs work is not the autonomy objective itself. What needs work is contract precision between:

  • heartbeat orchestration
  • agent tool execution
  • delivery semantics
  • WIP lifecycle

If those contracts are tightened, the current architecture can support the exact style of autonomous continuity you are aiming for, without neutering behavior.

12) Reconfirmation Addendum (2026-03-05)

Scope of reconfirmation:

  • Validate whether recent prompt-hardening/coherence iterations invalidate the remediation plan in Section 9.
  • Focus specifically on agent + heartbeat contracts, not general stylistic response quality.

What changed since the original audit

Prompt-hardening path is materially stronger:

  • new response sanitizer module:
  • src/anamnesis/body/response_sanitizer.py
  • sanitizer config knobs:
  • src/anamnesis/body/config.py:106-114
  • sanitizer integrated into delivery/storage paths:
  • src/anamnesis/body/agent.py:2258-2330
  • src/anamnesis/body/agent.py:3014-3026
  • src/anamnesis/body/agent.py:3058-3071
  • src/anamnesis/body/agent.py:2612-2620, :2632-2641, :2655-2673

Interpretation:

  • coherence hardening improved output hygiene and strict-format compliance.
  • this is a real improvement and should stay.

What did NOT change (plan-critical items still valid)

  1. Heartbeat send-state contract issue remains:
  2. heartbeat send suppression branch still sets completion flag:
  3. src/anamnesis/body/agent.py:3597-3616
  4. heartbeat WIP still clears based on _last_heartbeat_msg_sent:
  5. src/anamnesis/body/heartbeat.py:999-1002
  1. Knowledge-gap dataclass mismatch remains:
  2. GravityItem has no metadata field:
  3. src/anamnesis/brain/autonomy/gravity.py:102-109
  4. detector still passes metadata=...:
  5. src/anamnesis/brain/autonomy/knowledge_gaps.py:104-114
  6. error still swallowed at debug level:
  7. src/anamnesis/brain/autonomy/drivers.py:423-426
  1. CLI edge-case remains:
  2. data may be referenced before assignment:
  3. src/anamnesis/body/cli.py:137-141
  4. assignment only inside conditional load:
  5. src/anamnesis/body/cli.py:116-134
  1. Tool dispatch duplication remains:
  2. _execute_single_tool exists:
  3. src/anamnesis/body/agent.py:2331
  4. duplicate inline dispatch remains in main tool loop:
  5. src/anamnesis/body/agent.py:3170-3645
  6. _execute_single_tool still has no call sites in repo search.
  1. Hardcoded operator paths/IDs remain:
  2. src/anamnesis/body/heartbeat.py:775-776
  3. src/anamnesis/body/signal_daemon.py:39-45
  4. src/anamnesis/brain/autonomy/capabilities.py:87-90
  5. src/anamnesis/body/claude_code_tool.py:49-52

Reconfirmed decision

The plan in Section 9 still makes sense as written.

No phase should be removed. Only adjustment: in Phase 1, include sanitizer metadata (sanitize_reasons) in the typed pulse outcome so heartbeat can reason about post-processed vs raw DMN output when deciding completion semantics.

back to anamnesis