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.
| Module | Covers |
|---|---|
scientist.config | ScientistConfig and related dataclasses |
scientist.types | Task, Score, ScoreBundle, Attempt, CoordinationNode |
scientist.grader | GraderInterface, BaseGrader, TaskGrader, SubprocessGrader |
scientist.commons | Experiments, 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
| Field | Type | Description |
|---|---|---|
task | TaskConfig | Task definition |
grader | GraderConfig | Grader settings |
agents | AgentConfig | Agent spawning config |
sharing | SharingConfig | Shared state toggles |
workspace | WorkspaceConfig | Workspace layout |
run | RunConfig | Runtime/session flags and run-level stop conditions |
Methods
| Method | Description |
|---|---|
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 workspaceGraderConfig
@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 actionsCoordinationConfig
@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 seedLabsConfig
@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: intCadenceActionConfig
@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 epsilonSharingConfig
@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 conditionsRunStopConfig
@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 experimentsrun.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=30Types
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
| Field | Type | Description |
|---|---|---|
id | str | Unique identifier |
name | str | Display name |
description | str | What to optimize |
metadata | dict[str, Any] | Additional context |
Methods
| Method | Description |
|---|---|
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
| Field | Type | Description |
|---|---|---|
value | float | str | bool | None | The score value |
name | str | Score identifier |
explanation | str | None | Human-readable feedback |
metadata | dict[str, Any] | Extra data |
Methods
| Method | Description |
|---|---|
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:
| String | Float |
|---|---|
"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
| Field | Type | Description |
|---|---|---|
scores | dict[str, Score] | Named scores |
aggregated | float | None | Overall score |
is_public | bool | Whether scores are visible to agents (default: True) |
Methods
| Method | Description |
|---|---|
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
| Field | Type | Description |
|---|---|---|
commit_hash | str | Git commit hash |
agent_id | str | Which agent made this experiment |
title | str | Description from scientist eval -m |
score | float | None | Evaluation score (None if crashed/timeout) |
status | str | One of: improved, baseline, regressed, reverted, crashed, timeout |
parent_hash | str | None | Previous commit hash |
timestamp | str | ISO 8601 timestamp |
feedback | str | Grader feedback |
metadata | dict[str, Any] | Extra data such as budget_class and user_best |
Metadata keys
| Key | Type | Description |
|---|---|---|
budget_class | str | "real", "tune", or "grader_error"; controls ranking and cadence accounting |
user_best | bool | Set to true when the operator marks this experiment as best in the dashboard |
Methods
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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 |
TaskGrader (recommended)
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
| Method | Description |
|---|---|
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
| Attribute | Type | Description |
|---|---|---|
codebase_path | str | Absolute path to agent's worktree (set by framework) |
private_dir | str | Absolute path to .scientist/private/ (set by framework) |
args | dict | Extra arguments from grader.args in config |
Return types
evaluate() can return:
float: automatically wrapped in a ScoreBundleScoreBundle: returned as-is (useself.score()orself.fail())
GraderConfig fields
Configured under grader: in task.yaml.
| Field | Type | Default | Description |
|---|---|---|---|
entrypoint | str | "" | Required. Setuptools-style module.path:ClassName resolved inside the grader venv. |
setup | list[str] | [] | Shell commands run once in the grader venv at scientist launch / scientist check time. Typically a single uv pip install -e ./grader. |
timeout | int | 300 | Eval timeout in seconds (0 = no limit). |
args | dict | {} | Free-form kwargs accessible inside the grader as self.args. |
private | list[str] | [] | Extra files / directories copied to .scientist/private/ (hidden from agents). |
direction | str | "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
| Function | Description |
|---|---|
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()andupdate_node()list_nodes()andavailable_nodes()claim_node(),renew_node(),release_node()attach_attempt()andcomplete_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
| Function | Description |
|---|---|
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
| Kind | Description |
|---|---|
continue_from | Reset an agent worktree to hash on resume and inject instruction |
mark_best | User-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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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