Skip to content

0.6.0 — framework hardening (streaming, tools, parsing, packaging, facade) - #41

Merged
chiruu12 merged 6 commits into
mainfrom
harden/0.6.0
Jun 2, 2026
Merged

0.6.0 — framework hardening (streaming, tools, parsing, packaging, facade)#41
chiruu12 merged 6 commits into
mainfrom
harden/0.6.0

Conversation

@chiruu12

@chiruu12 chiruu12 commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Framework-hardening release for 0.6.0. Makes the agent runtime robust under streaming interruptions, hung tools, and corrupt state, and makes the package friendlier to import/embed. No breaking changes; DBs upgrade automatically. Poker/simulation is intentionally out of scope (separate repo).

Reviewable per-commit — each commit is one scope:

Commit Scope What
830554a B6/B7/B8 Brace-matching structured parser + StructuredParseError (no more invalid model_construct()); structured-first provider error heuristics; warn on malformed tool-call args
b88dde5 C9 Optional [anthropic] [openai] [mcp] [cli] extras (core deps unchanged) + friendly MissingDependencyError
61f4709 A2/A3/A4 Per-tool tool_timeout; surfaced silent failures (memory recall, suffering/persona restore, plugin init) with explicit fallbacks; single active generated goal per agent
32d3e30 A1/A5 Streaming falls back to non-streaming on no-DONE / mid-stream error; stream closed on cancel; documented one-generation budget overshoot
2c9162f C10 with / async with Hive() + lazy .hive init; version bump 0.6.0 + CHANGELOG + docs

Notable decisions

  • A3 uses a re-check guard in the existence loop, not a unique index — subgoals, delegation, and schedules legitimately create multiple active goals per agent, so a hard constraint would break them.
  • A5 keeps the post-generation budget check (the real gate) and documents the inherent one-generation overshoot. A projected pre-stop was implemented and rejected: estimating with the full output cap falsely refuses small-budget agents that emit short replies (a behavior regression).
  • Three original audit findings were verified as non-issues and left untouched: the nudge SELECT-vs-UPDATE race (already fixed by-id), the daemon cycle-count "race" (atomic SQL UPDATE), and per-operation store connections (deliberate).

Verification

  • uv run pytest — 1058 passed (added tests for streaming fallback, tool timeout, memory-recall warning, corrupt-checkpoint resume, existence guard, optional extras, context managers, robust parsing)
  • uv run ruff check src/ tests/ + ruff format --check src/ — clean
  • uv run mypy src/ — clean
  • uv run mkdocs build --strict — clean
  • Manual smoke: streaming fallback, per-tool timeout, missing-extra error, sync/async context managers + lazy init

Remaining audit findings filed as backlog issues (see linked issues).

Copilot AI review requested due to automatic review settings June 2, 2026 11:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chiruu12

chiruu12 commented Jun 2, 2026

Copy link
Copy Markdown
Owner Author

Backlog (remaining audit findings, deferred from this release):

@greptile-apps

greptile-apps Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens the Hive agent runtime across five well-scoped commits: streaming now falls back gracefully on interruption, tools have per-call timeouts, JSON parsing uses a proper brace-matching extractor with typed errors, optional provider extras are declared, and the Hive facade gains context-manager support with lazy init.

  • Streaming (_generate_message): accumulated text is returned on mid-stream failure; an empty stream retries non-streaming; CancelledError propagates cleanly after aclose() in finally.
  • Tool timeout (_run_tool in run()): asyncio.wait_for wraps each tool call when tool_timeout > 0, converting hangs into tool-error results fed back to the model.
  • Structured parsing (parse_structured_response): _extract_json_object walks string literals correctly and raises StructuredParseError (with .raw) on ValidationError; StructuredTaskResult.parsed is now T | None instead of model_construct().

Confidence Score: 4/5

Safe to merge with one pre-existing unresolved defect: run_once() dispatches tools without the asyncio.wait_for guard that run() now has, so hung tools still block the coroutine indefinitely when callers use the direct API path.

The daemon path is fully protected by tool_timeout and the streaming fallback is well-guarded. However, run_once() — the main entry point for direct API callers — still calls tool.call() with no timeout wrapper, a gap noted in the previous review that this PR did not close.

src/hive/runtime/agent.py — specifically the inline tool-dispatch loop inside run_once() around line 626.

Important Files Changed

Filename Overview
src/hive/runtime/agent.py Core runtime changes: streaming fallback logic, per-tool asyncio.wait_for timeout in _run_tool, synthesized GenerateResult for interrupted streams, and budget guard added before run_once wrap-up. The tool_timeout guard is not applied inside run_once's inline dispatch loop (pre-existing issue noted in prior review).
src/hive/runtime/structured.py New _extract_json_object correctly handles nested objects, quoted braces, and escape sequences. parse_structured_response raises StructuredParseError on ValidationError. Looks correct.
src/hive/api.py Adds sync/async context managers and lazy _get_store init. HiveStore uses per-operation connections so no explicit close is needed. aexit correctly calls synchronous stop().
src/hive/errors.py Adds MissingDependencyError, StructuredParseError, and require_dependency helper. Clean hierarchy: both new errors subclass HiveError and the relevant builtin for backward-compatible except clauses.
src/hive/agents/existence.py Re-check guard added between goal validation and goal save to prevent duplicate generated goals from concurrent cycles. TOCTOU window remains but is intentional (unique index would break subgoals/delegation).
src/hive/daemon/loop.py tool_timeout passed from config to both Agent branches (persona/no-persona). Suffering-state and persona-restore now have explicit exception handlers with clean fallbacks instead of silent failures.
src/hive/daemon/setup.py ensure_hive_dirs() cleanly extracted from initialize_hive() to be loop-safe (no asyncio.run). initialize_hive now delegates to ensure_hive_dirs. Refactor is correct and idempotent.
src/hive/models/openai.py _is_response_format_unsupported replaces fragile string matching with structured error field inspection (param, body.error.code) + string fallback. Malformed streaming tool-call JSON now logs a warning.
src/hive/config.py tool_timeout: float = 60.0 added to DaemonConfig with a >= 0 validator. Clean addition consistent with existing validators.
src/hive/runtime/types.py StructuredTaskResult.parsed changed from T to T

