Python API

Configuration, core records, evaluator interfaces, and shared commons modules.

Synthetic Scientists' Python API is organized into a few key modules. Most users interact through the CLI or task.yaml, but the Python API is what you write against for custom evaluators or framework extensions.

ModuleCovers
scientist.configScientistConfig and related dataclasses
scientist.typesTask, Score, ScoreBundle, Attempt, CoordinationNode
scientist.graderGraderInterface, BaseGrader, TaskGrader, SubprocessGrader
scientist.commonsExperiments, control actions, evidence, and methods

Configuration

Module: scientist.config

The configuration system uses Python dataclasses loaded from YAML.

ScientistConfig

Top-level configuration object.

from scientist.config import ScientistConfig

config = ScientistConfig.from_yaml("task.yaml")

Fields

FieldTypeDescription
taskTaskConfigTask definition
graderGraderConfigGrader settings
agentsAgentConfigAgent spawning config
sharingSharingConfigShared state toggles
workspaceWorkspaceConfigWorkspace layout
runRunConfigRuntime/session flags and run-level stop conditions

Methods

MethodDescription
from_yaml(path)Load config from a YAML file (resolves a top-level preset: relative to the file's directory)
from_dict(data, base_dir=None)Create config from a dictionary; base_dir resolves a relative preset: path
to_dict()Serialize to dictionary
to_yaml(path)Write to a YAML file

TaskConfig

@dataclass
class TaskConfig:
    name: str              # Task identifier
    description: str       # What agents should do
    files: list[str]       # Key files to focus on
    tips: str              # Additional hints for agents
    seed: list[str]        # Files/dirs copied into workspace

GraderConfig

@dataclass
class GraderConfig:
    entrypoint: str        # "module.path:ClassName", required, resolved inside the grader venv
    setup: list[str]       # Shell commands run in .scientist/private/grader_venv/ at start time
    timeout: int           # Eval timeout in seconds (default: 300)
    args: dict[str, Any]   # Extra grader arguments
    private: list[str]     # Files hidden from agents
    direction: str         # "maximize" or "minimize"

AgentConfig

@dataclass
class AgentConfig:
    count: int             # Number of worker agents (default: 1)
    runtime: str           # "claude_code", "codex", "opencode"
    model: str             # Model name or ID (default: "sonnet")
    max_turns: int         # Max conversation turns (default: 0 = unlimited)
    timeout: int           # Stall watchdog in seconds (default: 1200)
    research: bool         # Enable web search (default: True)
    cadence: list[CadenceActionConfig]  # Periodic actions

CoordinationConfig

@dataclass
class CoordinationConfig:
    enabled: bool
    mode: str                    # "auto", "advisory", or "managed"
    coordinator_threshold: int   # default: 8 workers
    workers_per_coordinator: int # default: 4
    auto_personas: bool
    seed: str                    # optional YAML/JSON DAG seed

LabsConfig

@dataclass
class LabsConfig:
    count: int                 # default: 1
    rotation: RotationConfig

@dataclass
class RotationConfig:
    enabled: bool
    every: int
    rank_window: int
    min_evals: int
    dest_weighting: str
    max_per_cycle: int
    rotation_cooldown: int

CadenceActionConfig

@dataclass
class CadenceActionConfig:
    name: str              # Action name (e.g. "reflect")
    every: int             # Trigger every N evals
    is_global: bool        # Use global eval count (default: False)
    trigger: str           # "interval" or "plateau"
    options: dict          # trigger-specific options such as epsilon

SharingConfig

@dataclass
class SharingConfig:
    attempts: bool            # Share experiment scores (default: True)
    notes: bool            # Enable shared notes (default: True)
    skills: bool           # Enable shared skills (default: True)

WorkspaceConfig

@dataclass
class WorkspaceConfig:
    results_dir: str       # Where to store results (default: "./results")
    repo_path: str         # Git repository root (default: ".")

RunConfig

@dataclass
class RunConfig:
    verbose: bool          # Print verbose manager output (default: False)
    ui: bool               # Start the web dashboard (default: False)
    session: str           # "local", "tmux", or "docker"
    docker_image: str      # Docker image override
    stop: RunStopConfig    # Optional run-level auto-stop conditions

RunStopConfig

@dataclass
class RunStopConfig:
    score_threshold: float | None     # Stop when best real score reaches this threshold
    max_real_attempts: int | None     # Stop after this many finalized real experiments

run.stop.score_threshold is direction-aware: maximize tasks stop when a finalized real experiment has score >= score_threshold; minimize tasks stop when score <= score_threshold. run.stop.max_real_attempts counts only finalized experiments with budget_class="real" across the whole run. Pending experiments, tune experiments, and grader_error experiments do not count.

Examples:

scientist launch -c task.yaml run.stop.score_threshold=0.8
scientist launch -c task.yaml run.stop.max_real_attempts=30

Types

Module: scientist.types

These dataclasses are the building blocks of Synthetic Scientists' data model.

Task

A unit of work for agents to optimize.

from scientist.types import Task

task = Task(
    id="my-task",
    name="My Task",
    description="Optimize solution.py",
    metadata={"files": ["solution.py"]},
)

Fields

FieldTypeDescription
idstrUnique identifier
namestrDisplay name
descriptionstrWhat to optimize
metadatadict[str, Any]Additional context

Methods

MethodDescription
to_dict()Serialize to dictionary
from_dict(data)Create from dictionary

Score

A single evaluation score.

from scientist.types import Score

score = Score(
    value=0.85,
    name="eval",
    explanation="Runtime: 1.2s",
)

Fields

FieldTypeDescription
valuefloat | str | bool | NoneThe score value
namestrScore identifier
explanationstr | NoneHuman-readable feedback
metadatadict[str, Any]Extra data

Methods

MethodDescription
to_float()Convert value to float (handles bool, str mappings)
to_dict()Serialize to dictionary
from_dict(data)Create from dictionary

String score mappings

When value is a string, to_float() maps it:

StringFloat
"CORRECT", "C"1.0
"INCORRECT", "I"0.0
"PARTIAL", "P"0.5
"NOANSWER", "N"0.0

ScoreBundle

A collection of scores from evaluation.

from scientist.types import Score, ScoreBundle

bundle = ScoreBundle(
    scores={"eval": Score(value=0.85, name="eval")},
    aggregated=0.85,
)

Fields

FieldTypeDescription
scoresdict[str, Score]Named scores
aggregatedfloat | NoneOverall score
is_publicboolWhether scores are visible to agents (default: True)

Methods

MethodDescription
get(name)Get a score by name
get_score_value(name, default=0.0)Get float value of a named score
compute_aggregated(weights=None)Compute weighted average of all scores
to_dict()Serialize to dictionary
from_dict(data)Create from dictionary

Attempt

Record of a single optimization experiment by an agent.

from scientist.types import Attempt

experiment = Attempt(
    commit_hash="abc1234",
    agent_id="agent-1",
    title="Optimized inner loop",
    score=0.85,
    status="improved",
    parent_hash="def5678",
    timestamp="2025-03-15T10:30:00+00:00",
    feedback="Runtime reduced from 2.3s to 1.1s",
)

Fields

FieldTypeDescription
commit_hashstrGit commit hash
agent_idstrWhich agent made this experiment
titlestrDescription from scientist eval -m
scorefloat | NoneEvaluation score (None if crashed/timeout)
statusstrOne of: improved, baseline, regressed, reverted, crashed, timeout
parent_hashstr | NonePrevious commit hash
timestampstrISO 8601 timestamp
feedbackstrGrader feedback
metadatadict[str, Any]Extra data such as budget_class and user_best

Metadata keys

KeyTypeDescription
budget_classstr"real", "tune", or "grader_error"; controls ranking and cadence accounting
user_bestboolSet to true when the operator marks this experiment as best in the dashboard

Methods

MethodDescription
to_dict()Serialize to dictionary
from_dict(data)Create from dictionary

CoordinationNode

One planned or completed unit in the run-global task-and-commit DAG.

Important fields include id, kind, status, owner, persona, lease_expires, base_source, depends_on, based_on_commits, combines, scope, decisions, experiments, result_commit, and score.

Coordination nodes exist before a commit and can collect several evaluated experiments before being explicitly completed.

Grader

Synthetic Scientists' grading system has three layers: a protocol, an abstract base class, and the recommended task grader. A separate SubprocessGrader is the runtime Synthetic Scientists spawns when your task uses grader.entrypoint.

GraderInterface

Module: scientist.grader.protocol

The protocol that all graders must satisfy:

from typing import Protocol, runtime_checkable
from scientist.types import ScoreBundle, Task

@runtime_checkable
class GraderInterface(Protocol):
    async def grade(
        self,
        codebase_path: str,
        tasks: list[Task],
        **kwargs,
    ) -> ScoreBundle: ...

Any object with a matching grade() method satisfies the protocol.

BaseGrader

Module: scientist.grader.base

Abstract base class with helper methods. Use this if you need full control over scoring.

from scientist.grader.base import BaseGrader
from scientist.types import Task, ScoreBundle

class MyGrader(BaseGrader):
    async def grade(self, codebase_path: str, tasks: list[Task]) -> ScoreBundle:
        score = self._make_score(0.85, explanation="Good result")
        return self._make_bundle(score, aggregated=0.85)

Constructor

BaseGrader(name: str, description: str = "", is_public: bool = True, **kwargs)

Methods

MethodDescription
grade(codebase_path, tasks)Abstract, implement this
grade_sync(codebase_path, tasks)Synchronous wrapper for grade()
_make_score(value, explanation, metadata)Create a Score with this grader's name
_make_bundle(score, aggregated)Create a ScoreBundle with this grader's settings

Module: scientist.grader.task_grader

The standard way to write graders. Simpler API with built-in helpers.

from scientist.grader import TaskGrader
from scientist.types import ScoreBundle

class Grader(TaskGrader):
    def evaluate(self) -> float | ScoreBundle:
        result = self.run_program("solution.py")
        return float(result.stdout.strip())

Methods

MethodDescription
evaluate()Abstract, implement this. Return float or ScoreBundle
run_program(filename, *args, timeout=300)Run a file from the agent's codebase
score(value, explanation)Return a single-score bundle
fail(explanation)Return a failed evaluation (null score)
bundle(value, explanation)Create a ScoreBundle directly

Attributes

AttributeTypeDescription
codebase_pathstrAbsolute path to agent's worktree (set by framework)
private_dirstrAbsolute path to .scientist/private/ (set by framework)
argsdictExtra arguments from grader.args in config

Return types

evaluate() can return:

  • float: automatically wrapped in a ScoreBundle
  • ScoreBundle: returned as-is (use self.score() or self.fail())

GraderConfig fields

Configured under grader: in task.yaml.

FieldTypeDefaultDescription
entrypointstr""Required. Setuptools-style module.path:ClassName resolved inside the grader venv.
setuplist[str][]Shell commands run once in the grader venv at scientist launch / scientist check time. Typically a single uv pip install -e ./grader.
timeoutint300Eval timeout in seconds (0 = no limit).
argsdict{}Free-form kwargs accessible inside the grader as self.args.
privatelist[str][]Extra files / directories copied to .scientist/private/ (hidden from agents).
directionstr"maximize""maximize" or "minimize": which direction makes a score better.

SubprocessGrader (runtime)

Module: scientist.grader.subprocess_grader

You don't usually construct this directly, scientist.grader.loader.load_grader returns one whenever grader.entrypoint is set. It implements GraderInterface by spawning a worker subprocess in .scientist/private/grader_venv/bin/python and exchanging JSON over stdin/stdout.

Errors raised inside the worker are returned as {"error": ..., "traceback": ...} and re-raised in the parent process so you see the original Python traceback.

Commons

The commons modules manage shared session state in .scientist/.

Attempts

Module: scientist.commons.attempts

Functions

FunctionDescription
write_attempt(scientist_dir, experiment)Write an experiment JSON file
read_attempts(scientist_dir)Read all experiments across all agents
get_agent_attempts(scientist_dir, agent_id)List experiments for a specific agent
get_ranking(scientist_dir, top_n=20, direction="maximize")Top N experiments sorted by score
get_recent(scientist_dir, n=10)Most recent experiments by timestamp
search_attempts(scientist_dir, query)Full-text search across experiment titles and feedback
set_user_best(scientist_dir, commit_hash)Mark one experiment as the user-selected best and clear prior marks
format_ranking(experiments)Format experiments as a markdown table
format_status_summary(scientist_dir, direction)Summary string with per-agent stats

Usage

from scientist.commons.attempts import read_attempts, write_attempt
from scientist.types import Attempt

# Read all experiments
experiments = read_attempts("/path/to/.scientist")
for a in experiments:
    print(f"{a.agent_id}: {a.score} ({a.status})")

# Write an experiment
experiment = Attempt(
    commit_hash="abc123",
    agent_id="agent-1",
    title="Improved performance",
    score=0.85,
    status="improved",
    parent_hash="def456",
    timestamp="2025-03-15T10:30:00+00:00",
)
write_attempt("/path/to/.scientist", experiment)

Coordination

Module: scientist.commons.coordination

Important functions:

  • create_node() and update_node()
  • list_nodes() and available_nodes()
  • claim_node(), renew_node(), release_node()
  • attach_attempt() and complete_node()
  • link_nodes()
  • seed_from_file()
  • graph_data()

Claims and lease mutations use a run-global file lock. The state remains inspectable under .scientist/public/coordination/.

Steering

Module: scientist.commons.steering

Steering actions are JSON files in .scientist/public/steering/. They are written with tmp-file + rename and read by scientist continue.

Functions

FunctionDescription
enqueue(scientist_dir, action)Write a pending steering action
read_pending(scientist_dir)Read unapplied actions sorted by creation time
mark_applied(scientist_dir, id)Mark one action applied

Action kinds

KindDescription
continue_fromReset an agent worktree to hash on resume and inject instruction
mark_bestUser-best selection; the web API applies this immediately via experiment metadata

Notes

Module: scientist.commons.notes

Notes are Markdown files with YAML frontmatter stored in .scientist/public/notes/.

Functions

FunctionDescription
list_notes(scientist_dir)List all notes (returns metadata dicts)
read_note(scientist_dir, index)Read a specific note by 1-based index
search_notes(scientist_dir, query)Search notes by keyword
get_recent_notes(scientist_dir, n=5)Most recent N notes
read_all_notes(scientist_dir)Concatenated content of all notes
format_notes_list(entries)Format note entries for terminal display

Note format

---
creator: agent-1
created: 2026-03-15T10:30:00+00:00
---

# Vectorization insights

Content goes here...

The title is extracted from the first # heading in the body.

Skills

Module: scientist.commons.skills

Skills are directories in .scientist/public/skills/ with a SKILL.md descriptor.

Functions

FunctionDescription
list_skills(scientist_dir)List all skills
read_skill(skill_dir)Read a skill's SKILL.md and list files
format_skills_list(skills)Format skills for terminal display
get_skill_tree(skill_dir)Formatted file tree of a skill directory

Skill structure

skills/
└── profiler/
    ├── SKILL.md       # Description and usage
    └── profile.py     # Implementation