Skip to content

Python API

Use the Python API to embed ChemGraph in notebooks, services, or larger workflows. The standard agent is asynchronous.

Run a single-agent query

import asyncio

from chemgraph.agent.llm_agent import ChemGraph


async def main():
    agent = ChemGraph(
        model_name="gpt-4o-mini",
        workflow_type="single_agent",
        return_option="last_message",
    )
    result = await agent.run(
        "Build water from SMILES O, optimize it with EMT, and report the energy."
    )
    print(result.content)


asyncio.run(main())

In an async notebook or application, call await agent.run(...) directly instead of starting a second event loop.

Token usage and per-call limits

ChemGraph.run honors an explicit positive integer config["recursion_limit"] before the agent's configured limit and does not mutate your configuration. The default remains 200 graph steps; this is not a model-call or token budget.

result = await agent.run(query, config={"recursion_limit": 350})
print(agent.last_usage)     # current user turn, including approvals and retries
print(agent.session_usage)  # all recorded turns in this session

if agent.session_store is not None:
    session_counts = agent.session_store.get_usage(agent.session_id)
    turn_counts = agent.session_store.get_usage(
        agent.session_id, turn_id=agent.last_usage["turn_id"]
    )

Usage mappings include input_tokens, output_tokens, total_tokens, cached_input_tokens, reasoning_output_tokens, call_count, incomplete_calls, partial, and per-field unreported_counts. Counts are known subtotals; unknown fields are None. Cache/reasoning details are subsets of input/output. A store query with no recorded usage has recorded=False; old conversation history is not retroactively counted.

Session summaries also expose history_unaccounted. When true, partial stays true regardless of complete counts for new turns. No calls or tokens are estimated for the missing history. On database upgrade, existing transcripts without the coverage marker are conservatively flagged, even if some usage rows exist; newly created sessions start with complete coverage. Individual new-turn summaries are independent of that historical gap.

MainAgentSession exposes the same last_usage and session_usage properties; its turn result also includes usage. resume() and retry() retain the original usage turn. run_turn returns usage and accepts an optional session_store; without it, it does not write a session database. Setting enable_memory=False on ChemGraph retains in-memory counters only.

Accounting runs independently of on_event. Model-finished events retain their existing payloads and include a call_id and provider token_counts when available. Available usage remains readable after a workflow fails. Python callers decide how to display it; the CLI prints numeric counters locally, without making another model call.

Return values

Use return_option="last_message" for the final message object or return_option="state" for the full graph state. Full state is useful when an application must inspect tool calls, messages, or structured output.

agent = ChemGraph(return_option="state")
state = await agent.run("What is the SMILES string for aspirin?")

Threads and checkpoints

Pass graph configuration when a workflow needs a stable thread identity:

config = {"configurable": {"thread_id": "my-run-001"}}
result = await agent.run("Continue the analysis.", config=config)

Choose thread IDs that are unique in your application and do not contain credentials or sensitive user data.

Main-agent sessions

The checkpointed main_agent performs direct workspace and chemistry work and optionally delegates to configured specialists. Drive it with MainAgentSession rather than ChemGraph.run():

from deepagents.backends import LocalShellBackend
from chemgraph.agent.llm_agent import ChemGraph, PromptConfig
from chemgraph.agent.main_session import MainAgentSession

agent = ChemGraph(
    workflow_type="main_agent",
    backend=LocalShellBackend(root_dir="/path/to/checkout", env={}),
    skill_dirs=["/path/to/shared-skills"],  # Host directories
    skills=["/workspace/site-skills/"],   # Backend-relative sources
    subagent_names=["single_agent", "deep_agent"],
    prompts=PromptConfig(main_agent="Complete requests directly or delegate as needed."),
    configuration_id="my-workspace-v1",   # Non-secret caller-owned configuration ID
)
session = MainAgentSession(
    agent.workflow,
    session_store=agent.session_store,
    session_metadata=agent.main_agent_metadata,
)
result = await session.run("Inspect the available skills and prepare a calculation.")
if result.status == "waiting_for_user":
    # Inspect result.interrupts before deciding. This example rejects the action.
    result = await session.resume({"decisions": [{"type": "reject"}]})

