Skip to main content

AI Agent execution engine — plug a model, get a running agent.

Project description

Mantis

Python License: MIT

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mantis_engine-0.6.0.tar.gz (101.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mantis_engine-0.6.0-py3-none-any.whl (107.3 kB view details)

Uploaded Python 3

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

Hashes for mantis_engine-0.6.0.tar.gz
Algorithm Hash digest
SHA256 a48a9084c1a4bef5c1823b949b01dd2da979ed6ea82a9ee42e19e2cb4847de76
MD5 fa42fe96c3fe600a46c8fdafcafc1163
BLAKE2b-256 9e5f37448df61d3a0767e38ccc0245ccb64515d4e69e3d736b769e5b7a7ac848

See more details on using hashes here.

Provenance

The following attestation bundles were made for mantis_engine-0.6.0.tar.gz:

Publisher: publish.yml on PlateerLab/mantis

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for mantis_engine-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4164325d28b08a7d6517474d3859fbfaf27cd7a0336c6f8d5147d13adbd373b6
MD5 949fa29236cb97dbb737bc120c719541
BLAKE2b-256 a20651e274beffa1bc9b7aa6600f50536071cc4646c1115272591da4c1cecbe4

See more details on using hashes here.

Provenance

The following attestation bundles were made for mantis_engine-0.6.0-py3-none-any.whl:

Publisher: publish.yml on PlateerLab/mantis

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page