Creator: Mahesh Vaijainthymala Krishnamoorthy (Mahesh Vaikri)
An agent can be clever and still be unreliable. MAPLE gives that agent a typed message, a bounded tool, a resource budget, a durable checkpoint, and an explainable result — so the host can decide what happens next.
Multi-agent systems fail quietly.
An agent sends a message to a peer that never started. The send returns success. A queue fills; the framework buffers past its own limit until the process is killed. A security control is configured, the transport does not implement it, and nothing says so. Each of these looks identical to working software right up until it does not.
The failures are not exotic. They are the ordinary consequence of a system that reports what it attempted instead of what happened.
MAPLE is a Python multi-agent runtime and protocol layer. Its organising principle is that every outcome is legible: a message is delivered, refused with a typed error, or counted as undeliverable. There is no fourth case where it simply vanishes.
from maple import Agent, Config, Message
agent = Agent(Config(agent_id="planner", broker_url="memory://team"))
agent.start()
result = agent.send(Message(message_type="PLAN", receiver="coder", payload={"task": "..."}))
if result.is_err():
error = result.unwrap_err()
if error["errorType"] == "QUEUE_FULL":
... # the consumer is behind - shed, slow down, or retry
elif error["errorType"] == "UNROUTABLE":
... # nobody is listening on that nameFailure arrives as a value with a machine-readable type, not as a silence you discover in production. That same discipline runs through the rest of the system: tools are approved before they execute, resources are negotiated against a budget, runs checkpoint so they can resume, and a control that cannot be enforced refuses rather than pretending.
An agent begins with a goal. A dependable system begins with boundaries. MAPLE connects those boundaries around the loop that turns intent into work:
goal → model decision → validated tool → typed result → event/checkpoint
Every transition is inspectable. A host can approve a tool before it runs, return a failure as data, checkpoint a run, redact an event, or cancel a cooperative operation. Credentials, deployment, tenancy, and external side effects remain the host's — MAPLE makes the local contract explicit and testable.
That loop is useful on a laptop, inside a service, or as a building block in a larger platform.
- Protocol — typed messages, resource requirements, priorities, errors, and interoperability formats.
- Runtime — brokers, discovery, state, leases, security, scheduling, and observability.
- Autonomy SDK — ReAct agents, tools, model providers, workflows, memory, retrieval, approvals, handoffs, sessions, and evaluations.
You can use the protocol without the autonomy layer, and the runtime without either. The layers are separable on purpose.
2.1.0 was a correctness release. Every change closed a case where a control silently did nothing. Kept here because the migration notes still apply to anyone coming from 2.0.0.
| Change | Before | Now |
|---|---|---|
| Backpressure | A "bounded" queue overflowed into an unbounded list; 25,000 sends were all accepted with 15,000 held in memory | send() refuses with QUEUE_FULL and the caller can act |
| Undeliverable messages | Delivered to zero handlers and discarded with no error, counter, or log | Counted in get_statistics(), logged once per receiver, and available through a dead-letter hook |
| Transport security | A nats:// agent accepted require_links and enforced nothing |
Construction refuses a transport that cannot honor the configured controls |
| Broker configuration | import maple pinned the broker to a throwaway config; every later SecurityConfig was discarded |
Configuration reaches the broker; importing has no side effects |
| Scoped runtimes | Every broker_url returned the same process-wide bus, so separate tenants saw each other in discovery |
memory://tenant-a and memory://tenant-b are isolated buses and registries |
| Transport contract | The two brokers shared no interface and had drifted six methods apart | A Broker protocol with a conformance suite every transport must pass |
2.1.0 changes behavior. These are corrections to controls that did nothing,
not new restrictions — but code that relied on the previous behavior will
notice. A send() that always succeeded can now fail, and agents on different
broker_url values no longer share a bus. See the
changelog for the migration notes.
2.2.0 continues what 2.1.0 started. 2.1.0 fixed controls that silently did nothing; 2.2.0 fixes the same class one layer out — numbers that never left the process, shutdowns that discarded work while returning cleanly, configuration accepted and ignored, waits that never woke, and a transport that had never once connected.
One item changes what MAPLE is: FileBroker makes it multi-process on one
host.
| Change | What it means |
|---|---|
| Metrics leave the process (ADR-162) | Thirteen components implement get_statistics() and none of it escaped. maple.monitoring renders Prometheus text using only the standard library — no new dependency. MAPLE renders; the host serves. |
| Two clocks, and a drain on shutdown (ADR-163) | Durations no longer ride the wall clock, so an NTP step cannot skip a circuit-breaker window or hold one open. stop() drains queued work to a deadline and reports what it could not. |
| Configuration is validated (ADR-164) | Nine invalid configurations were accepted silently. It also closed a hole in ADR-157: NATS:// and nats:/ fell back to the in-process broker, so a typo defeated a fail-closed guarantee. |
| Waits end when their subject does (ADR-165) | A thread parked in receive() never woke after stop(). It now returns AGENT_STOPPED. |
| Delivery on a signal (ADR-166) | The broker's 10 ms poll cost latency on every hop — p50 4.8 ms, now 0.33 ms. |
FileBroker (ADR-167) |
MAPLE is no longer a single-process runtime. A file-backed transport carries messages between processes on one host, and it passes the broker conformance suite unchanged. |
Through 2.1.0, MAPLE was an embedded single-process runtime. Scopes isolate agent groups within one process, and the in-memory broker does not survive a restart — both still true of the in-memory broker in 2.2.0.
FileBroker adds multi-process on one host — and nothing more.
It is not a network transport, its latency is a poll interval rather than a
signal, and it makes no durability, ordering, or exactly-once claim. Multi-host
operation still needs a transport that satisfies the broker contract, which the
bundled NATS adapter does not meet.
pip install maple-oss gives you this.
It now provides every contract member, but that is not conformance. The remaining gap is capability-shaped rather than method-shaped: a NATS publish is fire-and-forget, so backpressure, undeliverable reporting and routability have to be built over it. Until they are, NATS stays out of the conformance factories — because passing that suite is the only thing that counts as conforming, and the suite names what is missing so the day it conforms is a deliberate edit rather than a silent one.
Two of those members refuse rather than pretend: a separation-of-duties policy this transport cannot enforce is rejected instead of accepted and ignored, and a dead-letter hook it can never call is accepted with a warning saying so.
The following is the shipped and tested local surface. Preview means a bounded or opt-in contract that is ready for local integration and still requires the host to supply policy, persistence, credentials, or operations. It does not claim a hosted control plane or automatic distributed behavior.
- Synchronous and asynchronous ReAct-style agent loops with bounded reasoning and token budgets.
- OpenAI-compatible and Anthropic provider adapters, capability routing, native async completion when the optional SDK supports it, and explicit compatibility fallback otherwise.
- JSON Schema and typed tool contracts with bounded arguments/results, approval-by-default execution, structured-output repair, guardrails, cancellation tokens, timeouts, and concurrency limits.
- Agent handoffs and manager-style agent-as-tool delegation with bounded allowlisted context, local durable ownership records, optional local replay, and authenticated remote handoff payload delivery.
- Trusted local execution for host-supplied handlers. This is a bounded execution policy, not an untrusted-code sandbox.
- Markdown code-block extraction and content-addressed artifacts. Extracted code is data and is never executed by MAPLE.
- Typed workflow nodes, conditional routing, bounded fan-out/fan-in, deterministic joins, composable sub-workflows, and per-node retry/backoff.
- Durable in-memory and file-backed agent-run checkpoints with stable run IDs, bounded history, CAS versions, fencing leases, and cooperative cancellation.
- Durable approval and human-input records, schema-validated responses, bounded follow-up rounds, actor authorization hooks, notification outboxes, and fail-closed resume behavior.
- Bounded working and episodic memory, fail-closed summary archiving, keyword search, conversation sessions, compaction, file persistence, and data-only version-based forking.
- Loopback RunServer/RunClient control-plane routes for bounded local workflow, agent, task, approval, interaction, event, handoff, and checkpoint operations with per-route authorization scopes.
- Deterministic document chunking, source references, synchronous and asynchronous cursor ingestion, checkpointed ingestion, host-owned embedding providers, lexical retrieval, caller-supplied-vector retrieval, and an optional provider-neutral reranker.
- FileLexicalRetriever and FileVectorRetriever with bounded versioned JSON, atomic replacement, restart rebuilds, local instance refresh, and cross-process mutation fencing.
- Read-only retrieval/citation tools with bounded queries, top-k limits, source URI/title citations, output limits, and fail-closed backend/provider errors.
- Bounded sequenced event streams, cursor expiry, cooperative waiter cancellation, subscriber isolation, recursive credential redaction, provider correlation, local trace spans, journals, exporters, forwarding, and source-sequence deduplication.
- Deterministic evaluation for golden outputs, schemas, tool trajectories, retrieval/citation metrics, grounded-answer overlap, trace structure, judge calibration, and redacted bounded reports.
- In-memory and file-backed task queues with bounded admission, ownership-safe lifecycle transitions, terminal history, at-least-once restart recovery, and a trusted one-shot local task worker.
- Authenticated remote task queue control for bounded submit, inspect, claim, start, heartbeat, complete, fail, cancel, retry, and statistics operations.
- Result[T, E], resource lifecycles, custom resource dimensions, priority routing, in-memory/file leases, discovery, health monitoring, retry/backoff, circuit breakers, and cryptographic link/security layers.
MAPLE keeps the operational primitives close to the agent contract. A host can start with an in-memory broker and move individual boundaries to files or an injected transport as its deployment grows:
- Typed failure handling —
Result[T, E]keeps validation, capacity, transport, and provider failures explicit and composable. - Resource-aware messaging — CPU, memory, bandwidth, time, tokens, and caller-defined numeric dimensions travel with a request for negotiation.
- Link and identity security — cryptographic links, authentication, authorization scopes, token revocation, and recursive event redaction make security decisions visible at the boundary.
- Reliability primitives — priority queues, health-aware discovery, bounded retries, exponential backoff, circuit breakers, leases, and ownership-checked task transitions.
- State and coordination — local state stores, consistency policies, file-backed checkpoints, journals, cursor stores, and fencing leases support restartable single-host workflows.
MAPLE fails closed for surfaces that are not native local runtime features.
Optional integrations such as Redis state operations, mutual-TLS
authentication, and OAuth2 currently return typed NOT_IMPLEMENTED results
where they are not configured. JWT, API-key, and certificate paths remain
separate local mechanisms. TrustedLocalExecutor accepts explicitly trusted
host handlers; it is not an untrusted-code sandbox.
ResourceManager distinguishes renewable capacity from consumable budgets.
LeaseManager and FileLeaseManager provide bounded holds with fencing tokens;
expiry is the recovery mechanism when a local holder crashes. TaskQueue and
FileTaskQueue preserve ownership checks and explicit at-least-once restart
semantics. Nothing in these local primitives silently upgrades an external
side effect to exactly once.
The Python package contains eleven adapter modules under maple/adapters. Each
one is a translation boundary, not a claim that an external runtime is bundled
inside MAPLE:
| Adapter | Module | Boundary |
|---|---|---|
| Google A2A | a2a_adapter.py |
Message and agent-card translation through the optional HTTP adapter. |
| MCP | mcp_adapter.py |
Bounded Streamable HTTP initialization, live tool discovery, JSON-RPC calls, namespacing, and approval-aware registration. |
| FIPA ACL | fipa_acl_adapter.py |
Performative and message translation. |
| AutoGen | autogen_adapter.py |
Compatibility wrapper for participants and group chats. |
| CrewAI | crewai_adapter.py |
Compatibility wrapper for crews and tasks. |
| LangGraph | langgraph_adapter.py |
MAPLE-backed graph state and node integration. |
| OpenAI SDK | openai_sdk_adapter.py |
OpenAI-compatible message and tool format translation. |
| IBM ACP | acp_adapter.py |
ACP message and capability translation. |
| S2.dev | s2_adapter.py |
Optional durable stream and state backend integration. |
The sibling n8n integration provides TypeScript nodes and sample workflows. Each adapter is a deliberately narrow translation boundary: it maps messages, tools, or capabilities into MAPLE contracts while leaving identity, credentials, and deployment with the host.
The core package supports Python 3.8+.
python -m pip install maple-oss
python -m pip install "maple-oss[llm]" # OpenAI and Anthropic SDKs
python -m pip install "maple-oss[security]" # JWT and SSH crypto extras
python -m pip install "maple-oss[performance]" # optional speedups
python -m pip install "maple-oss[adapters]" # HTTP adapter dependency
python -m pip install "maple-oss[s2]" # S2.dev integration
python -m pip install "maple-oss[dev]" # test and quality toolingFor a source checkout:
git clone https://github.com/maheshvaikri-code/maple-oss.git
cd maple-oss
python -m pip install -e ".[dev,llm,security,adapters]"Verify the installed package and offline doctor:
python -c "import maple; print(maple.__version__)"
python -m maple.cli doctor --jsonfrom maple import Agent, Config, Message, Priority, Result
agent = Agent(Config(agent_id="worker", broker_url="memory://local"))
agent.start()
sent: Result = agent.send(
Message(
message_type="TASK_REQUEST",
receiver="specialist",
priority=Priority.HIGH,
payload={"task": "summarize", "document_id": "doc-42"},
)
)
if sent.is_ok():
print("queued", sent.unwrap())
else:
print("send failed", sent.unwrap_err())
agent.stop()This example uses a small AST parser rather than evaluating model text as Python. Credentials are read from the environment and are not placed in code.
import ast
import operator
import os
from maple import AutonomousAgent, AutonomousConfig, Config, LLMConfig, Result, Tool
_OPS = {ast.Add: operator.add, ast.Mult: operator.mul}
def calculate(expression: str = "") -> Result:
try:
tree = ast.parse(expression, mode="eval").body
if not isinstance(tree, ast.BinOp) or type(tree.op) not in _OPS:
raise ValueError("only addition and multiplication are supported")
if not all(
isinstance(node, ast.Constant) and isinstance(node.value, int)
for node in (tree.left, tree.right)
):
raise ValueError("operands must be integers")
return Result.ok(
{"result": _OPS[type(tree.op)](tree.left.value, tree.right.value)}
)
except (SyntaxError, ValueError, TypeError, OverflowError) as error:
return Result.err({"errorType": "VALIDATION_ERROR", "message": str(error)})
agent = AutonomousAgent(
Config(agent_id="math-agent", broker_url="memory://local"),
AutonomousConfig(
llm=LLMConfig(
provider="openai",
model="gpt-4o-mini",
api_key=os.environ["OPENAI_API_KEY"],
),
max_reasoning_steps=8,
max_total_tokens=8_000,
),
)
agent.register_tool(
Tool(
name="calculator",
description="Calculate a small integer expression.",
parameters={
"type": "object",
"properties": {"expression": {"type": "string"}},
"required": ["expression"],
},
handler=calculate,
)
)Teams are explicit objects. A supervisor can decompose a goal while specialist agents execute bounded work; the orchestrator returns typed per-member results and preserves the host's control over model credentials and side effects.
from maple import AutonomousAgent, AutonomousConfig, Config, LLMConfig
from maple.autonomy.orchestrator import AgentOrchestrator, TeamMember
llm = LLMConfig(
provider="openai",
model="gpt-4o-mini",
api_key=os.environ["OPENAI_API_KEY"],
)
supervisor = AutonomousAgent(
Config(agent_id="supervisor", broker_url="memory://local"),
AutonomousConfig(llm=llm, max_reasoning_steps=6),
)
researcher = AutonomousAgent(
Config(agent_id="researcher", broker_url="memory://local"),
AutonomousConfig(llm=llm, max_reasoning_steps=6),
)
orchestrator = AgentOrchestrator(max_parallel_agents=2)
team_id = orchestrator.form_team(
"review-team",
[
TeamMember(supervisor, role="supervisor", capabilities=["planning"]),
TeamMember(researcher, role="worker", capabilities=["research"]),
],
).unwrap()
result = orchestrator.execute_supervised(team_id, "Review the approved report")For message-level protection, construct the agent with a host-owned
SecurityConfig, establish a bounded link, and attach that link to the message.
State stores expose versioned reads and updates; the broker also supports topic
subscriptions for notifications that do not need request/response semantics.
import os
from maple import Config, Message, Priority, SecurityConfig
from maple import Agent
from maple.state import ConsistencyLevel, StateStore
agent = Agent(
Config(
agent_id="secure-worker",
broker_url="memory://local",
security=SecurityConfig(
auth_type="token",
credentials=os.environ["MAPLE_AGENT_TOKEN"],
require_links=True,
),
)
)
link = agent.establish_link("specialist", lifetime_seconds=3_600).unwrap()
secure_message = Message(
message_type="SENSITIVE_DATA",
receiver="specialist",
priority=Priority.HIGH,
payload={"status": "ready"},
).with_link(link)
agent.send_with_link(secure_message, "specialist")
state = StateStore(consistency=ConsistencyLevel.STRONG)
state.set("mission_status", {"phase": "active"}).unwrap()
print(state.get("mission_status").unwrap())The link handshake is a protocol boundary, not a replacement for TLS or host identity management. Hosts remain responsible for secret rotation, trust roots, network exposure, and authorization policy.
JWT support is intentionally fail-closed. MAPLE never invents a signing key:
the host must provide a secret through a secret manager or environment
variable, and the secret must contain at least 32 UTF-8 bytes. A missing or
short secret returns a typed JWT_SECRET_NOT_CONFIGURED result.
import os
from maple.security import AuthenticationConfig, AuthenticationManager
auth = AuthenticationManager(
AuthenticationConfig(jwt_secret=os.environ["MAPLE_JWT_SECRET"])
)
issued = auth.generate_jwt(
principal="worker-agent",
permissions=["tasks:read", "tasks:write"],
expires_in=3_600,
)
if issued.is_ok():
verified = auth.verify_token(issued.unwrap())
print(verified.unwrap().principal)The configuration object keeps policy visible at the call site. Hosts should rotate secrets outside the process, avoid logging tokens, and treat revocation as a deny decision: a revoked token cannot be authenticated again.
MAPLE uses Result[T, E] at important boundaries. A caller can compose work
without turning expected validation, capacity, or transport failures into
unstructured exceptions. Resource requests carry the budget alongside the
message so the receiving host can accept, reject, or negotiate it.
from maple import Message, Priority, Result
from maple.resources import ResourceRange, ResourceRequest, TimeConstraint
request = ResourceRequest(
compute=ResourceRange(min=2, preferred=4, max=8),
memory=ResourceRange(min="2GB", preferred="4GB", max="8GB"),
time=TimeConstraint(timeout="120s"),
priority="HIGH",
)
message = Message(
message_type="INDEX_DOCUMENTS",
receiver="retrieval-worker",
priority=Priority.HIGH,
payload={"document_id": "doc-42", "resources": request.to_dict()},
)
def accept(result: Result) -> str:
if result.is_err():
return f"rejected: {result.unwrap_err()}"
return f"accepted: {result.unwrap()}"Use the in-memory queue while shaping a system, then move to
FileTaskQueue or a host-owned remote control plane when restart behavior is
part of the deployment contract. Queue transitions are ownership-checked and
bounded; restart recovery is explicitly at-least-once.
from maple.task_management import TaskPriority, TaskQueue
queue = TaskQueue(max_queue_size=100)
submitted = queue.submit_task(
"summarize",
{"document_id": "doc-42"},
priority=TaskPriority.HIGH,
)
task_id = submitted.unwrap()
queue.assign_task(task_id, "worker-agent").unwrap()
queue.start_task(task_id, "worker-agent").unwrap()
queue.complete_task(task_id, "worker-agent", {"status": "done"}).unwrap()For stateful branches, a Workflow gives each node a read-only context and
commits bounded JSON state at node boundaries. The same model supports
conditional routing, bounded fan-out/fan-in, retry policies, checkpoint
stores, and explicit resume.
from maple import Workflow
workflow = Workflow("normalize-document")
workflow.add_node(
"normalize",
lambda context: {"text": context.state["text"].strip().lower()},
).unwrap()
workflow.set_entry_point("normalize").unwrap()
workflow.add_edge("normalize", None).unwrap()
run = workflow.run({"text": " Hello MAPLE "}).unwrap()
assert run.status == "completed"Working memory is bounded admission, not an unbounded transcript. Retrieval keeps source references attached to results so an application can decide how to cite or display them. Embedding generation and corpus authorization remain host-owned.
from maple import Document, InMemoryLexicalRetriever, SourceRef
from maple.autonomy import WorkingMemory
memory = WorkingMemory(max_tokens=2_048)
memory.add("mission", "The worker is indexing the approved corpus.")
retriever = InMemoryLexicalRetriever()
retriever.add_document(
Document(
document_id="doc-42",
text="MAPLE keeps source references with retrieval results.",
source=SourceRef(uri="https://example.test/doc-42", title="MAPLE note"),
)
).unwrap()
hits = retriever.search("source references", top_k=3).unwrap()
print(hits[0].chunk.source.uri)For restartable local search, replace the in-memory retriever with
FileLexicalRetriever or FileVectorRetriever. Both use bounded versioned
JSON, atomic replacement, and local fencing. A vector retriever accepts
caller-supplied embeddings; it does not call a model or a managed vector
service on your behalf.
Conversation sessions store JSON-safe turns with optimistic versions and data-only forking. Event streams retain a bounded, redacted window and expose cursor-based reads. Code-block extraction creates content-addressed artifacts; it never executes model-produced Python, shell, browser, or computer-use code.
from maple import EventStream, InMemorySessionStore, SessionMessage
sessions = InMemorySessionStore()
session = sessions.create("case-42").unwrap()
session = sessions.append(
session.session_id,
SessionMessage(role="user", content="Summarize the approved report."),
expected_version=session.version,
).unwrap()
events = EventStream(max_events=100)
events.publish(
"session.message.accepted",
{"session_id": session.session_id, "status": "stored"},
run_id="run-42",
).unwrap()
batch = events.read(limit=10).unwrap()
print(batch.events[0].event_type)The durable variants (FileSessionStore, FileEventJournal, and the local
run/checkpoint stores) are suitable for one host or a shared local filesystem.
They do not claim distributed consensus, exactly-once external effects, or
automatic background scheduling.
from maple.autonomy import InMemoryArtifactStore, extract_code_blocks, materialize_code_block
model_text = "```python\nprint('stored as data')\n```"
blocks = extract_code_blocks(model_text).unwrap()
store = InMemoryArtifactStore()
for block in blocks:
artifact = materialize_code_block(store, block).unwrap()
print(artifact.artifact_id, artifact.size)The artifact boundary validates sizes, names, UTF-8 bytes, and SHA-256 identity. It does not run Python, shell, browser, or computer-use code.
MAPLE is intentionally layered so a host can adopt the smallest useful surface first:
maple/
├── core/ Message, Result[T, E], serialization, and type contracts
├── agent/ Agent lifecycle, configuration, handlers, and routing
├── autonomy/ ReAct loops, tools, memory, retrieval, runs, events, and workflows
├── broker/ In-memory and optional broker-backed message delivery
├── discovery/ Agent registry, capabilities, health, and failure detection
├── resources/ Resource ranges, allocation, negotiation, and local leases
├── security/ Authentication, authorization, cryptographic links, and redaction
├── state/ State stores, consistency, and synchronization
├── task_management/ Bounded queues, scheduling, workers, and result collection
└── adapters/ A2A, MCP, FIPA ACL, ACP, S2.dev, and ecosystem translations
The autonomy loop sits above the protocol/runtime layer:
goal
│
▼
model provider ──► validated tool ──► typed Result[T, E]
│ │
└────────────── event + checkpoint ◄───┘
│
▼
host decision
This shape makes the important transitions inspectable. A tool can be approved before execution, a failure can be returned as data, a run can be checkpointed before a later side effect, and an event can be redacted before it reaches a subscriber or exporter.
MAPLE supplies local contracts. The application or platform hosting MAPLE supplies the environment around them:
| Host responsibility | MAPLE's local contribution |
|---|---|
| Secret storage and rotation | Explicit authentication configuration and token lifecycle |
| Model credentials and provider choice | Provider interfaces, capability checks, and typed failures |
| Authorization policy and tenancy | Scoped local control-plane routes and host policy hooks |
| External side effects | Approval, bounded tools, cancellation signals, and durable records |
| Deployment, TLS, and network exposure | Loopback transport with bounded requests and responses |
| Distributed coordination | Local file fencing, version checks, and clear at-least-once boundaries |
| Untrusted-code isolation | Trusted host handlers only; MAPLE is not a sandbox |
Keeping this boundary visible is part of using MAPLE correctly. Local durability is useful without pretending to be a hosted service, and an adapter is useful without silently changing who owns identity or side effects.
Two rules follow from the same principle — a configuration MAPLE cannot honor is reported, never quietly substituted (ADR-157):
- A
nats://ors2://broker_urlwhose driver is not installed raisesBrokerUnavailableErrorat agent construction. It does not fall back to the in-memory broker, because an agent that reports successful sends into a process-local bus is worse than one that fails to start. require_links=Trueis enforced. If the broker cannot build a link manager, link-enforced sends raiseSecurityErrorrather than proceeding unenforced — a security control that cannot run refuses.
import maple has no global side effects; it does not construct an agent or a
broker.
The repository also contains a separate TypeScript integration for visual workflows. It provides MAPLE Agent, MAPLE Coordinator, and MAPLE Resource Manager nodes plus sample workflows. The integration is not part of the Python wheel or source distribution and has its own Node/npm validation loop.
cd n8n-integration
npm install
npm run validateThe node package submits work to a host; it does not provide hosted MAPLE, credential storage, tenancy, or deployment by itself. See the integration README for node fields and sample workflows.
| Node | Purpose |
|---|---|
| MAPLE Agent | Submit bounded agent work to a configured MAPLE host. |
| MAPLE Coordinator | Orchestrate workflow steps and collect typed results. |
| MAPLE Resource Manager | Surface resource-aware allocation in a visual flow. |
The included workflows are starting points for research, content, and customer-service automations. They remain host integrations: credentials, network policy, deployment, and external effects are configured outside the Python runtime.
- Examples - small core and autonomy examples.
- External demo package - interactive demos; not included in the core wheel or sdist.
- n8n integration - TypeScript nodes and sample workflows; not included in the Python distribution.
- Launch materials - local launch/demo helpers and their publication boundary.
python -m pytest tests/ -q
python -m pytest tests/security/ -q
python -m pytest tests/autonomy/ -q
python -m pytest tests/task_management/ -q
python -m pytest tests/broker/ -q
python -m pytest tests/ --cov=maple --cov-report=term-missing
python -m black --check maple
python -m isort --check-only maple
python -m flake8 maple/ --max-line-length=88
python -m compileall -q maple
python -m maple.cli doctor --jsonThe full suite is the release gate. Focused suites are useful while iterating on a boundary, but nothing ships on a subset.
On the 2.1.0 tag the release run completed with 2,040 passed, 1 skipped
at 79% statement coverage. On main at the time of writing: 2,348 passed,
2 skipped at 80% statement coverage, both from the same run.
Both are dated measurements rather than standing claims — they move with every commit, so re-run the suite on your exact checkout rather than trusting a number in a README. Nothing written here authorizes publication.
One caveat stated rather than hidden: a small group of autonomy tests that start
a real loopback HTTP server flake intermittently under full-suite load on
Windows (ConnectionAbortedError), a different one each run. It reproduces on
an unmodified tree, passes in isolation, and is tracked as its own defect — so a
run of main may show one such failure that is not a regression in what you
changed.
The offline doctor (python -m maple.cli doctor --json) checks core,
evaluation, events, execution, interop, retrieval, server, and session
readiness without making a network request.
QA and review records live under docs/qa/ and docs/reviews/; architecture decisions under docs/adr/.
| Version | State |
|---|---|
| 2.2.0 | Published on PyPI as maple_oss-2.2.0 (2026-09-06) and tagged as a GitHub Release. |
| 2.1.0 | Published on PyPI as maple_oss-2.1.0 (2026-09-02) and tagged as a GitHub Release. The release assets are byte-identical to the PyPI artifacts, verified by SHA-256, and a fresh-virtualenv install was checked before the tag was announced. |
| 2.0.0 | Published on PyPI as maple_oss-2.0.0 (uploaded 2026-08-31) and tagged as a GitHub Release with source and wheel artifacts. |
Documentation ships with the release, not after it. Every public claim in this README, the docs, and the website is reconciled against the tree before a tag is cut.
If you are on 2.0.0, note that 2.1.0 fixes controls that silently did
nothing — a send() that always succeeded can now fail, and a transport that
accepted security configuration it ignored now refuses. The
changelog carries the migration notes.
Website source lives in website/public_html/ and is not tracked by Git; see
website/README.md for its status and the deployment
checklist.
- Getting started
- API reference
- Protocol specification
- Protocol comparison
- Type system
- Best practices
- Troubleshooting
- Roadmap - the consolidated backlog: what is Native, what is Preview and why, and what is deliberately not built
- Framework parity ledger - per-capability evidence behind each status
- Changelog
- Architecture decisions - 167 records, including the 2.1.0 delivery contract (157, 159), scoping (160), and the broker contract (161)
- 2.0.0 release checklist
maple-oss/
├── maple/ Python runtime and public package
├── docs/ specifications, ADRs, plans, reviews, and QA records
├── tests/ Python regression and contract tests
├── examples/ supported examples
├── demo/ adapter-focused demonstrations
├── demo_package/ external interactive demo package
├── n8n-integration/ companion TypeScript integration
├── website/ held static website assets and website notes
├── pyproject.toml package metadata and optional dependencies
├── VERSION Python package version
└── CHANGELOG.md release history
python -m pip install -e ".[dev,llm,security,adapters]"
python -m pytest tests/ -qKeep behavior changes covered by tests, preserve local versus hosted boundaries, and update the relevant docs, changelog, and review/QA artifact.
MAPLE is Copyright (C) 2025 Mahesh Vaijainthymala Krishnamoorthy (Mahesh Vaikri). The core project is licensed under the GNU Affero General Public License, version 3. Proprietary use may require the separate commercial license.
- GitHub: https://github.com/maheshvaikri-code/maple-oss
- Issues: https://github.com/maheshvaikri-code/maple-oss/issues
- Documentation site: https://mapleagent.org
