Skip to content

Research loops

Autoresearch’s behavior lives in plugin-declared triggers, native tools, and registered workflows. The core package supplies the deterministic Supervisor epoch frame, heartbeat, literature triage, advisory Elo sweep, and reflexive proposals without requiring an ML stack. The optional autoresearch-mlops adapter supplies run ingestion, profiles, coding/run proposals, backlogs, scheduling, and the two ML autonomy exceptions.

The adapter is a real dependency boundary, not only packaging: when it is disabled, heartbeat gather omits profile/backlog/budget context, reflection and persistence reject coding/run proposals, the run-ingest trigger is dormant, and scheduler/autonomy checks fail closed. Core goals, plans, sessions, hypotheses, reviews, evidence, observations, concepts, meta-reviews, overviews, literature triage, Elo, and reflexive proposals remain available.

research-supervisor runs autoresearch/supervisorEpoch against a running research/session page. Its arguments are {session, expectedEpoch, priorCheckpointHash}; the session itself pins exact goal and confirmed-plan semantic/page hashes. A run with drifted refs, another owner, a stale epoch, or the wrong prior checkpoint does no work.

The bounded workflow has a stable durable sequence: claim, dispatch/gather, finish, then exit. Each epoch claims a bounded task batch, fans the claimed tasks out as background research-specialist children, ingests their schema-validated results (generation candidates through safety screening into hypothesis pages, staged reviews with claim-level citations, ranking legs into canonical match records, evolution descendants), checkpoints, and exits. The claim/finish tools provide the production coordination contract:

  • one transactional writer and active epoch per owner-scoped session;
  • deterministic task/match identities and collection schemas for sessions, tasks, matches, ratings, proximity, strategies, reviews, meta-review versions, steering records, and hash-linked checkpoints;
  • a strict priority key with a hard six-task cap, current strategy-yield rotation, and persisted coverage/stagnation signals;
  • same-transaction effect memoization so a crash after collection mutation replays the structured result instead of repeating the effect (re-ingesting a child is replay-neutral, ratings can never double-apply, and reproduceRatings over the match log always equals the stored history); and
  • a new root research-session-runnable event only when the session is nonterminal and runnable work remains—never a recursive child-run chain.

At every checkpoint the session page mirrors the enriched state (statistics, safety breaker, proximity landscape, positional-bias metrics, active meta-review version); at the terminal boundary the engine composes exactly one research overview page. Use research.supervisor.inspect {session} to read the reconstructed queue statistics, signals, tasks, checkpoints, and the operational block (failed/held work with recoverable actions) through the run owner’s ACL. Session modes, budget ceilings, and recovery procedures are covered in rollout modes.

Ingest-run: turning a finished run into a draft (MLOps adapter)

Section titled “Ingest-run: turning a finished run into a draft (MLOps adapter)”

The adapter’s research-on-mlops-run-ingested trigger subscribes to the bounded mlops-run-ingested notice MLOps emits from every tracker sweep. It starts the versioned autoresearch-mlops/ingestRun workflow, whose native tool first refreshes a research goal’s shared profile. Then, for a terminal run (finished, failed, or killed) against a goal with open hypotheses (proposed, accepted, or testing, matched by goal id, slug, or title in the hypothesis’s goals list), it drafts evidence:

  1. Observer call

    One bounded LLM call (SUBSPACE_MODEL) returns {observations: [{hypothesis, direction, strength, text}]}. Each observes at most once per run, guarded by an ops.plugin_state marker, so a re-ingest of an already-observed run is a no-op.

  2. Blind draft

    The drafts land as author: 'agent' observation pages, deliberately not yet linked to anything, alongside one needs-confirm task card (kind: 'research-interpretation', stage: 'blind', scoped to the run). That card’s payload carries no draft text: it renders a bare textarea and a “Commit interpretation” action.

  3. Reveal and merge

    Writing your own reading and confirming persists it as an author: 'human' observation page, then, in the same transaction, reveals the agent’s drafts and composes a deterministic author: 'merged' draft with both as parents. A second card, stage: 'review', shows human, agent, and merged readings side by side.

  4. Link evidence

    Confirming the review card links every observation page id into the hypothesis’s metadata.research.evidence array, deduped. Dismissing either card leaves the drafts unlinked rather than deleting them, so the record stays auditable.

An empty interpretation is refused and the blind card stays open, so you can’t skip writing your own read.

Per-goal heartbeat: gather, reflect, propose, report (core)

Section titled “Per-goal heartbeat: gather, reflect, propose, report (core)”

The research-heartbeat agent runs the autoresearch/heartbeat workflow once per goal, invoked with {goal}, and does exactly four steps, none of which execute anything:

  • Gather always reads the goal, open hypotheses and their evidence, and new observations since the last run. It adds the pinned profile, backlog, and budget only while the MLOps adapter is enabled.
  • Reflect may park durably on human.ask if it needs a decision it can’t make alone.
  • Propose writes only pending artifacts: hypotheses, status-change proposals, and reflexive definition edits in core. Draft coding-task and run proposals are accepted only while the MLOps adapter is enabled. Every accepted proposal emits a research-proposal event.
  • Report files a goal-scoped summary card.

