> For the complete documentation index, see [llms.txt](https://dnotifier.gitbook.io/product-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dnotifier.gitbook.io/product-docs/workflows-and-agents/step-graph.md).

# Parallel agents & shared state

Use **`runAgent`** for one agent at a time, **`runAgents`** to run several in parallel, and **`ctx.state`** to share data across the workflow.

With **`observability: true`**, runs appear in the dashboard so you can inspect each step and the shared state over time.

## Run agents in sequence

{% tabs %}
{% tab title="JavaScript" %}

```js
async function entry(ctx) {
  ctx.state.stage = "draft";
  const draft = await ctx.runAgent("content-creator");

  ctx.state.stage = "edit";
  const edited = await ctx.runAgent("clarity-editor", {
    input: { content: draft.content },
  });

  return { article: edited.content };
}
```

{% endtab %}

{% tab title="Python" %}

```python
async def entry(ctx):
    ctx.state["stage"] = "draft"
    draft = await ctx.run_agent("content-creator")

    ctx.state["stage"] = "edit"
    edited = await ctx.run_agent(
        "clarity-editor",
        input_data={"content": draft.get("content")},
    )

    return {"article": edited.get("content")}
```

{% endtab %}

{% tab title="Dart" %}

```dart
Future<dynamic> entry(WorkflowContext ctx) async {
  ctx.state['stage'] = 'draft';
  final draft = await ctx.runAgent('content-creator');

  ctx.state['stage'] = 'edit';
  final edited = await ctx.runAgent(
    'clarity-editor',
    input: {'content': (draft as Map)['content']},
  );

  return {'article': (edited as Map)['content']};
}
```

{% endtab %}
{% endtabs %}

## Run agents in parallel

`runAgents` / `run_agents` starts multiple agents at once and returns their results when all finish.

{% tabs %}
{% tab title="JavaScript" %}

```js
const planning = await ctx.runAgents([
  { name: "outline-planner" },
  { name: "keyword-researcher" },
]);
const [outline, keywords] = planning.results;

const creation = await ctx.runAgent("content-creator", {
  input: {
    outline: outline.outline,
    keywords: keywords.keywords,
  },
});

const refinement = await ctx.runAgents([
  { name: "clarity-editor", input: { content: creation.content } },
  { name: "seo-optimizer", input: { content: creation.content } },
  { name: "social-snippets", input: { content: creation.content } },
]);
const [clarity, seo, social] = refinement.results;

return {
  article: clarity.content,
  seo,
  social: social.snippets,
};
```

{% endtab %}

{% tab title="Python" %}

```python
planning = await ctx.run_agents([
    {"name": "outline-planner"},
    {"name": "keyword-researcher"},
])
outline, keywords = planning["results"]

creation = await ctx.run_agent(
    "content-creator",
    input_data={
        "outline": outline.get("outline"),
        "keywords": keywords.get("keywords"),
    },
)

refinement = await ctx.run_agents([
    {"name": "clarity-editor", "input": {"content": creation.get("content")}},
    {"name": "seo-optimizer", "input": {"content": creation.get("content")}},
    {"name": "social-snippets", "input": {"content": creation.get("content")}},
])
clarity, seo, social = refinement["results"]

return {
    "article": clarity.get("content"),
    "seo": seo,
    "social": social.get("snippets"),
}
```

{% endtab %}

{% tab title="Dart" %}

```dart
final planning = await ctx.runAgents([
  {'name': 'outline-planner'},
  {'name': 'keyword-researcher'},
]);
final outline = (planning['results'] as List)[0] as Map;
final keywords = (planning['results'] as List)[1] as Map;

final creation = await ctx.runAgent(
  'content-creator',
  input: {
    'outline': outline['outline'],
    'keywords': keywords['keywords'],
  },
);

final refinement = await ctx.runAgents([
  {
    'name': 'clarity-editor',
    'input': {'content': (creation as Map)['content']},
  },
  {
    'name': 'seo-optimizer',
    'input': {'content': creation['content']},
  },
  {
    'name': 'social-snippets',
    'input': {'content': creation['content']},
  },
]);
final results = refinement['results'] as List;
final clarity = results[0] as Map;
final seo = results[1];
final social = results[2] as Map;

return {
  'article': clarity['content'],
  'seo': seo,
  'social': social['snippets'],
};
```

{% endtab %}
{% endtabs %}

You can also pass an object/map so results are keyed by name:

{% tabs %}
{% tab title="JavaScript" %}

```js
const { results } = await ctx.runAgents({
  outline: { name: "outline-planner" },
  keywords: { name: "keyword-researcher" },
});
console.log(results.outline, results.keywords);
```

{% endtab %}

{% tab title="Python" %}

```python
out = await ctx.run_agents({
    "outline": {"name": "outline-planner"},
    "keywords": {"name": "keyword-researcher"},
})
print(out["results"]["outline"], out["results"]["keywords"])
```

{% endtab %}

{% tab title="Dart" %}

```dart
final out = await ctx.runAgents({
  'outline': {'name': 'outline-planner'},
  'keywords': {'name': 'keyword-researcher'},
});
final results = out['results'] as Map;
print(results['outline']);
print(results['keywords']);
```

{% endtab %}
{% endtabs %}

## Share data with `ctx.state`

`ctx.state` is a mutable object shared for the whole run. Set values in one agent and read them in another (or in `entry`).

{% tabs %}
{% tab title="JavaScript" %}

```js
async run(ctx) {
  ctx.state.stage = "research";
  ctx.state.notes = [];
  const hits = await ctx.search({
    query: ctx.input.topic,
    label: "Research",
  });
  ctx.state.notes.push(...(hits?.results ?? []));
  return { notes: ctx.state.notes };
}
```

{% endtab %}

{% tab title="Python" %}

```python
async def run(ctx):
    ctx.state["stage"] = "research"
    ctx.state["notes"] = []
    hits = await ctx.search(query=ctx.input["topic"], label="Research")
    notes = hits.get("results") if isinstance(hits, dict) else []
    ctx.state["notes"].extend(notes or [])
    return {"notes": ctx.state["notes"]}
```

{% endtab %}

{% tab title="Dart" %}

```dart
Future<dynamic> run(WorkflowContext ctx) async {
  ctx.state['stage'] = 'research';
  ctx.state['notes'] = <dynamic>[];
  final hits = await ctx.search(query: ctx.input['topic'], label: 'Research');
  final notes = hits is Map ? (hits['results'] as List? ?? []) : [];
  (ctx.state['notes'] as List).addAll(notes);
  return {'notes': ctx.state['notes']};
}
```

{% endtab %}
{% endtabs %}

After `runWorkflow`, the final state is also on the return value:

```js
const run = await notifier.runWorkflow({ workflow, input });
console.log(run.state);
```

## Related

* [WorkflowContext reference](/product-docs/workflows-and-agents/workflow-context.md)
* [Workflow observability](/product-docs/workflows-and-agents/observability.md)
* [Build a workflow](/product-docs/workflows-and-agents/build-workflow.md)
