Skip to main content
Research Factories group repeated Managed Research work under one operating unit. A Factory owns policies and status; each Effort describes one recurring or long-lived line of work inside that Factory. Use Factories when a team wants repeated runs around the same goal: benchmark improvement, dataset generation, weekly repo maintenance, or an open research campaign. Use a one-off run when the work is isolated.

Concepts

ObjectRole
FactoryThe parent operating unit. It has a name, lifecycle status, budget policy, cap policy, publication policy, and metadata.
EffortOne line of work under a Factory and project. It carries a hypothesis or topic, status, optional recurrence policy, latest run/report links, and decision notes.
RunOne execution launched from an Effort. Runs still use the normal Managed Research run lifecycle and evidence model.

Python SDK

from synth_ai import SynthClient

client = SynthClient()
control = client.research.control()

project = control.projects.create_runnable({"name": "DungeonGrid policy work"})

factory = control.factories.create(
    {
        "name": "DungeonGrid optimizer factory",
        "description": "Repeated benchmark-improvement runs with reviewable evidence.",
        "cap_policy": {"max_active_runs": 1},
        "publication_policy": {"visibility": "private"},
    }
)

effort = control.efforts.create(
    {
        "factory_id": factory.factory_id,
        "project_id": project.project_id,
        "name": "Improve heldout score",
        "hypothesis_or_topic": "Search for policy changes that improve heldout DungeonGrid score.",
        "effort_type": "optimizer",
    }
)

run = control.efforts.launch(
    effort.effort_id,
    "Run the configured optimizer lane, require heldout scoring, and publish a final report.",
    host_kind="daytona",
    work_mode="directed_effort",
    providers=[{"provider": "openrouter"}],
    runbook="lite",
)

result = run.wait(timeout=60 * 60, poll_interval=15)
print(result.public_state.value)

status = control.factories.status(factory.factory_id)
print(status.efforts_by_status)

MCP

Ask your MCP client to create and inspect a Factory:
Create a Research Factory named "DungeonGrid optimizer factory", create an optimizer Effort under my project, then show the Factory status projection.
Useful tools:
  • smr_create_factory
  • smr_list_factories
  • smr_get_factory
  • smr_patch_factory
  • smr_create_effort
  • smr_list_factory_efforts
  • smr_get_effort
  • smr_get_factory_status
  • smr_wake_due_factory_efforts

Scheduling

An Effort can wait for a later wake time or recurrence policy:
control.efforts.schedule(
    effort.effort_id,
    next_wake_at="2026-06-16T13:00:00Z",
    recurrence_policy={"cadence": "daily", "max_active_runs": 1},
    launch_request={
        "host_kind": "daytona",
        "work_mode": "directed_effort",
        "providers": [{"provider": "openrouter"}],
        "runbook": "lite",
    },
)

due = control.factories.wake_due(factory.factory_id, dry_run=True)
print(due.evaluated_count)
Use dry_run=True to inspect which Efforts would launch before starting runs. Use allow_overlap=False unless overlapping runs are intentional.

Status and decisions

Factory status is the read model for operators. It includes project summaries, Efforts by status, latest runs, latest reports and work products, open decisions, paused items, wake metadata, publication summaries, and cost summaries. When an Effort needs human input, mark it explicitly:
control.efforts.mark_ready_for_review(
    effort.effort_id,
    note="Heldout score improved; review report before publishing.",
)

open_decisions = control.factories.list_open_decisions(factory.factory_id)
Resolve decisions only after the operator has acted:
control.efforts.resolve_decision(
    effort.effort_id,
    note="Approved next run with the same heldout gate.",
)

Privacy

Factory and Effort details are private Managed Research control-plane context by default. Public Open Research pages should show only intentionally published run, report, or work-product summaries.

Next