Skip to main content

SimAgentPlg

English | 简体中文

SimAgentPlg 0.2.1 is a lightweight framework for stateful OpenAI-compatible agents, composable tool handlers, MCP integration, and role-based multi-agent workflows.

Features

  • Stateful BaseAgent with explicit reset() support
  • Immutable, required agent_id owned by each agent
  • Reusable local and external tool handlers
  • Built-in BashHandler for bounded command execution
  • Built-in FinishHandler for explicit completion and Git change reporting
  • AgentManager with per-agent serialization and cross-agent concurrency
  • Linear AgentWorkflow for planner, executor, reviewer, and other roles
  • Optional MCP tools and local skills
  • OpenAI-compatible model configuration

Python 3.12 or newer is required.

Installation

pip install simagentplg

Or install the local project with uv:

uv sync

Configuration

Create a .env file:

CHAT_MODEL=deepseek-v4-flash
SKILL_MODEL=deepseek-v4-flash
MODEL_API_KEY=sk-xxxxxxxx
MODEL_URL=https://api.deepseek.com
LLM_TIMEOUT=60
LLM_TEMPERATURE=0.7

ModelConfig.from_env() reads these variables. A configuration can also be constructed directly and shared by multiple agents:

from simagentplg import ModelConfig

config = ModelConfig(
    model="deepseek-v4-flash",
    api_key="sk-xxxxxxxx",
    base_url="https://api.deepseek.com",
)

Quick Start

Plain Chat

Tool execution is disabled by default. A plain agent keeps conversation history between runtime() calls:

from simagentplg import BaseAgent, ModelConfig

agent = BaseAgent(
    config=ModelConfig.from_env(),
    agent_id="tutor",
    system_prompt="You are a concise Python tutor.",
)

first = await agent.runtime(task="Remember that I prefer Python.")
second = await agent.runtime(task="Which language do I prefer?")

agent.reset()
await agent.shutdown()

Tool Mode

Set enable_tools=True to expose the built-in tools:

import json

from simagentplg import BaseAgent, ModelConfig

agent = BaseAgent(
    config=ModelConfig.from_env(),
    agent_id="developer",
    system_prompt="Complete coding tasks using the available tools.",
    enable_tools=True,
)

result = await agent.runtime(
    task="Create hello.py that prints 'hello'."
)
report = json.loads(result)
print(report["summary"])
print(report["changes"])

await agent.shutdown()

Tool-enabled agents automatically include two sibling handlers:

BaseAgent
  -> BashHandler
       -> bash_run
  -> FinishHandler
       -> run_finish
  -> custom handlers
  -> McpToolHandler

bash_run executes a bounded Bash command. When the task is complete, the model must call run_finish with a non-empty summary. Returning ordinary text does not finish a tool task.

run_finish returns a JSON result and exits the current runtime():

{
  "summary": "Created hello.py",
  "changes": {
    "available": true,
    "repository": "/repo/root",
    "added": ["hello.py"],
    "modified": [],
    "deleted": []
  }
}

The change report compares Git state at the beginning and end of the current task. Existing dirty files are omitted unless the task changes them again. run_finish does not commit, stage, or revert files. Outside a Git repository, the task can still finish with changes.available set to false.

Tool mode stops with an error when:

  • run_finish is not called within max_steps
  • the same tool and arguments are requested three consecutive times

Custom Tool Handlers

MethodToolHandler maps a tool named add to an async method named do_add:

from collections.abc import Mapping
from typing import Any

from simagentplg import (
    BaseAgent,
    MethodToolHandler,
    ModelConfig,
    StepOutcome,
)

ADD_TOOL = {
    "type": "function",
    "function": {
        "name": "add",
        "description": "Add two numbers.",
        "parameters": {
            "type": "object",
            "properties": {
                "left": {"type": "number"},
                "right": {"type": "number"},
            },
            "required": ["left", "right"],
        },
    },
}


class MathHandler(MethodToolHandler):
    def __init__(self) -> None:
        super().__init__((ADD_TOOL,))

    async def do_add(
        self,
        arguments: Mapping[str, Any],
    ) -> StepOutcome:
        return StepOutcome(
            {"value": arguments["left"] + arguments["right"]}
        )


agent = BaseAgent(
    config=ModelConfig.from_env(),
    agent_id="calculator",
    handlers=[MathHandler()],
    enable_tools=True,
)

Handler startup builds one routing table. Duplicate tool names are rejected instead of silently overriding another handler. A custom tool may also return StepOutcome(data=..., should_exit=True) to terminate the task.

Agent Manager

Each agent owns its identity, so registration does not repeat the ID:

from simagentplg import AgentManager, BaseAgent, ModelConfig

config = ModelConfig.from_env()
manager = AgentManager()

manager.register(
    BaseAgent(
        config=config,
        agent_id="writer",
        system_prompt="You write concise release notes.",
    )
)
manager.register(
    BaseAgent(
        config=config,
        agent_id="reviewer",
        system_prompt="You review software changes for risk.",
    )
)

results = await manager.run_many(
    {
        "writer": "Write release notes for version 0.2.1.",
        "reviewer": "Review the release for compatibility risks.",
    }
)

await manager.shutdown()

Calls to the same agent are serialized because they share message history. Calls to different agents can run concurrently. run_many() returns failures as values so one failed agent does not cancel the others.

run_isolated(agent_id, task) resets and executes an agent while holding the same per-agent lock. It is used by workflows to prevent implicit history from leaking between roles or steps.

Role-Based Workflow

AgentWorkflow executes different agent roles as a validated linear pipeline:

