Type: Implementation guide. Normative spec: PROTOCOL_SPEC §12 SDK Implementation Guide.
The APCore class is the recommended high-level entry point for the apcore framework. It provides a single unified interface that manages the Registry, Executor, Config, and optional subsystems (system modules, events, metrics) so that users do not need to wire these components together manually. The client supports zero-config initialization for quick prototyping and full-config initialization for production deployments with system modules, observability, and event handling.
- Provide a single
APCoreclass that wraps Registry, Executor, and optionally Config and MetricsCollector. - Accept configuration via a
Configobject (config). UseConfig.load()to load from a file path. In Rust,APCore::from_path()is kept as a convenience shortcut. - If no Registry or Executor is provided, the client MUST create them automatically with sensible defaults.
- If a Config is provided (via either parameter) with
sys_modules.enabled: true, the client MUST auto-register system modules and configure associated middleware (metrics, observability).
- Support decorator-based module registration (
client.module()), direct registration (client.register()), and auto-discovery (client.discover()). - Support module listing with optional tag and prefix filtering.
- Support module description generation (
client.describe()) for AI/LLM tool discovery.
- Provide synchronous (
call()), asynchronous (call_async()), and streaming (stream()) execution methods that delegate to the underlying Executor. - Provide a non-destructive preflight validation method (
validate()) that runs pipeline steps 1–6 plus optional module-level preflight (7 checks total) without executing the module.
- Support chainable middleware registration:
use(),use_before(),use_after(). - Support middleware removal by identity.
- When system modules with events are enabled, expose
on()/off()methods for subscribing to framework events. - The
eventsproperty MUST expose the underlyingEventEmitter(or returnNone/nullif events are not configured).
- When system modules are enabled, expose
disable()/enable()methods for runtime module toggling. - These methods MUST delegate to
system.control.toggle_featureinternally.
- Python and TypeScript SDKs SHOULD provide module-level functions (e.g.,
apcore.call(),apcore.module()) backed by a default singleton client. - Rust does not provide a global singleton — explicit instances only.
| Mode | Config Required | System Modules | Use Case |
|---|---|---|---|
| Zero-config | No | No | Quick prototyping, tests |
| With config object | Yes (config=) |
If sys_modules.enabled |
Production |
| With defaults | Yes (from_defaults()) |
If configured | No YAML file needed |
| Pre-built components | No | Via provided executor | Advanced / custom setups |
When a Config with sys_modules.enabled: true is provided:
- System modules are registered:
system.health.*,system.manifest.*,system.usage.*,system.control.*. - MetricsCollector is created (if not provided) and
MetricsMiddlewareis added to the executor. - Event handling is configured if
sys_modules.events.enabled: true:EventEmitteris created.PlatformNotifyMiddlewareis added for health monitoring.- Subscribers are instantiated from config (webhook, a2a, custom types).
- Internal
_sys_modules_contexttracks references needed by system module implementations.
| Category | Method | Returns | Description |
|---|---|---|---|
| Registration | module(id, ...) |
Decorator / FunctionModule | Register function as module |
register(module_id, module) |
None | Direct module registration | |
discover() |
int | Auto-discover and register modules | |
| Execution | call(module_id, inputs?, context?) |
dict | Synchronous call |
call_async(module_id, inputs?, context?) |
dict | Asynchronous call | |
stream(module_id, inputs?, context?) |
AsyncIterator | Streaming output | |
validate(module_id, inputs?, context?) |
PreflightResult | Non-destructive preflight | |
| Inspection | list_modules(tags?, prefix?) |
list[str] | List module IDs (sorted) |
describe(module_id) |
str | Markdown description for AI | |
| Middleware | use(middleware) |
self | Add class-based middleware (Rust: use_middleware() — use is a reserved keyword) |
use_before(callback) |
self | Add before-middleware | |
use_after(callback) |
self | Add after-middleware | |
remove(middleware) |
bool | Remove by identity | |
| Events | on(event_type, handler) |
EventSubscriber | Subscribe to events |
off(subscriber) |
None | Unsubscribe | |
| Control | disable(module_id, reason?) |
dict | Disable module at runtime |
enable(module_id, reason?) |
dict | Re-enable module | |
| Properties | registry |
Registry | Underlying registry |
executor |
Executor | Underlying executor | |
events |
EventEmitter | None | Event emitter (if configured) |
!!! note "Rust keyword conflict: use → use_middleware"
In Rust, use is a reserved keyword. The method that Python and TypeScript expose as .use(middleware) is named .use_middleware(middleware) in the Rust SDK. All other method names are identical across languages. This is the only renamed method in the APCore client API.
```rust
// Rust — use use_middleware() where Python/TS use .use()
client.use_middleware(Box::new(logging_middleware));
client.use_before(|ctx| { ... });
client.use_after(|ctx| { ... });
```
!!! note "Sync/async divergence"
Python call() is synchronous and blocks until the module returns. TypeScript and Rust call() return a Promise/Future and MUST be awaited. Python provides a separate call_async() for async contexts (e.g., inside async def functions or running under an event loop).
The on() method creates a lightweight internal subscriber that filters events by type and delegates to the user's handler:
class _CallbackSubscriber:
def __init__(self, event_type: str, handler: Callable) -> None:
self._event_type = event_type
self._handler = handler
self._is_async = asyncio.iscoroutinefunction(handler)
async def on_event(self, event: ApCoreEvent) -> None:
if event.event_type != self._event_type:
return
if self._is_async:
await self._handler(event)
else:
self._handler(event)This allows users to subscribe with both sync and async callbacks without implementing the full EventSubscriber protocol.
| Condition | Error |
|---|---|
Config file not found or invalid (via Config.load()) |
ValueError (Python), ConfigNotFoundError (TypeScript), Err(ModuleError) with ConfigNotFound or ConfigInvalid (Rust) |
on(), off() without events enabled |
RuntimeError |
disable(), enable() without sys_modules |
RuntimeError |
The APCore interface follows each language's idioms while maintaining functional equivalence.
TypeScript:
| Spec method | TypeScript name | Notes |
|---|---|---|
call_async() |
callAsync() |
camelCase |
use_before() |
useBefore() |
camelCase |
use_after() |
useAfter() |
camelCase |
list_modules() |
listModules() |
camelCase |
| Constructor | new APCore({ config }) |
Options object pattern |
Rust:
| Spec method | Rust name | Notes |
|---|---|---|
use() |
use_middleware() |
use is a reserved keyword |
use_before() |
use_before() |
Accepts Box<dyn BeforeMiddleware>, returns Result<&mut Self, ModuleError> |
use_after() |
use_after() |
Accepts Box<dyn AfterMiddleware>, returns Result<&mut Self, ModuleError> |
on() |
on() |
Returns String (subscriber ID) instead of an EventSubscriber object |
off() |
off() |
Accepts &str (subscriber ID) instead of an EventSubscriber object |
stream() |
stream() |
Returns Stream<Item = Result<Value, ModuleError>> (true incremental streaming) |
disable() |
disable() |
Returns Result<Value, ModuleError>; reason is Option<&str> |
enable() |
enable() |
Returns Result<Value, ModuleError>; reason is Option<&str> |
| Constructor | APCore::new(), APCore::with_config(config), APCore::from_path(path) |
Three construction forms |
module() |
N/A | Rust has no decorators; use impl Module + register() |
events property |
events() method |
Accessor methods instead of properties |
registry property |
registry() method |
Accessor methods instead of properties |
executor property |
executor() method |
Accessor methods instead of properties |
Rust-only methods (not in the cross-language spec):
| Method | Purpose |
|---|---|
with_components(registry, config) |
Build client from a pre-configured Registry |
with_options(registry, executor, config, metrics_collector) |
Full constructor with all optional parameters |
reload() |
Reload config and re-discover modules |
=== "Python" ```python from apcore import APCore
client = APCore()
@client.module(id="math.add", description="Add two numbers")
def add(a: int, b: int) -> dict:
return {"sum": a + b}
result = client.call("math.add", {"a": 10, "b": 5})
print(result) # {"sum": 15}
```
=== "TypeScript" ```typescript import { APCore } from "apcore-js";
const client = new APCore();
client.module({
id: "math.add",
description: "Add two numbers",
inputSchema: { type: "object", properties: { a: { type: "number" }, b: { type: "number" } } },
outputSchema: { type: "object", properties: { sum: { type: "number" } } },
execute: ({ a, b }: { a: number; b: number }) => ({ sum: a + b }),
});
const result = await client.call("math.add", { a: 10, b: 5 });
console.log(result); // { sum: 15 }
```
=== "Rust" ```rust use apcore::APCore; use apcore::module::Module; use apcore::context::Context; use apcore::errors::ModuleError; use async_trait::async_trait; use serde_json::{json, Value};
struct AddModule;
#[async_trait]
impl Module for AddModule {
fn description(&self) -> &str { "Add two numbers" }
fn input_schema(&self) -> Value {
json!({"type": "object", "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}}})
}
fn output_schema(&self) -> Value {
json!({"type": "object", "properties": {"sum": {"type": "integer"}}})
}
async fn execute(&self, inputs: Value, _ctx: &Context<Value>) -> Result<Value, ModuleError> {
let a = inputs["a"].as_i64().unwrap_or(0);
let b = inputs["b"].as_i64().unwrap_or(0);
Ok(json!({"sum": a + b}))
}
}
let mut client = APCore::new();
client.register("math.add", Box::new(AddModule)).unwrap();
let result = client.call("math.add", json!({"a": 10, "b": 5}), None, None).await?;
```
=== "Python" ```python from apcore import APCore from apcore.config import Config
config = Config.load("apcore.yaml")
client = APCore(config=config)
# System modules, metrics, and events are auto-configured
sub = client.on("apcore.health.error_threshold_exceeded", lambda e: alert(e.data))
# Runtime control
client.disable("risky.module", reason="Investigating issue")
```
=== "TypeScript" ```typescript import { APCore, Config } from "apcore-js";
const config = Config.load('apcore.yaml');
const client = new APCore({ config });
// System modules, metrics, and events are auto-configured
const sub = client.on("apcore.health.error_threshold_exceeded", (e) => alert(e.data));
// Runtime control
await client.disable("risky.module", "Investigating issue");
```
=== "Rust" ```rust use apcore::APCore;
let client = APCore::from_path("apcore.yaml")?;
// System modules, metrics, and events are auto-configured
let sub = client.on("apcore.health.error_threshold_exceeded", Box::new(AlertSubscriber));
// Runtime control
client.disable("risky.module", Some("Investigating issue"))?;
```
- Registry — Module registration, discovery, and lookup.
- Executor — Module execution, middleware, ACL, and approval.
- Config Bus — Configuration loading and system module setup.
- Event System — Event emission and subscription (optional).
- System Modules — Health, manifest, usage, and control modules (optional).
- Observability — MetricsCollector and MetricsMiddleware (optional).
??? info "Python SDK reference"
The following table is not a protocol requirement — it documents the Python SDK's source layout for implementers/users of apcore-python.
**Source files:**
| File | Purpose |
|------|---------|
| `src/apcore/client.py` | `APCore`, `_CallbackSubscriber` |
| `src/apcore/__init__.py` | Module-level singleton functions |
- Zero-config tests verify that
APCore()creates a functional Registry and Executor without any arguments. - Config object tests verify that passing a
Configobject (loaded viaConfig.load()) applies the config file correctly. - Config-based tests verify that system modules are auto-registered when config provides
sys_modules.enabled: true. - Decorator tests verify that
client.module()registers functions as modules and they are callable. - Execution tests verify that
call(),call_async(),stream(), andvalidate()delegate correctly to the underlying Executor. - Middleware tests verify chainable
use(),use_before(),use_after(), andremove(). - Event tests verify that
on()/off()work correctly and raiseRuntimeErrorwhen events are not configured. - Control tests verify that
disable()/enable()delegate to system.control.toggle_feature and raiseRuntimeErrorwhen sys_modules are not enabled.
module_id(str/string/&str, required) — target module ID; validated againstMODULE_ID_PATTERN; reject empty or malformed withInvalidInputError(code=INVALID_MODULE_ID)inputs(dict/object/Value, required) — validated against the module'sinput_schemacontext(Context, optional) — execution context; created fresh when absentversion_hint(str/string/&str, optional) — preferred version constraint; falls back to latest on TS/Rust pending implementation
InvalidInputError(code=INVALID_MODULE_ID)—module_idis empty or malformedModuleNotFoundError(code=MODULE_NOT_FOUND)— no module registered undermodule_idSchemaValidationError(code=SCHEMA_VALIDATION_FAILED)—inputsfails the module'sinput_schema- Any error raised by the module's
executehandler propagates unchanged
- On success:
dict/Record<string, unknown>/serde_json::Value— the module's validated output
- async: sync surface (
call) + async surface (call_async) in Python; async-only in TypeScript and Rust - thread_safe: true (Executor holds an internal lock on shared state)
- pure: false (side-effects: span created, metrics emitted, middleware hooks invoked)
- idempotent: false (module
executeis not guaranteed idempotent)
- No required inputs (uses configuration from constructor)
ConfigError(code=CONFIG_INVALID)— if configuration validation fails on startup
- On success: void/None/()
- async: false in Python; async in TypeScript and Rust
- thread_safe: false (call once before any concurrent usage)
- idempotent: false
- No required inputs
- No errors raised under normal operation
- On success: void/None/()
- async: false in Python; async in TypeScript and Rust
- thread_safe: false (do not call concurrently with active requests)
- idempotent: true (multiple stops are safe)
event_type(str/string, required) — canonical event type string (e.g."apcore.registry.module_registered"); MUST be a non-empty string; filtered by exact equality match inside the subscriberhandler(callable/Function, required) — sync or async callback receiving anApCoreEvent; MUST NOT be null/None; Python accepts both sync and async callables (detected viaasyncio.iscoroutinefunction); TypeScript accepts(event: ApCoreEvent) => void | Promise<void>
RuntimeError(Python) /Error(TypeScript) — raised immediately ifsys_modules.eventsis not enabled (i.e., theeventsproperty returnsNone/null); message:"Events are not enabled. Set sys_modules.enabled=true and sys_modules.events.enabled=true in config."
- On success:
EventSubscriber— the created subscriber object; pass tooff()to cancel the subscription
- async: false (synchronous in both Python and TypeScript)
- thread_safe: true (Python
EventEmitter.subscribeholds an internal lock before appending) - pure: false (registers handler into the emitter's internal subscriber list)
- idempotent: false (registering the same handler twice creates two independent subscriptions that each fire)
subscriber(EventSubscriber, required) — the handle returned by a prior call toon(); MUST NOT be null/None
RuntimeError(Python) /Error(TypeScript) — raised immediately ifsys_modules.eventsis not enabled (same guard ason()); message identical toon()error
- On success: void/None — no return value
- async: false (synchronous in both Python and TypeScript)
- thread_safe: true (Python
EventEmitter.unsubscribeholds an internal lock before removing) - pure: false (mutates the emitter's internal subscriber list)
- idempotent: true (unsubscribing a subscriber that is not present is a no-op; Python implementation uses
list.removeunder a guard that tolerates absence)
module_id(str/string, required) — target module ID; validated via_validate_module_id; MUST be a non-empty string matching the module ID patterninputs(dict/object, optional) — input arguments for the module;None/nullis treated as{}context(Context, optional) — execution context; auto-created when absentversion_hint(str/string, optional) — preferred version constraint; falls back to latest
InvalidInputError(code=INVALID_MODULE_ID)—module_idis empty or malformed (raised before pipeline starts)ModuleNotFoundError(code=MODULE_NOT_FOUND)— no module registered undermodule_idSchemaValidationError(code=SCHEMA_VALIDATION_FAILED)—inputsfails the module'sinput_schemaExecutionCancelledError— propagated unchanged if the execution context is cancelled mid-stream- Any error raised by the module's
execute/streamhandler: in Python, a recovery dict chunk is yielded and the generator returns cleanly; retry signals during streaming are ignored and the original error is re-raised
- On success: async generator /
AsyncGenerator/AsyncIteratorthat yieldsdict/Record<string, unknown>chunks - If the module does not implement a
stream()method, the pipeline falls back to a singleexecute()call and yields its output as one chunk
- async: true (Python:
async def stream()coroutine / async generator; TypeScript:async *stream()async generator) - thread_safe: true (delegates to Executor which holds internal locks)
- pure: false (side-effects: span created, metrics emitted, middleware hooks invoked)
- idempotent: false (module execution is not guaranteed idempotent)
module_id(str/string, required) — target module ID; MUST be a non-empty string matching the module ID patterninputs(dict/object, optional) — input data to validate against the module'sinput_schema;None/nullis treated as{}context(Context, optional) — execution context used for ACL and call-chain checks; auto-created when absent
- No errors are raised for validation failures — failures are captured in the returned
PreflightResult InvalidInputError(code=INVALID_MODULE_ID)— raised ifmodule_idis empty or malformed (before pipeline begins)
- On success:
PreflightResult— an object with:valid: bool—Trueonly if all checks passedchecks: list[PreflightCheckResult]— per-step results covering:module_id,module_lookup,call_chain,acl,approval,schema,module_preflight(7 checks total)requires_approval: bool—Trueif the module carries arequires_approvalannotation (informational only; not enforced by validate)errors: list[dict]— convenience property; aggregateserrorfields from failed checks
- async: sync in Python (delegates to async impl via sync-in-thread model); async in TypeScript (
Promise<PreflightResult>) - thread_safe: true
- pure: false (pipeline dry-run creates a span and invokes middleware up to the execute step)
- idempotent: true (no state mutation; repeated calls with the same arguments return equivalent results)
module_id(str/string, required) — ID of the module to disable; passed directly tosystem.control.toggle_featurereason(str/string, optional) — audit reason string; defaults to"Disabled via APCore client"
RuntimeError(Python) /Error(TypeScript) /Err(ModuleError(code=SYS_MODULES_DISABLED))(Rust) — raised immediately ifsys_modulesare not enabled. Python message:"disable() requires sys_modules to be enabled. Pass a Config with sys_modules.enabled=true to APCore()."TypeScript message:"Cannot call disable(): sys_modules must be enabled in config."Rust message:"disable() requires sys_modules to be enabled. Pass a Config with sys_modules.enabled=true to APCore::new()."- Any error raised by
system.control.toggle_feature(e.g.,ModuleNotFoundErrorifmodule_idis not registered) propagates unchanged
- On success:
dict/Record<string, unknown>— result fromsystem.control.toggle_feature, containing at minimum:success(bool),module_id(str),enabled(bool,falseon success)
- async: sync in Python (delegates via sync-in-thread model); async in TypeScript (
Promise<Record<string, unknown>>) - thread_safe: true (delegates to Executor)
- pure: false (mutates the runtime disabled-modules registry)
- idempotent: true (disabling an already-disabled module SHOULD succeed without error)
module_id(str/string, required) — ID of the module to re-enable; passed directly tosystem.control.toggle_featurereason(str/string, optional) — audit reason string; defaults to"Enabled via APCore client"
RuntimeError(Python) /Error(TypeScript) /Err(ModuleError(code=SYS_MODULES_DISABLED))(Rust) — raised immediately ifsys_modulesare not enabled. Python message:"enable() requires sys_modules to be enabled. Pass a Config with sys_modules.enabled=true to APCore()."TypeScript message:"Cannot call enable(): sys_modules must be enabled in config."Rust message:"enable() requires sys_modules to be enabled. Pass a Config with sys_modules.enabled=true to APCore::new()."- Any error raised by
system.control.toggle_featurepropagates unchanged
- On success:
dict/Record<string, unknown>— result fromsystem.control.toggle_feature, containing at minimum:success(bool),module_id(str),enabled(bool,trueon success)
- async: sync in Python (delegates via sync-in-thread model); async in TypeScript (
Promise<Record<string, unknown>>) - thread_safe: true (delegates to Executor)
- pure: false (mutates the runtime disabled-modules registry)
- idempotent: true (enabling an already-enabled module SHOULD succeed without error)
registry(Registry, optional) — pre-built Registry instance; a newRegistry()is created when absentexecutor(Executor, optional) — pre-built Executor instance; a newExecutor(registry=..., config=...)is created when absentconfig(Config, optional) — framework configuration object; useConfig.load(path)to load from a YAML file; when absent the client runs in zero-config mode with no system modulesmetrics_collector(MetricsCollector, optional) — observability collector; auto-created whenconfig.sys_modules.enabled=trueand none is provided; ignored in zero-config mode
- No errors are raised by
__init__itself; ifsys_modulesregistration fails, the error is caught, logged at WARNING level, and the client continues with an empty_sys_modules_context(system-module methods will raiseRuntimeErroron use)
- On success: a fully initialized
APCoreinstance
- async: false (synchronous in all languages)
- thread_safe: false (do not share a partially-constructed instance across threads; all concurrent usage must start after construction is complete)
- pure: false (creates Registry/Executor, optionally registers system modules, adds middleware)
- idempotent: false
id(str/string, optional) — module ID to register the function under; MUST matchMODULE_ID_PATTERNwhen provided; when absent the registry derives an ID from the decorated function's namedescription(str/string, optional) — short human-readable description of the moduledocumentation(str/string, optional) — extended Markdown documentationannotations(dict/object, optional) — key-value annotations for routing or platform metadata (e.g.requires_approval)tags(list[str]/string[], optional) — tag strings for filtering vialist_modules(tags=...)version(str/string, optional) — semver version string; defaults to"1.0.0"metadata(dict/object, optional) — additional metadata stored alongside the module descriptordisplay(dict/object, optional) — display hints (name, icon, color) for UIsexamples(list/array, optional) — input/output examples for documentation and AI tool discovery
InvalidInputError(code=INVALID_MODULE_ID)—idis provided but empty, malformed, exceedsMAX_MODULE_ID_LENGTH, or contains a reserved first-segment wordInvalidInputError—idis already registered (duplicate registration)
- On success: the decorated function, unchanged (the function is registered as a
FunctionModuleas a side effect; the original callable is returned so it remains directly callable in Python)
- async: false (synchronous decorator in Python and TypeScript)
- thread_safe: true (delegates to
Registry.registerwhich holds an internal RLock) - pure: false (registers the function into the client's Registry as a side effect)
- idempotent: false (applying the decorator twice registers two entries and raises
InvalidInputErroron the second)
module_id(str/string/&str, required) — unique ID to register the module under; MUST be a non-empty string matchingMODULE_ID_PATTERN, ≤192 characters, with no reserved first-segment wordmodule_obj(any Module instance, required) — the module object to register; MUST NOT be null/None; raw-dictinput_schema/output_schemaare wrapped in a_DictSchemaAdapterautomatically
InvalidInputError(code=INVALID_MODULE_ID)—module_idis empty, malformed, exceeds the length limit, contains a reserved word, or is already registered under that IDRuntimeError— if the module'son_load()hook raises (the partial registration is rolled back before propagating)
- On success: void/None/() — no return value
- async: false (synchronous in all languages)
- thread_safe: true (Registry holds an internal RLock around the write)
- pure: false (mutates the registry's module map, triggers
registerevent callbacks) - idempotent: false (registering the same
module_idtwice raisesInvalidInputErroron the second call)
- No parameters — discovery roots come from
extensions.rootin the config provided at construction time (defaults to the framework default root when no config is given)
CircularDependencyError— if circular inter-module dependencies are detected in the discovered setConfigNotFoundError— if a configured extension root directory does not exist on disk- File-level errors (import failures, validation failures,
on_load()failures) are logged at WARNING/ERROR level and silently skipped; they do NOT propagate to the caller
- On success:
int— count of modules successfully registered in this discovery pass (0 if no modules are found or all fail validation)
- async: false (synchronous in all languages; file-system I/O and module imports run on the calling thread)
- thread_safe: true (each
Registry.registercall inside discovery holds the registry's RLock) - pure: false (imports Python files, instantiates module classes, and mutates the registry)
- idempotent: false (calling
discover()twice on a directory that has not changed will attempt to re-register already-registered modules, raisingInvalidInputErrorfor duplicates; callers should guard withlist_modules()or unregister first)
tags(list[str]/string[], optional) — when provided, only modules possessing ALL listed tags (via module attribute or merged metadata) are included;None/nullmeans no tag filteringprefix(str/string, optional) — when provided, only modules whose ID starts with this string are included;None/nullmeans no prefix filtering
- No errors raised under normal operation
- On success:
list[str]/string[]— alphabetically sorted list of matching module IDs; empty list when no modules match
- async: false (synchronous in all languages)
- thread_safe: true (Registry takes a snapshot under its RLock before filtering)
- pure: true (read-only; no state mutation)
- idempotent: true
module_id(str/string/&str, required) — ID of the module to describe; MUST be a non-empty string
ModuleNotFoundError— raised if no module is registered undermodule_id
- On success:
str/string— Markdown-formatted description string; if the module defines adescribe()method, its return value is used verbatim; otherwise the registry auto-generates a description from the module'sModuleDescriptor(title, description, tags, parameter list, documentation)
- async: false (synchronous in all languages)
- thread_safe: true (reads from the Registry under its RLock)
- pure: true (read-only; no state mutation)
- idempotent: true
middleware(Middleware instance, required) — a class-based middleware object implementing theMiddlewareprotocol; MUST NOT be null/None;priorityattribute (int, 0–1000) controls insertion order — higher priority runs first; equal priorities preserve registration order
ValueError— ifmiddleware.priorityexceeds 1000
- On success:
self/APCore— returns the client instance for method chaining (e.g.client.use(a).use(b).use(c))
- async: false (synchronous in all languages)
- thread_safe: true (MiddlewareManager holds an internal lock during insertion)
- pure: false (mutates the executor's middleware chain)
- idempotent: false (adding the same middleware instance twice inserts it twice, running it twice per execution)
!!! note "Rust keyword conflict"
In Rust, this method is named use_middleware() because use is a reserved keyword. Python and TypeScript expose it as .use().
callback(callable/Function, required) — a sync or async function invoked before module execution; signature:(context: Context) -> None(Python) /(ctx: Context) => void | Promise<void>(TypeScript); MUST NOT be null/None; the callback is wrapped in aBeforeMiddlewareadapter with default priority 0
- No errors raised during registration; errors raised inside
callbackat execution time propagate through the middleware chain
- On success:
self/APCore— returns the client instance for method chaining
- async: false (the registration call is synchronous; the callback itself may be sync or async)
- thread_safe: true (delegates to MiddlewareManager which holds an internal lock)
- pure: false (mutates the executor's middleware chain by inserting a wrapped
BeforeMiddleware) - idempotent: false (registering the same callback twice inserts two independent
BeforeMiddlewarewrappers)
callback(callable/Function, required) — a sync or async function invoked after module execution; signature:(context: Context) -> None(Python) /(ctx: Context) => void | Promise<void>(TypeScript); MUST NOT be null/None; the callback is wrapped in anAfterMiddlewareadapter with default priority 0
- No errors raised during registration; errors raised inside
callbackat execution time propagate through the middleware chain
- On success:
self/APCore— returns the client instance for method chaining
- async: false (the registration call is synchronous; the callback itself may be sync or async)
- thread_safe: true (delegates to MiddlewareManager which holds an internal lock)
- pure: false (mutates the executor's middleware chain by inserting a wrapped
AfterMiddleware) - idempotent: false (registering the same callback twice inserts two independent
AfterMiddlewarewrappers)
middleware(Middleware instance, required) — the exact middleware object to remove; identity comparison (is) is used, not equality (==); pass the same object reference returned or stored when originally callinguse(),use_before(), oruse_after()
- No errors raised under normal operation
- On success:
bool—Trueif the middleware was found by identity and removed;Falseif no matching instance was present in the chain
- async: false (synchronous in all languages)
- thread_safe: true (MiddlewareManager holds an internal lock during removal)
- pure: false (mutates the executor's middleware chain)
- idempotent: true (calling
remove()on a middleware not in the chain returnsFalsewithout error; calling it again after a successful removal also returnsFalsesafely)