For cross-process persistence, supply a durable checkpointer to ChemGraph, retain session.thread_id, reconstruct the same configuration, and call await session.restore() before answering pending interrupts. The default checkpointer is process-local. MainAgentSession inherits the graph's recursion limit; an explicit limit must match supplied session metadata.

The saved configuration includes the canonical artifact directory selected by log_dir, CHEMGRAPH_LOG_DIR, or the generated default, plus a non-secret model endpoint descriptor. CLI restoration uses these saved values even if the current directory, environment, or endpoint settings changed. Credentials are resolved again; they are never saved in the configuration. Endpoint URLs containing user information, query parameters, or fragments require Python reconstruction and a matching configuration_id. Python callers must reconstruct with the original artifact directory and model routing as well as the original graph options. Schema-1 through schema-3 checkpoints require a new session; transcripts remain readable. Relative chemistry outputs use a session-scoped directory, while caller-owned shell backends retain their caller-supplied environments.

backend=None provides checkpoint-backed file tools and no shell. Local shell backends mount host files at /workspace/; shell commands use host paths and are not confined to the workspace. Registry tools always execute on the host, even without a workspace. tool_registry=None enables the default catalog; ToolRegistry([]) disables it. Interactive catalog tools require human_supervised=True or explicit inclusion. Review policies cover direct workspace mutations, execution, and selected registry operations.

subagent_names=None exposes the non-test built-in worker catalog with no active workers; a list restricts it and [] disables discovery. agent_registry supplies a custom AgentRegistry. Discovery returns metadata without constructing graphs; load_agents constructs selected workers and makes them available to task for one turn. Selection survives pauses, retries, and restart, then clears on completion. Aliases canonicalize and duplicate catalog names are rejected. subagent_options provides per-worker constructor overrides. Named deep_agent workers inherit the main workspace and skills, with Python overrides taking precedence. Their tool catalogs are separately configured. Registry workers inherit the parent checkpointer and mandatory parent reviews; additional worker reviews are merged without weakening the parent policy. PromptConfig.system, formatter, and report configure the lazy single_agent worker; PromptConfig.main_agent configures the parent.

Durable restoration validates both checkpoint and readable-session identities. Custom backends, tool implementations, and opaque worker settings require a matching configuration_id when creating another session instance. Change this ID whenever caller-owned behavior or environment changes; do not use credentials as IDs. Environment values and object representations are not serialized into configuration metadata. Raw graphs can supply configuration_id directly to MainAgentSession. Without an identity they can continue only in their existing session instance. A custom ID enables Python restoration, not CLI reconstruction.

CLI-created backend metadata records the environment allowlist policy; approved variables are read from the current process when recreating the backend. Python backends with arbitrary environment settings remain caller-owned. Unpersisted worker prompts and options also require Python reconstruction. Old graph checkpoints cannot resume after this upgrade; their transcripts remain readable.

Built-in tool description edits remain compatible with stored sessions. Changes to selected tools, approval policies, or built-in behavior can require a new session; restoration rejects incompatible graphs before executing an action. Maintainers must bump BUILTIN_TOOL_CATALOG_VERSION for built-in schema, behavior, or safety changes and WORKSPACE_REVIEW_POLICY_VERSION for approval semantics changes. Worker constructor paths, defaults, requirements, and AgentSpec.compatibility_version are part of restoration identity. Bump the worker compatibility version or BUILTIN_AGENT_CATALOG_VERSION when changing worker behavior. Custom registry metadata and constructor options retain full identity checks and their caller-owned configuration requirements.

The optional on_event(event_name, payload) callback reports direct activity and tagged worker activity; worker events include subagent_name. Skills and active registry selections remain private to each agent, while checkpoint-backed files may be exchanged with workers.

Workspace Deep Agent

The workspace workflow is separately callable through ChemGraph.run():

import os

from deepagents.backends import LocalShellBackend

from chemgraph.agent.llm_agent import ChemGraph

agent = ChemGraph(
    model_name="claude-sonnet-4-20250514",
    workflow_type="deep_agent",
    deepagent_backend=LocalShellBackend(
        root_dir="/path/to/checkout",
        virtual_mode=True,
        env={
            name: os.environ[name]
            for name in ("PATH", "PYTHONPATH", "VIRTUAL_ENV", "CONDA_PREFIX", "TMPDIR")
            if name in os.environ
        },
        inherit_env=False,
    ),
    deepagent_skills=[
        "/workspace/shared-skills/",
        "/workspace/.agents/skills/",
    ],
)
result = await agent.run(
    "Review the test failures.",
    config={"configurable": {"thread_id": "workspace-review"}},
)

