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