Skip to content

Commit e3c442d

Browse files
committed
feat: Add AI error guidance fields, AI intent metadata, and expand protocol context and error codes.
1 parent 7d8ce1d commit e3c442d

File tree

5 files changed

+138
-20
lines changed

5 files changed

+138
-20
lines changed

PROTOCOL_SPEC.md

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
> Version: 1.3.0-draft
66
> Status: Draft Specification (RFC 2119 Conformant)
77
> Stability: Specification content is stable, pending reference implementation verification
8-
> Last Updated: 2026-03-01
8+
> Last Updated: 2026-03-05
99
1010
---
1111

@@ -1972,6 +1972,22 @@ context_schema:
19721972
data:
19731973
type: object
19741974
description: "Shared pipeline state (reference passing, readable/writable along call chain)"
1975+
1976+
# ====== Optional extension fields (MAY be provided by implementations) ======
1977+
1978+
cancel_token:
1979+
nullable: true
1980+
description: "Cooperative cancellation token for long-running operations (MAY)"
1981+
1982+
services:
1983+
type: object
1984+
nullable: true
1985+
description: "Dependency injection container for sharing services across the call chain (MAY)"
1986+
1987+
redacted_inputs:
1988+
type: object
1989+
nullable: true
1990+
description: "Copy of inputs with x-sensitive fields replaced by REDACTED_VALUE, for safe logging (MAY)"
19751991
```
19761992
19771993
**Field Classification Rationale:**
@@ -1984,6 +2000,9 @@ context_schema:
19842000
| `executor` | Framework engine dependency | Only channel for inter-module calls |
19852001
| `identity` | Widely needed | ACL is framework first-class citizen, needs standardized "who" |
19862002
| `data` | Universal bag | span_id, locale, pipeline intermediate state, etc. all mutable data |
2003+
| `cancel_token` | Optional extension | Cooperative cancellation for timeout enforcement |
2004+
| `services` | Optional extension | DI container for framework integrations |
2005+
| `redacted_inputs` | Optional extension | Safe logging of sensitive inputs |
19872006

19882007
**Context Serialization Specification (Cross-process Scenarios):**
19892008

@@ -2000,6 +2019,9 @@ context_serialization:
20002019
- "identity: MUST serialize as JSON object"
20012020
- "data: SHOULD serialize, but MUST exclude non-serializable values"
20022021
- "When data contains functions/connections etc. non-serializable values, MUST silently skip and log warning"
2022+
- "cancel_token: MUST NOT serialize (runtime object)"
2023+
- "services: MUST NOT serialize (runtime injected)"
2024+
- "redacted_inputs: MAY serialize as JSON object"
20032025
```
20042026

20052027
### 5.8 Async Module Specification
@@ -2610,7 +2632,7 @@ Implementations **must** handle module edge cases according to the following tab
26102632
| Module called before load completion | Wait for load completion or throw `MODULE_NOT_FOUND` | **SHOULD** |
26112633
| Module called after unload | Throw `MODULE_NOT_FOUND` | **MUST** |
26122634
| Repeated `discover()` of same module | If `metadata.yaml` unchanged, skip (idempotent) | **MUST** |
2613-
| Hot reload while module executing | See §11.7.3 Hot Reload Race Conditions | **MUST** |
2635+
| Hot reload while module executing | See §12.7.3 Hot Reload Race Conditions | **MUST** |
26142636

