AI-Perceivable: application capabilities expose machine-readable contracts and behavioral metadata so agents do not need to infer basic invocation rules from prose.
📖 Full Documentation · Getting Started · Protocol Spec
Define a governed capability once. Expose it through any supported surface.
A governed, protocol-neutral runtime and module standard for agent-callable application capabilities.
apcore enforces schemas, behavioral annotations, ACL rules, approval gates, middleware, and observability at the execution boundary. Surface adapters then project the same capability to MCP, A2A, CLI, HTTP, or direct code.
apcore is a protocol specification. Language implementations are maintained in separate repositories:
| SDK | Language | Install | Repository |
|---|---|---|---|
| apcore | Python | pip install apcore |
github.com/aiperceivable/apcore-python |
| apcore-js | TypeScript | npm install apcore-js |
github.com/aiperceivable/apcore-typescript |
| apcore | Rust | cargo add apcore |
github.com/aiperceivable/apcore-rust |
- What is apcore?
- Why AI-Perceivable?
- Core Principles
- Architecture Overview
- Quick Start
- Module Development
- Schema System
- Context Object
- ACL Access Control
- Middleware
- Configuration
- Observability
- Error Handling & AI Guidance
- Cross-Language Support
- Relationship with Other Tools
- Implementations
- Ecosystem
- Documentation Index
- Contributing
- License
apcore is a governed capability runtime and module standard. It gives agents and code a machine-readable contract while enforcing validation and governance before business logic runs.
┌─────────────────────────────────────────────────────────────┐
│ apcore — AI-Perceivable Core │
│ │
│ Governed capability runtime + enforced schemas │
│ - Directory as ID (zero-config module discovery) │
│ - Schema-driven (input/output mandatory) │
│ - ACL / Observability / Middleware │
└─────────────────────────────────────────────────────────────┘
↓ Modules callable by
┌──────────┬──────────┬──────────┬──────────┐
│ │ │ │ │
Legacy Code AI/LLM HTTP API CLI Tool MCP Server
(import) (agent) (REST) (terminal) (client)
A protocol-neutral execution contract, not an agent framework or transport protocol.
Traditional software provides UI for humans and APIs for programs. apcore adds an agent-readable capability contract:
- Machine-readable intent: descriptions and schemas expose what an operation accepts and returns.
- Strict contracts: mandatory schemas let the runtime validate every input and output.
- Behavioral metadata: annotations such as
readonlyanddestructiveinform policy and callers.
MCP defines client-server communication and tool metadata. apcore addresses a different boundary: defining and executing application capabilities consistently before they are exposed through MCP or another surface.
| Concern | MCP | apcore |
|---|---|---|
| Primary role | Client-server protocol and tool surface | Capability definition and governed execution runtime |
| Schema and hints | Tool input/output schemas and annotations | Required schemas plus runtime-enforced validation and governance |
| Access and approval | Implemented by the server or deployment | ACL and approval gates enforced by the execution pipeline |
| Auditability | Implemented by the server or deployment | Trace context, structured errors, events, and usage hooks |
| Language model | Protocol SDKs | Semantically aligned Python, TypeScript, and Rust SDKs |
Use MCP directly when a protocol server is all you need. Add apcore when the same business capability must retain validation, access, approval, and audit semantics across MCP, CLI, HTTP, and direct code.
Traditional module development faces a fundamental contradiction:
Traditional modules: callers depend on code-specific signatures and prose
apcore modules: callers receive a structured contract enforced at runtime
Agents have become important callers in software systems, but many application operations lack a portable, enforced contract. apcore addresses this by requiring input_schema, output_schema, and description, then applying governance in the execution pipeline.
apcore solves how to build modules (module standard), not how to call tools (communication protocol). Once modules are built, they can be called by code / AI / HTTP / CLI / MCP or any other means.
Machine-readable metadata reduces avoidable interface guessing. Model selection and semantic understanding remain caller responsibilities.
| Stage | Meaning | apcore Mechanism |
|---|---|---|
| Perceived | AI can discover and read the module | Schema-enforced description, input_schema, output_schema |
| Interpreted | A caller can inspect intended use and behavioral hints | Behavioral annotations (x-when-to-use, x-common-mistakes) |
| Governed | The runtime decides whether and how the call executes | ACL, requires_approval, validation, structured errors |
apcore organizes module metadata into a coherent lifecycle that guides an Agent through every stage of a task:
- Discovery (Identity) —
description: Helps the Agent find the right tool for its intent. - Strategy (Wisdom) —
metadata: Teaches the Agent when and how to use the tool correctly (e.g.,x-when-to-use,x-common-mistakes). - Governance (Safety) —
requires_approval: Sets the safety boundary for sensitive operations. - Recovery (Resilience) —
ai_guidance: Provides a clear path for the Agent to fix errors autonomously.
| Scenario | Without apcore | With apcore |
|---|---|---|
| LLM calling your business functions | Manually duplicate tool descriptions and parameter maps | Adapters project the enforced module schema |
| New team members onboarding | Read source code, guess parameters | Clear from Schema + annotations |
| Cross-team module reuse | Outdated docs, unclear interfaces | Schema is doc, enforced validation |
| Security audit | Manually trace call relationships | ACL + call chain auto-tracked |
| Expose as MCP Server | Rewrite interface definitions | Adapter reads Schema directly |
Reality: AI has become a key caller in software systems
Decision: Enforce input_schema / output_schema / description
Result: One machine-readable contract can be validated across supported surfaces
| Principle | Description |
|---|---|
| Schema-Driven | All modules enforce input_schema / output_schema / description |
| Directory as ID | Directory path auto-maps to module ID, zero config |
| AI-Perceivable | Schema enables AI/LLM perception and understanding—a design requirement, not optional |
| Universal Standard | Modules callable by code/AI/HTTP/CLI or any other means |
| Progressive Integration | Existing code gains AI-Perceivable capability via decorators, function calls, or YAML binding |
| Cross-Language Spec | Language-agnostic protocol specification, any language can implement conformant SDK |
| Traditional Frameworks | apcore | |
|---|---|---|
| Schema | Optional | Enforced |
| AI-Perceivable | Not guaranteed | Guaranteed |
| Module Discovery | Manual registration | Auto-discovery from directory |
| Input Validation | Implement yourself | Framework automatic |
| Behavior Annotations | None | readonly / destructive / requires_approval etc. |
| Call Tracing | Implement yourself | trace_id auto-propagated |
apcore's architecture consists of two orthogonal dimensions: Framework Technical Architecture (vertical) and Business Layering Recommendations (horizontal).
The technical layers of the standard itself, defining the complete flow from module registration to execution:
┌─────────────────────────────────────────────────┐
│ Application Layer │
│ HTTP API / CLI / MCP Server / Custom Interface│
└─────────────────────┬───────────────────────────┘
↓ calls
┌─────────────────────────────────────────────────┐
│ Execution Layer │
│ ACL check → Input validation → Middleware chain│
│ → Execute → Output validation │
└──────────┬──────────────────────────────────────┘
↓ lookup module
┌─────────────────────────────────────────────────┐
│ Registry Layer │
│ Scan & discover → ID mapping → Interface │
│ validation → Module storage │
└──────────┬──────────────────────────────────────┘
↓ read
┌─────────────────────────────────────────────────┐
│ Module Layer │
│ User-written business modules (conforming to │
│ Module interface specification) │
└─────────────────────────────────────────────────┘
Under the extensions/ directory, modules should be organized by responsibility (enforced by ACL):
extensions/
├── api/ # API Layer: Handle external requests
│ └── ACL: Can only call orchestrator.*
│
├── orchestrator/ # Orchestration Layer: Compose business flows
│ └── ACL: Can only call executor.* and common.*
│
├── executor/ # Execution Layer: Concrete business operations
│ └── ACL: Can call common.*, can connect to external systems
│
└── common/ # Common Layer: Shared utilities and helpers
└── ACL: Read-only operations, called by all layers
Key Points:
- Framework technical architecture (Application → Execution → Registry → Module) is apcore's implementation mechanism
- Business layering (api → orchestrator → executor → common) is a best practice recommendation, enforced through ACL configuration
- The two are orthogonal: any business layer module (api/orchestrator/executor/common) goes through the same framework layer processing
A module call goes through a rigorous Execution Pipeline:
executor.call("executor.email.send_email", inputs, context)
│
├─ 1. Context processing: Create/update call context (trace_id, caller_id, call_chain)
├─ 2. Safety checks: Verify call depth and detect circular calls
├─ 3. Lookup module: Find target module from Registry
├─ 4. ACL check: Verify caller has permission to call target module
├─ 5. Approval Gate: Check requires_approval, await human decision
├─ 6. Input validation: Validate input parameters against input_schema
├─ 7. Middleware before: Execute middleware before() hooks in sequence
├─ 8. Module execution: Call module.execute(inputs, context)
├─ 9. Output validation: Validate output result against output_schema
├─ 10. Middleware after: Execute middleware after() hooks in reverse order
├─ 11. Return result
│
└─ (on error: middleware on_error hooks in reverse order)
Module IDs are automatically generated from relative paths under the module root directory (default root is extensions/, multiple roots can be configured):
File path: Canonical ID:
extensions/api/handler/user.py → api.handler.user
extensions/executor/email/send_email.py → executor.email.send_email
extensions/common/util/validator.py → common.util.validator
Rules:
1. Remove module root prefix (default `extensions/`)
2. Remove file extension
3. Replace `/` with `.`
4. Must match: ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$
5. Maximum length: 192 characters
**Multiple Roots and Namespaces**
- If multiple module root directories are configured, each root directory automatically uses the directory name as a **namespace**, ensuring Module ID uniqueness within the same Registry.
- For example: `extensions_roots: ["./extensions", "./plugins"]` → `extensions.executor.email.send_email`, `plugins.my_tool`.
- Automatic namespacing can be overridden with explicit configuration (e.g., `{root: "./extensions", namespace: "core"}` → `core.executor.email.send_email`).
- Single root mode has no namespace by default (backward compatible); in multi-root mode, at most one root can set `namespace: ""` to omit the prefix.
**Cross-Project Conflicts**
- Same IDs from different projects **will not conflict**, unless they are merged into the same Registry / call domain (e.g., unified gateway or shared executor).
For a detailed multi-language guide, visit the Getting Started Guide.
=== "Python"
```bash
pip install apcore
```
```python
import apcore
# Use the global client for easy registration and calling
@apcore.module(id="math.add", description="Add two integers")
def add(a: int, b: int) -> int:
return a + b
# Call directly
print(apcore.call("math.add", {"a": 10, "b": 5})) # {'result': 15}
```
Or use the explicit client:
```python
from apcore import APCore
client = APCore()
@client.module(id="math.add")
def add(a: int, b: int) -> int:
return a + b
print(client.call("math.add", {"a": 10, "b": 5}))
```
=== "TypeScript"
```bash
npm install apcore-js
```
```typescript
import { Type } from '@sinclair/typebox';
import { FunctionModule, Registry, Executor } from 'apcore-js';
const add = new FunctionModule({
moduleId: 'math.add',
description: 'Add two numbers',
inputSchema: Type.Object({ a: Type.Number(), b: Type.Number() }),
outputSchema: Type.Object({ sum: Type.Number() }),
execute: (inputs) => ({ sum: (inputs.a as number) + (inputs.b as number) }),
});
const registry = new Registry();
registry.register('math.add', add);
const executor = new Executor({ registry });
console.log(await executor.call('math.add', { a: 10, b: 5 })); // { sum: 15 }
```
my-project/
├── apcore.yaml # Framework configuration
├── extensions/ # Module directory (directory path = module ID)
│ ├── api/ # API layer
│ ├── orchestrator/ # Orchestration layer
│ └── executor/ # Execution layer
├── schemas/ # Schema definitions (YAML, shared across languages)
└── acl/ # Permission configuration
Detailed definitions: Module Interface | Creating Modules Guide
- Native SDK (Recommended): Best for new projects. Full type safety and lifecycle control.
- Zero-Intrusion Patch: Best for legacy code. Upgrade via decorators or YAML bindings without rewriting business logic.
apcore provides four ways to define modules, suitable for different scenarios:
The most complete approach, supporting all features:
# extensions/executor/email/send_email.py
# Module ID auto-generated: executor.email.send_email
from apcore import Module, ModuleAnnotations, Context
from pydantic import BaseModel, Field
class SendEmailInput(BaseModel):
"""LLM understands what parameters are needed through this Schema"""
to: str = Field(..., description="Recipient email address")
subject: str = Field(..., description="Email subject")
body: str = Field(..., description="Email body")
class SendEmailOutput(BaseModel):
"""LLM understands what is returned through this Schema"""
success: bool
message_id: str = None
class SendEmailModule(Module):
"""Send email module
Detailed documentation:
- Supports text and HTML format emails
- Uses SMTP protocol to connect to external mail server
- Configuration items: smtp_host, smtp_port, smtp_user, smtp_pass
Usage example:
Input: {"to": "user@example.com", "subject": "Hello", "body": "World"}
Output: {"success": true, "message_id": "msg_123"}
Notes:
- SMTP server information must be configured in the configuration file
- Gmail limits 500 emails/day, other providers may have different limits
- EmailSendError exception will be raised on send failure
"""
# Core layer (must be defined)
input_schema = SendEmailInput
output_schema = SendEmailOutput
description = "Send email to specified recipient. Uses SMTP protocol, non-idempotent operation, requires mail server configuration."
# Optional: Detailed documentation (for complex modules)
documentation = """
# Features
Send emails via SMTP protocol, supporting plain text and HTML formats.
## Version Compatibility
The repositories below are the current maintained release lines as of
**2026-07-16**. Core SDKs are version-aligned; adapters have independent
version lines and declare their supported core range in their package metadata.
| Component | Version | Notes |
|---|---|---|
| **Protocol specification** | 1.9.0-draft | `docs/spec/protocol-spec.md` |
| apcore Python / TypeScript / Rust | 0.26.0 | Core SDK release line |
| apcore-mcp Python / TypeScript / Rust | 0.17.2 | MCP surface adapters |
| apcore-a2a Python / TypeScript / Rust | 0.4.4 | A2A surface adapters |
| apcore-cli Python / TypeScript / Rust | 0.10.4 | CLI surface adapters |
| apcore-toolkit Python / TypeScript / Rust | 0.10.1 | Adapter-building utilities |
## Configuration Requirements
- SMTP server information must be configured in apcore.yaml
- Valid SMTP authentication credentials required
## Use Cases
- Send notification emails, verification codes, reports
## Limitations
- Gmail: 500 emails/day
- Attachment size: ≤25MB
"""
# Annotation layer (optional, type-safe)
annotations = ModuleAnnotations(
readonly=False, # Has side effects
destructive=False, # Won't delete/overwrite data
idempotent=False, # Repeated calls will send repeatedly
requires_approval=True, # Requires user confirmation
open_world=True, # Connects to external system (SMTP)
)
tags = ["email", "notification"]
def execute(self, inputs: dict, context: Context) -> dict:
validated = SendEmailInput(**inputs)
# ... send email logic ...
return SendEmailOutput(success=True, message_id="msg_123").model_dump()This module automatically has:
- LLM-understandable Schema and behavior annotations
- Auto-generated ID (
executor.email.send_email) - Input/output validation
- Call chain tracing, observability
Suitable for scenarios where source code can be modified, one-line integration:
# Before: Plain function
def send_email(to: str, subject: str, body: str) -> dict:
"""Send email"""
return {"success": True, "message_id": "msg_123"}
# After: Add one line decorator, automatically becomes an apcore module
from apcore import module
@module(id="email.send", tags=["email"])
def send_email(to: str, subject: str, body: str) -> dict:
"""Send email"""
return {"success": True, "message_id": "msg_123"}
# Schema automatically inferred from type annotationsSuitable for scenarios where you don't want to modify source code, completely non-invasive to existing code:
from apcore import module
# Existing business code, no modification needed
class EmailService:
def send(self, to: str, subject: str, body: str) -> dict:
"""Send email"""
return {"success": True}
service = EmailService()
module(service.send, id="email.send") # Register as apcore moduleSuitable for scenarios where source code cannot be modified (third-party libraries, legacy systems, etc.), pure YAML configuration:
# bindings/email.binding.yaml
bindings:
- module_id: "email.send"
target_id: "myapp.services.email:send_email" # Callable object path
description: "Send email"
auto_schema: true # Auto-generate Schema from type annotations
annotations:
open_world: true
requires_approval: true
tags: ["email"]| Approach | Code Invasiveness | Use Case | Schema Definition |
|---|---|---|---|
| Class-based | High (write new class) | New module development | Manual definition (most complete) |
@module Decorator |
Low (add one line) | Modifiable code | Inferred from type annotations |
module() Function Call |
Very low (don't modify original function) | Existing classes/methods | Inferred from type annotations |
| External Binding | Zero | Cannot modify source code scenarios | Auto-inferred or manually specified |
Detailed definitions: Schema Definition Guide | ModuleAnnotations
Each module's metadata is divided into three layers, progressing from required to optional:
┌──────────────────────────────────────────────────┐
│ Core Layer (REQUIRED) │
│ input_schema / output_schema / description │
│ → AI understands "what this module does" │
│ │
│ + documentation (OPTIONAL, detailed docs) │
│ → AI understands "detailed use cases and │
│ constraints" │
├──────────────────────────────────────────────────┤
│ Annotation Layer (OPTIONAL, type-safe) │
│ annotations / examples / tags / version │
│ → AI understands "how to use correctly" │
├──────────────────────────────────────────────────┤
│ Extension Layer (OPTIONAL, free dictionary) │
│ metadata: dict[str, Any] │
│ → Custom requirements (framework doesn't │
│ validate); AI tactical wisdom │
│ (x-when-to-use, etc.) also lives here │
└──────────────────────────────────────────────────┘
Borrowing from Claude Skill's Progressive Disclosure design, apcore uses two fields to organize module documentation:
| Field | Required | Length Limit | Markdown | Purpose |
|---|---|---|---|---|
description |
Required | ≤200 characters | No | Brief module function description for AI quick matching and understanding |
documentation |
Optional | ≤5000 characters | Yes | Detailed documentation including use cases, constraints, configuration requirements |
- Module discovery phase: AI reads all modules'
description, quickly determines candidate modules - Call decision phase: AI loads
documentationon-demand, learns detailed usage and constraints
Complete format rules and correspondence with Claude Skill / OpenAPI: see Protocol Specification §4.8. Code examples: see Class-based Modules above.
Schema is based on JSON Schema Draft 2020-12, supports YAML format definition (shared across languages). Schema files are placed in the schemas/ directory, with paths corresponding to module IDs.
Complete Schema format and YAML examples: see Schema Definition Guide | Protocol Specification §4.
Annotations describe module behavior characteristics, helping AI make safer call decisions:
| Annotation | Type | Description | AI Behavior Impact |
|---|---|---|---|
readonly |
bool | No side effects, read-only operation | AI can safely call autonomously |
destructive |
bool | May delete or overwrite data | AI should request user confirmation before calling |
idempotent |
bool | Repeated calls have same result | AI can safely retry |
requires_approval |
bool | Requires explicit user consent | AI must wait for human approval (enforced by Executor) |
open_world |
bool | Connects to external systems | AI should inform user of external interaction |
streaming |
bool | Supports streaming execution | AI can use streaming response mode |
cacheable |
bool | Output can be cached | AI can reuse previous results within cache_ttl |
cache_ttl |
int | Cache duration in seconds | AI knows how long cached results remain valid |
paginated |
bool | Returns paginated results | AI knows to pass cursor/offset and expect partial results |
cache_key_fields |
list[str] | Input fields used as cache key | AI knows which inputs affect caching |
pagination_style |
str | Pagination style: cursor, offset, or page |
AI knows which pagination parameters to use |
# Read-only query - AI can call autonomously
annotations = ModuleAnnotations(readonly=True)
# Delete operation - AI needs to request confirmation
annotations = ModuleAnnotations(destructive=True, requires_approval=True)
# External API call - AI needs to inform user
annotations = ModuleAnnotations(open_world=True, idempotent=True)
# Cacheable query with 5-minute TTL
annotations = ModuleAnnotations(readonly=True, cacheable=True, cache_ttl=300)
# Paginated list endpoint
annotations = ModuleAnnotations(readonly=True, paginated=True, pagination_style="cursor")Fields with x- prefix in Schema are LLM-specific extensions, don't affect standard JSON Schema validation:
| Field | Description | Example |
|---|---|---|
x-llm-description |
Extended description for LLM (more detailed than description) | "User's login password, at least 8 characters" |
x-examples |
Example values to help LLM understand format | ["user@example.com"] |
x-sensitive |
Mark sensitive fields (password, API Key, etc.) | true |
x-constraints |
Business constraints described in natural language | "Must be a registered user" |
x-deprecated |
Deprecation information | {"since": "2.0", "use": "new_field"} |
Complete usage and examples: see Schema Definition Guide | Protocol Specification §4.3.
In the extension layer (metadata dictionary), you can provide optional AI metadata to help agents understand when, how, and at what cost to use the module. These are conventions, not enforced by the framework.
Intent & Planning:
| Key | Purpose |
|---|---|
x-when-to-use |
Positive guidance: scenarios where this module is the right choice |
x-when-not-to-use |
Negative guidance: scenarios where a different module should be used |
x-common-mistakes |
Known pitfalls that AI agents frequently encounter |
x-workflow-hints |
Suggested pre/post steps or related modules in a typical workflow |
x-preconditions |
What must be true before calling (e.g., "User must be authenticated") |
x-postconditions |
What will be true after successful execution |
x-side-effects |
External state changes caused by this module |
Performance, Cost & Trust:
| Key | Purpose |
|---|---|
x-cost-per-call |
Estimated cost per invocation |
x-avg-latency-ms |
Average execution latency in milliseconds |
x-max-latency-ms |
Maximum expected latency in milliseconds |
x-sla |
SLA targets (availability, latency percentiles) |
x-output-source |
Data provenance: database, api, generated, cached, computed |
x-verification-hint |
How to cross-check the output for correctness |
Detailed usage: see Protocol Specification §4.6.
Context is the execution context that runs through the entire call chain, carrying tracing, permissions, and shared data:
class Context:
trace_id: str # Call trace ID (32-char lowercase hex, W3C Trace Context compatible)
caller_id: str | None # Caller module ID (None for top-level calls)
call_chain: list[str] # Call chain (accumulated in call order)
executor: Executor # Executor reference (entry point for inter-module calls)
identity: Identity # Caller identity
data: dict # Shared data (reference-shared within call chain)# Top-level call
context = Context(trace_id="abc-123", identity=Identity(id="user_1", roles=["admin"]))
# Module A is called
# trace_id: "abc-123" ← Stays the same
# caller_id: None ← No caller at top level
# call_chain: ["module_a"]
# Module A internally calls Module B
result = context.executor.call("module_b", inputs, context)
# trace_id: "abc-123" ← Stays the same
# caller_id: "module_a" ← Caller is module_a
# call_chain: ["module_a", "module_b"]
# Module B internally calls Module C
# trace_id: "abc-123" ← Same trace_id for entire chain
# caller_id: "module_b"
# call_chain: ["module_a", "module_b", "module_c"]Key Feature: context.data is reference-shared throughout the entire call chain, allowing modules to pass intermediate results and implement pipeline-style data flow.
Detailed definitions: ACL Configuration Guide | Protocol Specification §6
ACL (Access Control List) controls which modules can call which modules, default deny:
# acl/global_acl.yaml
rules:
# API layer can only call orchestration layer
- callers: ["api.*"]
targets: ["orchestrator.*"]
effect: allow
# Orchestration layer can call execution layer
- callers: ["orchestrator.*"]
targets: ["executor.*"]
effect: allow
# Forbid cross-layer calls (API directly calling execution layer)
- callers: ["api.*"]
targets: ["executor.*"]
effect: deny
# System internal modules unrestricted
- callers: ["@system"]
targets: ["*"]
effect: allow
default_effect: deny # Default deny when no rules match
audit:
enabled: true
log_level: info
include_denied: true| Identifier | Description |
|---|---|
@external |
Top-level external call (HTTP request, CLI command, etc.) |
@system |
Framework internal call |
* |
Wildcard, matches all |
rules:
- callers: ["api.*"]
targets: ["executor.payment.*"]
effect: allow
conditions:
identity_types: ["user"] # Only user identity
roles: ["admin", "finance"] # Only admin or finance roles
max_call_depth: 5 # Maximum call depthDetailed definitions: Middleware Guide
Middleware uses the Onion Model, allowing custom logic to be inserted before and after module execution:
Request → [MW1.before → [MW2.before → [MW3.before →
[Module.execute()]
← MW3.after] ← MW2.after] ← MW1.after] ← Response
class LoggingMiddleware(Middleware):
def before(self, module_id: str, inputs: dict, context: Context) -> dict:
log.info(f"Calling {module_id} with trace_id={context.trace_id}")
return inputs # Can modify inputs
def after(self, module_id: str, inputs: dict, output: dict, context: Context) -> dict:
log.info(f"Result from {module_id}: success")
return output # Can modify output
def on_error(self, module_id: str, inputs: dict, error: Exception, context: Context):
log.error(f"Error in {module_id}: {error}")
# Return None to continue error propagation
# Return a dict to stop propagation and use it as recovery output
return NoneTypical middleware scenarios: logging, performance monitoring, caching, rate limiting, retry, auditing.
The runtime is configured through apcore.yaml (legacy mode — fully backward compatible):
# apcore.yaml
version: "1.0.0"
project:
name: "my-ai-project"
version: "0.1.0"
extensions:
root: "./extensions" # Module root directory
auto_discover: true # Auto-scan and discover
lazy_load: true # Lazy load (load module only on first call)
max_depth: 8 # Maximum directory depth
schema:
root: "./schemas"
strategy: "yaml_first" # yaml_first | native_first | yaml_only
validation:
strict: true # Strict validation mode
coerce_types: true # Automatic type coercion
acl:
root: "./acl"
default_effect: "deny" # Default deny
audit:
enabled: true
logging:
level: "info" # trace | debug | info | warn | error | fatal
format: "json" # json | text
observability:
tracing:
enabled: true
sampling_rate: 1.0 # 1.0 = full collection, 0.1 = 10% sampling
exporter: "stdout" # stdout | otlp | jaeger
metrics:
enabled: true
exporter: "prometheus"When using multiple apcore ecosystem packages, a single project.yaml can configure everything through the Config Bus (see PROTOCOL_SPEC §9.4–9.14):
# project.yaml — one file, all packages
apcore:
version: "1.0.0"
extensions:
root: ./extensions
project:
name: my-project
apflow:
api:
server_url: http://localhost:8000
timeout: 30.0
apcore-mcp:
transport: streamable-http
port: 8000
apcore-a2a:
name: "My Agent"
url: http://localhost:9000Each package registers its own namespace with Config.register_namespace(). Third-party projects can also participate via config.mount() without modifying their existing configuration files. See the protocol spec for the full integration spectrum — from zero-coupling to full unification.
Environment variable overrides work per namespace: APCORE_EXECUTOR_DEFAULT__TIMEOUT=5000 for apcore, APFLOW_API_TIMEOUT=60 for apflow, APCORE_MCP_PORT=9000 for apcore-mcp.
apcore has built-in three pillars of observability, compatible with OpenTelemetry:
trace_idis automatically generated and propagated through the call chain- Span naming convention:
apcore.{component}.{operation} - Supports export to stdout / OTLP / Jaeger
- Structured logging, automatically includes
trace_id - Fields marked with
x-sensitiveare automatically redacted (e.g., passwords show as***REDACTED***) - Executor automatically provides
context.redacted_inputs, middleware and logs should use redacted data
| Metric Name | Type | Description |
|---|---|---|
apcore_module_calls_total |
Counter | Total module calls |
apcore_module_duration_seconds |
Histogram | Module execution duration distribution |
apcore_module_errors_total |
Counter | Total module errors |
apcore defines a unified error format including ai_guidance. While standard errors tell a program what went wrong, ai_guidance tells the Agent how to fix it and retry, enabling Self-Healing Agents.
Self-Healing serves two higher-level goals: Self-Repair (autonomous error correction within a single interaction) and Self-Evolution (continuous system adaptation through health monitoring, feedback loops, and runtime reconfiguration).
{
"code": "SCHEMA_VALIDATION_ERROR",
"message": "Input validation failed",
"details": {"field": "email", "reason": "invalid format"},
"cause": "ValidationError: value is not a valid email address",
"ai_guidance": "Please ask the user for a valid email address.",
"trace_id": "abc-123",
"timestamp": "2026-01-01T00:00:00Z"
}| Category | Error Code | Description | Retryable |
|---|---|---|---|
| Module | MODULE_NOT_FOUND |
Module does not exist | No |
| Module | MODULE_EXECUTE_ERROR |
Execution exception | Depends |
| Module | MODULE_TIMEOUT |
Execution timeout | Yes |
| Schema | SCHEMA_VALIDATION_ERROR |
Input/output validation failed | No |
| Schema | SCHEMA_NOT_FOUND |
Schema file does not exist | No |
| ACL | ACL_DENIED |
Permission denied | No |
| Binding | BINDING_INVALID_TARGET |
Invalid binding target path | No |
| Binding | BINDING_CALLABLE_NOT_FOUND |
Bound callable object not found | No |
| Approval | APPROVAL_DENIED |
Approval explicitly denied | No |
| Approval | APPROVAL_TIMEOUT |
Approval request timed out | Yes |
| Approval | APPROVAL_PENDING |
Approval still pending | Yes |
| General | GENERAL_INTERNAL_ERROR |
Internal error | Yes |
apcore is a language-agnostic module standard. Canonical IDs are automatically adapted to local naming conventions in different languages:
Canonical ID (universal): executor.email.send_email
Local representation:
Python: executor/email/send_email.py class SendEmailModule
Rust: executor/email/send_email.rs struct SendEmailModule
Go: executor/email/send_email.go type SendEmailModule
Java: executor/email/SendEmail.java class SendEmailModule
TypeScript: executor/email/sendEmail.ts class SendEmailModule
- Automatic language detection (based on file extension)
- Case conversion (PascalCase ↔ snake_case ↔ camelCase)
- Path separator normalization (
/vs::vs.) - Supports manual override (
id_mapconfiguration)
Any language SDK implementation can choose different conformance levels:
| Level | Scope | Includes |
|---|---|---|
| Level 0 (Core) | Minimally viable | ID mapping, Schema loading, Registry, Executor |
| Level 1 (Standard) | Production ready | + ACL, middleware, error handling, observability |
| Level 2 (Full) | Complete implementation | + Extension point system, async task management, W3C Trace Context, Prometheus metrics, version negotiation, schema migration, module isolation, multi-version coexistence |
Reference Implementation: apcore-python
| apcore | MCP | |
|---|---|---|
| Positioning | Module standard | Communication protocol |
| Solves | How to build modules | How to call tools |
| Focus | Code organization, Schema, ACL, observability | Transport format, RPC |
| Relationship | apcore modules can be exposed as MCP Server | MCP is one exposure method |
| apcore | LangChain etc. | |
|---|---|---|
| Positioning | AI-Perceivable module standard | LLM application development framework |
| Focus | Module standardization, Schema, permissions | Chaining, Prompt, RAG |
| Relationship | Complementary — apcore modules can serve as LangChain Tools |
| apcore | CrewAI etc. | |
|---|---|---|
| Positioning | AI-Perceivable module standard | Agent orchestration framework |
| Focus | Standardizing individual modules | Multi-agent collaboration strategies |
| Relationship | Complementary — Agents can call apcore modules |
In short: apcore focuses on building standardized, AI-understandable modules, complementary rather than competitive with upper-layer AI protocols/frameworks.
Language SDK implementations of the apcore protocol specification:
| Language | Repository | Features | Install |
|---|---|---|---|
| Python | apcore-python | Schema validation, Registry, Executor, @module decorator, YAML bindings, ACL, Middleware, Observability, Async support | pip install apcore |
| Typescript | apcore-typescript | Schema validation, Registry, Executor, @module decorator, YAML bindings, ACL, Middleware, Observability, Async support | npm install apcore-js |
| Rust | apcore-rust | Schema validation, Registry, Executor, #[module] macro, YAML bindings, ACL, Middleware, Observability, Async support | cargo add apcore |
Interested in implementing apcore for another language? See the Protocol Specification and Conformance Definition.
The apcore ecosystem uses a core + independent adapters architecture. The core does not include any framework-specific implementations; adapters are developed in independent repositories by official or community contributors. All packages share a unified configuration system via the Config Bus (§9.4).
apcore (Protocol Spec)
Config Bus (§9.4–9.14)
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
apcore-python apcore-typescript apcore-rust
│ │ │
└───────────┬────────┴───────────┬────────┘
│ │
┌─────────────┼─────────────┬──────┴──────┐
▼ ▼ ▼ ▼
apcore-mcp apcore-a2a apcore-cli (others)
(MCP Server) (A2A Agent) (CLI)
| Adapter | Description | Python | TypeScript | Install |
|---|---|---|---|---|
| apcore-mcp | Expose apcore modules as MCP Server — auto-discovery, annotation mapping, display overlay (§5.13), Tool Explorer UI | apcore-mcp-python | apcore-mcp-typescript | pip install apcore-mcp / npm install apcore-mcp |
| apcore-a2a | Expose apcore modules as A2A Agent — auto Agent Card, skill mapping, display overlay (§5.13), streaming, push notifications | apcore-a2a-python | apcore-a2a-typescript | pip install apcore-a2a / npm install apcore-a2a |
| apcore-cli | Expose apcore modules as CLI commands — auto command routing from display overlay (§5.13), descriptor cache, JSON output | apcore-cli-python | apcore-cli-typescript | pip install apcore-cli / npm install apcore-cli |
One module definition, multiple protocol endpoints:
# Define once with apcore
@module(id="email.send", description="Send email")
def send_email(to: str, subject: str, body: str) -> dict:
return {"success": True}
# Expose as MCP Server (for Claude, Cursor, etc.)
from apcore_mcp import serve as mcp_serve
mcp_serve(registry)
# Expose as A2A Agent (for agent-to-agent communication)
from apcore_a2a import serve as a2a_serve
a2a_serve(registry)
# Expose as CLI commands (for terminal usage)
from apcore_cli import serve as cli_serve
cli_serve(registry)| Project | Description | Install |
|---|---|---|
| apcore-toolkit | Shared scanner, schema extraction, display resolver, and output writers — used by framework integrations and surface adapters | pip install apcore-toolkit / npm install apcore-toolkit |
| Type | Examples | Description |
|---|---|---|
| Web Frameworks | nestjs-apcore, flask-apcore, express-apcore |
Expose modules as HTTP APIs |
| AI Protocols | apcore-openai-tools |
Expose modules as OpenAI-compatible tools |
| RPC | apcore-grpc, apcore-thrift |
Expose modules as RPC services |
All adapters are built on the core's module() and External Binding mechanisms.
Development guide: see Adapter Development Guide.
| Document | Description |
|---|---|
| Protocol Specification | Complete standard specification (RFC 2119 Conformant) |
| Scope Definition | Responsibility boundaries (what's in/out of scope) |
| Positioning | Where apcore sits in the stack — relationship to MCP, A2A, CLI, REST |
| Roadmap | Project roadmap, milestones, and the path to 1.0 |
| Adopters | Projects and organizations building on apcore (add yourself) |
| Migration Guide — v0.18.0 | Consolidated breaking-change migration guide for the v0.18.0 release (annotations wire format, apcore-rust Config restructure, apcore-python event aliases) |
| Document | Description |
|---|---|
| Core Concepts | Design philosophy and core concepts explained |
| Architecture Design | Internal architecture, component interaction, memory model |
| Document | Description |
|---|---|
| Module Interface | Module Protocol contract: required attributes, lifecycle hooks, function-based form |
| Context Object | Per-invocation execution context: trace, identity, call chain, redaction, shared data |
| APCore Client | Unified high-level client managing Registry, Executor, and subsystems |
| ACL System | Pattern-based Access Control List with first-match-wins evaluation |
| Approval System | Runtime enforcement of requires_approval via pluggable ApprovalHandler |
| Async Task Management | Background module execution with concurrency limiting and task lifecycle |
| Call Chain Guard | Depth limiting, circular detection, and frequency throttling |
| Cancellation | Cooperative cancellation via CancelToken with executor timeout integration |
| Config Bus | Unified multi-package configuration with per-namespace env overrides |
| Core Executor | Central execution engine with a secured execution lifecycle |
| Decorator & YAML Bindings | @module decorator and YAML-based module creation |
| Display Overlay | §5.13 — sparse display section in binding entries for per-surface alias, description, guidance, and tags |
| Error System | Structured error hierarchy with AI guidance fields and error code registry |
| Event System | Event emission, subscription, delivery lifecycle |
| Extension System | Pluggable extension points for discoverers, middleware, ACL, exporters |
| Identity System | Caller identity with types, roles, and ContextFactory protocol |
| Middleware System | Composable middleware pipeline with onion execution model |
| Multi-Module Discovery | Opt-in multi-class discovery: multiple Module classes per file, each with a snake_case-derived ID |
| Observability | Distributed tracing, metrics, and structured logging |
| Registry System | Module discovery, registration, and querying |
| Schema System | Schema loading, validation, $ref resolution, and export |
| Streaming | Three-phase streaming pipeline with deep merge accumulation |
| System Modules | Built-in system.* modules for health, manifest, usage, control |
| Document | Description |
|---|---|
| Creating Modules | Module creation tutorial (including four approaches) |
| Schema Definition | Complete Schema usage |
| ACL Configuration | Access control configuration |
| Middleware | Middleware development |
| Adapter Development | Framework adapter development |
| Testing Modules | Module testing guide |
| Multi-Language Development | Cross-language development guide |
| Document | Description |
|---|---|
| Type Mapping | Cross-language type mapping |
| API Surface & Naming Conventions | When a symbol is public API; why cross-boundary contract members MUST NOT carry a private name; per-language visibility-vs-discoverability idioms |
| Conformance Definition | Implementation conformance levels |
RFC — preview() Method |
Accepted RFC: optional Module.preview() for structured pre-execution diff (promoted to protocol-spec in v0.21.0) |
| RFC — Ephemeral Modules | Accepted RFC: ephemeral.* namespace + discoverable annotation for runtime-registered modules (promoted to protocol-spec in v0.21.0) |
RFC — include: Config Composition |
Proposed RFC (D-65, #75): top-level include: for cross-file apcore.yaml composition — relative paths, deep-merge local-wins, cycle detection |
| Algorithm Reference | Core algorithm summary (including namespace, redaction, etc.) |
| Durability Boundary | Stable hooks and explicit non-goals for retry/replay/workflow layers built on apcore |
Have a question, an idea, or want to introduce your project? Start a thread in GitHub Discussions.
Contributions are welcome in the following forms:
- Specification Feedback: Suggest improvements to the protocol specification in Issues
- SDK Implementation: Implement SDKs for other languages based on Protocol Specification
- Adapter Development: Develop adapters for web frameworks or AI protocols
- Documentation Improvements: Fix, translate, or supplement documentation
New contributors should read CONTRIBUTING.md first. Project governance is documented in:
- GOVERNANCE.md — decision-making and the contributor → reviewer → maintainer ladder
- MAINTAINERS.md — current maintainers and sub-project ownership
- CODE_OF_CONDUCT.md — community standards
- SECURITY.md — reporting vulnerabilities (never open public issues for these)
- ADOPTERS.md — who builds on apcore
Apache 2.0