fix(dreamer): threshold and time-guard semantics

Finding 2: filter count_stmt on documents.level == 'explicit' in
check_and_schedule_dream. Dreamer-created levels (deductive, inductive,
contradiction) are consolidation output, not input, and would otherwise
inflate the threshold count and create a feedback loop.

Finding 3 (code-level): relocate last_dream_at write from enqueue_dream
(enqueue.py) to process_dream (orchestrator.py), inside the
'if result is not None' block. Duplicate enqueues can no longer reset
the 8-hour time guard clock. Failed/never-run dreams don't advance it.

Success criteria: lenient (any non-null DreamResult counts). Pending
Vineeth confirmation — will adjust to strict/middle if requested.

Tests pending in follow-up commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lilyplasticlabs 2026-04-17 15:13:43 -04:00
parent 952435848c
commit 1487cd269f
3 changed files with 18 additions and 5 deletions

View File

@ -1,5 +1,4 @@
import logging import logging
from datetime import datetime, timezone
from typing import Any, Literal from typing import Any, Literal
from sqlalchemy import exists, insert, select from sqlalchemy import exists, insert, select
@ -516,8 +515,8 @@ async def enqueue_dream(
stmt = insert(QueueItem).returning(QueueItem) stmt = insert(QueueItem).returning(QueueItem)
await db_session.execute(stmt, [dream_record]) await db_session.execute(stmt, [dream_record])
# Update collection metadata (CRUD handles cache invalidation) # Update collection metadata (CRUD handles cache invalidation).
now_iso = datetime.now(timezone.utc).isoformat() # last_dream_at is written at completion in process_dream, not here.
await crud.update_collection_internal_metadata( await crud.update_collection_internal_metadata(
db_session, db_session,
workspace_name, workspace_name,
@ -526,7 +525,6 @@ async def enqueue_dream(
update_data={ update_data={
"dream": { "dream": {
"last_dream_document_count": document_count, "last_dream_document_count": document_count,
"last_dream_at": now_iso,
} }
}, },
) )

View File

@ -254,11 +254,13 @@ async def check_and_schedule_dream(
last_dream_document_count = dream_metadata.get("last_dream_document_count", 0) last_dream_document_count = dream_metadata.get("last_dream_document_count", 0)
last_dream_at = dream_metadata.get("last_dream_at") last_dream_at = dream_metadata.get("last_dream_at")
# Count current documents in the collection # Count explicit-level docs only: dreamer output (deductive/inductive/
# contradiction) would inflate the threshold and create a feedback loop.
count_stmt = select(func.count(models.Document.id)).where( count_stmt = select(func.count(models.Document.id)).where(
models.Document.workspace_name == collection.workspace_name, models.Document.workspace_name == collection.workspace_name,
models.Document.observer == collection.observer, models.Document.observer == collection.observer,
models.Document.observed == collection.observed, models.Document.observed == collection.observed,
models.Document.level == "explicit",
) )
current_document_count = int(await db.scalar(count_stmt) or 0) current_document_count = int(await db.scalar(count_stmt) or 0)

View File

@ -17,6 +17,7 @@ import logging
import time import time
import uuid import uuid
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any from typing import Any
import sentry_sdk import sentry_sdk
@ -323,6 +324,18 @@ DREAM: {payload.dream_type} documents for {workspace_name}/{payload.observer}/{p
+ f"duration={result.total_duration_ms:.0f}ms" + f"duration={result.total_duration_ms:.0f}ms"
) )
# Write last_dream_at at completion (not enqueue) so duplicate
# enqueues can't reset the 8h guard. Lenient: any non-null result.
now_iso = datetime.now(timezone.utc).isoformat()
async with tracked_db("dream.last_dream_at_write") as db:
await crud.update_collection_internal_metadata(
db,
workspace_name,
payload.observer,
payload.observed,
update_data={"dream": {"last_dream_at": now_iso}},
)
except Exception as e: except Exception as e:
logger.error( logger.error(
f"Error processing dream task {payload.dream_type} for {payload.observer}/{payload.observed}: {str(e)}", f"Error processing dream task {payload.dream_type} for {payload.observer}/{payload.observed}: {str(e)}",