Back to blog
Guide
API
Runs
Threads

Runs vs. Threads: When to Use Which

Crewship has two ways to execute your AI crews: stateless runs and stateful threads. How they work, how they differ, and when to pick one over the other.

Valentina
10 min read
Share:
Runs vs. Threads: When to Use Which

Crewship has two ways to execute a deployed crew: the Run API and the Thread API. If you've used the platform at all, you've already used runs. Threads are newer and less obvious, and the question we keep hearing is: when should I use which?

Runs are for one-shot tasks. Threads are for conversations. That's the short version. The rest of this post is the long version.

Runs: the default

A run is a single execution of your crew. You send input, it does its thing, you get output. Each run gets its own container, shares nothing with other runs, and the environment gets torn down when it finishes.

crewship invoke --input '{"topic": "AI agents in logistics"}'

Or via the API:

curl -X POST https://api.crewship.dev/v1/runs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"deployment_id": "dep_abc123", "input": {"topic": "AI agents in logistics"}}'

You get back a run ID. The run moves through pending, running, then lands on succeeded, failed, or canceled. You can stream events in real time to watch your agents work, or just poll for the result.

No setup, no cleanup, no state to manage.

When runs make sense

Runs are the right choice when your crew's job starts and ends in a single execution. "Write a blog post about X" — input in, content out, done. "Analyze this dataset and generate a report" — same deal. Batch operations where you process a list of items independently, background jobs triggered by a webhook or a cron schedule, pipeline steps where your crew is one stage in a larger workflow. The work is self-contained every time.

The common pattern: the crew doesn't need to ask clarifying questions, doesn't need to remember what happened last time. It takes input and produces output, and that's the whole interaction.

What runs don't do

Runs are stateless. When a run finishes, it's gone. If you kick off another run with the same deployment, it has zero context about the previous one. It doesn't know you ran it five minutes ago with slightly different input. It doesn't know the output last time was almost right but needed one small tweak.

For a lot of workloads, that's exactly what you want. But for some, it's a problem.

Threads: when you need memory

A thread is a persistent conversation context scoped to a deployment. You create it once, then run your crew inside it as many times as you need. Each run receives the thread's current state, and when the run finishes, it can update that state. The next run picks up where the last one left off.

# Create a thread
crewship thread create dep_abc123

# Run inside it
crewship invoke dep_abc123 --thread thr_xyz789 -i '{"message": "Research AI agents in healthcare"}'

# Follow up — the crew remembers the first message
crewship invoke dep_abc123 --thread thr_xyz789 -i '{"message": "Now focus on diagnostic applications"}'

Via the API:

# Create the thread
curl -X POST https://api.crewship.dev/v1/threads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"deployment_id": "dep_abc123"}'

# Run inside it
curl -X POST https://api.crewship.dev/v1/threads/thr_xyz789/runs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": {"message": "Research AI agents in healthcare"}}'

The thread tracks state through a values field — a JSON object that your crew can read and write. After each run, Crewship saves a checkpoint, so you have a full history of how the state changed over time.

Thread lifecycle

Threads have their own status:

  • idle — ready for a new run
  • busy — a run is executing; new run requests get a 409 until the current one finishes
  • interrupted — the run was interrupted
  • error — the last run failed, but the thread still accepts new runs

Only one run can execute in a thread at a time. That's by design — it keeps state consistent. No risk of two concurrent runs stepping on each other's updates.

When threads make sense

Threads are useful anywhere that context carries over between interactions. The most obvious case is a conversational agent — a chatbot or support assistant where the user sends a message, the crew responds, the user follows up, and so on. The thread holds the full conversation history so each response accounts for everything that came before.

But it goes beyond chat. Iterative refinement is another good fit: "Generate a marketing plan." Then: "Make the budget section more detailed." Then: "Add a timeline." Each run builds on the previous output instead of starting from scratch. You could try to stuff the entire prior result into the next run's input, but that gets unwieldy fast.

Multi-step workflows with human approval also work well with threads. The crew does research, presents findings, and waits. The user reviews, gives direction, and kicks off the next run. The thread holds the intermediate state between steps without you having to manage it yourself.

Checkpoints

Every time a run inside a thread finishes, Crewship saves a checkpoint — a snapshot of the thread's state at that moment.

curl https://api.crewship.dev/v1/threads/thr_xyz789/history \
  -H "Authorization: Bearer YOUR_API_KEY"

This gives you an audit trail. It's also useful for debugging: if the crew's response went sideways on turn 5, you can look at the checkpoint from turn 4 to see what state it was working with.

Thread metadata

Threads support a metadata field that's separate from the conversation state. Use it for things like user IDs, channels, tags — anything you want to filter or search by later:

curl -X POST https://api.crewship.dev/v1/threads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "deployment_id": "dep_abc123",
    "metadata": {"user_id": "user_42", "channel": "web", "priority": "high"}
  }'

This matters once you have hundreds of threads across different users and use cases.

Side by side

RunsThreads
StateNone — each run is isolatedPersistent across runs via values
Lifecyclependingrunning → terminal stateidlebusyidle (repeats)
ConcurrencyUnlimited parallel runsOne run at a time per thread
HistoryIndividual run recordsCheckpoints after each run
SetupNone — just create a runCreate thread first, then run inside it
CleanupAutomaticManual — delete thread when done
Cost profilePredictable per runGrows with conversation length

How other platforms handle this

If you've used other agent platforms, you'll recognize the split. OpenAI's Assistants API had Threads and Runs — literally the same names. They've since replaced it with the Responses API and Conversations, but it's the same idea: a stateless execution primitive and an optional persistence layer.

LangGraph does the same thing. Call graph.invoke() for a one-shot execution. Pass a thread_id and you get persistence, checkpoints, and the ability to resume from any point. CrewAI has kickoff() for one-shot execution and a separate conversational mode for multi-turn interactions.

The pattern across all of these: runs and threads are independent concerns. Runs are always the execution primitive. Threads optionally chain runs together with shared state. You don't need threads until you do.

So which one?

One question gets you most of the way there: does the crew need to remember anything from previous executions?

If no, use a run. Adding a thread just adds complexity you don't need.

If yes, use a thread. Trying to fake statefulness by jamming prior context into run inputs gets ugly fast, and you lose checkpoints, history, and the concurrency guarantees that threads give you.

A few more signals that point toward threads: your users will interact with the crew multiple times per session, the crew's output depends on conversation history rather than just the current input, or you need an audit trail of how state evolved over time.

And toward runs: the crew can do its job with a single set of inputs, you want to fire off many executions in parallel, or the workload is triggered by an automated system rather than a person sending messages.

Getting started with threads

If you've been using runs and want to try threads, there's nothing to change about your deployment. Threads work with any deployed crew — CrewAI, LangGraph Python, or LangGraph JS.

Create a thread, run inside it, and your crew receives the thread state. Update the state from within your crew, and the next run picks up where you left off.

# Create a thread
crewship thread create dep_abc123 --metadata '{"user_id": "demo"}'

# First turn
crewship invoke dep_abc123 --thread thr_xyz789 -i '{"message": "What can you help me with?"}'

# Second turn — crew receives context from the first
crewship invoke dep_abc123 --thread thr_xyz789 -i '{"message": "Tell me more about option 2"}'

# Check the history
crewship thread history thr_xyz789

Full API reference is in the threads documentation.


Questions about runs, threads, or anything else? Check the docs or reach out at mail@crewship.dev.