feat(agents): enforce one librarian timeout budget

- add LIBRARIAN_TIMEOUT config (default 180s) and enforce it with
  asyncio.wait_for inside delegate_to_librarian, covering the live
  paths (steward direct delegation and SSE streaming) that had no cap
- timeouts fail honestly: success=False with a curated butler sentence,
  detail in logs
- set an explicit timeout on TatlockOllamaProvider's AsyncOpenAI client
  from OLLAMA_TIMEOUT instead of the SDK default (~600s per LLM call)
- remove the contradictory unused 60s default from
  AgentRequest.timeout_seconds; coordination falls back to the
  configured budget

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 10:19:16 +02:00
co-authored by Claude Fable 5
parent 99e1fe33ca
commit 18f2e0efbd
9 changed files with 145 additions and 18 deletions
+9 -6
View File
@@ -6,7 +6,7 @@ Defines standardized request/response formats for communication between:
- Tatlock (coordination) → Expert agents (Librarian, Developer, etc.)
"""
from enum import Enum
from typing import Any, Optional
from typing import Any
from pydantic import BaseModel, Field
@@ -53,13 +53,16 @@ class AgentRequest(BaseModel):
default="default",
description="User identifier for multi-tenant operations"
)
max_tokens: Optional[int] = Field(
max_tokens: int | None = Field(
default=None,
description="Optional token limit for response"
)
timeout_seconds: Optional[int] = Field(
default=60,
description="Maximum time for task completion"
timeout_seconds: int | None = Field(
default=None,
description=(
"Maximum time for task completion; None uses the configured "
"expert budget (LIBRARIAN_TIMEOUT)"
)
)
@@ -103,7 +106,7 @@ class AgentResponse(BaseModel):
default_factory=list,
description="Sources or references used"
)
error_message: Optional[str] = Field(
error_message: str | None = Field(
default=None,
description="Error details if success=False"
)