Skip to content

Commit 36860da

Browse files
committed
feat: Enhance protocol specification with streaming support and module interface improvements
1 parent 4c76dc2 commit 36860da

File tree

8 files changed

+323
-154
lines changed

8 files changed

+323
-154
lines changed

PROTOCOL_SPEC.md

Lines changed: 172 additions & 92 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -264,27 +264,17 @@ pip install apcore
264264
```
265265

266266
```python
267-
from apcore import Module, Registry, Executor, Context
268-
from pydantic import BaseModel, Field
269-
270-
# 1. Define module (Schema-driven)
271-
class GreetInput(BaseModel):
272-
name: str = Field(..., description="User name")
273-
274-
class GreetOutput(BaseModel):
275-
message: str
267+
from apcore import module, Registry, Executor
276268

277-
class GreetModule(Module):
269+
# 1. Define module with @module decorator (Schema auto-inferred from type annotations)
270+
@module(id="executor.greet", tags=["greeting"])
271+
def greet(name: str) -> dict:
278272
"""Generate greeting message"""
279-
input_schema = GreetInput
280-
output_schema = GreetOutput
281-
282-
def execute(self, inputs: dict, context: Context) -> dict:
283-
return {"message": f"Hello, {inputs['name']}!"}
273+
return {"message": f"Hello, {name}!"}
284274

285275
# 2. Register & call
286276
registry = Registry()
287-
registry.register("executor.greet", GreetModule())
277+
registry.register("executor.greet", greet)
288278

289279
executor = Executor(registry=registry)
290280
result = executor.call("executor.greet", {"name": "World"})
@@ -838,7 +828,7 @@ Any language SDK implementation can choose different conformance levels:
838828
|------|------|------|
839829
| **Level 0 (Core)** | Minimally viable | ID mapping, Schema loading, Registry, Executor |
840830
| **Level 1 (Standard)** | Production ready | + ACL, middleware, error handling, observability |
841-
| **Level 2 (Full)** | Complete implementation | + Hot reload, distributed execution, advanced monitoring |
831+
| **Level 2 (Full)** | Complete implementation | + Extension point framework, async task management, W3C Trace Context, Prometheus metrics, version negotiation, schema migration, module isolation, multi-version coexistence |
842832

