How ArcFlow thinks
Before you start
Complete Install and build so from arcflow import Agent, Workflow works in your virtual environment. You do not need API keys for this lesson.
Concept
Most workflow libraries blur two jobs: describing what should happen, and actually running it. ArcFlow keeps them separate on purpose.
In Python you declare agents (who does the work) and register them as ordered steps on a Workflow. You call run(input) when you want execution to start. Python does not call an LLM, loop over steps, or manage retries. Those concerns live in the Rust runtime crate arcflow-core.
When run() fires, the Python SDK serializes your workflow into workflow specification (ArcFlow workflow specification) JSON: agent definitions, step order, run input, and execution config. That payload crosses the native binding boundary into Rust. WorkflowEngine validates the shape, picks a provider, runs step one, then step two, and returns a result object back to Python.
For learning and CI, the default in-process agent backend returns deterministic placeholder text with no network calls. The same declaration path works when you pass provider=OpenAI(model="gpt-4o"); only the execution backend changes, not how you build the workflow.
The mental model in one line: your script declares structure, workflow specification carries it, Rust executes it.
This split is why Python, TypeScript, and the HTTP server can share behavior: they all speak workflow specification to the same engine. You can read more in Workflow specification when you want the schema-level detail. For now, treat workflow specification as the wire format your declarations become at run time.
Minimal example
Save as think_arcflow.py:
Run:
You should see non-empty output on the first line and status=completed steps=1 on the second. Exact wording can vary by runtime version; the integration tests only require len(result.output) > 0.
Verify
Confirm the separation yourself:
- Add a
print("about to run")immediately beforepipeline.run(...). - Add another
print("run finished")after it. - Run the script again.
Execution happens inside run(). If you comment out the run() line, the prints around it never show output because Rust never received a run request.
Optional: import succeeds without touching the network. That is expected. The default path needs no OPENAI_API_KEY.
Next lesson
Anatomy of an agent: what name, role, and instructions mean, and how Agent() validates them.