Every proposal from this step is inert until a human confirms it. Proposal events reuse the ordinary event triage severity routing: a silent route appends a link to the pending page in the goal’s inbox, and a queue route creates a research-proposal needs-confirmation card in the task queue. Confirming applies the approved transition and emits a typed research-proposal-confirmed hand-off that the scheduler and coding lane consume; dismissing preserves the pending page rather than deleting it.

With the adapter enabled, confirming a run proposal specifically promotes it into the goal’s backlog page.

Research scheduler: draining the backlog within budget (MLOps adapter)

Section titled “Research scheduler: draining the backlog within budget (MLOps adapter)”

The research-scheduler agent runs autoresearch-mlops/scheduler for one bounded drain pass per invocation ({goal}, run by hand, on a cron, or from a trigger):

  1. Plan

    research.scheduler.plan reads the goal’s backlog, its metadata.research.budget {maxConcurrent?, runsPerWeek?}, and this week’s live-run and submitted-run counters.

  2. Probe capacity

    Calls mlops.local.capacity (or the bound launcher’s equivalent) against the goal’s metadata.research.launcher binding for a live free-slot count. See launchers for what feeds that number for each provider.

  3. Enforce budget

    Budget checks run in code, not as an LLM judgment call: a pass never submits past maxConcurrent or the week’s runsPerWeek allowance.

  4. Submit

    One mlops.local.submit per drained backlog entry, gated or template-approved exactly like any other MLOps launch, the scheduler never introduces a new execution primitive of its own.

  5. Record

    research.scheduler.record stamps metadata.research {goal, hypothesis, proposal} lineage onto the newly launched run page, drains the entry off the backlog bullet, and advances the runs-this-week counter. Every step is claim-guarded, so a replay after a crash is safe.

A denial (an unapproved template, an out-of-budget request) stops the pass; whatever entries didn’t launch simply stay queued for the next drain.

Confirming a heartbeat-drafted coding-task proposal appends a dispatch hint carrying the hypothesis’s [[ref]] onto the resulting task page before it goes to a coding agent: whichever lane you’ve wired, a bd issue, a prefilled agent session, or a Devin session inherits that research context directly in its prompt. When the resulting code lands and gets used in a launched run, the scheduler records the merged gitRef on that run’s lineage stamp, closing the chain from proposal to shipped code to the run that exercised it.

A goal opts in with metadata.research.elo {judge, k?, maxHypotheses?}. The explicit research-elo-sweep agent’s autoresearch/eloSweep workflow (not run automatically) compares bounded pairs of open hypotheses under a pinned judge rubric page and writes research.elo plus eloMeta onto each hypothesis. A content fingerprint makes a repeat sweep over an unchanged set of hypotheses a no-op. Ratings are purely advisory: they never change a hypothesis’s status, approve anything, or gate a launch.

Per-goal autonomy policies (MLOps adapter)

Section titled “Per-goal autonomy policies (MLOps adapter)”

research.autonomy on a goal has exactly two independent opt-ins, both narrow by design:

  • autoAcceptObservationMerges lifts only the second review gate, the merge/review card, after you’ve already committed the blind human interpretation yourself. The blind step itself is never skippable.
  • autoLaunchUnderBudget lifts only the scheduler’s Docker-lane submit card, and only for that exact goal’s exact launcher binding with an explicit finite budget set. It rides the same template-approval seam MLOps uses.

Everything else keeps its gate regardless of these opt-ins: bash-lane submits, any other workflow, a mismatched launcher, an unbudgeted goal, hypothesis status changes, coding-task dispatch, and plugin installation itself are never auto-approved by a research autonomy policy.

Scheduling the core heartbeat: triggers, not cloned definitions

Section titled “Scheduling the core heartbeat: triggers, not cloned definitions”

Every one of these loops is invoked, not always-on. To put the heartbeat on a schedule, use a trigger: copy the shipped triggers/research-heartbeat-daily example (an inert 0 9 * * * cron trigger) once per goal, point its action.args.goal at the goal page, choose a cadence, and enable it. Every goal’s schedule reuses the same research-heartbeat agent definition rather than a per-goal clone, the pattern the trigger system replaced. An older definition-level metadata.agent.schedule with a static workflowArgs still runs on existing definitions, but cloning the heartbeat definition per goal is no longer how you set a new one up.

Literature triage follows the same rule. The core package ships a page-created trigger whose action is autoresearch/triageSource; the tool exits unless the page is a captured third-party source and an opted-in goal names Autoresearch as its triage specialist. Disabling the package makes both imported triggers and their workflow aliases dormant.