Phase B -- Goal lifecycle correctness¶
Status: Done
Goal¶
Make daemon goal pursuit honest: honor profile limits, handle MAX_STEPS explicitly, eliminate zombie active goals, unify custom GoalStrategy persistence with ExistenceLoop, and wire easy profile knobs (temperature, per-agent cost cap).
Why (problems addressed -- bullet list with severity)¶
- P0: Profile
max_stepsignored --AgentCycleRunnerbuildsAgent(...)withoutmax_steps=profile.max_steps;DaemonAgentAdapter.pursue_goal()createsTask(instruction=...)with default 25 (src/hive/daemon/agent_cycle.py,src/hive/runtime/bridge.py). - P0:
MAX_STEPSzombie goals --DaemonAgentAdaptermaps onlyFAILEDtosteps_failed=1;MAX_STEPSyieldssuccess=Falsewithsteps_failed=0, so neither complete nor abandon branch runs (agent_cycle.pylines 313--411). - P0: Active goal +
AgentStatus.IDLEafter partial pursuit -- agent forgets progress next heartbeat (feeds Phase C). - P1: Custom
GoalStrategyskips validation --agent_cycle.pysaves goal directly withoutExistenceLoop._validate_goal()/ duplicate-active check. - P1:
ExistenceLoopvsGoalStrategysave divergence -- existence saves insidegenerate_goal(); strategy path saves inagent_cycle.pywith different hooks/events. - P2:
temperature,max_cost_usd,max_tokensfromAgentProfilenot passed to runtimeAgentor provider (src/hive/agents/profile.py,agent_context.pyprovider cache). - P2:
assess_conditionsbias -- only runs after pursuit withoutcome.steps_done; MAX_STEPS / idle paths skew suffering stats.
Related issues bundled¶
| ID | Finding |
|---|---|
| LOOP-GOAL-01 | Profile max_steps not wired |
| LOOP-GOAL-02 | MAX_STEPS / zombie active goals |
| LOOP-GOAL-03 | GoalStrategy bypasses validation |
| LOOP-GOAL-04 | Temperature / per-agent budget not wired |
| LOOP-GOAL-05 | GeneratedGoal metering only on generation path (partial) |
Current state (files)¶
| Area | Location | Behavior today |
|---|---|---|
| Pursuit entry | src/hive/daemon/agent_cycle.py |
New Agent per cycle; no profile step limit |
| Bridge | src/hive/runtime/bridge.py |
Task(instruction=...) only |
| Runtime loop | src/hive/runtime/agent.py |
max_steps = task.max_steps or self._max_steps; returns TaskStatus.MAX_STEPS |
| Outcome mapping | bridge.py |
success = status == COMPLETED only |
| Goal generation | src/hive/agents/existence.py |
Validates + saves goal; returns GeneratedGoal with spend |
| Custom strategy | src/hive/agents/goal_strategy.py |
Protocol only; caller saves raw string |
| Profile | src/hive/agents/profile.py |
max_steps=20, temperature, max_cost_usd defined |
| Telemetry | src/hive/logging/trace.py, GoalLog |
Events for completed/abandoned; gap for max_steps / parked |
Proposed changes (numbered)¶
- Wire profile limits into pursuit:
- Pass
max_steps=profile.max_steps,temperature=profile.temperature, andmax_cost_usd=profile.max_cost_usd(ifAgentsupports) when constructing runtimeAgentinagent_cycle.py. -
Pass
max_stepsintoTask(...)inDaemonAgentAdapter.pursue_goal()(or set onAgentctor consistently). -
Define explicit
MAX_STEPSpolicy (document indocs/guide/daemon-mode.md): - Recommended default: treat
MAX_STEPSas continuable -- keep goalactive, set statusIDLE, emitgoal_progress/max_steps_reachedtelemetry; Phase C adds transcript resume. - Alternative (config flag):
daemon.max_steps_policy: abandon | continuefor operators who prefer auto-abandon. -
Map
TaskStatus.MAX_STEPSinbridge.pyto a distinctGoalOutcomeflag (e.g.hit_step_limit: bool). -
Fix zombie / fall-through handling in
agent_cycle.py: - After pursuit, if not success / waiting_approval / abandoned, branch on
hit_step_limitor explicitoutcome.status. -
Never leave
activegoal with unlogged indeterminate outcome. -
Unify goal persistence:
- Extract shared
save_generated_goal(agent_id, objective, *, validate=True)used byExistenceLoopandGoalStrategypaths. - Apply
_validate_goal()for custom strategies unlessGoalContext.skip_validationopt-in for trusted plugins. -
Emit consistent
GoalLog+goal_generatedhook in one place. -
Provider temperature: Ensure
create_runtime_provider/ cached provider calls pass profile temperature ingenerate_with_metadata(auditsrc/hive/daemon/loop.py, model providers). -
Telemetry:
- Add
GoalLogeventmax_stepsor extendeventenum in logs. - Update
tests/test_narrative_in_prompt.py/ addtests/test_goal_lifecycle.pyfor wiring assertions.
Non-goals¶
- Cross-heartbeat transcript (Phase C).
- Changing default profile
max_stepsvalue (20 vs 25 mismatch with runtime default -- document only). - Rewriting
ExistenceLoopprompt strategy.
Risks / rollback¶
| Risk | Mitigation |
|---|---|
| Continue policy increases LLM spend on stuck goals | Pair with profile max_steps + Phase D budget; optional cap on continuations per goal |
| Stricter validation breaks custom strategies | skip_validation flag on GoalStrategy registration |
| Abandon policy loses work | Default to continue; abandon opt-in |
Rollback: revert bridge outcome mapping; feature-flag max_steps_policy.
Acceptance criteria (testable)¶
uv run pytest tests/test_goal_lifecycle.py tests/test_narrative_in_prompt.py tests/agents/test_existence.py -v
uv run pytest tests/test_daemon_integration.py -v -k goal
- [x] Agent with profile
max_steps: 3stops pursuit at 3 tool/model steps (mock provider counts steps). - [x]
TaskStatus.MAX_STEPSno longer leaves goal active with no recorded outcome (per chosen policy). - [x] Custom
GoalStrategythat returns duplicate/rejected goal does not create store row when validation enabled. - [x]
Agentconstructed in daemon uses profiletemperaturein provider call (mock asserts kwargs). - [x]
docs/guide/prompt-assembly.mdanddocs/guide/daemon-mode.mdstate max_steps + MAX_STEPS policy.
Suggested implementation order¶
- Failing tests for max_steps wiring + MAX_STEPS outcome.
- Bridge + agent_cycle outcome branches.
- Shared goal save/validate helper.
- Temperature / max_cost_usd wiring.
- Docs + telemetry.
Estimate¶
M (2--3 days).
Dependencies (prior phases)¶
None (start in parallel with Phase A). Blocks Phase C (policy must exist before resume) and Phase D (spend on all outcomes).