from simagentplg import (
    AgentManager,
    AgentWorkflow,
    BaseAgent,
    ModelConfig,
    WorkflowStep,
)

config = ModelConfig.from_env()
manager = AgentManager()
manager.register(
    BaseAgent(
        config=config,
        agent_id="planner",
        system_prompt="Create concise implementation plans.",
    )
)
manager.register(
    BaseAgent(
        config=config,
        agent_id="executor",
        system_prompt="Execute the plan using tools.",
        enable_tools=True,
    )
)
manager.register(
    BaseAgent(
        config=config,
        agent_id="reviewer",
        system_prompt="Review completed work for correctness and risk.",
    )
)

workflow = AgentWorkflow(
    manager,
    [
        WorkflowStep(
            name="plan",
            agent_id="planner",
            prompt="Plan this task:\n{input}",
        ),
        WorkflowStep(
            name="execute",
            agent_id="executor",
            prompt=(
                "Original task:\n{original_task}\n\n"
                "Execute this plan:\n{input}"
            ),
        ),
        WorkflowStep(
            name="review",
            agent_id="reviewer",
            prompt="Review the execution result:\n{execute}",
        ),
    ],
)

result = await workflow.run("Implement user login")
print(result.final_output)
await manager.shutdown()

Workflow templates support:

  • {input}: previous step output, or the original task for the first step
  • {original_task}: the task passed to workflow.run()
  • {step_name}: output from an already completed named step

Unknown and forward references are rejected when the workflow is created. Steps stop at the first failure and WorkflowExecutionError preserves the failed step, original cause, and completed results. Version 0.2.1 supports linear steps only; branching, loops, and automatic retries are not included.

MCP Tools

MCP is opt-in and follows the same handler contract:

from simagentplg import BaseAgent, McpToolHandler, ModelConfig

agent = BaseAgent(
    config=ModelConfig.from_env(),
    agent_id="browser",
    handlers=[McpToolHandler("my_project/mcp_config.json")],
    enable_tools=True,
)

Example MCP configuration:

{
  "playwright": {
    "command": "npx",
    "args": ["@playwright/mcp@latest", "--headless"]
  }
}

Only tools explicitly registered by McpToolHandler are routed to MCP.

Skills

Skills are optional prompt extensions and remain separate from tool handlers:

from pathlib import Path

from simagentplg import BaseAgent, ModelConfig

skills_dir = Path("example/skills")

agent = BaseAgent(
    config=ModelConfig.from_env(),
    agent_id="skilled-agent",
    skills_dir=skills_dir,
    enable_tools=True,
)

SkillManager scans each child directory containing SKILL.md. The routing model selected by SKILL_MODEL chooses the matching skill from its YAML front matter. The skill definition and optional template and sample are then injected into the agent context:

example/skills/
  release_notes/
    SKILL.md
    template.md
    examples/
      sample.md

Skills currently run through the tool-mode lifecycle, so set enable_tools=True and finish with run_finish. See example/06_skill.py for a complete local skill example.

Examples

Runnable examples are available in example/:

uv run python example/01_stateful_chat.py
uv run python example/02_custom_tool.py
uv run python example/03_multi_agent.py
uv run python example/04_mcp_tools.py
uv run python example/05_role_workflow.py
uv run python example/06_skill.py

Public API

BaseAgent(
    config: ModelConfig | None = None,
    *,
    agent_id: str,
    system_prompt: str = REACT_LOOP_PROMPT,
    handlers: Iterable[BaseHandler] | None = None,
    enable_tools: bool = False,
    skills_dir: str | Path | None = None,
    max_steps: int = 20,
)

await agent.runtime(*, task: str) -> str | None
agent.reset(history=None)
await agent.startup()
await agent.shutdown()

The top-level package exports BaseAgent, ModelConfig, StepOutcome, AgentManager, workflow types, handler base classes, BashHandler, FinishHandler, McpToolHandler, and resource defaults.

Changes in 0.2.1

  • Added the sibling FinishHandler and built-in run_finish tool
  • Added per-task Git change reporting
  • Required explicit run_finish completion in tool mode
  • Added protection against three identical consecutive tool calls
  • Raised a clear error when tool mode exhausts max_steps
  • Kept BashHandler focused exclusively on bash_run

Development

uv run python -m unittest discover -s tests -p "test*.py" -v

License

MIT

Download files

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

Source Distribution

simagentplg-0.2.1.tar.gz (103.3 kB view details)

Uploaded Source

Built Distribution

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

simagentplg-0.2.1-py3-none-any.whl (28.5 kB view details)

Uploaded Python 3

File details

Details for the file simagentplg-0.2.1.tar.gz.

File metadata

  • Download URL: simagentplg-0.2.1.tar.gz
  • Upload date:
  • Size: 103.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for simagentplg-0.2.1.tar.gz
Algorithm Hash digest
SHA256 1644e98b99ba123fe29e217091902cf2deaf7e77e2eaf37953fca3fb03190b89
MD5 aad2c42c11ef147f514ab75cd7784e0a
BLAKE2b-256 7dffb6956445c696d33fdf93a06fb2b9d3554c2a35d819bc2def3638df6689ea

See more details on using hashes here.

File details

Details for the file simagentplg-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: simagentplg-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 28.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for simagentplg-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 684417a03f819d29602ece5957d8a12f0fac95a3e2bd20e1e2be8bc21a1d064e
MD5 034aa0271bfee5e5b3465f209f4ebde3
BLAKE2b-256 758fb360f39da3310091a97fa08a0dc0d6d031733c266265bdd0ffd9e18d7bf4

See more details on using hashes here.

Supported by

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