honcho/src
Aakash Kattelu ccdb8ba113
fix(deriver): fix create_documents deadlock (#1033)
* fix(deriver): eliminate create_documents deadlock and stop silently burning batches on transient errors

Two concurrent work units writing the same (workspace, observer, observed)
collection deadlocked on times_derived reinforcement UPDATEs issued in
batch order (DEV-1975, 682 events in 90 days). The deadlock was swallowed
per-document, the loop cascaded PendingRollbackErrors against the dead
session, the whole batch was lost, and the queue item was marked processed.

- serialize writers per collection with a transaction-scoped advisory lock
  (pg_advisory_xact_lock + SET LOCAL lock_timeout), skipped for insert-only
  batches; covers all three row-lock sites in one move
- hoist external-vector-store dup-candidate resolution ahead of the first
  DB statement so the lock's critical section contains no network calls
- abort the batch on SQLAlchemyError instead of continuing through an
  aborted transaction; per-document skip semantics kept for non-DB errors
- classify transient errors (new src/utils/retryable_errors.py) and retry
  them via a bounded in-process counter instead of marking items errored

* fix(deriver): replace create_documents advisory lock with id-ordered row locks

Advisory locks are database-scoped and would serialize every writer to a
collection, including across Groudon tenants that share names. Collect
reinforcement and replace ops during the loop, lock target rows with
SELECT ... ORDER BY id FOR UPDATE, then apply. populate_existing reloads
times_derived so a prefetched identity-map row cannot lose a concurrent
increment.

* fix(deriver): harden create_documents candidate hoist and test isolation

Skip empty embeddings on the external-store path, isolate per-document
resolve failures, and keep replacement times_derived in the in-batch
ledger. Patch get_external_vector_store in the hoist test and cover
in-loop SQLAlchemyError abort.

* fix(deriver): address CodeRabbit findings on create_documents deadlock fix

- Distinguish external resolve failure ([] skip) from pgvector fallback (None)
  so _semantic_dup_decision never re-enters external I/O under an open session
- Bound external candidate hoist concurrency with a semaphore
- Map in-loop IntegrityError to ValidationException for a uniform contract
- Persist transient retry attempts on the oldest unprocessed queue item so
  every deriver instance shares one MAX_RETRYABLE_ATTEMPTS budget
- Cover resolve-failure skip and multi-manager reclaim of the retry budget

* fix(deriver): harden retry metadata cleanup and stale reinforce fallback

- Strip _retry_attempts from payloads in the same transaction as
  mark_queue_items_as_processed / mark_queue_item_as_errored
- Clear shared retry metadata only after a successful terminal mark
- On reinforce, if the locked target is gone or soft-deleted, insert the
  incoming document instead of dropping it
- Skip pgvector semantic lookup when embedding is empty so query_documents
  cannot embed under an open session

* fix(deriver): address review on deadlock retry and row-lock apply

Strip _retry_attempts before payload validation so non-representation
tasks are not burned as extra_forbidden. Re-raise retryable observer
save errors after telemetry so the queue actually retries. Skip
same-batch reinforce fallbacks after a replace. Revert unordered
FOR UPDATE on mark processed/errored and drop post-commit retry
cleanup from the success path.

* fix: add test and simplify queue query

---------

Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com>
2026-09-02 11:07:50 -04:00
..
cache perf(cache): hash-tag the cache namespace so an instance uses one shard (#1058) 2026-08-25 12:34:42 -04:00
crud fix(deriver): fix create_documents deadlock (#1033) 2026-09-02 11:07:50 -04:00
deriver fix(deriver): fix create_documents deadlock (#1033) 2026-09-02 11:07:50 -04:00
dialectic fix(dialectic): revamp workspace and pair chat system prompts (#1066) 2026-08-25 12:30:06 -04:00
dreamer Scopes Phase 2a: scope-kind peers, guardrails, and scopes CRUD routes (#884) 2026-08-12 16:21:40 -04:00
llm fix(openai): fix content normalization in openai backend history adapter (#1064) 2026-08-26 12:14:32 -04:00
reconciler fix(embedding): truncate in batch embed and return results breakdown (#1019) 2026-08-20 11:42:38 -04:00
routers feat: Add workspace-level chat (#931) 2026-08-24 15:54:23 -04:00
schemas fix(deriver): strip NUL bytes from model-generated observations (#1095) 2026-08-31 13:32:43 -04:00
startup Make embeddings configurable (#678) 2026-05-14 15:03:35 -04:00
telemetry review: fix db_connections_open leak on the detach path; comment/doc polish 2026-08-24 14:37:20 -04:00
utils fix(deriver): fix create_documents deadlock (#1033) 2026-09-02 11:07:50 -04:00
vector_store fix: stop top_k=0 from reaching Turbopuffer on message search (#1084) 2026-08-26 17:07:41 -04:00
webhooks Tighten Transaction Scopes (#525) 2026-04-08 11:14:50 -04:00
__init__.py v0.0.8 Documentation Updates (#55) 2024-05-15 00:07:25 -04:00
_version.py feat: add new cloudevents for api routes (#637) 2026-05-20 18:25:30 -04:00
config.py telemetry: zero-initialize bounded-label metrics so an absent series means a broken scrape (#927) 2026-08-20 10:24:49 -04:00
db.py review: fix db_connections_open leak on the detach path; comment/doc polish 2026-08-24 14:37:20 -04:00
dependencies.py add read db (#773) 2026-06-10 13:28:36 -04:00
embedding_client.py fix: only classify genuine oversize input as a token-limit error (#791) 2026-08-25 09:45:01 -04:00
exceptions.py fix(embedding): truncate in batch embed and return results breakdown (#1019) 2026-08-20 11:42:38 -04:00
main.py feat(telemetry): add physical DB-connection metrics 2026-08-24 14:35:51 -04:00
models.py Make embeddings configurable (#678) 2026-05-14 15:03:35 -04:00
security.py Fix scoped JWTs (#679) 2026-06-22 17:30:00 -04:00