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.
Deterministic Supervisor epoch (core)
Section titled “Deterministic Supervisor epoch (core)”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
reproduceRatingsover the match log always equals the stored history); and - a new root
research-session-runnableevent 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:
-
Observer call
One bounded LLM call (
SUBSPACE_MODEL) returns{observations: [{hypothesis, direction, strength, text}]}. Each observes at most once per run, guarded by anops.plugin_statemarker, so a re-ingest of an already-observed run is a no-op. -
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. -
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 deterministicauthor: 'merged'draft with both as parents. A second card,stage: 'review', shows human, agent, and merged readings side by side. -
Link evidence
Confirming the review card links every observation page id into the hypothesis’s
metadata.research.evidencearray, 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.askif 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-proposalevent. - 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):
-
Plan
research.scheduler.planreads the goal’s backlog, itsmetadata.research.budget {maxConcurrent?, runsPerWeek?}, and this week’s live-run and submitted-run counters. -
Probe capacity
Calls
mlops.local.capacity(or the bound launcher’s equivalent) against the goal’smetadata.research.launcherbinding for a live free-slot count. See launchers for what feeds that number for each provider. -
Enforce budget
Budget checks run in code, not as an LLM judgment call: a pass never submits past
maxConcurrentor the week’srunsPerWeekallowance. -
Submit
One
mlops.local.submitper drained backlog entry, gated or template-approved exactly like any other MLOps launch, the scheduler never introduces a new execution primitive of its own. -
Record
research.scheduler.recordstampsmetadata.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.
The coding lane (MLOps adapter)
Section titled “The coding lane (MLOps adapter)”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.
Advisory Elo sweeps (core)
Section titled “Advisory Elo sweeps (core)”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:
autoAcceptObservationMergeslifts 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.autoLaunchUnderBudgetlifts 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.