Built-in system.* modules provide AI bidirectional introspection — allowing AI agents to query, monitor, and control the apcore runtime. System modules are registered automatically when sys_modules.enabled: true in config, and use the reserved system.* namespace (see PROTOCOL_SPEC §2.5, §6.6).
system.health.summary— Aggregate health status across all modules with classification (healthy / degraded / error / unknown).system.health.module— Per-module health detail with latency metrics and recent errors.
system.manifest.module— Single module introspection (schema, annotations, tags, source path).system.manifest.full— Full registry manifest with filtering by tags and prefix.
system.usage.summary— Usage statistics across all modules with trend detection.system.usage.module— Per-module usage detail with caller breakdown and hourly distribution.
system.control.update_config— Hot-patch runtime config values with constraint validation.system.control.reload_module— Hot-reload a module from disk without restart.system.control.toggle_feature— Enable/disable modules at runtime with reason tracking.
Control modules require requires_approval: true and are only registered when sys_modules.enabled: true.
Aggregated health overview of all registered modules.
Annotations: readonly=True, idempotent=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
error_rate_threshold |
float | 0.01 | Threshold for healthy status (1%) |
include_healthy |
bool | true | Include healthy modules in output |
Output:
{
"project": { "name": "my-project" },
"summary": {
"total_modules": 12,
"healthy": 10,
"degraded": 1,
"error": 1,
"unknown": 0
},
"modules": [
{
"module_id": "math.add",
"status": "healthy",
"error_rate": 0.002,
"top_error": null
},
{
"module_id": "email.send",
"status": "degraded",
"error_rate": 0.05,
"top_error": {
"code": "MODULE_TIMEOUT",
"message": "Module timed out",
"ai_guidance": "consider increasing timeout",
"count": 3
}
}
]
}Health classification:
| Status | Condition |
|---|---|
| healthy | error rate < 1% (configurable via error_rate_threshold) |
| degraded | error rate 1% – 10% |
| error | error rate >= 10% |
| unknown | No calls recorded |
Detailed health information for a single module.
Annotations: readonly=True, idempotent=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
module_id |
string | (required) | Module to query |
error_limit |
int | 10 | Max recent errors to return |
Output:
{
"module_id": "email.send",
"status": "degraded",
"total_calls": 1542,
"error_count": 77,
"error_rate": 0.05,
"avg_latency_ms": 245.3,
"p99_latency_ms": 1200.0,
"recent_errors": [
{
"code": "MODULE_TIMEOUT",
"message": "Module timed out",
"ai_guidance": "consider increasing timeout",
"count": 3,
"first_occurred": "2026-03-08T10:00:00Z",
"last_occurred": "2026-03-08T11:30:00Z"
}
]
}Full manifest for a single registered module.
Annotations: readonly=True, idempotent=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
module_id |
string | (required) | Module to describe |
Output:
{
"module_id": "math.add",
"description": "Add two numbers",
"documentation": "Adds two integers and returns the sum.",
"source_path": "extensions/math/add.py",
"input_schema": { "type": "object", "properties": { "a": { "type": "integer" }, "b": { "type": "integer" } } },
"output_schema": { "type": "object", "properties": { "sum": { "type": "integer" } } },
"annotations": {
"readonly": true,
"idempotent": true,
"requires_approval": false,
"destructive": false
},
"tags": ["math", "utility"],
"dependencies": [],
"metadata": {}
}Complete system manifest with filtering.
Annotations: readonly=True, idempotent=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
include_schemas |
bool | true | Include input/output schemas |
include_source_paths |
bool | true | Include source file paths |
prefix |
string | (none) | Filter by module ID prefix |
tags |
list[string] | (none) | Filter by tags (all must match) |
Output:
{
"project_name": "my-project",
"module_count": 5,
"modules": [ ... ]
}Usage overview with trend detection across all modules.
Annotations: readonly=True, idempotent=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
period |
string | "24h" | Time period |
Output:
{
"period": "24h",
"total_calls": 15420,
"total_errors": 77,
"modules": [
{
"module_id": "math.add",
"call_count": 5000,
"error_count": 2,
"avg_latency_ms": 12.5,
"unique_callers": 8,
"trend": "stable"
}
]
}Modules sorted by call_count descending.
Trend values: stable, rising, declining, new, inactive
Detailed usage for a single module with caller breakdown.
Annotations: readonly=True, idempotent=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
module_id |
string | (required) | Module to query |
period |
string | "24h" | Time period |
Output:
{
"module_id": "math.add",
"period": "24h",
"call_count": 5000,
"error_count": 2,
"avg_latency_ms": 12.5,
"p99_latency_ms": 45.0,
"trend": "stable",
"callers": [
{
"caller_id": "orchestrator.main",
"call_count": 3000,
"error_count": 1,
"avg_latency_ms": 11.2
}
],
"hourly_distribution": [
{ "hour": "2026-03-08T10:00:00Z", "call_count": 200, "error_count": 0 },
{ "hour": "2026-03-08T11:00:00Z", "call_count": 350, "error_count": 1 }
]
}Hourly distribution padded with zeros for gaps.
Update a runtime configuration value by dot-path key.
Annotations: requires_approval=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
key |
string | (required) | Dot-path config key (e.g., executor.default_timeout) |
value |
any | (required) | New value |
reason |
string | (required) | Audit reason |
Output:
{
"success": true,
"key": "executor.default_timeout",
"old_value": 30000,
"new_value": 60000
}Restrictions:
- Cannot change
sys_modules.enabled(restricted key). - Sensitive keys (containing
token,secret,key,password,auth,credential) are logged with masked values. - Changes are in-memory only; not persisted to YAML.
- Emits
apcore.config.updatedevent.
key: string, required- validation: non-empty string
- reject_with:
InvalidInputError(message="'key' is required and must not be empty")
value: any, required- validation: none — any JSON-serializable value accepted; constraint checking applied post-set
reason: string, required- validation: non-empty string
- reject_with:
InvalidInputError(message="'reason' is required and must not be empty")
keymust not be in the restricted keys set (currently:sys_modules.enabled)- reject_with:
ModuleError(code=CONFIG_KEY_RESTRICTED)
- reject_with:
- If
keyhas a registered constraint,valuemust satisfy it; checked immediately afterConfig.set- reject_with:
ConfigError—Configis rolled back toold_valuebefore raising
- reject_with:
- Read current value of
keyfromConfig(capturesold_value) - Set
keytovalueinConfig(in-memory only; not persisted to YAML) - Validate constraint for
keyif one exists; on failure, roll back and raiseConfigError - Emit
apcore.config.updatedevent viaEventEmitter(values masked for sensitive keys) - Log change at INFO level (values masked for sensitive keys)
- On success:
config.get(key)returnsvalue - On
ConfigError:config.get(key)returns the originalold_value(atomically rolled back)
InvalidInputError—keyis absent or empty; orreasonis absent or emptyModuleError(code=CONFIG_KEY_RESTRICTED)—keyis in the restricted setConfigError—valueviolates a registered constraint;Configrolled back before raising
- On success:
dict—{success: true, key: str, old_value: any, new_value: any}old_valueandnew_valuereplaced with redaction sentinel for sensitive key segments
- idempotent: false — repeated calls with different values produce different state
- thread_safe: false —
Config.setis not internally locked; concurrent callers must serialize - async: false
- pure: false — mutates
Configstate and emits an event - reentrant: false
Hot-reload a module from disk without restart.
Annotations: requires_approval=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
module_id |
string | (required) | Module to reload |
reason |
string | (required) | Audit reason |
Output:
{
"success": true,
"module_id": "math.add",
"previous_version": "1.0.0",
"new_version": "1.1.0",
"reload_duration_ms": 45.2
}Process: safe_unregister() with drain → discover() re-load → re-register → emit apcore.module.reloaded event.
module_id: string, required- validation: non-empty string
- reject_with:
InvalidInputError
reason: string, required- validation: non-empty string
- reject_with:
InvalidInputError(message="'reason' is required and must be a non-empty string")
module_idmust be present in theRegistrybefore reload begins- reject_with:
ModuleNotFoundError(module_id=module_id)
- reject_with:
- Read current module from
Registryto captureprevious_version - Call
module.on_suspend()if the method is defined — captures suspended state; errors are logged at ERROR and suppressed (best-effort) - Call
registry.safe_unregister(module_id)— drains in-flight calls then removes the module - Call
registry.discover()to reload module source from disk; ifmodule_idis absent after discovery, raiseReloadFailedError - Call
registry.register_internal(module_id, new_module)to re-register the freshly loaded module - Call
new_module.on_resume(suspended_state)if the method is defined and state is non-None; errors are logged at ERROR and suppressed (best-effort) - Emit
apcore.module.reloadedevent viaEventEmitter - Log reload at INFO level
- On success:
registry.get(module_id)returns a freshly loaded module instance - If step 4 raises,
module_idis unregistered and callers must handle the partial state
InvalidInputError—module_idorreasonis absent, wrong type, or emptyModuleNotFoundError—module_idis not registered before reload beginsReloadFailedError—registry.discover()raised ormodule_idwas absent after discovery
- On success:
dict—{success: true, module_id: str, previous_version: str, new_version: str, reload_duration_ms: float}
- idempotent: false — each call unregisters and re-registers; invoking twice reloads twice
- thread_safe: false — concurrent reload calls are not serialized beyond
safe_unregister - async: false
- pure: false — mutates
Registry, performs file I/O, emits an event - reentrant: false
Disable or enable a module without unloading it.
Annotations: requires_approval=True
Input:
| Field | Type | Default | Description |
|---|---|---|---|
module_id |
string | (required) | Module to toggle |
enabled |
bool | (required) | true to enable, false to disable |
reason |
string | (required) | Audit reason |
Output:
{
"success": true,
"module_id": "risky.module",
"enabled": false
}Disabled modules remain registered but calls raise ModuleDisabledError. Toggle state is thread-safe (via ToggleState class) and survives module reload. Emits apcore.module.toggled event.
module_id: string, required- validation: non-empty string
- reject_with:
InvalidInputError(message="'module_id' is required and must be a non-empty string")
enabled: bool, required- validation: must be a
boolinstance (notNone, not a string or integer) - reject_with:
InvalidInputError(message="'enabled' is required and must be a boolean")
- validation: must be a
reason: string, required- validation: non-empty string
- reject_with:
InvalidInputError(message="'reason' is required and must be a non-empty string")
module_idmust be registered in theRegistry- reject_with:
ModuleNotFoundError(module_id=module_id)
- reject_with:
- Query
Registry.has(module_id)(read-only existence check) - Acquire internal lock on
ToggleState._lock - Mutate
ToggleState._disabledset: addmodule_idwhenenabled=false; discard it whenenabled=true - Release
ToggleState._lock - Emit
apcore.module.toggledevent viaEventEmitter - Log toggle at INFO level
- When
enabled=false:is_module_disabled(module_id)returnstrue; calls raiseModuleDisabledError(code=MODULE_DISABLED) - When
enabled=true:is_module_disabled(module_id)returnsfalse; module calls proceed normally - Toggle state persists across module reload (held by
ToggleState, external toRegistry)
InvalidInputError—module_idis absent/empty,enabledis absent/non-boolean, orreasonis absent/emptyModuleNotFoundError—module_idis not registered in theRegistry
- On success:
dict—{success: true, module_id: str, enabled: bool}
- idempotent: true — toggling to the current state produces the same outcome
- thread_safe: true —
ToggleStateusesthreading.Lockto serialize all mutations - async: false
- pure: false — mutates
ToggleState, emits an event, writes a log entry - reentrant: false
from apcore.sys_modules.registration import register_sys_modules
context = register_sys_modules(
registry=registry,
executor=executor,
config=config,
metrics_collector=None, # auto-created if needed
)Workflow:
- Check
config.get("sys_modules.enabled")— exit iffalse. - Create
ErrorHistoryand registerErrorHistoryMiddleware. - Create
UsageCollectorand registerUsageMiddleware. - Register health, manifest, and usage modules.
- If
sys_modules.events.enabled:- Create
EventEmitterandPlatformNotifyMiddleware. - Register control modules.
- Instantiate event subscribers from config.
- Bridge registry events to EventEmitter.
- Create
Return value:
{
"error_history": ErrorHistory,
"error_history_middleware": ErrorHistoryMiddleware,
"usage_collector": UsageCollector,
"usage_middleware": UsageMiddleware,
"event_emitter": EventEmitter, # if events enabled
"platform_notify_middleware": PlatformNotifyMiddleware, # if events enabled
}=== "Python"
```python
from apcore import APCore
from apcore.config import Config
config = Config.load("apcore.yaml")
client = APCore(config=config)
# System modules auto-registered! Query them directly:
health = client.call("system.health.summary", {})
usage = client.call("system.usage.summary", {"period": "24h"})
# Control via convenience methods:
client.disable("some.module", reason="maintenance")
client.enable("some.module", reason="done")
```
=== "TypeScript"
```typescript
import { APCore, Config } from 'apcore-js';
const config = Config.load('apcore.yaml');
const client = new APCore({ config });
// System modules auto-registered! Query them directly:
const health = await client.call('system.health.summary', {});
const usage = await client.call('system.usage.summary', { period: '24h' });
// Control via convenience methods:
await client.disable('some.module', 'maintenance');
await client.enable('some.module', 'done');
```
=== "Rust"
```rust
use apcore::APCore;
use serde_json::json;
let client = APCore::from_path("apcore.yaml")?;
// System modules auto-registered! Query them directly:
let health = client.call("system.health.summary", json!({}), None, None).await?;
let usage = client.call("system.usage.summary", json!({"period": "24h"}), None, None).await?;
// Control via convenience methods:
client.disable("some.module", Some("maintenance"))?;
client.enable("some.module", Some("done"))?;
```
sys_modules:
enabled: true
error_history:
max_entries_per_module: 50 # Ring buffer per-module capacity
max_total_entries: 1000 # Ring buffer total capacity
events:
enabled: true # Required for control modules
thresholds:
error_rate: 0.1 # 10% triggers apcore.error.threshold_exceeded
latency_p99_ms: 5000.0 # 5s triggers apcore.latency.threshold_exceeded
subscribers:
- type: "webhook"
url: "https://platform.example.com/events"
headers:
Authorization: "Bearer token"Registry— Module lookup and registration.Executor— Module execution and middleware management.Config— Configuration values and hot reload.MetricsCollector— Call counts and latency histograms for health modules.ErrorHistory— Recent error tracking for health modules.UsageCollector— Call tracking for usage modules.EventEmitter— Event dispatch for control modules.
System modules use the reserved system.* namespace. Registration bypasses reserved word checks via registry.register_internal(). See PROTOCOL_SPEC §6.6 for the defense-in-depth permission model.
??? info "Python SDK reference"
The following table is not a protocol requirement — it documents the Python SDK's source layout for implementers/users of apcore-python.
| File | Purpose |
|------|---------|
| `src/apcore/sys_modules/registration.py` | `register_sys_modules()`, subscriber factory registry |
| `src/apcore/sys_modules/health.py` | `HealthSummaryModule`, `HealthModuleModule` |
| `src/apcore/sys_modules/manifest.py` | `ManifestModuleModule`, `ManifestFullModule` |
| `src/apcore/sys_modules/usage.py` | `UsageSummaryModule`, `UsageModuleModule` |
| `src/apcore/sys_modules/control.py` | `UpdateConfigModule`, `ReloadModuleModule`, `ToggleFeatureModule`, `ToggleState` |
| Parameter | Type | Required | Description |
|---|---|---|---|
module_id |
str |
Yes | Fully-qualified module ID to inspect. |
registry |
Registry |
Yes | Registry that holds toggle state. |
| Code | Condition |
|---|---|
MODULE_DISABLED |
The module's current ToggleState is DISABLED. |
None — raises/throws on disabled; returns normally when enabled.
- Pure: Yes — reads registry state only, no side effects.
- Throws:
ModuleDisabledError(codeMODULE_DISABLED).
| Parameter | Type | Required | Description |
|---|---|---|---|
module_id |
str |
Yes | Fully-qualified module ID to inspect. |
registry |
Registry |
Yes | Registry that holds toggle state. |
- None — this function never raises; returns
falsefor unknown module IDs.
bool — true if disabled, false if enabled or toggle state not set.
- Pure: Yes — reads registry state only, no side effects.
- Does not throw.
- Health modules: Verify status classification thresholds, error aggregation from ErrorHistory, latency metrics from MetricsCollector.
- Manifest modules: Verify schema/annotation extraction, prefix/tag filtering, source path computation.
- Usage modules: Verify call counting, trend computation, hourly distribution padding, per-caller breakdown.
- Control modules: Verify approval requirement, config update with constraint validation, module reload lifecycle, toggle state persistence across reload.
- Registration: Verify auto-registration workflow, config-driven subscriber creation, middleware ordering.
Currently system.control.update_config and system.control.toggle_feature changes are in-memory only (see line 299).
- Implementations MUST support an optional
overrides_pathconfiguration field. When set, changes fromsystem.control.update_configandsystem.control.toggle_featureMUST be persisted tooverrides_pathas YAML. - The overrides file MUST be loaded on startup and applied AFTER the base config, so manual restores of the base config do not erase runtime overrides.
- Implementations SHOULD support KV-store persistence (Redis, etcd) as an alternative backend via a pluggable
OverridesStoreinterface with methods:set(key, value),get(key) → value | None,get_all() → dict,delete(key). - When no
overrides_pathor KV store is configured, the existing in-memory-only behavior MUST be preserved (backward compatible).
sys_modules:
control:
overrides_path: "/etc/apcore/overrides.yaml"
# OR
overrides_store:
type: "redis"
url: "redis://localhost:6379"
key_prefix: "apcore:overrides:"=== "Python"
```python
from apcore import APCore
from apcore.config import Config
# Startup: load base config, then apply overrides from disk
config = Config.load("apcore.yaml")
# overrides_path is declared in apcore.yaml under sys_modules.control
client = APCore(config=config)
# Runtime update — persisted to overrides_path automatically
await client.executor.call(
"system.control.update_config",
{"key": "executor.default_timeout", "value": 60000, "reason": "increase timeout"},
context,
)
```
=== "TypeScript"
```typescript
import { APCore, Config } from 'apcore-js';
// Startup: load base config, then apply overrides from disk
const config = Config.load('apcore.yaml');
// overrides_path is declared in apcore.yaml under sys_modules.control
const client = new APCore({ config });
// Runtime update — persisted to overrides_path automatically
await client.executor.call(
'system.control.update_config',
{ key: 'executor.default_timeout', value: 60000, reason: 'increase timeout' },
context,
);
```
=== "Rust"
```rust
use apcore::APCore;
use serde_json::json;
// Startup: load base config, then apply overrides from disk
// overrides_path is declared in apcore.yaml under sys_modules.control
let client = APCore::from_path("apcore.yaml")?;
// Runtime update — persisted to overrides_path automatically
client.executor.call(
"system.control.update_config",
json!({
"key": "executor.default_timeout",
"value": 60000,
"reason": "increase timeout"
}),
None,
None,
).await?;
```
System control modules that modify state MUST record an audit entry for every change.
system.control.update_config,system.control.reload_module, andsystem.control.toggle_featureMUST extract the caller identity fromcontext.identityand record it in a structured audit entry.- Each audit entry MUST contain:
timestamp,module_id(the target),action(update_config/reload_module/toggle_feature),actor_id(fromcontext.identity.id),actor_type(fromcontext.identity.type),change(before/after for config; enabled/disabled for toggle; module_version for reload). - Implementations MUST support an
AuditStoreinterface withappend(entry)andquery(module_id?, actor_id?, since?) → List[AuditEntry]. - When no AuditStore is configured, audit entries SHOULD be logged at INFO level and discarded (not stored).
AuditEntry:
timestamp: str # ISO 8601
action: enum # update_config | reload_module | toggle_feature
target_module_id: str
actor_id: str
actor_type: str # user | service | agent | api_key | system
trace_id: str
change:
before: any # previous value / null
after: any # new value / null=== "Python"
```python
from apcore import APCore
from apcore.config import Config
from apcore.sys_modules.audit import InMemoryAuditStore
config = Config.load("apcore.yaml")
audit_store = InMemoryAuditStore()
client = APCore(config=config, audit_store=audit_store)
# After a control call, query the audit log
await client.executor.call(
"system.control.toggle_feature",
{"module_id": "risky.module", "enabled": False, "reason": "maintenance"},
context,
)
entries = audit_store.query(module_id="risky.module")
# entries[0].actor_id == context.identity.id
# entries[0].change == {"before": True, "after": False}
```
=== "TypeScript"
```typescript
import { APCore, Config } from 'apcore-js';
import { InMemoryAuditStore } from 'apcore-js/sys-modules/audit';
const config = Config.load('apcore.yaml');
const auditStore = new InMemoryAuditStore();
const client = new APCore({ config, auditStore });
// After a control call, query the audit log
await client.executor.call(
'system.control.toggle_feature',
{ module_id: 'risky.module', enabled: false, reason: 'maintenance' },
context,
);
const entries = await auditStore.query({ moduleId: 'risky.module' });
// entries[0].actorId === context.identity.id
// entries[0].change === { before: true, after: false }
```
=== "Rust"
```rust
use apcore::APCore;
use apcore::sys_modules::audit::InMemoryAuditStore;
use std::sync::Arc;
use serde_json::json;
let audit_store = Arc::new(InMemoryAuditStore::new());
let client = APCore::from_path("apcore.yaml")?
.with_audit_store(audit_store.clone());
// After a control call, query the audit log
client.executor.call(
"system.control.toggle_feature",
json!({ "module_id": "risky.module", "enabled": false, "reason": "maintenance" }),
None,
None,
).await?;
let entries = audit_store.query(Some("risky.module"), None, None)?;
// entries[0].actor_id == context.identity.id
// entries[0].change.before == Some(json!(true)), entries[0].change.after == Some(json!(false))
```
- When
observability.prometheus.enabled: true, the UsageCollector MUST expose its data via the/metricsendpoint established in observability hardening (§ Observability Hardening 1.6). - The UsageCollector MUST emit these additional Prometheus metrics:
apcore_usage_calls_total{module_id, status}— counterapcore_usage_error_rate{module_id}— gauge (0.0–1.0)apcore_usage_p50_latency_ms{module_id},apcore_usage_p95_latency_ms{module_id},apcore_usage_p99_latency_ms{module_id}— gauges
- The Prometheus exporter MUST call
collector.get_module_stats()and transform to the text format; MUST NOT block the HTTP handler for more thanexport_timeout_ms(default 1000ms).
observability:
prometheus:
enabled: true
export_timeout_ms: 1000 # default; controls UsageCollector export budget=== "Python"
```python
from apcore import APCore
from apcore.config import Config
config = Config.load("apcore.yaml")
# observability.prometheus.enabled: true in apcore.yaml
client = APCore(config=config)
# UsageCollector metrics are now included in GET /metrics:
# apcore_usage_calls_total{module_id="math.add",status="success"} 5000
# apcore_usage_error_rate{module_id="math.add"} 0.0004
# apcore_usage_p99_latency_ms{module_id="math.add"} 45.0
```
=== "TypeScript"
```typescript
import { APCore, Config } from 'apcore-js';
const config = Config.load('apcore.yaml');
// observability.prometheus.enabled: true in apcore.yaml
const client = new APCore({ config });
// UsageCollector metrics are now included in GET /metrics:
// apcore_usage_calls_total{module_id="math.add",status="success"} 5000
// apcore_usage_error_rate{module_id="math.add"} 0.0004
// apcore_usage_p99_latency_ms{module_id="math.add"} 45.0
```
=== "Rust"
```rust
use apcore::APCore;
// observability.prometheus.enabled: true in apcore.yaml
let client = APCore::from_path("apcore.yaml")?;
// UsageCollector metrics are now included in GET /metrics:
// apcore_usage_calls_total{module_id="math.add",status="success"} 5000
// apcore_usage_error_rate{module_id="math.add"} 0.0004
// apcore_usage_p99_latency_ms{module_id="math.add"} 45.0
```
Currently system.control.reload_module reloads a single module by ID.
- Implementations MUST support a
path_filterinput field onsystem.control.reload_modulethat accepts a glob pattern. When specified, the module MUST reload all modules whose IDs match the pattern. path_filterandmodule_idMUST be mutually exclusive. If both are provided, implementations MUST raise aMODULE_RELOAD_CONFLICTerror.- Reload order for multiple matches MUST follow the dependency topological order (leaf modules first, then modules that depend on them).
| Field | Type | Default | Description |
|---|---|---|---|
module_id |
string | (one of required) | Single module to reload (mutually exclusive with path_filter) |
path_filter |
string | (one of required) | Glob pattern for bulk reload (mutually exclusive with module_id) |
reload_dependents |
bool | false |
When true, also reload modules that depend on matched modules |
reason |
string | (required) | Audit reason |
=== "Python"
```python
from apcore import APCore
from apcore.config import Config
config = Config.load("apcore.yaml")
client = APCore(config=config)
# Reload all executor modules
result = await client.executor.call(
"system.control.reload_module",
{"path_filter": "executor.*", "reload_dependents": False, "reason": "deploy"},
context,
)
# result["reloaded_modules"] == ["executor.email.send", "executor.math.add", ...]
# Reload a single module by ID (existing behavior unchanged)
result = await client.executor.call(
"system.control.reload_module",
{"module_id": "executor.email.send", "reason": "hotfix"},
context,
)
```
=== "TypeScript"
```typescript
import { APCore, Config } from 'apcore-js';
const config = Config.load('apcore.yaml');
const client = new APCore({ config });
// Reload all executor modules
const result = await client.executor.call(
'system.control.reload_module',
{ path_filter: 'executor.*', reload_dependents: false, reason: 'deploy' },
context,
);
// result.reloaded_modules === ['executor.email.send', 'executor.math.add', ...]
// Passing both fields raises MODULE_RELOAD_CONFLICT
// await client.executor.call('system.control.reload_module',
// { module_id: 'x', path_filter: 'y.*', reason: 'test' }, context);
// → throws ModuleReloadConflictError
```
=== "Rust"
```rust
use apcore::APCore;
use serde_json::json;
let client = APCore::from_path("apcore.yaml")?;
// Reload all executor modules
let result = client.executor.call(
"system.control.reload_module",
json!({ "path_filter": "executor.*", "reload_dependents": false, "reason": "deploy" }),
None,
None,
).await?;
// result["reloaded_modules"] contains the list of reloaded module IDs
```
- Python:
register_sys_modules()MUST accept afail_on_error: bool = Falseparameter. WhenTrue, any system module registration failure MUST raise immediately. WhenFalse(default), failures MUST be logged at ERROR level but execution continues. - TypeScript:
registerSysModules()MUST acceptfailOnError: boolean = falsewith the same behavior. - Rust:
register_sys_modules()MUST returnResult<(), SysModuleError>instead of returningOptionor panicking. The caller MUST handle the Result.
=== "Python"
```python
from apcore.sys_modules.registration import register_sys_modules
# Default: log errors and continue
context = register_sys_modules(
registry=registry,
executor=executor,
config=config,
fail_on_error=False, # default — errors logged at ERROR, execution continues
)
# Strict: raise immediately on any failure
try:
context = register_sys_modules(
registry=registry,
executor=executor,
config=config,
fail_on_error=True,
)
except SysModuleRegistrationError as exc:
print(f"System module registration failed: {exc}")
raise SystemExit(1)
```
=== "TypeScript"
```typescript
import { registerSysModules, SysModuleRegistrationError } from 'apcore-js/sys-modules';
// Default: log errors and continue
const context = await registerSysModules({
registry,
executor,
config,
failOnError: false, // default — errors logged at ERROR, execution continues
});
// Strict: raise immediately on any failure
try {
const context = await registerSysModules({
registry,
executor,
config,
failOnError: true,
});
} catch (err) {
if (err instanceof SysModuleRegistrationError) {
console.error(`System module registration failed: ${err.message}`);
process.exit(1);
}
throw err;
}
```
=== "Rust"
```rust
use apcore::sys_modules::registration::register_sys_modules;
use apcore::sys_modules::errors::SysModuleError;
// register_sys_modules always returns Result — caller MUST handle it
let context = register_sys_modules(®istry, &executor, &config)?;
// Explicit match for fine-grained handling
match register_sys_modules(®istry, &executor, &config) {
Ok(ctx) => {
// All system modules registered successfully
serve(ctx).await;
}
Err(SysModuleError::RegistrationFailed { module_id, source }) => {
eprintln!("Failed to register system module {module_id}: {source}");
std::process::exit(1);
}
}
```
| Parameter | Type | Required | Description |
|---|---|---|---|
executor |
Executor |
Yes | The executor to register modules on. |
registry |
Registry |
Yes | Registry to register system modules into. |
config |
Config |
Yes | Config instance; reads sys_modules.* keys. |
metrics_collector |
MetricsCollector | None |
No | If None, a new one is created and attached. |
fail_on_error |
bool |
No (default False) |
Whether to raise on registration failure. [Python/TypeScript only] |
| Code | Condition |
|---|---|
SYS_MODULE_REGISTRATION_FAILED |
A system module failed to register (only raised when fail_on_error=True in Python/TypeScript; always returned as Err in Rust). |
- On success:
SysModulesContext— all system modules registered (void/None/() in Rust:Result<(), SysModuleError>). - Rust:
Result<(), SysModuleError>
- async: false
- thread_safe: false — call once at startup before serving requests
- pure: false — registers modules into executor
- idempotent: false — registering twice causes
MODULE_ALREADY_REGISTERED