A virtual LocalShellBackend is exposed to the agent at /workspace, so file tool path /workspace/src/example.py maps directly to /path/to/checkout/src/example.py. The generated Deep Agents system context also supplies that host path for shell commands. Custom backends, existing composite routes, and non-virtual local execution are preserved when adding the bundled skill route.

Bundled skills load without configuration. For supported local workspaces, personal ~/.chemgraph/skills/ and project .agents/skills/ directories are also discovered. deepagent_skill_dirs=["../shared-skills/"] mounts host collections independently of the workspace. deepagent_skills adds ordered POSIX backend-relative sources; later sources override matching names. Set deepagent_discover_skills=False to use only bundled and explicit sources. On construct_deep_agent_graph, the same options are skills= and discover_skills=. See skills for custom backends, source precedence, and session behavior.

With StateBackend, only explicitly supplied state-backed skills need files seeded in graph state. Bundled resources work without seeding. Standalone and supervisor-hosted Deep Agents share DEFAULT_DEEPAGENT_PROMPT, which permits using attached chemistry tools. Tool availability is configured separately; the built-in supervisor workspace worker has no chemistry tools attached. PromptConfig.deepagent=None selects the shared default; a supplied string, including an empty string, is preserved.

The default approval policy interrupts before shell commands and file mutations. Without a human_input_handler, run() raises HumanInputRequired; its payload retains the structured Deep Agents action requests and must be resumed with matching structured decisions. Its interrupts tuple retains every pending request and its LangGraph interrupt ID; callers must resume concurrent requests with an exact mapping from those IDs to responses. question and payload continue to describe the first request for compatibility. A configured handler may use the legacy handler(question) signature or handler(question, payload) when it needs the raw structured request; both synchronous and asynchronous handlers are supported. Setting deepagent_auto_approve=True removes this boundary and should be limited to an externally isolated, explicitly trusted workspace.

Handlers can reject an action with feedback using the existing decision format:

response = {"decisions": [{"type": "reject", "message": "Use EMT instead of MACE."}]}

The rejected tool is not executed; its feedback is returned to the model, and revised tool calls follow the normal approval policy. Supply one decision per action in request order. The CLI builds this response from typed guidance; Enter at a CLI review approves only that action, not future actions.

The same constructor can be composed as a worker:

from chemgraph.graphs.deep_agent import construct_deep_agent_graph
from chemgraph.registry import AgentRegistry

standalone_graph = construct_deep_agent_graph(model, backend=backend)
worker = AgentRegistry().as_subagent(
    "deepagent",
    llm=model,
    backend=backend,
    skills=["/workspace/.agents/skills/"],
)

as_subagent() compiles the graph with checkpointer=None so it inherits its parent checkpoint. The registry returns the canonical worker name deep_agent, even when requested through the deepagent alias; use worker["name"] when composing task calls. construct_main_agent_graph(enable_deepagent=True, ...) uses this same workflow under the stable subagent name deepagent.

Caller-owned asynchronous checkpointers, including AsyncSqliteSaver, are supported by await agent.run(...) and await agent.apersist_run_state(config). Keep the saver open on the same event loop for the run and any resumes. The synchronous state methods remain available for synchronous checkpointers.

Custom tools

ChemGraph can be extended with compatible LangChain tools. Keep tools narrow, validate their inputs, and avoid exposing destructive filesystem or shell operations to untrusted prompts. Optional dependencies in application code should be imported lazily so a core installation can still load.

Human supervision

Supported workflows can pause for human input when supervision is enabled. Design non-interactive applications so they do not unexpectedly wait forever, and treat an approval boundary as part of the application's security model.

Artifacts

Set CHEMGRAPH_LOG_DIR before constructing the agent to choose the parent directory for session artifacts:

import os

os.environ["CHEMGRAPH_LOG_DIR"] = "/absolute/path/to/runs"

API stability

ChemGraph is evolving and does not currently re-export ChemGraph from the package root. Prefer the documented module import, pin a version for deployed applications, and check release notes before upgrading.