Agent execution engine with Plan-Observe-Replan (POR) and plugin architecture
Project description
AxcAgentEngine
Agent execution engine with Plan-Observe-Replan, tool calling, and a plugin system
Quick Start · Features · Architecture · Plugins · Examples
Most agent frameworks rely on a ReAct loop: think, call a tool, observe, repeat. As tasks grow more complex, plain ReAct tends to drift.
AxcAgentEngine adds POR (Plan-Observe-Replan) on top of ReAct: the agent produces a structured plan, schedules steps by dependency, observes outcomes, and replans when needed.
🚀 Quick Start
pip install axc-agent-engine
# Pin the current 2.0 release
pip install axc-agent-engine==2.0
# Optional extras
pip install "axc-agent-engine[api]"
pip install "axc-agent-engine[workflow]"
pip install "axc-agent-engine[api,knowledge,workflow]"
Requires Python 3.11 or newer.
from axc_agent_engine import Engine, LLMConfig, PluginRegistry
from axc_agent_engine.plugins.builtin import BuiltinToolsPlugin
registry = PluginRegistry()
registry.register(BuiltinToolsPlugin)
engine = Engine(
default_llm=LLMConfig(
base_url="https://api.openai.com/v1",
api_key="sk-xxx",
model="gpt-4o",
),
plugin_registry=registry,
)
agent = engine.load_agent("./agents/my_agent.yaml")
# Non-streaming
result = await agent.chat("Analyze last month's sales data")
# Streaming
async for event in agent.stream("Build a REST API for user management"):
if event.type == "stream_delta":
print(event.content, end="")
elif event.type == "tool_call":
print(f"\n[Tool: {event.tool_name}]")
elif event.type == "plan_created":
print(f"\n[Plan: {event.content}, {len(event.steps)} steps]")
✨ Features
- POR planning - structured plans, dependency scheduling, replanning; routes via
auto/react_only/por_first - ReAct executor - standard think / call / observe loop
- Plugin system - built-in spec registry, YAML-driven loading; optional capabilities live in plugins
- Tool protocol - every tool returns
ToolOutput; read-only runs concurrent, write serial; safe function-name mapping - Durable workflow -
WorkflowRuntime+CheckpointStore+ Agent resume API; Burr is optional viaaxc-agent-engine[workflow] - OpenAI compatible - provider protocol + OpenAI-compatible HTTP client and API subset
- Memory & knowledge - four-layer memory (KV, dedup, decay, graph hooks) + semantic chunking + vector/BM25 hybrid retrieval
- MCP - stdio, JSON-RPC HTTP, official SDK transports
- Human-in-the-loop - approval queue and
ask_humantool - Sidecar suite - multi-agent, simulation, eval, cost, failure mining, trace distillation
Full capability matrix
| Capability | Implementation |
|---|---|
| ReAct loop | Executor |
| POR planning | auto / react_only / por_first |
| Durable workflow | MemoryWorkflowRuntime by default; optional BurrWorkflowRuntime via axc-agent-engine[workflow] |
| Plugin system | spec registry + YAML-driven loading |
| LLM provider | provider protocol + OpenAI-compatible HTTP |
| Parallel tools | read concurrent, write serial |
| Tool output | enforced ToolOutput |
| Tool name mapping | provider-side model-safe mapping |
| Context compression | built-in compress plugin |
| Memory | four layers + KV fallback + dedup + decay + graph hooks |
| Knowledge | semantic chunking + embeddings + BM25/vector + optional rerank |
| MCP | stdio / JSON-RPC HTTP / official SDK |
| Human approval | approval queue + ask_human |
| Sidecar | multi-agent / simulation / eval / cost / failure mining / distillation |
| API server | OpenAI Chat Completions compatible subset |
📦 Docs
| Architecture | Engine and plugin boundaries |
| API | HTTP API subset notes |
| Plugin development | Build your own plugin |
| Security model | Capabilities, risk, workspace |
| Examples | 7 end-to-end demos |
| Contributing / Security / Changelog / LICENSE | Apache-2.0 |
Agent YAML
name: "data-analyst"
description: "Data analysis assistant"
runtime:
max_rounds: 50
thinking: "auto"
workspace: "/tmp/agent-workspace"
allowed_capabilities:
- "file_read"
- "file_write"
- "http_request"
system_prompt: |
You are a data analysis assistant...
plugins:
builtin_tools:
enabled: true
load: ["get_time", "file_read", "file_write", "http_request", "result_read"]
defer: ["file_write", "http_request"]
knowledge:
enabled: true
sources: ["./docs"]
namespace: "default"
embedding:
base_url: "https://api.openai.com/v1"
api_key: "sk-xxx"
model: "text-embedding-3-small"
memory:
enabled: true
namespace: "default"
scope_keys: ["tenant_id", "user_id", "agent_name"]
sensitive_policy: "redact"
compress:
enabled: true
summary_after_rounds: 8
risk_guard:
enabled: true
Notes:
- Plugin registration is explicit host code via
PluginRegistry. Agent YAML only enables and configures already-registered plugins. builtin_toolsloads onlyget_timewhenloadis omitted; other built-ins must be explicitly enabled.- Tools with a non-empty capability are denied by default; list them in
runtime.allowed_capabilities. - File and command tools require
runtime.workspaceby default. - LLM configuration is provided in code, not in Agent YAML.
Provider Configuration
Engine accepts an LLMConfig or any object implementing the full LLMProvider protocol (model, tool_name_mapping, chat, stream, ask, close).
from axc_agent_engine import ConcurrencyConfig, Engine, LLMConfig
from axc_agent_engine.tools.name_mapping import ToolNameMappingConfig
default_llm = LLMConfig(
base_url="https://api.openai.com/v1",
api_key="sk-xxx",
model="gpt-4o",
timeout=120,
max_concurrent_requests=32,
requests_per_minute=0,
rate_limit_queue_timeout=10,
tool_name_mapping=ToolNameMappingConfig(),
)
engine = Engine(
default_llm=default_llm,
concurrency=ConcurrencyConfig(
max_engine_concurrent_runs=128,
queue_timeout=30,
),
)
Multiple named providers can be registered on engine.provider_registry and selected by name:
engine.provider_registry.register("fast", fast_provider)
agent = engine.load_agent("./agents/my_agent.yaml", default_llm="fast")
Tool-name mapping is the provider's job. Internal tool names are encoded to model-safe function names before the LLM call and decoded before hooks and tool execution.
API
The HTTP API is an OpenAI Chat Completions compatible subset.
POST /v1/chat/completionsGET /v1/agentsGET /v1/capabilities
Request-level tools and tool_choice are intentionally unsupported. Tools come from Agent YAML and plugins so the engine can enforce capabilities, risk metadata, plugin hooks, workspace policy, and audit events.
Clients should not assume full OpenAI parity; call /v1/capabilities first. See docs/API.md.
Built-in Plugins
Capabilities not required by a basic agent live in plugins. The default Engine.plugin_registry is empty; both built-in and custom plugins must be registered explicitly.
from axc_agent_engine import Engine, LLMConfig, PluginRegistry
from axc_agent_engine.plugins.builtin import BuiltinToolsPlugin, MemoryPlugin
from my_project.plugins import MyCustomPlugin
registry = PluginRegistry()
registry.register_many([BuiltinToolsPlugin, MemoryPlugin, MyCustomPlugin])
engine = Engine(default_llm=llm, plugin_registry=registry)
| Plugin | Purpose |
|---|---|
builtin_tools |
Basic tools and artifact paging |
knowledge |
Ingestion, semantic chunking, hybrid retrieval, citations, rerank |
memory |
Memory, governance tools, sensitive-data policy, decay, TTL |
output_format |
Final output contracts, validation, repair, audit |
graph |
Entity/relation graph search and CRUD |
skill |
Load skills, run scripts through sandbox |
mcp |
MCP server tool loading and guarding |
hooks |
Declarative LLM/tool hook rules |
compress |
Context window management, summaries, recall, file restore |
human_in_the_loop |
Human approval and ask_human |
risk_guard |
Dynamic tool risk classification |
safety |
Input sanitization, prompt-injection checks, PII masking |
tracing |
Trace/span collection, audit mode, query tools |
reflexion |
End-of-round and end-of-run self-reflection |
repetition_guard |
Repeated tool / response / result detection |
cost_statistics |
Token and tool-call accounting |
collaboration |
Agent-to-agent calls and host orchestration entry |
swarm |
Lightweight parallel fan-out |
Sidecar Capabilities
Sidecars live under axc_agent_engine.sidecar and are invoked explicitly by the host, not part of the agent's core execution path. See axc_agent_engine/sidecar/README.md.
| Package | Purpose |
|---|---|
sidecar.multi_agent |
Multi-agent sessions, schedulers, stop conditions, shared context |
sidecar.simulation |
Structured simulation kernel |
sidecar.eval |
Evaluation cases, annotation stores, matcher, runner, reports |
sidecar.agent_selector |
Host-side agent routing and candidate scoring |
sidecar.distiller |
Distill rules, tool preferences, and skill candidates from traces |
sidecar.failure_miner |
Cluster failures and suggest remediation / eval coverage |
sidecar.cost_optimizer |
Cost estimation and optimization findings |
from axc_agent_engine.sidecar import OrchestrationTaskService
from axc_agent_engine.storage.in_memory import InMemoryMessageBus
engine = Engine(default_llm=default_llm, message_bus=InMemoryMessageBus())
red = engine.load_agent("./agents/red.yaml")
blue = engine.load_agent("./agents/blue.yaml")
service = OrchestrationTaskService(
agent_getter=engine.get_agent,
agent_lister=engine.list_agents,
dispatcher=engine._dispatcher,
utility_llm=utility_llm,
)
task = await service.run_task(
agent_names=[red.name, blue.name],
mode="redblue",
topic="Plugin marketplace security tabletop",
max_rounds=3,
)
Runtime Flow
Load time
flowchart TD
A["Application creates Engine"] --> B["Inject providers and services"]
B --> C["Engine.load_agent(agent.yaml)"]
C --> D["Parse AgentConfig"]
D --> E["Build PluginContext"]
E --> F["Load enabled plugins"]
F --> G["Plugin.initialize()"]
G --> H["Plugin.get_tools()"]
H --> I["Register ToolDefinition"]
I --> J["Create Agent"]
One agent run
flowchart TD
A["User message"] --> B["Agent.chat() / Agent.stream()"]
B --> C["ExecutionContext"]
C --> D["Executor"]
D --> E["ExecutionRunLifecycle + checkpoints"]
D --> F["ReActKernel"]
F --> G["MessageStore"]
F --> H["Plugin hooks"]
F --> I["LLMCaller"]
I --> J["TransactionRouter"]
J -->|final answer| K["done event"]
J -->|tool calls| L["Tool pipeline"]
L --> G
J -->|plan or por_first| M["PORRunner"]
M --> N["PORGraphRuntime (pydantic_graph)"]
N --> K
Plugin Development
from axc_agent_engine import BasePlugin, ToolDefinition, ToolOutput
from axc_agent_engine.plugins.config_schema import config_field, config_schema
class MyPlugin(BasePlugin):
name = "my_plugin"
display_name = "My Plugin"
priority = 30
phase = "core"
config_schema = config_schema(
"my_plugin",
"My Plugin",
"Configuration for MyPlugin.",
[
config_field(
"api_url",
"接口地址",
"string",
"插件调用的后端接口地址。",
label_en="API URL",
default="http://localhost:5000",
required=True,
),
],
display_name_en="My Plugin",
)
def initialize(self, config: dict, ctx) -> None:
super().initialize(config, ctx)
self.api_url = config["api_url"]
def get_tools(self) -> list[ToolDefinition]:
return [ToolDefinition(
name="my_tool",
description="Does something useful",
parameters={"type": "object", "properties": {"query": {"type": "string"}}},
execute=self._execute,
)]
async def pre_tool_call(self, exec_ctx, tool_name, arguments):
return True, arguments
async def _execute(self, args: dict, context: dict) -> ToolOutput:
return ToolOutput.text(f"Result for {args['query']}")
Plugins must inherit BasePlugin, declare config_schema, and return tools as ToolDefinition instances. ToolRegistry does not accept dicts.
Hosts can inspect registered plugin schemas through registry.list_plugin_config_schemas() or registry.get_plugin_config_schema("my_plugin"). The schema is for UI, templates, default display, and optional validation; YAML extra keys are still passed to initialize(config, ctx).
plugins:
my_plugin:
enabled: true
api_url: "http://localhost:5000"
CLI
export AXC_LLM_BASE_URL="https://api.openai.com/v1"
export AXC_LLM_API_KEY="sk-xxx"
export AXC_LLM_MODEL="gpt-4o"
axc chat --agent ./agents/my_agent.yaml
axc serve --agent ./agents/my_agent.yaml --port 8000
axc --log-level DEBUG --json-logs chat --agent ./agents/my_agent.yaml
CLI logging flags are global and must be placed before the subcommand.
Design Decisions
- Engine core = Executor + ReActKernel + LLMCaller. Reads Agent YAML, calls LLM providers, runs the ReAct loop, emits events/results.
- POR state transitions live in pydantic-graph.
PORGraphRuntimeowns the plan/step/observe/replan loop; services execute work and persist checkpoints. - Workflow resume is a runtime boundary.
MemoryWorkflowRuntimeis the default;BurrWorkflowRuntimeis selected when the optional workflow dependency is installed. - Plugins are the runtime extension boundary. Knowledge, memory, graph, MCP, output repair, skills belong in plugins.
- Orchestration is sidecar. Multi-agent sessions, simulation, mode adapters are host-driven.
- Evaluation is sidecar. EvalRunner, stores, matchers, reports are host-driven test framework pieces.
- Registration is not loading. The spec registry is the full plugin table; agents load only YAML-enabled plugins.
- Plugin schema is mandatory. A plugin without
config_schemacannot be registered. - Tools come from plugins. The engine core embeds no business tools.
- Tools must return
ToolOutput. Non-ToolOutputreturns are rejected. - Tool definitions must be
ToolDefinition. No dicts. - Business protocols stay out. Internal APIs, private DBs, company auth belong in private plugins.
- LLM config lives in code. Agent YAML describes runtime limits, capabilities, and plugins.
- The API is a subset. Request-level
tools,tool_choice,n > 1are rejected.
Tests
python3 -m pytest -q
python3 -m pytest --cov --cov-report=term-missing:skip-covered -q
The release gate requires at least 90% total coverage.
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
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 axc_agent_engine-2.1.0.tar.gz.
File metadata
- Download URL: axc_agent_engine-2.1.0.tar.gz
- Upload date:
- Size: 412.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7273db8c0f552386db69956165c74f008d2c22b6d60fac30db4bfafcd98a913
|
|
| MD5 |
7a50eb5420f0843874c040a8d0940aad
|
|
| BLAKE2b-256 |
2fefce3bf3a9fc687d4e4c884a28581afcfeb08b0d043a50dd11bfb4f256395b
|
File details
Details for the file axc_agent_engine-2.1.0-py3-none-any.whl.
File metadata
- Download URL: axc_agent_engine-2.1.0-py3-none-any.whl
- Upload date:
- Size: 368.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6aa5e9045ac9d2f9f064c3db09bc1ff80b512822ac4082e5609f1c3e1433fd35
|
|
| MD5 |
bcb6b2b9fd01a2e9a7b9084cc7d1b955
|
|
| BLAKE2b-256 |
3008371ee9723c735a3dce57ebc589beac1bb89747b13b7f6f0f22985d85e97f
|