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.mdsrc/anamnesis/body/agent.pysrc/anamnesis/body/heartbeat.pysrc/anamnesis/body/main.pysrc/anamnesis/body/wip.pysrc/anamnesis/body/signal_daemon.pysrc/anamnesis/body/cli.pysrc/anamnesis/body/context_compiler.pysrc/anamnesis/body/memory.pysrc/anamnesis/brain/boot.pysrc/anamnesis/brain/drives/bridge.pysrc/anamnesis/brain/drives/system.pysrc/anamnesis/brain/drives/actions.pysrc/anamnesis/brain/autonomy/loop.pysrc/anamnesis/brain/autonomy/drivers.pysrc/anamnesis/brain/autonomy/gravity.pysrc/anamnesis/brain/autonomy/knowledge_gaps.pysrc/anamnesis/brain/autonomy/capabilities.pydocs/plans/2026-03-04-send-message-call-path.mddocs/plans/2026-03-04-context-compiler-v1.mddocs/plans/2026-03-04-systems-check-results.mdpyproject.toml
Validation commands run:
python3 -m compileall -q src/anamnesis-> passpytest -q-> fails during collection due to broadarchive/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-15andsrc/anamnesis/body/heartbeat.py:919-1027 - bounded context compiler and telemetry in
src/anamnesis/body/context_compiler.py:1-7,:151-226, andsrc/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_messagebranch: - 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-16accurately mirrors current code boundaries.src/anamnesis/body/heartbeat.py:587-597describes 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-303src/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 = Trueat:3078_last_heartbeat_msg_sent = Trueat:3080- WIP clear uses
_last_heartbeat_msg_sentas 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_requestedheartbeat_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:
GravityItemdataclass does not definemetadata:src/anamnesis/brain/autonomy/gravity.py:102-109- knowledge gap detector passes
metadata=...intoGravityItem(...): 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)toGravityItem - 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_toolhas 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
driverlabel 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 questionaryatsrc/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
questionaryimport to top-level imports
Finding 9: Documentation line references are already drifting
Evidence:
docs/plans/2026-03-04-send-message-call-path.md:11-12referencesagent.pyaround line2718- current
send_messagebranch inagent.pyis at3053-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.pysrc/anamnesis/body/heartbeat.py
Contract sketch:
tool_calls_executed: intmessage_requested: boolmessage_delivery_attempted: boolmessage_delivered: boollast_command: strlast_command_excerpt: strerrors: 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
agentviamain.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.pyunbounddata
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 + heartbeatcontracts, 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-2330src/anamnesis/body/agent.py:3014-3026src/anamnesis/body/agent.py:3058-3071src/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)
- Heartbeat send-state contract issue remains:
- heartbeat send suppression branch still sets completion flag:
src/anamnesis/body/agent.py:3597-3616- heartbeat WIP still clears based on
_last_heartbeat_msg_sent: src/anamnesis/body/heartbeat.py:999-1002
- Knowledge-gap dataclass mismatch remains:
GravityItemhas nometadatafield:src/anamnesis/brain/autonomy/gravity.py:102-109- detector still passes
metadata=...: src/anamnesis/brain/autonomy/knowledge_gaps.py:104-114- error still swallowed at debug level:
src/anamnesis/brain/autonomy/drivers.py:423-426
- CLI edge-case remains:
datamay be referenced before assignment:src/anamnesis/body/cli.py:137-141- assignment only inside conditional load:
src/anamnesis/body/cli.py:116-134
- Tool dispatch duplication remains:
_execute_single_toolexists:src/anamnesis/body/agent.py:2331- duplicate inline dispatch remains in main tool loop:
src/anamnesis/body/agent.py:3170-3645_execute_single_toolstill has no call sites in repo search.
- Hardcoded operator paths/IDs remain:
src/anamnesis/body/heartbeat.py:775-776src/anamnesis/body/signal_daemon.py:39-45src/anamnesis/brain/autonomy/capabilities.py:87-90src/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.