26152637
**Note**:
26162638
- Dependency topological sorting uses algorithm A07 (§5.3)
@@ -3143,6 +3165,14 @@ The four optional fields (`retryable`, `ai_guidance`, `user_fixable`, `suggestio
31433165

31443166
```yaml
31453167
error_codes:
3168+
# Configuration-related (CONFIG_*)
3169+
CONFIG_NOT_FOUND:
3170+
description: "Configuration file not found"
3171+
http_status: 500
3172+
CONFIG_INVALID:
3173+
description: "Invalid configuration file"
3174+
http_status: 500
3175+
31463176
# Module-related (MODULE_*)
31473177
MODULE_NOT_FOUND:
31483178
description: "Module doesn't exist"
@@ -3167,6 +3197,9 @@ error_codes:
31673197
SCHEMA_PARSE_ERROR:
31683198
description: "Schema parse error"
31693199
http_status: 500
3200+
SCHEMA_CIRCULAR_REF:
3201+
description: "Schema circular reference detected"
3202+
http_status: 500
31703203
31713204
# Permission-related (ACL_*)
31723205
ACL_DENIED:
@@ -3200,6 +3233,32 @@ error_codes:
32003233
BINDING_SCHEMA_MISSING:
32013234
description: "Binding Schema missing"
32023235
http_status: 500
3236+
BINDING_FILE_INVALID:
3237+
description: "Binding file parse error"
3238+
http_status: 500
3239+
3240+
# Middleware-related (MIDDLEWARE_*)
3241+
MIDDLEWARE_CHAIN_ERROR:
3242+
description: "Middleware chain execution failed"
3243+
http_status: 500
3244+
3245+
# Version-related (VERSION_*)
3246+
VERSION_INCOMPATIBLE:
3247+
description: "SDK/config version incompatible"
3248+
http_status: 500
3249+
3250+
# Error code registry (ERROR_CODE_*)
3251+
ERROR_CODE_COLLISION:
3252+
description: "Custom error code collides with framework or other module code"
3253+
http_status: 500
3254+
3255+
# Dependency-related (CIRCULAR_*, DEPENDENCY_*)
3256+
CIRCULAR_DEPENDENCY:
3257+
description: "Module dependency cycle detected"
3258+
http_status: 500
3259+
DEPENDENCY_NOT_FOUND:
3260+
description: "Dependent module doesn't exist"
3261+
http_status: 500
32033262
32043263
# General errors (GENERAL_*)
32053264
GENERAL_INVALID_INPUT:
@@ -3397,10 +3456,13 @@ Implementations **must not** default retry failed module invocations. Retry beha
33973456
| `BINDING_SCHEMA_MISSING` | **No** | Schema missing for binding, needs code fix |
33983457
| `BINDING_FILE_INVALID` | **No** | Binding file parse error, needs config fix |
33993458
| `CIRCULAR_DEPENDENCY` | **No** | Module dependency cycle, needs architecture fix |
3459+
| `MIDDLEWARE_CHAIN_ERROR` | **No** | Middleware failed, needs code fix |
3460+
| `VERSION_INCOMPATIBLE` | **No** | Version mismatch, needs upgrade or config fix |
3461+
| `ERROR_CODE_COLLISION` | **No** | Error code conflict, needs code fix |
34003462

34013463
Implementations **should** use this table as the default `retryable` value for each error subclass. Callers may override the default on a per-instance basis.
34023464

3403-
> **Note:** The following error codes are forward-declared for future features and do not yet have exception classes or retryability defaults: `GENERAL_NOT_IMPLEMENTED`, `DEPENDENCY_NOT_FOUND`, `MIDDLEWARE_CHAIN_ERROR`. Implementations **should** assign retryability defaults when the corresponding exception classes are introduced.
3465+
> **Note:** The following error codes are forward-declared for future features and do not yet have exception classes or retryability defaults: `GENERAL_NOT_IMPLEMENTED`, `DEPENDENCY_NOT_FOUND`. Implementations **should** assign retryability defaults when the corresponding exception classes are introduced.
34043466

34053467
Retry middleware (if implemented) **should**:
34063468
- Only retry errors marked as retryable
@@ -3433,18 +3495,22 @@ ModuleError (base error for all framework errors)
34333495
├── BindingCallableNotFoundError # BINDING_CALLABLE_NOT_FOUND — Can't find target callable
34343496
├── BindingNotCallableError # BINDING_NOT_CALLABLE — Target not callable
34353497
├── BindingSchemaMissingError # BINDING_SCHEMA_MISSING — Schema missing
3498+
├── BindingFileInvalidError # BINDING_FILE_INVALID — Binding file parse error
34363499
├── CircularDependencyError # CIRCULAR_DEPENDENCY — Circular dependency
34373500
├── DependencyNotFoundError # DEPENDENCY_NOT_FOUND — Dependent module doesn't exist
34383501
├── CallDepthExceededError # CALL_DEPTH_EXCEEDED — Call depth exceeded limit
34393502
├── CircularCallError # CIRCULAR_CALL — Circular call
34403503
├── CallFrequencyExceededError # CALL_FREQUENCY_EXCEEDED — Call frequency exceeded limit
3504+
├── MiddlewareChainError # MIDDLEWARE_CHAIN_ERROR — Middleware chain execution failed
34413505
├── ApprovalError # Base class for approval errors (§7)
34423506
│ ├── ApprovalDeniedError # APPROVAL_DENIED — Approval explicitly rejected
34433507
│ ├── ApprovalTimeoutError # APPROVAL_TIMEOUT — Approval timed out
34443508
│ └── ApprovalPendingError # APPROVAL_PENDING — Approval pending (Phase B)
34453509
├── InvalidInputError # GENERAL_INVALID_INPUT — Invalid input
34463510
├── InternalError # GENERAL_INTERNAL_ERROR — Internal error
3447-
└── NotImplementedError # GENERAL_NOT_IMPLEMENTED — Feature not implemented
3511+
├── NotImplementedError # GENERAL_NOT_IMPLEMENTED — Feature not implemented
3512+
├── VersionIncompatibleError # VERSION_INCOMPATIBLE — SDK/config version incompatible
3513+
└── ErrorCodeCollisionError # ERROR_CODE_COLLISION — Error code collision detected
34483514
```
34493515
34503516
Each error class carries a `code` attribute set to the corresponding error code string (e.g., `MODULE_NOT_FOUND`). Implementations **must** ensure all framework-thrown errors are instances of `ModuleError`. Module custom errors **should** also extend `ModuleError` directly.
@@ -4030,7 +4096,7 @@ Extension Point: Executor
40304096
execute(module: Module, method: String, inputs: Map, context: Context) → Map
40314097
```
40324098

4033-
> **NOTE:** The interface contracts above use the original theoretical names. See the mapping table in §10.3 for the actual extension point names used in SDK implementations (`discoverer`, `middleware`, `acl`, `span_exporter`, `module_validator`).
4099+
> **NOTE:** The interface contracts above use the original theoretical names. See the mapping table in §11.3 for the actual extension point names used in SDK implementations (`discoverer`, `middleware`, `acl`, `span_exporter`, `module_validator`).
40344100

40354101
### 11.7 Extension Loading Order
40364102

@@ -4091,7 +4157,7 @@ Implementations **must** handle middleware edge cases according to the following
40914157

40924158
**Note**:
40934159
- Timeout timer should start at first `before()` call
4094-
- Timeout enforcement algorithm see §11.7.4 and algorithms.md A22
4160+
- Timeout enforcement algorithm see §12.7.4 and algorithms.md A22
40954161

40964162
---
40974163

@@ -4226,7 +4292,7 @@ Interface: MiddlewareManager
42264292
Interface: TracingProvider
42274293
/**
42284294
* Create new Span
4229-
* @param name — Span name (follows §9.7 naming convention)
4295+
* @param name — Span name (follows §10.7 naming convention)
42304296
* @param context — Execution context (contains trace_id, parent_span_id)
42314297
* @return span — Span object
42324298
*/
@@ -4286,14 +4352,14 @@ All SDKs **MUST** export these event names as named constants (e.g., TypeScript:
42864352
42874353
#### Error Code Constants Export Requirement
42884354
4289-
All SDKs **MUST** export the framework error codes defined in Section 7 as enumerated constants. This prevents magic string dependencies and enables IDE autocomplete.
4355+
All SDKs **MUST** export the framework error codes defined in Section 8 as enumerated constants. This prevents magic string dependencies and enables IDE autocomplete.
42904356
42914357
Example (TypeScript):
42924358
```typescript
42934359
export const ErrorCodes = {
42944360
MODULE_NOT_FOUND: "MODULE_NOT_FOUND",
42954361
SCHEMA_VALIDATION_ERROR: "SCHEMA_VALIDATION_ERROR",
4296-
// ... all codes from Section 7
4362+
// ... all codes from Section 8
42974363
} as const;
42984364
```
42994365

@@ -4461,7 +4527,7 @@ Phase 4: Advanced
44614527

44624528
This section defines apcore's concurrency model and thread safety requirements, ensuring SDK implementers correctly implement the framework in multi-threaded/coroutine environments.
44634529

4464-
#### 11.7.1 Module Instance Lifecycle
4530+
#### 12.7.1 Module Instance Lifecycle
44654531

44664532
**Singleton Model (MUST)**:
44674533

@@ -4506,7 +4572,7 @@ class CounterModule:
45064572
| `execute()` | 0..N times | **Multi-thread** | Business logic |
45074573
| `on_unload()` | 0..1 time | Single-thread | Resource cleanup |
45084574

4509-
#### 11.7.2 Context.data Sharing Semantics
4575+
#### 12.7.2 Context.data Sharing Semantics
45104576

45114577
**Reference Sharing (MUST)**:
45124578

@@ -4544,7 +4610,7 @@ print(context.data["key"]) # Reads "value" (reference sharing)
45444610
- If `context.data` might be accessed by multiple threads (e.g., async middleware), **should** use thread-safe Map implementation
45454611
- Python's `dict` is partially thread-safe in CPython (GIL protected), but **should** avoid relying on implementation details
45464612

4547-
#### 11.7.3 Hot Reload Race Conditions
4613+
#### 12.7.3 Hot Reload Race Conditions
45484614

45494615
**Problem**: During `unregister()`, module might be executing in other threads.
45504616

@@ -4578,7 +4644,7 @@ Return:
45784644
| `unregister()` | `unregister()` same ID | Idempotent, succeed silently | **MUST** |
45794645
| `get()` | `unregister()` | If get executes first, return instance; if unregister executes first, throw `MODULE_NOT_FOUND` | **SHOULD** |
45804646

4581-
#### 11.7.4 Timeout Enforcement
4647+
#### 12.7.4 Timeout Enforcement
45824648

45834649
**Cooperative Cancellation (SHOULD)**:
45844650

@@ -4623,7 +4689,7 @@ Return:
46234689
| ACL check timeout | ACL rule evaluation | 1000ms | Separate timing |
46244690
| Schema validation timeout | Input/output validation | Included in global timeout | Not separately timed |
46254691

4626-
#### 11.7.5 Middleware Chain Atomicity
4692+
#### 12.7.5 Middleware Chain Atomicity
46274693

46284694
**Call-level Isolation (MUST)**:
46294695

@@ -4652,7 +4718,7 @@ Thread 2: before1 → ...
46524718
- Middleware **should** store call-level state through `context.data` (e.g., request ID, timer)
46534719
- **Must not** use middleware instance variables to store call-level state (causes race conditions)
46544720

4655-
#### 11.7.6 Sync/Async Mixing
4721+
#### 12.7.6 Sync/Async Mixing
46564722

46574723
**Bridging Strategy (MUST)**:
46584724

@@ -4680,7 +4746,7 @@ Implementations **must** support mixed calls of sync and async modules, bridging
46804746
- Sync→Async bridging blocks caller thread, **should** avoid frequent use in async contexts
46814747
- Async→Sync bridging needs thread pool, **should** configure reasonable thread pool size (default CPU cores × 2)
46824748

4683-
#### 11.7.7 Resource Cleanup Guarantees
4749+
#### 12.7.7 Resource Cleanup Guarantees
46844750

46854751
Implementations **must** guarantee resource cleanup according to this table:
46864752

SCOPE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ Things explicitly **not within the project scope**:
8989
| **Workflow Engine** | Application-layer orchestration logic, not framework core | apflow and other upstream projects |
9090
| **MCP/A2A Adapters** | Usage scenarios, not core protocol | Independent adapter projects |
9191
| **Concrete Business Modules** | We are a framework, not an application | Upstream projects |
92-
| **LLM Invocation Wrappers** | Stay neutral, don't bind to a specific LLM | LangChain/LlamaIndex |
93-
| **Agent Strategies** | Too high-level, belongs to application logic | CrewAI/AutoGen |
92+
| **LLM Invocation Wrappers** | Stay neutral, don't bind to a specific LLM | LangChain/LlamaIndex/pydantic-ai |
93+
| **Agent Strategies** | Too high-level, belongs to application logic | CrewAI/AutoGen/pydantic-ai |
94+
| **Prompt Templates** | AI orchestration belongs to upper layers, not the module framework | Agent frameworks |
9495
| **Distributed Execution** | Advanced runtime feature | Independent extension projects |
9596
| **UI/CLI Applications** | Focus on core protocol | Upstream projects |
9697
| **Specific Cloud Service Integrations** | Stay neutral | Extension packages |

docs/api/context-object.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ class Context:
8181
# Redacted data automatically provided by Executor (x-sensitive fields replaced with "***REDACTED***")
8282
redacted_inputs: dict[str, Any] | None = None
8383

84+
# ====== Optional extension fields (MAY be provided by implementations) ======
85+
86+
# Cooperative cancellation token for long-running operations
87+
cancel_token: CancelToken | None = None
88+
89+
# Dependency injection container for sharing services across the call chain
90+
services: T | None = None
91+
8492
# ====== Everything else ======
8593

8694
# Shared pipeline state (passed by reference, readable/writable along the call chain)
@@ -97,7 +105,9 @@ class Context:
97105
| `executor` | Executor | **MUST** || Thread-safe | **MUST NOT** |
98106
| `identity` | Identity \| null | **SHOULD** || Read-only, safe | **MUST** |
99107
| `logger` | ContextLogger | **SHOULD** || Thread-safe | **MUST NOT** |
100-
| `redacted_inputs` | dict \| null | **SHOULD** || Read-only, safe | **MUST** |
108+
| `redacted_inputs` | dict \| null | **SHOULD** || Read-only, safe | **MAY** |
109+
| `cancel_token` | CancelToken \| null | **MAY** || Thread-safe | **MUST NOT** |
110+
| `services` | T \| null | **MAY** || Read-only, safe | **MUST NOT** |
101111
| `data` | dict[str, Any] | **MUST** || Not thread-safe | **SHOULD** |
102112

103113
**call_chain depth limit:** Implementations **MUST** reject new module calls when `call_chain` length reaches 32, throwing a `CALL_DEPTH_EXCEEDED` error.

docs/api/executor-api.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,27 @@ except ModuleError as e:
410410
)
411411
```
412412

413+
### 5.3 AI Error Guidance Fields
414+
415+
All `ModuleError` instances carry four optional guidance fields (see PROTOCOL_SPEC §8.1.1) that enable AI agents to programmatically understand and respond to errors without parsing human-readable messages:
416+
417+
| Field | Type | Purpose |
418+
|-------|------|---------|
419+
| `retryable` | `bool \| None` | Whether retrying the same call may succeed. Each error code has a default (see §8.6). `None` means "depends on context". |
420+
| `ai_guidance` | `str \| None` | Machine-readable hint for AI agents, e.g. `"validate input schema before retry"`. |
421+
| `user_fixable` | `bool \| None` | Whether the end-user (not developer) can fix the issue. |
422+
| `suggestion` | `str \| None` | Actionable suggestion for resolving the error. |
423+
424+
Fields with `None` values are omitted from serialized output (sparse serialization).
425+
426+
```python
427+
except SchemaValidationError as e:
428+
print(e.retryable) # False (default for this error code)
429+
print(e.user_fixable) # True — user can fix their input
430+
print(e.suggestion) # "Table names must use only lowercase letters and underscores"
431+
print(e.ai_guidance) # "validate input against schema before retry"
432+
```
433+
413434
---
414435

415436
## 6. Execution Flow

docs/concepts.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ apcore organizes module metadata into a coherent lifecycle that guides an Agent
1919
1. **Discovery (Identity) — `description`**: Helps the Agent find the right tool for its intent.
2020
2. **Strategy (Wisdom) — `metadata`**: Teaches the Agent *when* and *how* to use the tool correctly (e.g., `x-when-to-use`, `x-common-mistakes`).
2121
3. **Governance (Safety) — `requires_approval`**: Sets the safety boundary for sensitive operations.
22-
4. **Recovery (Resilience) — `ai_guidance`**: Provides a clear path for the Agent to fix errors autonomously.
22+
4. **Recovery (Resilience) — error guidance fields**: Every `ModuleError` carries optional fields that enable AI agents to recover autonomously: `retryable` (can the call be retried?), `ai_guidance` (machine-readable recovery hint), `user_fixable` (can the end-user fix it?), and `suggestion` (actionable fix). See PROTOCOL_SPEC §8.1.1.
2323

2424
---
2525

@@ -384,6 +384,26 @@ class SendEmailModule(Module):
384384
| Annotation Layer | Optional but type-safe, framework understands | Occasionally add fields |
385385
| Extension Layer | Completely free, framework doesn't interpret | Can extend anytime |
386386
387+
**Recommended AI Intent Metadata Keys** (PROTOCOL_SPEC §4.6):
388+
389+
The following `x-*` keys are conventions for guiding AI agents. They are not enforced by the framework but provide tactical wisdom in the Extension Layer:
390+
391+
| Key | Purpose | Example |
392+
|-----|---------|---------|
393+
| `x-when-to-use` | Scenarios where this module is the right choice | `"Use when the user wants to send transactional email"` |
394+
| `x-when-not-to-use` | Scenarios where a different module should be used | `"Do not use for marketing/bulk emails"` |
395+
| `x-common-mistakes` | Known pitfalls AI agents frequently encounter | `"Forgetting to set reply_to; passing HTML in plain text field"` |
396+
| `x-workflow-hints` | Suggested pre/post steps or related modules | `"Call validate-email first to verify recipient exists"` |
397+
398+
```yaml
399+
# In module metadata or schema x-* fields
400+
metadata:
401+
x-when-to-use: "Use when the user wants to send transactional email"
402+
x-when-not-to-use: "Do not use for marketing/bulk emails"
403+
x-common-mistakes: "Forgetting to set reply_to; passing HTML in plain text field"
404+
x-workflow-hints: "Call validate-email first to verify recipient exists"
405+
```
406+
387407
---
388408

389409
### 2.6 Module ID

0 commit comments

Comments
 (0)