| 1 | # AutoResearch Runtime Design |
| 2 | |
| 3 | > Superseded: the active AutoResearch runtime was removed when Goal became the |
| 4 | > sole runtime. This document is retained as historical design context only; |
| 5 | > new Goal runs do not create or mutate `.reasonix/autoresearch/` state. |
| 6 | |
| 7 | ## Context |
| 8 | |
| 9 | Reasonix already has Goal mode and AutoResearch instructions. When a goal looks |
| 10 | long-running, `activeGoalBlock` injects an AutoResearch protocol that asks the |
| 11 | model to create `.reasonix/autoresearch/<task-id>/` and maintain files such as |
| 12 | `task_spec.json`, `progress.json`, `findings.jsonl`, `directions_tried.json`, and |
| 13 | `heartbeat.jsonl`. |
| 14 | |
| 15 | The current behavior is useful, but the durable state is mostly prompt-driven. |
| 16 | The host does not create the task directory, validate schemas, compute |
| 17 | `stale_count`, require pivots, expose a structured status API, or provide a real |
| 18 | resume mechanism. This design upgrades AutoResearch from a prompt convention to |
| 19 | a host-managed runtime. |
| 20 | |
| 21 | ## Goals |
| 22 | |
| 23 | - Host creates and owns the AutoResearch task id and directory layout. |
| 24 | - Host validates task state with typed schemas. |
| 25 | - Host records heartbeat, progress, findings, directions tried, and iteration log. |
| 26 | - Host computes stale and pivot pressure from accepted evidence and direction |
| 27 | repetition. |
| 28 | - Goal completion is blocked until required success criteria have evidence. |
| 29 | - Existing AutoResearch task ids can be resumed by the controller. |
| 30 | - The desktop/API layer can query AutoResearch status without parsing prompt text. |
| 31 | |
| 32 | ## Non-Goals |
| 33 | |
| 34 | - No large desktop panel in the first implementation. |
| 35 | - No autonomous background daemon. AutoResearch advances only through normal Goal |
| 36 | turns. |
| 37 | - No parallel writable sub-agent redesign in this feature. |
| 38 | - No network, publish, payment, credential, or destructive-operation bypass. |
| 39 | Existing Reasonix gates still apply. |
| 40 | |
| 41 | ## Proposed Architecture |
| 42 | |
| 43 | Add a new package: |
| 44 | |
| 45 | ```text |
| 46 | internal/autoresearch/ |
| 47 | task.go |
| 48 | store.go |
| 49 | schema.go |
| 50 | summary.go |
| 51 | readiness.go |
| 52 | ``` |
| 53 | |
| 54 | The package is responsible for all filesystem state under: |
| 55 | |
| 56 | ```text |
| 57 | .reasonix/autoresearch/<task-id>/ |
| 58 | state/ |
| 59 | task_spec.json |
| 60 | progress.json |
| 61 | directions_tried.json |
| 62 | findings.jsonl |
| 63 | iteration_log.jsonl |
| 64 | logs/ |
| 65 | heartbeat.jsonl |
| 66 | ``` |
| 67 | |
| 68 | The controller remains the owner of Goal lifecycle. AutoResearch state is a |
| 69 | durable sidecar attached to a running Goal when research mode is on or auto |
| 70 | research is triggered. |
| 71 | |
| 72 | ## Core Types |
| 73 | |
| 74 | `TaskSpec`: |
| 75 | |
| 76 | ```json |
| 77 | { |
| 78 | "task_id": "20260629-153000-debug-lag", |
| 79 | "goal": "Find the root cause of UI event-loop lag and verify the fix", |
| 80 | "scope": ["desktop/frontend", "desktop"], |
| 81 | "non_goals": [], |
| 82 | "allowed_operations": { |
| 83 | "write": true, |
| 84 | "network": false, |
| 85 | "publish": false |
| 86 | }, |
| 87 | "success_criteria": [ |
| 88 | { |
| 89 | "id": "root_cause", |
| 90 | "description": "A reproducible root cause is identified", |
| 91 | "required": true, |
| 92 | "evidence_ids": [] |
| 93 | } |
| 94 | ] |
| 95 | } |
| 96 | ``` |
| 97 | |
| 98 | `Progress`: |
| 99 | |
| 100 | ```json |
| 101 | { |
| 102 | "status": "running", |
| 103 | "iteration": 4, |
| 104 | "current_direction": "profile markdown rendering", |
| 105 | "stale_count": 1, |
| 106 | "pivot_count": 0, |
| 107 | "blocked_reason": "", |
| 108 | "updated_at": "2026-06-29T15:30:00Z" |
| 109 | } |
| 110 | ``` |
| 111 | |
| 112 | `Finding` JSONL entries: |
| 113 | |
| 114 | ```json |
| 115 | { |
| 116 | "id": "f1", |
| 117 | "kind": "test", |
| 118 | "summary": "A markdown render benchmark reproduces the lag", |
| 119 | "source": "command", |
| 120 | "command": "pnpm --dir desktop/frontend test", |
| 121 | "paths": ["desktop/frontend/src/components/MarkdownRenderer.tsx"], |
| 122 | "accepted": true, |
| 123 | "created_at": "2026-06-29T15:30:00Z" |
| 124 | } |
| 125 | ``` |
| 126 | |
| 127 | `DirectionTried` entries record normalized direction fingerprints so the host can |
| 128 | detect repeated work: |
| 129 | |
| 130 | ```json |
| 131 | { |
| 132 | "fingerprint": "profile-markdown-rendering", |
| 133 | "summary": "Profile markdown rendering", |
| 134 | "first_seen_iteration": 2, |
| 135 | "last_seen_iteration": 4, |
| 136 | "count": 2 |
| 137 | } |
| 138 | ``` |
| 139 | |
| 140 | ## Store API |
| 141 | |
| 142 | The first implementation should expose a small host API: |
| 143 | |
| 144 | ```go |
| 145 | type Store struct { /* workspace root + autoresearch root */ } |
| 146 | |
| 147 | func (s *Store) CreateTask(goal string, opts CreateOptions) (*Task, error) |
| 148 | func (s *Store) LoadTask(taskID string) (*Task, error) |
| 149 | func (s *Store) ResumeFromGoalText(goal string) (*Task, bool, error) |
| 150 | func (s *Store) AppendHeartbeat(taskID string, h Heartbeat) error |
| 151 | func (s *Store) AppendFinding(taskID string, f Finding) error |
| 152 | func (s *Store) RecordDirection(taskID string, d Direction) (*Progress, error) |
| 153 | func (s *Store) UpdateProgress(taskID string, patch ProgressPatch) (*Progress, error) |
| 154 | func (s *Store) ValidateTask(taskID string) (*ValidationReport, error) |
| 155 | func (s *Store) Readiness(taskID string) (*ReadinessReport, error) |
| 156 | func (s *Store) Summary(taskID string) (*Summary, error) |
| 157 | ``` |
| 158 | |
| 159 | All writes should be atomic: write to a temp file in the same directory, fsync |
| 160 | where practical, then rename. JSONL appends should validate each entry before |
| 161 | writing. |
| 162 | |
| 163 | ## Controller Integration |
| 164 | |
| 165 | When Goal mode starts: |
| 166 | |
| 167 | 1. If research mode is off, behavior is unchanged. |
| 168 | 2. If AutoResearch is on, the controller creates a task unless the goal contains |
| 169 | an explicit `.reasonix/autoresearch/<task-id>/` path. |
| 170 | 3. If an explicit task path exists, the controller loads and validates that task. |
| 171 | 4. The active goal state stores `AutoResearchTaskID`. |
| 172 | |
| 173 | Before each AutoResearch turn: |
| 174 | |
| 175 | - The controller appends a heartbeat with `status=starting_turn`. |
| 176 | - The composed user input includes a concise host-generated summary: |
| 177 | task id, status, iteration, current direction, stale count, pivot count, |
| 178 | blockers, open success criteria, and next required runtime action. |
| 179 | - The static prompt still explains the protocol, but host state is authoritative. |
| 180 | |
| 181 | After each turn: |
| 182 | |
| 183 | - The controller appends a heartbeat with `status=turn_done`. |
| 184 | - If tools ran, it records basic iteration metadata. |
| 185 | - If no accepted evidence was recorded, or the same direction repeated, the host |
| 186 | increments `stale_count`. |
| 187 | - At `stale_count >= 2`, the next turn summary requires a structural pivot. |
| 188 | - At `stale_count >= 4`, the goal is blocked unless the agent asks for the |
| 189 | smallest external input needed. |
| 190 | |
| 191 | The model may still write detailed notes, but host-owned JSON files are the |
| 192 | source of truth. |
| 193 | |
| 194 | ## Completion Gate |
| 195 | |
| 196 | When the model emits `[goal:complete]`, the controller runs AutoResearch |
| 197 | readiness before normal Goal completion: |
| 198 | |
| 199 | - `task_spec.json` and `progress.json` must exist and validate. |
| 200 | - Every required success criterion must have at least one accepted evidence id. |
| 201 | - Evidence ids must resolve to entries in `findings.jsonl`. |
| 202 | - If code was changed, there must be accepted verification evidence or an |
| 203 | explicit accepted reason why verification could not run. |
| 204 | - If `progress.status` is blocked, completion is rejected. |
| 205 | - If `stale_count > 0`, completion is allowed only when the final iteration added |
| 206 | accepted evidence that addresses the stale direction. |
| 207 | |
| 208 | Failure returns a concrete intercept message to the model listing missing |
| 209 | criteria and required next actions. |
| 210 | |
| 211 | ## Resume Behavior |
| 212 | |
| 213 | AutoResearch resume has two paths: |
| 214 | |
| 215 | - Explicit: a goal or prompt includes `.reasonix/autoresearch/<task-id>/`. |
| 216 | - Session-sidecar: the persisted Goal state contains `AutoResearchTaskID`. |
| 217 | |
| 218 | On resume, the host validates the task. If state is corrupt, it blocks execution |
| 219 | with a repair message instead of silently asking the model to infer state. |
| 220 | |
| 221 | ## Desktop/API Surface |
| 222 | |
| 223 | The first UI implementation should make AutoResearch visible without turning the |
| 224 | chat surface into a project-management app. It should use the existing desktop |
| 225 | layout patterns: status bar for compact state, side panels for inspectable |
| 226 | details, and transcript cards for turn-local events. |
| 227 | |
| 228 | Host methods: |
| 229 | |
| 230 | - `AutoResearchStatus(taskID string)` |
| 231 | - `AutoResearchList()` |
| 232 | - `AutoResearchCurrent()` |
| 233 | - `AutoResearchFindings(taskID string, limit int)` |
| 234 | - `AutoResearchOpenTask(taskID string)` |
| 235 | |
| 236 | The status payload should include: |
| 237 | |
| 238 | - task id |
| 239 | - goal |
| 240 | - status |
| 241 | - iteration |
| 242 | - current direction |
| 243 | - stale count |
| 244 | - pivot count |
| 245 | - last heartbeat |
| 246 | - finding count |
| 247 | - open success criteria |
| 248 | - blocker |
| 249 | |
| 250 | `AutoResearchOpenTask` opens `.reasonix/autoresearch/<task-id>/` in the |
| 251 | workspace panel or OS file browser, matching existing workspace reveal behavior. |
| 252 | |
| 253 | ## Deferred Desktop UI Design |
| 254 | |
| 255 | The runtime PR exposes the desktop API and compact tab metadata first. The |
| 256 | default frontend tool/status surface intentionally stays unchanged until the UI |
| 257 | entry points below are implemented and reviewed as a separate product decision. |
| 258 | |
| 259 | ### Entry Points |
| 260 | |
| 261 | AutoResearch should appear in three places: |
| 262 | |
| 263 | 1. Status bar chip: a compact always-visible indicator when the active tab has a |
| 264 | running or resumable AutoResearch task. |
| 265 | 2. Context/side panel section: an inspectable task summary for the active tab. |
| 266 | 3. Transcript event cards: lightweight markers for task creation, pivot required, |
| 267 | blocked, resumed, and completed. |
| 268 | |
| 269 | This keeps the primary chat workflow intact while making durable research state |
| 270 | visible and recoverable. |
| 271 | |
| 272 | ### Status Bar Chip |
| 273 | |
| 274 | Add an `autoresearch` status item to the existing status bar item system. It is |
| 275 | hidden when no AutoResearch task is active for the current tab. |
| 276 | |
| 277 | Display states: |
| 278 | |
| 279 | - `Research 4` for running iteration 4. |
| 280 | - `Pivot` when `pivot_required` is true. |
| 281 | - `Blocked` when status is blocked. |
| 282 | - `Done` briefly after completion. |
| 283 | |
| 284 | The chip should include an icon, short label, and tooltip. The tooltip contains: |
| 285 | |
| 286 | - task id |
| 287 | - current direction |
| 288 | - stale count |
| 289 | - open criteria count |
| 290 | - last heartbeat age |
| 291 | |
| 292 | Clicking the chip opens the AutoResearch detail panel. |
| 293 | |
| 294 | ### Detail Panel |
| 295 | |
| 296 | Add a compact AutoResearch section to the right-side context/workspace area. The |
| 297 | panel should be dense and operational, not decorative. |
| 298 | |
| 299 | Header: |
| 300 | |
| 301 | - task id |
| 302 | - status |
| 303 | - iteration |
| 304 | - last heartbeat |
| 305 | - open task folder button |
| 306 | |
| 307 | Summary rows: |
| 308 | |
| 309 | - goal |
| 310 | - current direction |
| 311 | - stale count |
| 312 | - pivot count |
| 313 | - blocker |
| 314 | |
| 315 | Success criteria list: |
| 316 | |
| 317 | - criterion description |
| 318 | - required/optional marker |
| 319 | - evidence count |
| 320 | - status: open, satisfied, blocked |
| 321 | |
| 322 | Findings list: |
| 323 | |
| 324 | - newest accepted findings first |
| 325 | - kind badge: command, file, test, benchmark, manual, review, verification |
| 326 | - summary |
| 327 | - source command/path if present |
| 328 | - created time |
| 329 | |
| 330 | Controls: |
| 331 | |
| 332 | - Resume: starts or continues `/goal --research .reasonix/autoresearch/<task-id>/` |
| 333 | for the active tab when not running. |
| 334 | - Pause: clears active Goal continuation without deleting task state. |
| 335 | - Open Folder: reveals the task directory. |
| 336 | - Copy Task ID: copies the task id. |
| 337 | |
| 338 | The first implementation can omit inline editing of task spec fields. Task state |
| 339 | is owned by the host and model workflow; UI edits would need validation and a |
| 340 | separate audit path. |
| 341 | |
| 342 | ### Transcript Cards |
| 343 | |
| 344 | Emit lightweight notices or typed events for important AutoResearch lifecycle |
| 345 | changes: |
| 346 | |
| 347 | - task created |
| 348 | - task resumed |
| 349 | - heartbeat failed |
| 350 | - pivot required |
| 351 | - readiness blocked completion |
| 352 | - task completed |
| 353 | - task blocked |
| 354 | |
| 355 | The transcript should not render the full runtime summary every turn. It should |
| 356 | show only meaningful lifecycle changes, because the full summary is already |
| 357 | available in the detail panel and injecting it visually every turn would add |
| 358 | noise. |
| 359 | |
| 360 | ### Frontend State Flow |
| 361 | |
| 362 | Extend bridge/types with: |
| 363 | |
| 364 | ```ts |
| 365 | interface AutoResearchStatusView { |
| 366 | taskId: string; |
| 367 | goal: string; |
| 368 | status: "running" | "blocked" | "complete" | "stopped" | "invalid"; |
| 369 | iteration: number; |
| 370 | currentDirection: string; |
| 371 | staleCount: number; |
| 372 | pivotCount: number; |
| 373 | pivotRequired: boolean; |
| 374 | lastHeartbeatAt: string; |
| 375 | findingCount: number; |
| 376 | openCriteria: AutoResearchCriterionView[]; |
| 377 | blocker: string; |
| 378 | taskPath: string; |
| 379 | } |
| 380 | ``` |
| 381 | |
| 382 | `MetaForTab` should include only the small active-task summary needed to render |
| 383 | the status chip. The heavier findings list should be loaded on demand through |
| 384 | `AutoResearchFindings`, so normal chat turns do not pull large JSONL data into |
| 385 | frontend state. |
| 386 | |
| 387 | Refresh strategy: |
| 388 | |
| 389 | - Refresh current AutoResearch status after `turn_done`. |
| 390 | - Refresh when switching tabs. |
| 391 | - Refresh when receiving an AutoResearch lifecycle event. |
| 392 | - Do not poll every second. The heartbeat is persisted for durability, not for a |
| 393 | live dashboard animation. |
| 394 | |
| 395 | ### Empty and Error States |
| 396 | |
| 397 | - No active task: hide the chip and show no panel section by default. |
| 398 | - Invalid state: show an `Invalid` status with the exact validation error and an |
| 399 | Open Folder action. |
| 400 | - Missing task folder on resume: show a blocked state and keep the Goal from |
| 401 | silently continuing. |
| 402 | - Stale state: show the pivot requirement prominently but do not mark it as an |
| 403 | error. |
| 404 | |
| 405 | ### Accessibility and Layout |
| 406 | |
| 407 | - Use existing button, tooltip, panel, and status bar patterns. |
| 408 | - Keep the status chip width stable so the status bar does not shift during |
| 409 | streaming. |
| 410 | - Long goals and directions should wrap in the panel but truncate in the chip |
| 411 | tooltip label. |
| 412 | - Findings should be keyboard navigable and copyable. |
| 413 | - Use icons only where the existing design system already uses them; avoid a |
| 414 | marketing-style card layout. |
| 415 | |
| 416 | ## Error Handling |
| 417 | |
| 418 | - Missing task directory: create only when starting a new task; otherwise return |
| 419 | a clear resume error. |
| 420 | - Corrupt JSON: block AutoResearch continuation and surface the exact file. |
| 421 | - Schema mismatch: return validation errors with paths and fields. |
| 422 | - Failed heartbeat write: warn and continue for non-critical writes, but block |
| 423 | completion if state cannot be validated. |
| 424 | - Concurrent writes: serialize per task id inside the store. |
| 425 | |
| 426 | ## Testing |
| 427 | |
| 428 | Unit tests: |
| 429 | |
| 430 | - task id generation is stable in shape and collision-safe |
| 431 | - store creates the expected directory layout |
| 432 | - schema validation rejects missing required fields |
| 433 | - JSONL append rejects invalid entries |
| 434 | - direction repetition increments stale count |
| 435 | - pivot threshold produces a pivot requirement |
| 436 | - readiness blocks missing evidence |
| 437 | - readiness accepts complete criteria with accepted findings |
| 438 | - resume loads explicit task id from goal text |
| 439 | |
| 440 | Controller tests: |
| 441 | |
| 442 | - AutoResearch goal creates task state |
| 443 | - active goal block includes host-generated summary |
| 444 | - every turn appends heartbeat |
| 445 | - `[goal:complete]` is intercepted when readiness fails |
| 446 | - explicit `.reasonix/autoresearch/<task-id>/` resumes existing state |
| 447 | |
| 448 | Desktop/API tests: |
| 449 | |
| 450 | - app method returns current AutoResearch status for the active tab |
| 451 | - tab metadata includes only compact AutoResearch summary |
| 452 | - findings are loaded on demand and capped by limit |
| 453 | - status chip hides when no task is active |
| 454 | - status chip opens the detail panel |
| 455 | - detail panel renders running, pivot, blocked, invalid, and complete states |
| 456 | - Open Folder calls the existing reveal/open path behavior |
| 457 | - tab switch refreshes the visible AutoResearch summary |
| 458 | |
| 459 | ## Rollout |
| 460 | |
| 461 | Phase 1: implement `internal/autoresearch` store, schemas, summary, readiness, |
| 462 | and tests. |
| 463 | |
| 464 | Phase 2: integrate with Goal controller create/resume/heartbeat/summary and |
| 465 | completion intercept. |
| 466 | |
| 467 | Phase 3: add desktop/API status methods, status bar chip, detail panel, transcript |
| 468 | lifecycle cards, and frontend tests. |
| 469 | |
| 470 | Phase 4: optionally let tools or a dedicated host tool record structured |
| 471 | findings directly, reducing reliance on model-authored JSON. |
| 472 | |
| 473 | ## Compatibility and Cache Impact |
| 474 | |
| 475 | This design should not change provider-visible tool schemas in phase 1. The |
| 476 | active goal prompt changes only when AutoResearch is active. Cache impact is |
| 477 | therefore low for ordinary sessions and medium for AutoResearch sessions because |
| 478 | the injected runtime summary changes each turn. |
| 479 | |
| 480 | No existing `.reasonix/autoresearch` task should be deleted or rewritten without |
| 481 | validation and explicit migration logic. |
| 482 |