AI Agent execution engine — plug a model, get a running agent.
Project description
Mantis
Workflow execution engine.
Feed a workflow graph (nodes + edges), get logical execution. Replaces tangled, spaghetti executors with clean 4-phase pipeline: PARSE → PLAN → EXECUTE → FINALIZE.
from mantis import WorkflowRuntime
runtime = WorkflowRuntime(model=my_llm, tools=[calculate, get_weather])
# Execute a workflow graph
async for event in runtime.execute(workflow_data, input_data):
print(event)
# Or collect the final result
result = await runtime.execute_collect(workflow_data, input_data)
Installation
pip install mantis-engine # core only (httpx)
pip install mantis-engine[search] # + graph-tool-call (>=0.15)
pip install mantis-engine[sandbox] # + Docker sandbox
pip install mantis-engine[state] # + PostgreSQL checkpointing
pip install mantis-engine[all] # everything
How It Works
┌─ Caller (xgen, FastAPI, script) ────────────────────────────────────┐
│ │
│ workflow_data ─── {nodes: [...], edges: [...]} │
│ input_data ────── data for startnode │
│ model ─────────── LLMProvider (for agent nodes) │
│ tools ─────────── [@tool functions] (for agent nodes) │
│ hooks ─────────── [TraceHook, ApprovalHook, ...] │
│ providers ─────── search / sandbox / workflows / state │
│ │
└──────────────────────────┬───────────────────────────────────────────┘
│ runtime.execute(workflow_data, input_data)
▼
╔═══════════════════════════════════════════════════════════════════════════╗
║ WorkflowRuntime ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌─ PARSE ──────────────────────────────────────────────────────────┐ ║
║ │ JSON → NodeInfo + EdgeInfo │ ║
║ │ disabled/disconnected removal · port dependency · runner mapping │ ║
║ └──────────────────────────┬───────────────────────────────────────┘ ║
║ ▼ ║
║ ┌─ PLAN ───────────────────────────────────────────────────────────┐ ║
║ │ Kahn's algorithm topological sort · cycle detection │ ║
║ └──────────────────────────┬───────────────────────────────────────┘ ║
║ ▼ ║
║ ┌─ EXECUTE ────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ for node in execution_order: │ ║
║ │ excluded? → skip │ ║
║ │ PortStore.wire() → inputs │ ║
║ │ Hook.before_node() │ ║
║ │ NodeRunner.run() → result ┌─────────────────────┐ │ ║
║ │ Hook.after_node() │ NodeRunner 6 types │ │ ║
║ │ PortStore.store() │ Default (compute) │ │ ║
║ │ RouteResolver.evaluate() │ Agent (LLM) │ │ ║
║ │ │ Router (N-way) │ │ ║
║ │ yield event │ Provider (config) │ │ ║
║ │ │ IO (in/out) │ │ ║
║ │ │ Bypass (pass) │ │ ║
║ │ └─────────────────────┘ │ ║
║ └──────────────────────────┬───────────────────────────────────────┘ ║
║ ▼ ║
║ ┌─ FINALIZE ───────────────────────────────────────────────────────┐ ║
║ │ collect endnode results · cleanup streams · Hook.on_complete() │ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌─ Internal Modules ───────────────────────────────────────────────┐ ║
║ │ PortStore ────── port-based data wiring between nodes │ ║
║ │ StreamManager ── generator fan-out · BufferedFactory · replay │ ║
║ │ RouteResolver ── router branching · subgraph DFS exclusion │ ║
║ │ ToolRegistry ─── @tool · file · dir · runtime creation · bridge │ ║
║ │ ToolGenerator ── LLM code gen → Docker verify → register │ ║
║ │ ToolTester ──── schema → smoke → pytest 3-level verification │ ║
║ │ WorkflowGenerator ── LLM → WorkflowDef auto-design │ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
║ ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ HOOKS (applied per node) ║
║ before_node → [execute] → after_node → on_complete ║
║ Approval · Trace · custom ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ ADAPTERS ║
║ event_to_sse · langchain_adapter · xgen_adapter · bridge ║
╚═══════════════════════════════════════════════════════════════════════════╝
│
▼
Event Stream / Result
The main loop has zero if/elif for node types. All node-specific logic dispatches to NodeRunner handlers.
Node Runners
| Runner | Handles | What it does |
|---|---|---|
| DefaultRunner | compute, transform | node.execute(**inputs) → store result |
| AgentRunner | LLM agents | PipelineExecutor loop with ToolRegistry, search, sandbox. Streams tokens + tool calls. |
| RouterRunner | conditional branch | Extract routing key → RouteResolver excludes unselected subgraphs via DFS |
| ProviderRunner | model, MCP, config | Create config object → downstream nodes receive via port wiring |
| IORunner | start, end | Start: inject input_data. End: collect results, stream generators. |
| BypassRunner | bypass=True | Pass through input without execution |
Agent Nodes
When an agent node has no node_class, AgentRunner creates a PipelineExecutor with all connected providers:
AgentRunner._run_mantis_agent()
→ PipelineExecutor(model, ToolRegistry, search, sandbox, workflows, state)
→ RESOLVE: LLM call (with tool search filtering)
→ ACT: tool execution (with create_tool + Docker verify if sandbox)
→ OBSERVE: checkpoint
→ loop until done
→ intermediate events (tool_call, tool_result) propagate to workflow stream
Tool Creation & Verification
When sandbox is provided, agent nodes can create tools at runtime:
create_tool → LLM generates @tool code
→ Docker sandbox: syntax check
→ ToolTester: smoke test + pytest
→ pass → ToolRegistry registration → available next iteration
3-level verification:
| Level | Method | Sandbox |
|---|---|---|
| 1 | validate_schema() |
No |
| 2 | smoke_test() |
Optional |
| 3 | run_pytest() |
Required |
Tool Search (graph-tool-call)
from mantis.search import GraphToolManager, GraphToolConfig
manager = GraphToolManager(GraphToolConfig(search_mode="enhanced"))
tools = await manager.aretrieve("cancel order", top_k=5)
plan = manager.plan_workflow("Process refund for order #123")
compressed = manager.compress_result(huge_response, max_chars=4000)
PipelineExecutor (Agent Mode)
For standalone agent execution without a workflow graph:
from mantis import PipelineExecutor, tool
from mantis.providers import ModelClient
@tool(name="calc", description="Calculate", parameters={"expr": {"type": "string"}})
async def calc(expr: str) -> dict:
return {"result": eval(expr)}
executor = PipelineExecutor(
model=ModelClient(model="gpt-4o-mini", api_key="sk-..."),
tools=[calc],
)
result = await executor.run("What is 42 * 17?")
Events
async for event in runtime.execute(workflow_data, input_data):
match event["type"]:
case "workflow_start": ... # node count, edge count
case "execution_plan": ... # order, node_count
case "node_start": ... # node_id, function_id, name, input_keys, timestamp
case "node_complete": ... # node_id, result_keys, output_data, duration_ms
case "node_skip": ... # node_id, reason (excluded/bypass)
case "route_decision": ... # selected_port
case "stream_chunk": ... # node_id, chunk
case "agent_tool_call": ... # node_id, tool_name, args
case "agent_tool_result":... # node_id, tool_name, result
case "workflow_complete": ... # final results
case "workflow_error": ... # error detail
Package Structure
mantis/
├── runtime/ # WorkflowRuntime (core engine)
│ ├── runtime.py # entry point: PARSE → PLAN → EXECUTE → FINALIZE
│ ├── parse.py # workflow JSON → NodeInfo/EdgeInfo
│ ├── plan.py # topological sort (Kahn's algorithm)
│ ├── execute.py # main loop + event emission
│ ├── port_store.py # port-based data wiring
│ ├── stream_manager.py # generator fan-out / BufferedFactory
│ ├── route_resolver.py # router branching + DFS exclusion
│ ├── runners/ # NodeRunner handlers (6 types)
│ └── hooks/ # ExecutionHook (trace, approval)
│
├── executor/ # PipelineExecutor (agent mode)
│ ├── pipeline.py # PREPARE → RESOLVE → ACT → OBSERVE loop
│ ├── flow.py # FlowState for deterministic flows
│ └── phases/ # pluggable phase implementations
│
├── tools/ # @tool decorator, ToolRegistry, bridge
├── search/ # GraphToolManager (graph-tool-call)
├── sandbox/ # DockerSandbox
├── generate/ # ToolGenerator (LLM → code → verify → register)
├── testing/ # ToolTester (schema → smoke → pytest)
├── workflow/ # WorkflowDef, Store, Generator
├── adapters/ # SSE, xgen, LangChain adapters
├── models/ # NodeInfo, PortInfo, EdgeInfo
└── exceptions.py # MantisError hierarchy
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mantis_engine-0.6.0.tar.gz.
File metadata
- Download URL: mantis_engine-0.6.0.tar.gz
- Upload date:
- Size: 101.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a48a9084c1a4bef5c1823b949b01dd2da979ed6ea82a9ee42e19e2cb4847de76
|
|
| MD5 |
fa42fe96c3fe600a46c8fdafcafc1163
|
|
| BLAKE2b-256 |
9e5f37448df61d3a0767e38ccc0245ccb64515d4e69e3d736b769e5b7a7ac848
|
Provenance
The following attestation bundles were made for mantis_engine-0.6.0.tar.gz:
Publisher:
publish.yml on PlateerLab/mantis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mantis_engine-0.6.0.tar.gz -
Subject digest:
a48a9084c1a4bef5c1823b949b01dd2da979ed6ea82a9ee42e19e2cb4847de76 - Sigstore transparency entry: 1195711265
- Sigstore integration time:
-
Permalink:
PlateerLab/mantis@6a7e6c4e9f05ac658de2af5cde3585db2d10f532 -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/PlateerLab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a7e6c4e9f05ac658de2af5cde3585db2d10f532 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mantis_engine-0.6.0-py3-none-any.whl.
File metadata
- Download URL: mantis_engine-0.6.0-py3-none-any.whl
- Upload date:
- Size: 107.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4164325d28b08a7d6517474d3859fbfaf27cd7a0336c6f8d5147d13adbd373b6
|
|
| MD5 |
949fa29236cb97dbb737bc120c719541
|
|
| BLAKE2b-256 |
a20651e274beffa1bc9b7aa6600f50536071cc4646c1115272591da4c1cecbe4
|
Provenance
The following attestation bundles were made for mantis_engine-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on PlateerLab/mantis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mantis_engine-0.6.0-py3-none-any.whl -
Subject digest:
4164325d28b08a7d6517474d3859fbfaf27cd7a0336c6f8d5147d13adbd373b6 - Sigstore transparency entry: 1195711278
- Sigstore integration time:
-
Permalink:
PlateerLab/mantis@6a7e6c4e9f05ac658de2af5cde3585db2d10f532 -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/PlateerLab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a7e6c4e9f05ac658de2af5cde3585db2d10f532 -
Trigger Event:
release
-
Statement type: