Build once, invoke by Code or AI.
A schema-enforced module standard for the AI-Perceivable era.
apcore is an AI-Perceivable module standard that makes every interface naturally perceivable and understandable by AI through enforced Schema definitions and behavioral annotations. It provides strict type safety, access control, middleware pipelines, and built-in observability — enabling you to define modules with structured input/output schemas that are easily consumed by both code and AI.
- Schema-driven modules -- Define input/output contracts using Pydantic models (with automatic validation) or plain JSON Schema dicts
- Execution Pipeline -- Context creation, safety checks, ACL enforcement, approval gate, validation, middleware chains, and execution with timeout support
@moduledecorator -- Turn plain functions into fully schema-aware modules with zero boilerplate- YAML bindings -- Register modules declaratively without modifying source code
- Access control (ACL) -- Pattern-based, first-match-wins rules with wildcard support
- Middleware system -- Composable before/after hooks with error recovery
- Observability -- Tracing (spans), metrics collection, and structured context logging
- Async support -- Seamless sync and async module execution
- Safety guards -- Call depth limits, circular call detection, frequency throttling
- Approval system -- Pluggable approval gate (Step 5) with sync/async handlers, Phase B resume, and audit events
- Extension points -- Unified extension management for discoverers, middleware, ACL, approval handlers, span exporters, and module validators
- Async task management -- Background module execution with status tracking, cancellation, and concurrency limiting
- Behavioral annotations -- Declare module traits (readonly, destructive, idempotent, cacheable, paginated, streaming) for AI-aware orchestration
- W3C Trace Context -- traceparent header injection/extraction for distributed tracing interop
Core
| Class | Description |
|---|---|
APCore |
High-level client -- register modules, call, stream, validate |
Registry |
Module storage -- discover, register, get, list, watch |
Executor |
Execution engine -- call with middleware pipeline, ACL, approval |
Context |
Request context -- trace ID, identity, call chain, cancel token |
Config |
Configuration -- load from YAML, get/set values |
Identity |
Caller identity -- id, type, roles, attributes |
FunctionModule |
Wrapped function module created by @module decorator |
Access Control & Approval
| Class | Description |
|---|---|
ACL |
Access control -- rule-based caller/target authorization |
ApprovalHandler |
Pluggable approval gate protocol |
AlwaysDenyHandler / AutoApproveHandler / CallbackApprovalHandler |
Built-in approval handlers |
Middleware
| Class | Description |
|---|---|
Middleware |
Pipeline hooks -- before/after/on_error interception |
BeforeMiddleware / AfterMiddleware |
Single-phase middleware adapters |
LoggingMiddleware |
Structured logging middleware |
RetryMiddleware |
Automatic retry with backoff |
ErrorHistoryMiddleware |
Records errors into ErrorHistory |
PlatformNotifyMiddleware |
Emits events on error rate/latency spikes |
ObsLoggingMiddleware |
Observability-aware structured logging middleware |
Schema
| Class | Description |
|---|---|
SchemaLoader |
Load schemas from YAML or native types |
SchemaValidator |
Validate data against schemas |
SchemaExporter |
Export schemas for MCP, OpenAI, Anthropic, generic |
RefResolver |
Resolve $ref references in JSON Schema |
Observability
| Class | Description |
|---|---|
TracingMiddleware |
Distributed tracing with span export |
MetricsMiddleware / MetricsCollector |
Call count, latency, error rate metrics |
ContextLogger |
Context-aware structured logging |
ErrorHistory |
Ring buffer of recent errors with deduplication |
UsageCollector |
Per-module usage statistics and trends |
UsageMiddleware |
Per-call usage tracking middleware |
TraceContext |
W3C Trace Context propagation (traceparent/tracestate) |
InMemoryExporter |
Span exporter that stores spans in memory |
StdoutExporter |
Span exporter that writes spans to stdout |
OTLPExporter |
Span exporter using OpenTelemetry Protocol |
Events & Extensions
| Class | Description |
|---|---|
EventEmitter |
Event system -- subscribe, emit, flush |
WebhookSubscriber / A2ASubscriber |
Built-in event subscribers |
ExtensionManager |
Unified extension point management |
AsyncTaskManager |
Background module execution with status tracking |
CancelToken |
Cooperative cancellation token |
BindingLoader |
Load modules from YAML binding files |
ErrorCodeRegistry |
Central registry for structured error codes |
For full documentation, including Quick Start guides for both Python and TypeScript, visit: https://aiperceivable.github.io/apcore/getting-started.html
- Python >= 3.11
pip install apcorepip install -e ".[dev]"For simple scripts or prototypes, you can use the global apcore functions:
import apcore
@apcore.module(id="math.add", description="Add two integers")
def add(a: int, b: int) -> int:
return a + b
# Directly call it
result = apcore.call("math.add", {"a": 10, "b": 5})
print(result) # {'result': 15}The APCore client provides a unified entry point that manages everything for you:
from apcore import APCore
client = APCore()
@client.module(id="math.add", description="Add two integers")
def add(a: int, b: int) -> int:
return a + b
# Call the module
result = client.call("math.add", {"a": 10, "b": 5})
print(result) # {'result': 15}from pydantic import BaseModel
from apcore import Context, APCore
client = APCore()
class GreetInput(BaseModel):
name: str
class GreetOutput(BaseModel):
message: str
class GreetModule:
input_schema = GreetInput
output_schema = GreetOutput
description = "Greet a user"
def execute(self, inputs: dict, context: Context) -> dict:
return {"message": f"Hello, {inputs['name']}!"}
client.register("greet", GreetModule())
result = client.call("greet", {"name": "Alice"})
# {"message": "Hello, Alice!"}If you prefer not to use Pydantic, pass raw JSON Schema dicts directly:
from apcore import APCore
client = APCore()
class WeatherModule:
input_schema = {"type": "object", "properties": {"city": {"type": "string"}}}
output_schema = {"type": "object", "properties": {"temp": {"type": "number"}}}
description = "Get current temperature"
def execute(self, inputs: dict, context=None) -> dict:
return {"temp": 22.5}
client.register("weather", WeatherModule())
result = client.call("weather", {"city": "Tokyo"})
# {"temp": 22.5}Note: Dict schemas skip Pydantic input validation. Use Pydantic models when you need automatic type coercion and validation, or validate inside
execute().
from apcore import LoggingMiddleware, TracingMiddleware
client.use(LoggingMiddleware())
client.use(TracingMiddleware())from apcore import ACL, ACLRule, Executor, Registry
registry = Registry()
acl = ACL(rules=[
ACLRule(callers=["admin.*"], targets=["*"], effect="allow", description="Admins can call anything"),
ACLRule(callers=["*"], targets=["admin.*"], effect="deny", description="Others cannot call admin modules"),
])
executor = Executor(registry=registry, acl=acl)The examples/ directory contains runnable demos:
Initializes an APCore client, registers modules with @client.module(), and calls them directly.
from apcore import APCore
client = APCore()
@client.module(id="math.add", description="Add two integers")
def add(a: int, b: int) -> int:
return a + b
result = client.call("math.add", {"a": 10, "b": 5})
print(result) # {'result': 15}
@client.module(id="greet")
def greet(name: str, greeting: str = "Hello") -> dict:
return {"message": f"{greeting}, {name}!"}
result = client.call("greet", {"name": "Alice"})
print(result) # {'message': 'Hello, Alice!'}No explicit initialization needed — use the default global client directly.
import apcore
@apcore.module(id="math.add")
def add(a: int, b: int) -> int:
return a + b
result = apcore.call("math.add", {"a": 10, "b": 5})
print(result) # {'result': 15}Demonstrates the class-based module interface with Pydantic BaseModel for input/output schemas.
from pydantic import BaseModel
class GreetInput(BaseModel):
name: str
class GreetOutput(BaseModel):
message: str
class GreetModule:
input_schema = GreetInput
output_schema = GreetOutput
description = "Greet a user by name"
def execute(self, inputs: dict, context) -> dict:
name = inputs["name"]
return {"message": f"Hello, {name}!"}Demonstrates behavioral annotations (readonly, idempotent) and simulated database lookup.
from pydantic import BaseModel
from apcore.module import ModuleAnnotations
class GetUserInput(BaseModel):
user_id: str
class GetUserOutput(BaseModel):
id: str
name: str
email: str
class GetUserModule:
input_schema = GetUserInput
output_schema = GetUserOutput
description = "Get user details by ID"
annotations = ModuleAnnotations(readonly=True, idempotent=True)
_users = {
"user-1": {"id": "user-1", "name": "Alice", "email": "alice@example.com"},
"user-2": {"id": "user-2", "name": "Bob", "email": "bob@example.com"},
}
def execute(self, inputs: dict, context) -> dict:
user_id = inputs["user_id"]
user = self._users.get(user_id)
if user is None:
return {"id": user_id, "name": "Unknown", "email": "unknown@example.com"}
return dict(user)Shows x-sensitive on schema fields (for log redaction), ModuleAnnotations with metadata, ModuleExample for AI-perceivable documentation, and ContextLogger usage.
from pydantic import BaseModel, Field
from apcore.module import ModuleAnnotations, ModuleExample
from apcore.observability import ContextLogger
class SendEmailInput(BaseModel):
to: str
subject: str
body: str
api_key: str = Field(..., json_schema_extra={"x-sensitive": True})
class SendEmailOutput(BaseModel):
status: str
message_id: str
class SendEmailModule:
input_schema = SendEmailInput
output_schema = SendEmailOutput
description = "Send an email message"
tags = ["email", "communication", "external"]
version = "1.2.0"
metadata = {"provider": "example-smtp", "max_retries": 3}
annotations = ModuleAnnotations(destructive=True, idempotent=False, open_world=True)
examples = [
ModuleExample(
title="Send a welcome email",
inputs={"to": "user@example.com", "subject": "Welcome!", "body": "...", "api_key": "sk-xxx"},
output={"status": "sent", "message_id": "msg-12345"},
description="Sends a welcome email to a new user.",
),
]
def execute(self, inputs: dict, context) -> dict:
logger = ContextLogger.from_context(context, name="send_email")
logger.info("Sending email", extra={"to": inputs["to"], "subject": inputs["subject"]})
message_id = f"msg-{hash(inputs['to']) % 100000:05d}"
logger.info("Email sent successfully", extra={"message_id": message_id})
return {"status": "sent", "message_id": message_id}from apcore.decorator import module
@module(description="Add two integers", tags=["math", "utility"])
def add(a: int, b: int) -> int:
return a + bpytestpytest --cov=src/apcore --cov-report=htmlruff check --fix src/ tests/
ruff format src/ tests/mypy src/ tests/Apache-2.0
- Documentation: docs/apcore - Complete documentation
- Website: aiperceivable.com
- GitHub: aiperceivable/apcore-python
- PyPI: apcore
- Issues: GitHub Issues
- Discussions: GitHub Discussions