Skip to content

Domain model

Autoresearch adds no database tables. Every concept in the domain model is a page in the research/ directory, typed through metadata.research.type the same way any other page metadata typing works, and the research/ namespace is version-captured so you can see how a goal or hypothesis read at any point in the past.

Type metadata.research.type Carries
Goal goal {metric: {name, direction, evalSet?}, budget?}, plus the MLOps workspace bindings (metadata.fs.path / metadata.mlops.launcher) live on the same page
Hypothesis hypothesis {status: proposed | accepted | testing | supported | refuted | parked, goals: [refs], evidence: [observation page ids]}
Observation observation {run, hypothesis, direction: supports | contradicts | neutral, strength, author: agent | human | merged, parents?}
Experiment experiment Free-form experiment notes/design, linked from hypotheses and runs
Concept concept Literature-triage output: a distilled idea linked source ↔ concept ↔ hypothesis
Profile profile The shared system card described below

A hypothesis is multi-goal by design: its goals field is an array of page refs, so one hypothesis can be evidence-tracked against several goals at once rather than being forked per goal. An observation always names both the run it came from and the hypothesis it speaks to, plus a direction (supports, contradicts, or neutral), a strength, and an author that is agent, human, or merged, the three-way split the blind-first review flow in research loops produces.

A completed run is never a second, research-flavored page type. It is the exact same mlops/run page MLOps already materializes from whichever tracker produced it, decorated with a metadata.research {goal, hypothesis, proposal} lineage stamp once the scheduler launches it (see lineage records below). This one-page-two-namespaces design means a run page you’re looking at from the MLOps dashboard and a run page an observation links to are literally the same row: there is nothing to keep in sync, because there is only ever one page.

Confirming a heartbeat-drafted run proposal (a task-queue Confirm, see research loops) promotes it into that goal’s backlog page, auto-created in research/ as <goal> - backlog. The backlog is an ordinary outliner page: each queued run spec lives as one bullet carrying its structured spec in props, and bullet order is priority. Drag a bullet up to move it earlier in the drain order, exactly the outliner gesture you’d use to reorder anything else. The research.scheduler loop reads this page top to bottom every time it runs; see research loops for how it drains entries against a launcher’s live capacity and a goal’s budget.

A goal references a shared research/profile page through metadata.research.profile. The profile exists because ingest triage and the heartbeat can’t judge whether a paper’s architecture trick or training recipe actually applies to your project without knowing what you actually run, and a hand-maintained description goes stale the moment a hyperparameter changes. The profile solves this by being run-config-derived: every ingested run normalizes its config (ignoring configured ephemeral keys like timestamps or random seeds) and updates the profile’s machine-derived subtree before the observer even looks at the run.

  • Runs off a main or release git ref replace or fold into the profile’s baseline.
  • Off-main configs cluster by a stable config signature, branch name is provenance, not identity, retain a bounded set of run/git-ref stamps, and expire once they go runless or end up tied to a refuted or parked hypothesis.
  • A config that stabilizes and lands on main disappears as its own cluster once the matching recipe becomes the baseline.
  • The profile’s human-authored root nodes (intent, constraints) are never touched by this machinery, only the derived subtree updates automatically.

A baseline signature change emits a research-profile-drift event through trusted triage into the profile’s inbox, so a meaningful shift in what “the current recipe” means surfaces to you rather than silently rewriting history. Goals that opt in automatically pin their profile in triage.context, and both the heartbeat’s gather step and literature triage read the same live profile body as context.

{goal, hypothesis, proposal} is the lineage triple the scheduler stamps onto metadata.research of every run page it launches: which goal the run serves, which hypothesis it’s meant to move evidence on, and which backlog proposal produced it. Combined with the coding lane’s dispatch hints (a launched task carries a hypothesis [[ref]]), the full chain goal → hypothesis → task/proposal → run → observation is traversable as ordinary page links, click through from a hypothesis to every run that tested it, or from a run back to the goal it served, with no separate lineage database to query.

See research loops for the workflows that create, drain, and consume these pages, and MLOps overview for the tracker/launcher layer the run pages themselves come from.