Reviews (2): Last reviewed commit: "fix(ci,review): format tests; estimate i..." | Re-trigger Greptile

Comment thread src/hive/runtime/agent.py Outdated
Comment thread pyproject.toml Outdated
@chiruu12

chiruu12 commented Jun 2, 2026

Copy link
Copy Markdown
Owner Author

Addressed Greptile's two P2s in fa2e8c2:

  • Interrupted-stream usage: _synthesize_stream_result now estimates output tokens/cost from the streamed text (~4 chars/token) so an interrupted generation stays visible to budget tracking instead of being zeroed.
  • Extras pin duplication: provider/CLI extras are now unversioned aliases (["anthropic"] etc.); the version pins live only in [project.dependencies], which stays authoritative — no drift.

Also formatted tests/ (CI runs ruff format --check src/ tests/). Copilot review was skipped (quota).

chiruu12 added 6 commits June 2, 2026 22:05
…arg logging

- structured.py: brace-matching JSON extractor that respects string literals/
  escapes (replaces naive find/rfind slice); raise typed StructuredParseError
  carrying raw text on validation failure
- types.py/agent.py: StructuredTaskResult.parsed is T|None; FAILED results carry
  parsed=None instead of an unvalidated model_construct() object
- openai.py: _is_response_format_unsupported() structured-first detector replaces
  brittle substring match; document why streaming is not retried
- conversion.py/openai.py: warn (not silently drop) on malformed tool-call args

Audit findings B6, B7, B8.
…ndency errors

- pyproject: add [anthropic] [openai] [mcp] [cli] extras (SDKs stay in core deps
  so existing installs are unchanged; extras pave the way for a slimmer core)
- errors.py: MissingDependencyError + require_dependency() helper that points at
  the matching extra (pip install 'hive-agent[openai]')
- openai/anthropic/mcp toolkit: lazy imports now raise the clear error instead of
  a bare ImportError; top-level 'import hive' still needs no provider SDK
- export StructuredParseError + MissingDependencyError from the package

Audit finding C9.
…ailures

- runtime/agent.py: configurable tool_timeout wraps each tool call in wait_for;
  a hung tool becomes a tool-error result instead of stalling the whole cycle
- config.py: DaemonConfig.tool_timeout (default 60s, validated >= 0); daemon
  passes it into runtime agents
- agent.py: memory-recall failure now warns (was debug) and states it continues
  without recalled memories
- daemon/loop.py: corrupt suffering snapshot falls back to a fresh SufferingState
  (was silently unset); persona restore wrapped in try/except; plugin-init warning
  carries agent_id + traceback
- agents/existence.py: re-check get_active_goal before saving so a generated goal
  isn't piled on top of one that appeared mid-cycle (delegation/schedule/concurrent)

Audit findings A2, A3, A4. (A3 uses a re-check guard, not a unique index, because
subgoals/delegation/schedules legitimately create multiple active goals.)
…budget overshoot

- _generate_message: when a stream ends without a terminal DONE event (or errors
  mid-stream), keep the text already shown to on_text if any, otherwise fall back
  to a retried non-streaming call -- instead of failing the task with RuntimeError
- close the stream (aclose) in a finally so cancellation releases the underlying
  HTTP connection
- run_once: guard the post-loop wrap-up generation with a budget check
- document that a hard budget can overshoot by at most one generation, and why a
  projected pre-stop was rejected (full-output-cap estimate falsely refuses
  small-budget agents)

Audit findings A1, A5.
- api.py: with Hive() / async with Hive() context managers; Hive(path).spawn(...)
  now scaffolds .hive/ lazily, so init() is optional (kept for back-compat)
- daemon/setup.py: split out loop-safe ensure_hive_dirs() (no asyncio.run) from
  initialize_hive(); export ensure_hive_dirs from the package
- bump version 0.5.4 -> 0.6.0; CHANGELOG + docs/changelog 0.6.0 section
- docs: tool_timeout config row, install extras + MissingDependencyError,
  StructuredTaskResult.parsed nullability, context-manager usage

Audit finding C10 + release.
…pe extras pins

- ruff format tests/ (CI checks src/ + tests/)
- _synthesize_stream_result: estimate output tokens/cost from streamed text so an
  interrupted streaming generation stays visible to budget tracking (Greptile P2)
- pyproject extras: unversioned aliases instead of duplicating the core version
  pins, removing drift risk; dependencies stays authoritative (Greptile P2)
@chiruu12

chiruu12 commented Jun 2, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto main after #40 merged — PR is now a clean 6-commit hardening diff (the stacked #40 commits are gone). All CI green (build, lint & type-check, tests 3.11/3.12/3.13). Both Greptile P2s are addressed in code:

  • interrupted-stream usage is now estimated from the streamed text (no longer zeroed for budgets)
  • provider/CLI extras are unversioned aliases (no duplicated pins; dependencies stays authoritative)

Ready for your review.

@chiruu12
chiruu12 merged commit 92bd268 into main Jun 2, 2026
5 checks passed
@chiruu12
chiruu12 deleted the harden/0.6.0 branch June 2, 2026 17:10
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.

2 participants