honcho/tests/deriver
Rajat Ahuja 26be90f60d fix(deriver): bound the shutdown drain and release claims before exit
Shutdown waited on asyncio.gather(*active_tasks) with no timeout, and released
work-unit claims only via the finally in initialize(). Both assume the process
gets as long as it needs.

It does not. A platform can terminate the process on a budget shorter than a
single work unit takes -- a deriver LLM call alone can run for tens of seconds
-- and when that budget expires the process is killed outright. The unbounded
wait is then still in progress, initialize() never returns, its finally never
runs, and the claims are left held. The work is not lost, but nothing may touch
it until cleanup_stale_work_units notices, which takes
STALE_SESSION_TIMEOUT_MINUTES plus a jittered poll interval.

So shutdown is a race to hand the claims back, not an opportunity to finish the
work. Two changes:

Drain is bounded by SHUTDOWN_DRAIN_TIMEOUT_SECONDS (default 8). Work that fits
in the budget still completes; work that does not is cancelled rather than
waited on, since the work is reclaimable and the remaining budget is not.

cleanup() is called at the end of shutdown() rather than relying on the finally
in initialize(). Claims are given up while the process is still alive, so
another worker repicks the work on its next poll instead of waiting out the
stale sweep. cleanup() clears worker_ownership, so the existing call in the
finally becomes a no-op and the ordering is idempotent.

The drain deliberately runs before the release rather than after. Releasing
first would leave a window in which a task that completes during shutdown has
already had its claim taken by another worker, and the same queue items get
derived twice. Cancelling first and releasing after keeps that window closed,
and both steps fit inside a short budget.

Cancelling also keeps the error reporting honest without special-casing it.
asyncio.CancelledError derives from BaseException rather than Exception, so an
abandoned task passes straight through the `except Exception` handlers that
report to Sentry. Shutdown stops generating spurious errors on its own, while a
task that genuinely fails during the drain, or a cleanup() that cannot reach the
database, still reports as it should.

Tests cover a task that outlives the budget being abandoned with cleanup still
awaited, and a task that fits in the budget still finishing.
2026-08-26 14:53:38 -04:00
..
README.md create Representation class and use it to unify all formatting (#214) 2025-10-07 15:28:44 -04:00
__init__.py Vineeth/dev 1027 (#177) 2025-08-06 16:20:22 -04:00
conftest.py feat: honcho 3.0, sdks 2.0, excise stainless, update v3 docs, changelogs (#331) 2026-01-22 15:16:28 -05:00
test_deriver_processing.py fix(embedding): truncate in batch embed and return results breakdown (#1019) 2026-08-20 11:42:38 -04:00
test_embed_now.py add prometheus metric for in_flight 2026-07-09 12:01:09 -04:00
test_enqueue_dream.py fix(dreamer): threshold and time-guard semantics (#573) 2026-04-30 11:40:51 -04:00
test_prompts.py feat: deriver custom instructions (#609) 2026-05-11 18:05:42 -04:00
test_queue_operations.py feat: webhooks (#168) 2025-08-06 17:52:35 -04:00
test_queue_processing.py rename to REPRESENTATION_BATCH_TARGET_INPUT_TOKENS 2026-07-09 10:42:52 -04:00
test_representation_crud.py feat: agentic dreamer and agentic dialectic (#309) 2026-01-12 15:12:17 -05:00
test_scope_backfill.py feat: scope backfill-by-copy and removal reconciliation jobs (#904) 2026-08-14 15:08:43 -04:00
test_shutdown_drain.py fix(deriver): bound the shutdown drain and release claims before exit 2026-08-26 14:53:38 -04:00
test_vector_reconciliation.py fix(embedding): truncate in batch embed and return results breakdown (#1019) 2026-08-20 11:42:38 -04:00

README.md

Deriver Testing

This directory contains tests for the deriver system, which handles background processing of messages to extract insights and update working representations.

Structure

  • conftest.py - Shared fixtures for deriver testing
  • test_queue_operations.py - Tests for basic queue operations
  • test_deriver_processing.py - Tests for deriver processing logic
  • test_queue_processing.py - Tests for queue manager and work unit processing

Key Fixtures

Database Fixtures

  • sample_session_with_peers - Creates a session with multiple peers having different observation configurations
  • sample_messages - Creates sample messages for testing
  • sample_queue_items - Creates queue items with various payload types (representation, summary)

Queue Fixtures

  • create_queue_payload - Helper to create queue payloads for testing
  • add_queue_items - Helper to add queue items to the database
  • create_active_queue_session - Helper to create active queue sessions for work unit tracking

Mocking Fixtures

  • mock_critical_analysis_call - Mocks the critical analysis LLM call
  • mock_queue_manager - Mocks the queue manager for testing
  • mock_representation_manager - Mocks the representation manager operations

Testing Patterns

Creating Queue Items

# Create representation payloads
payload = create_queue_payload(
    message=message,
    task_type="representation",
    observer=observer_peer.name,
    observed=message.peer_name
)

# Add to queue
queue_items = await add_queue_items([payload], session.id)

Testing Work Units

# Create a work unit
work_unit = WorkUnit(
    session_id=session.id,
    task_type="representation",
    observer=observer,
    observed=observed
)

# Test string representation
assert str(work_unit) == f"({session.id}, {observed.name}, {observer.name}, representation)"