# Bounded research delivery New general research tasks should use `task_type: "research"` on `delegate_task` or `spawn_agent`. The coordinator is instructed to select this for search-and-guide requests. Omitted task types remain standard, including old saved tasks. Existing research is not migrated or restarted. ## Delivery The runtime stores research state with the task, including its start time, source reservations, publications and source results. - Initial collection stops at 120 seconds, two searches or six page reservations. Limits are checked before starting a new source call and on each model round; already running calls may finish. - `publish_research` sends a first guide with summary, actionable steps, observed source URLs and unknowns. It can declare zero to two material gaps. - Each gap permits one additional search and two pages. Every supplement source call needs its zero-based `gap` index. Supplement collection also stops after 120 seconds. - A second publication contains only additions and remaining unknowns. It cannot introduce new gaps. An empty gap list completes the task immediately. - Opening acknowledgements and meaningful progress messages remain available. They do not count as a first guide. - Synthesis gets at most three model rounds after collection closes. Failure to publish then ends the task with an explicit error and retains existing evidence and publications, rather than resuming exploration. - Other execution/delegation tools cannot bypass research limits. Browser navigation counts as a page; browser snapshots and page reading are available during collection for HTTP-blocked sources. Shell/MCP-based research requires a standard task. The 2–3 minute first-guide target is a goal, not a deadline for external model responses. Network source calls have a 30 second timeout; model response and queue delays are measured separately. ## Reuse and persistence Within a research task, identical search queries and canonical URLs reuse the saved result, including errors and timeouts. Different URLs can run concurrently; concurrent requests for the same URL share one execution. URL fragments are ignored; query parameters remain significant. Cached access does not spend a new reservation. Failed source attempts spend their original reservation. Large tool results include `output_id`. Use `read_tool_output` with that ID, `offset` (default 0) and `limit` (default 6000, maximum 12000). Offsets count Unicode characters. The reader is scoped to the current task/session and rejects traversal or another task's output. It does not access the Docker filesystem. Publications are stored independently of foreground conversation checkpoints, restored into chat history and made available to subsequent foreground turns. Completed research does not enqueue a redundant coordinator summary. ## Diagnostics and validation Persisted `timing` events carry task/agent association, timestamp and elapsed milliseconds. Model queue and response records share a round identifier; source and tool records carry the task's request number, with call IDs on tool records. Stages include `worker_model_queue`, `foreground_model_queue`, `model_response`, `model_retry_backoff`, `tool_execution`, `research_source`, `research_first_delivery`, and `research_final_delivery`. Retry-backoff values are scheduled wait durations. No prompts, arguments or credentials are stored in timing events. `LAZYBOY_TIMING=1` additionally prints latency logs. Offline checks: ```sh cargo test -p lazyboy-core --lib cargo build -p lazyboy python3 tests/research_flow.py ``` The integration fixture makes no paid model calls and is not evidence of live provider latency. After restarting the local backend, use new ordinary research requests to measure actual first-guide latency from the recorded events. ## Accurate work indicators `GET /api/agents/:id/activity` (daemon RPC `activity`) reports `idle | queued | running | waiting_input`, active task IDs and a pending question. Roster and agent detail use the same computation. A saved `running` task is active only while the service owns a live worker. Completing one task does not hide another active task; a human question by itself is not active computation. The frontend reconciles on lifecycle events and every two seconds, independently of streamed progress text. Only running activity animates; queued work has a static label. The old OR of typing, stale detail, stale roster and status text is removed. Status requests time out after eight seconds; connection failure shows an unknown/reconnecting message instead of an endless working animation. Late responses from another agent or before a newly sent message are discarded. Browser regression (with the local Vite UI running): `node tests/activity_ui.mjs`. This uses an isolated headless browser and mocked API responses to verify running, idle, queued, waiting-input, disconnect and reconnect, including stale progress events and roster data.