843833
**Reference Implementation**: [apcore-python](https://github.com/aipartnerup/apcore-python)
844834

@@ -896,7 +886,7 @@ The apcore ecosystem uses a **core + independent adapters** architecture. The co
896886

897887
| Type | Examples | Description |
898888
|------|------|------|
899-
| **Web Frameworks** | `apcore-fastapi`, `apcore-flask`, `apcore-express` | Expose modules as HTTP APIs |
889+
| **Web Frameworks** | `nestjs-apcore`, `flask-apcore`, `express-apcore` | Expose modules as HTTP APIs |
900890
| **AI Protocols** | `apcore-mcp`, `apcore-openai-tools` | Expose modules as AI tools |
901891
| **RPC** | `apcore-grpc`, `apcore-thrift` | Expose modules as RPC services |
902892

SCOPE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ Things explicitly **not within the project scope**:
9494
| **Distributed Execution** | Advanced runtime feature | Independent extension projects |
9595
| **UI/CLI Applications** | Focus on core protocol | Upstream projects |
9696
| **Specific Cloud Service Integrations** | Stay neutral | Extension packages |
97-
| **Framework Adapters** (Flask/FastAPI/Django) | Keep core pure, belongs to ecosystem projects | Independent repositories (e.g., apcore-fastapi) |
97+
| **Framework Adapters** (Flask/FastAPI/Django) | Keep core pure, belongs to ecosystem projects | Independent repositories (e.g., django-apcore) |
9898

99-
> apcore only defines the adapter interface specification (see [Adapter Development Guide](./docs/guides/adapter-development.md)); it does not include any framework-specific implementations. The community or official team can develop adapters in independent repositories (e.g., `apcore-fastapi`, `apcore-flask`, `apcore-django`), built on top of the core `module()` and External Binding mechanisms.
99+
> apcore only defines the adapter interface specification (see [Adapter Development Guide](./docs/guides/adapter-development.md)); it does not include any framework-specific implementations. The community or official team can develop adapters in independent repositories (e.g., `flask-apcore`, `django-apcore`), built on top of the core `module()` and External Binding mechanisms.
100100
101101
---
102102

docs/api/registry-api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ Steps:
677677

678678
### 9.1 Custom Discoverer
679679

680-
> **Reserved — Not Implemented.** The `set_discoverer()` API below is a reserved design; current SDKs do not support it. Documentation is retained for future reference.
680+
> Implemented in apcore-python v0.5.1+ and apcore-typescript v0.3.0+.
681681
682682
```python
683683
from apcore import Registry, ModuleDiscoverer
@@ -706,7 +706,7 @@ registry.discover()
706706

707707
### 9.2 Module Validator
708708

709-
> **Reserved — Not Implemented.** The `set_validator()` API below is a reserved design; current SDKs do not support it. Documentation is retained for future reference.
709+
> Implemented in apcore-python v0.5.1+ and apcore-typescript v0.3.0+.
710710
711711
```python
712712
from apcore import Registry, ModuleValidator
@@ -735,7 +735,7 @@ registry.discover()
735735

736736
### 9.3 Hot Reload (Development Mode)
737737

738-
> **Reserved — Not Implemented.** The `watch()` / `unwatch()` API below is a reserved design; current SDKs do not support it. Documentation is retained for future reference.
738+
> Implemented in apcore-python v0.5.1+ and apcore-typescript v0.3.0+. Python requires the optional `watchdog` dependency.
739739
740740
```python
741741
from apcore import Registry

docs/architecture.md

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,26 +87,39 @@ extensions/
8787

8888
### 2.1 Module
8989

90-
Modules are the smallest execution unit in apcore.
90+
Modules are the smallest execution unit in apcore. Modules can be defined via the `@module` decorator (primary approach) or by providing a class with an `execute()` method and required schema attributes. No abstract base class inheritance is required.
91+
92+
**Decorator approach (recommended):**
93+
94+
```python
95+
from apcore import module
96+
97+
@module(id="executor.greet", tags=["greeting"])
98+
def greet(name: str) -> dict:
99+
"""Generate greeting message"""
100+
return {"message": f"Hello, {name}!"}
101+
```
102+
103+
**Class-based approach (no ABC required):**
91104

92105
```python
93-
class Module(ABC):
94-
"""Module base class"""
106+
class SendEmailModule:
107+
"""Send email module"""
95108

96109
# ====== Core Layer (Required) ======
97-
input_schema: ClassVar[Type[BaseModel]]
98-
output_schema: ClassVar[Type[BaseModel]]
99-
description: ClassVar[str]
110+
input_schema = SendEmailInput # Type[BaseModel] or JSON Schema dict
111+
output_schema = SendEmailOutput # Type[BaseModel] or JSON Schema dict
112+
description = "Send email to specified recipient"
100113

101114
# ====== Annotation Layer (Optional) ======
102-
name: ClassVar[str | None]
103-
tags: ClassVar[list[str]]
104-
version: ClassVar[str]
105-
annotations: ClassVar[ModuleAnnotations | None] # Behavior annotations
106-
examples: ClassVar[list[ModuleExample]] # Usage examples
115+
name = None # Human-readable name
116+
tags = ["email"] # Tag list
117+
version = "1.0.0" # Semantic version
118+
annotations = None # ModuleAnnotations instance
119+
examples = [] # Usage examples
107120

108121
# ====== Extension Layer (Optional) ======
109-
metadata: ClassVar[dict[str, Any]] # Free metadata
122+
metadata = {} # Free metadata
110123

111124
# Core methods (def or async def both supported, framework auto-detects)
112125
def execute(self, inputs: dict, context: Context) -> dict: ...
@@ -488,10 +501,49 @@ my-project/
488501

489502
## 5. Extension Points
490503

491-
### 5.1 Custom Discoverer
504+
apcore provides a formal `ExtensionManager` API for managing pluggable extension points. The five built-in extension points are: `discoverer`, `middleware`, `acl`, `span_exporter`, and `module_validator`.
505+
506+
### 5.1 ExtensionManager API
507+
508+
The `ExtensionManager` provides a unified interface for registering, retrieving, and applying extensions:
509+
510+
| Method | Signature | Description |
511+
|--------|-----------|-------------|
512+
| `register` | `register(point_name, extension)` | Register an extension for a named extension point |
513+
| `get` | `get(point_name)` | Retrieve the single registered extension, or `None` |
514+
| `get_all` | `get_all(point_name)` | Retrieve all registered extensions for a point as a list |
515+
| `unregister` | `unregister(point_name, extension)` | Remove a registered extension; returns `bool` |
516+
| `apply` | `apply(registry, executor)` | Apply all registered extensions to the given registry and executor |
517+
| `list_points` | `list_points()` | List all registered extension points as a list of `ExtensionPoint` |
518+
519+
```python
520+
from apcore import ExtensionManager
521+
522+
manager = ExtensionManager()
523+
524+
# Register a custom discoverer
525+
manager.register("discoverer", remote_discoverer)
526+
527+
# Register a custom module validator
528+
manager.register("module_validator", strict_validator)
529+
530+
# Retrieve all discoverers
531+
discoverers = manager.get_all("discoverer")
532+
533+
# Retrieve single extension (or None)
534+
acl_ext = manager.get("acl")
535+
536+
# Remove an extension
537+
removed = manager.unregister("discoverer", remote_discoverer) # True
492538

493-
> **Note:** This example shows the conceptual interface pattern. The actual
494-
> Python SDK uses a function-based API (`apcore.registry.scanner.scan_extensions`).
539+
# Apply all extensions to registry and executor
540+
manager.apply(registry, executor)
541+
542+
# List registered points
543+
points = manager.list_points() # [ExtensionPoint(...), ...]
544+
```
545+
546+
### 5.2 Custom Discoverer
495547

496548
```python
497549
from apcore.registry.scanner import scan_extensions
@@ -510,10 +562,7 @@ class RemoteDiscoverer:
510562
return modules
511563
```
512564

513-
### 5.2 Custom Validator
514-
515-
> **Note:** This example shows the conceptual interface pattern. The actual
516-
> Python SDK uses a function-based API (`apcore.registry.validation.validate_module`).
565+
### 5.3 Custom Validator
517566

518567
```python
519568
from apcore.registry.validation import validate_module
@@ -532,7 +581,7 @@ class StrictValidator:
532581
return errors
533582
```
534583

535-
### 5.3 Custom ACL
584+
### 5.4 Custom ACL
536585

537586
```python
538587
from apcore import ACL
@@ -550,6 +599,8 @@ class RBACAuthorizer(ACL):
550599
return bool(set(context.identity.roles) & set(required_roles))
551600
```
552601

602+
> **Backward compatibility note:** The informal patterns shown above (subclassing, function-based APIs) continue to work. The `ExtensionManager` provides a formal unified API on top of these patterns for implementations that need programmatic extension point management.
603+
553604
---
554605

555606
## 6. Design Principles
@@ -666,13 +717,13 @@ def sync_caller():
666717
- **ACL check timeout**: independent timing (default 1 second)
667718
- Timing starts from first `before()` middleware
668719

669-
**Cancellation Strategy:** **Future (Not Implemented)** — The following is a planned design; current SDKs have not yet implemented this.
720+
**Cancellation Strategy:** Cooperative cancellation is implemented in both SDKs via `CancelToken`. Forced termination fallback is not yet implemented.
670721

671-
1. **Cooperative cancellation** (recommended):
722+
1. **Cooperative cancellation** (implemented):
672723
- Module checks `context.cancel_token.is_cancelled()` and actively exits
673724
- Suitable for long-running tasks (loops, I/O, etc.)
674725

675-
2. **Forced termination** (fallback):
726+
2. **Forced termination** (fallback, not yet implemented):
676727
- After cooperative cancellation fails, wait grace period (default 5 seconds)
677728
- If still not exited, force terminate thread/coroutine (may cause resource leaks)
678729

docs/features/observability.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ Comprehensive observability with distributed tracing, metrics collection, and st
2525
- Implement `ContextLogger` as a standalone structured logger with JSON and text output formats.
2626
- Support log levels: trace, debug, info, warn, error, fatal.
2727
- Inject trace context (trace_id, module_id, caller_id) into every log entry.
28-
- Automatically redact fields with `_secret_` prefix when `redact_sensitive=True`.
28+
- Automatically redact sensitive data using two mechanisms:
29+
1. `x-sensitive` schema annotation: used by the Executor to redact annotated fields from input/output before logging.
30+
2. `_secret_` key prefix: used by `ContextLogger` to redact matching keys in log extras when `redact_sensitive=True`.
2931
- Provide `ContextLogger.from_context()` factory for automatic context extraction.
3032
- Implement `ObsLoggingMiddleware` using `ContextLogger` with stack-based timing and configurable input/output logging.
3133

@@ -97,6 +99,35 @@ As documented in the package `__init__.py`:
9799
2. `MetricsMiddleware` -- Captures execution timing.
98100
3. `ObsLoggingMiddleware` -- Logs with timing already set up (innermost).
99101

102+
### W3C Trace Context
103+
104+
apcore supports [W3C Trace Context](https://www.w3.org/TR/trace-context/) for distributed tracing interoperability. The `TraceContext` class provides methods to inject and extract `traceparent` headers, enabling trace propagation across service boundaries.
105+
106+
| Method | Description |
107+
|--------|-------------|
108+
| `TraceContext.inject(context)` | Convert an apcore `Context` into a headers dict (`dict[str, str]` / `Record<string, string>`) containing a `traceparent` key |
109+
| `TraceContext.extract(headers)` | Parse a `traceparent` header from an incoming request headers dict and return a `TraceParent` |
110+
| `TraceContext.from_traceparent(str)` | Strict parsing of a `traceparent` string into a `TraceParent` object |
111+
112+
Integration with `Context.create()`:
113+
114+
```python
115+
from apcore import Context
116+
from apcore.observability import TraceContext
117+
118+
# Extract trace parent from incoming request
119+
trace_parent = TraceContext.extract(request.headers)
120+
121+
# Create context with propagated trace
122+
context = Context.create(trace_parent=trace_parent)
123+
124+
# Inject trace parent into outgoing request headers
125+
outgoing_headers = TraceContext.inject(context)
126+
# outgoing_headers = {"traceparent": "00-<trace_id>-<span_id>-01"}
127+
```
128+
129+
The `traceparent` header follows the W3C format: `{version}-{trace_id}-{parent_id}-{trace_flags}`.
130+
100131
## Key Files
101132

102133
| File | Lines | Purpose |

docs/guides/adapter-development.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ The apcore core remains pure and **does not include** any web framework-specific
1414
↑ Built on core mechanisms
1515
┌──────────┬──────────┬──────────┬──────────┐
1616
│ │ │ │ │
17-
apcore- apcore- apcore- apcore- ...
18-
fastapi flask django express
17+
tiptap- flask- django- express- ...
18+
apcore apcore apcore apcore
1919
(separate) (separate) (separate) (separate)
2020
```
2121

@@ -39,16 +39,16 @@ Adapters **should not**:
3939
### Repository Naming
4040

4141
```
42-
apcore-{framework}
42+
{framework}-apcore
4343
```
4444

45-
Examples: `apcore-fastapi`, `apcore-flask`, `apcore-django`, `apcore-express`
45+
Examples: `flask-apcore`, `django-apcore`, `express-apcore`
4646

4747
### Package Naming
4848

4949
```
50-
pip install apcore-{framework}
51-
npm install apcore-{framework}
50+
pip install {framework}-apcore
51+
npm install {framework}-apcore
5252
```
5353

5454
## 4. Adapter Interface Reference

docs/spec/conformance.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Level 1 adds permission control, middleware, basic observability, and structured
110110
| **Context complete implementation** | `trace_id`, `caller_id`, `call_chain`, `executor`, `identity`, `data` | PROTOCOL_SPEC §5.7 |
111111
| **Circular call detection** | Detect circular calls based on `call_chain` | PROTOCOL_SPEC §5.7 |
112112
| **Call depth limit** | Limit maximum call depth based on `call_chain` | PROTOCOL_SPEC §5.7 |
113-
| **Error hierarchy system** | Complete error type hierarchy (ApCoreError → ModuleError/SchemaError/ACLError/...) | PROTOCOL_SPEC §7.7 |
113+
| **Error hierarchy system** | Flat error hierarchy under `ModuleError` base class | PROTOCOL_SPEC §7.7 |
114114
| **Custom error codes** | Module custom error code registration and collision detection | PROTOCOL_SPEC §7.4 |
115115
| **ID Map all-language conversion** | Support ID conversion for all five languages | PROTOCOL_SPEC §2.2 |
116116
| **Dependency resolution** | `resolve_dependencies()` topological sort and circular dependency detection | PROTOCOL_SPEC §5.3 |
@@ -164,11 +164,11 @@ Level 2 adds all extension points, async modules, hot loading, and advanced obse
164164

165165
| Component | Responsibility | Reference Section |
166166
|------|------|---------|
167-
| **Extension point framework** | All five extension points (SchemaLoader, ModuleLoader, IDConverter, ACLChecker, Executor) | PROTOCOL_SPEC §10.3, §10.6 |
167+
| **Extension point framework** | All five extension points (discoverer, middleware, acl, span_exporter, module_validator) via `ExtensionManager` with `register()`, `get()`, `get_all()`, `unregister()`, `apply()`, `list_points()`. Note: these names map to actual runtime extension needs rather than the original theoretical design names (SchemaLoader, ModuleLoader, IDConverter, ACLChecker, Executor). | PROTOCOL_SPEC §10.3, §10.6 |
168168
| **Extension point chain** | `first_success`, `all`, `fallback` strategies | PROTOCOL_SPEC §10.3 |
169169
| **Extension loading order** | `load_extensions()` algorithm | PROTOCOL_SPEC §10.7 |
170-
| **Async modules** | `execute_async()`, `get_status()`, `cancel()` | PROTOCOL_SPEC §5.8 |
171-
| **Async state machine** | State transition rules (idlependingrunning → completed/failed/cancelled) | PROTOCOL_SPEC §5.8 |
170+
| **Async modules** | `submit()`, `get_status()`, `cancel()`, `list_tasks()` via `AsyncTaskManager` | PROTOCOL_SPEC §5.8 |
171+
| **Async state machine** | State transition rules (PENDINGRUNNINGCOMPLETED/FAILED/CANCELLED) via `TaskStatus` enum | PROTOCOL_SPEC §5.8 |
172172
| **Middleware state machine** | Complete state transitions (init → before → execute → after → done, with error branches) | PROTOCOL_SPEC §10.5 |
173173
| **Version negotiation** | `negotiate_version()` algorithm | PROTOCOL_SPEC §12.3 |
174174
| **Schema migration** | `migrate_schema()` algorithm | PROTOCOL_SPEC §12.4 |
@@ -539,10 +539,8 @@ conformance:
539539
total: 10
540540
541541
# Known deviations (if any)
542-
known_deviations:
543-
- id: "T03-012"
544-
reason: "$ref circular reference detection is Level 0 non-mandatory, not yet implemented (target: v0.2.0)"
545-
severity: "minor"
542+
# Format: - { id: "T03-xxx", reason: "...", severity: "minor|major" }
543+
known_deviations: []
546544
547545
# Optional feature support
548546
optional_features:
@@ -576,7 +574,26 @@ apcore Conformant — Level 2 (Full)
576574

577575
---
578576

579-
## 7. References
577+
## 7. Known Deviations
578+
579+
The following features are specified in PROTOCOL_SPEC but not yet fully implemented in current SDK releases (apcore-python, apcore-typescript). Implementers **should** document these deviations in their conformance declarations.
580+
581+
| Feature | Spec Reference | Current Status |
582+
|---------|---------------|----------------|
583+
| `Config` class (YAML loading, env override, schema validation) | PROTOCOL_SPEC §8.1, §8.2, §8.3 | Stub implementation only. YAML loading, environment variable override, and `validate_config()` schema validation are not implemented. |
584+
| Registry schema query/export methods (`get_schema`, `export_schema`, etc.) | PROTOCOL_SPEC §11.2 | Not on `Registry`. A standalone `SchemaExporter` class is available for schema export. |
585+
| Error codes `GENERAL_NOT_IMPLEMENTED` and `DEPENDENCY_NOT_FOUND` | PROTOCOL_SPEC §7.2, §7.7 | Error code constants defined but corresponding error classes not yet implemented. |
586+
| Version negotiation | PROTOCOL_SPEC §12.3 | `negotiate_version()` algorithm not yet implemented. |
587+
| Schema migration | PROTOCOL_SPEC §12.4 | `migrate_schema()` algorithm not yet implemented. |
588+
| Module isolation | PROTOCOL_SPEC §5.5 | Process-level or container-level isolation not yet implemented. |
589+
| Multi-version coexistence | PROTOCOL_SPEC §5.4 | Multiple versions of the same module running concurrently not yet implemented. |
590+
| `AsyncTaskManager.submit()` / `cancel()` sync vs async | PROTOCOL_SPEC §5.8 | Python `AsyncTaskManager.submit()` and `cancel()` are async methods; TypeScript equivalents are synchronous. |
591+
592+
Implementations declaring conformance **must** list any of these deviations that apply in their `known_deviations` section.
593+
594+
---
595+
596+
## 8. References
580597

581598
- [PROTOCOL_SPEC §11 — SDK Implementation Guide](../../PROTOCOL_SPEC.md#11-sdk-implementation-guide)
582599
- [PROTOCOL_SPEC §11.4 — Conformance Testing Requirements](../../PROTOCOL_SPEC.md#114-conformance-testing-requirements)

0 commit comments

Comments
 (0)