All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
DependencyNotFoundError(error codeDEPENDENCY_NOT_FOUND) — raised byresolve_dependencieswhen a module's required dependency is not registered. Aligns Python with PROTOCOL_SPEC §5.15.2, which has always mandated this error code. Details includemodule_idanddependency_id. Exported fromapcore.DependencyVersionMismatchError(error codeDEPENDENCY_VERSION_MISMATCH) — raised byresolve_dependencieswhen a declaredversionconstraint is not satisfied by the registered version of the target module. Details includemodule_id,dependency_id,required,actual. Exported fromapcore.TaskLimitExceededError(error codeTASK_LIMIT_EXCEEDED) — raised byAsyncTaskManager.submitwhen the manager is at capacity. Replaces the previous untypedRuntimeErrorand makes the failure dispatchable viaerror.codeacross language SDKs. Retryable=True.VersionConstraintError(error codeVERSION_CONSTRAINT_INVALID) — raised bymatches_version_hint/VersionedStorecallers on malformed constraint strings (empty, operator-without-operand, non-digit-leading operand such as"v1.0"or"latest"). Previously, malformed constraints silently degraded to(0,0,0)comparisons that always passed.resolve_dependencies(..., module_versions=...)— new optional keyword argument mappingmodule_id → version_string. When provided, declared dependency version constraints are enforced per PROTOCOL_SPEC §5.3. When absent, theDependencyInfo.versionfield is silently ignored (back-compat for callers that do not wire versions through yet).ModuleRegistry._resolve_load_ordernow populates this map from YAML version / classversionattr /DEFAULT_MODULE_VERSION("1.0.0") fallback, and includes already-registered modules (from_versioned_modules) so inter-batch constraints resolve against the live registry's multi-version state — not just the latest-only primary map.- Caret (
^) and tilde (~) constraint support inmatches_version_hint/select_best_version(npm/Cargo semantics):^1.2.3 → >=1.2.3,<2.0.0,^0.2.3 → >=0.2.3,<0.3.0,^0.0.3 → >=0.0.3,<0.0.4,~1.2.3 → >=1.2.3,<1.3.0,~1.2 → >=1.2.0,<1.3.0,~1 → >=1.0.0,<2.0.0. apcore.registry.registry.DEFAULT_MODULE_VERSIONconstant ("1.0.0") — canonical default applied by every registration path (register,_register_in_order,ModuleDescriptor.versionfallback) for modules without an explicitversion=argument orversionclass/instance attribute.ExecutorProtocol— Protocol describing the minimal async-call surface required byAsyncTaskManager. ConcreteExecutorstill satisfies it. Decouples the task manager from the concrete executor for testing.Executor.close()plus sync (__enter__/__exit__) and async (__aenter__/__aexit__) context-manager support — releases the cached_sync_loopdeterministically. Long-lived singleton executors can continue to ignore this; short-lived executors (per-request, per-test) should callclose()or usewith Executor(...) as executor:.Registry.get_callback_errors(event=None)— public accessor returning the per-event callback-exception count recorded by_trigger_event. Ops can watch these counters to spot misbehaving subscribers. Event-callback exceptions remain logged + suppressed so the registry's register/unregister contract stays crash-free.
resolve_dependenciescycle path accuracy —_extract_cyclepreviously returned a phantom path (all remaining nodes plus the first one re-appended) when the arbitrarily-picked start node had no outgoing edge insideremaining. Rewritten to DFS from each remaining node (sorted) and return a true back-edge cycle[n0, ..., nk, n0]. When no back-edge exists (e.g., Kahn's sort stalled on a non-cycle blocker), the resolver now raisesModuleLoadErrornaming the blocked modules instead of emitting aCircularDependencyErrorwith a phantomsorted(remaining)path.Registry._register_in_orderpopulates_versioned_modulesand_versioned_meta— discover()-path modules were previously written only to the latest-only_modulesmap, leavingRegistry.get(id, version_hint=…)unable to resolve them (version-hint queries route through the versioned store first). All registration paths now produce equivalent state.- Default version alignment —
Registry.register()with noversion=now falls back toDEFAULT_MODULE_VERSION("1.0.0"), matching_resolve_load_orderandModuleDescriptor.version. Previouslyregister()used"0.0.0"while_resolve_load_orderused"1.0.0"— the same module routed through the two paths got different effective versions. - Non-string versions warn-and-coerce — a module with
version: 1in YAML (integer) or a numeric class attribute is no longer silently dropped from constraint enforcement. The resolver logs a WARN naming the module and coerces viastr(...). - Malformed version constraints raise
VersionConstraintError— previously">=not_a_version","v1.0", and"~"alone passed_CONSTRAINT_REand degraded to(0,0,0)comparisons that always returned True. The constraint regex now requires a digit-leading operand, and_check_single_constraintraises explicitly on malformed input. - Scanner confines
follow_symlinks=Trueto the extension root — symlinks whose real path escapes the root (e.g., into/etc,$HOME, a sibling project) are now refused with a WARN log. The previous code would walk any target once per real-path visit. Combined with a WARN log on the first discover() whenfollow_symlinks=Trueis configured, this makes the trust boundary visible. Executor._run_in_new_threadboundsthread.join()by_global_timeout— a dead-locked coroutine can no longer indefinitely hang the sync caller. On timeout the daemon thread is left running (process exit stays clean) and the caller receives aModuleTimeoutError.Executor.streampost-stream validation failures log at WARNING (not DEBUG) — unvalidated output that already reached the consumer is worth investigating even if it can't be un-sent. Previously such failures were invisible in default production observability.AsyncTaskManager.cancelnarrows itsexcept— catchesasyncio.CancelledErrorspecifically (the expected cancellation path). Unexpected exceptions from the cancelled task are now logged at WARNING with a stack trace instead of being silently swallowed alongside CancelledError.errors.py __all__— addsDependencyNotFoundError,DependencyVersionMismatchError,TaskLimitExceededError,VersionConstraintError.from apcore.errors import *now picks them up.- Inline
__import__('time')/__import__('os')in_ModuleChangeHandlerreplaced with top-level imports. No behavior change.
- Missing required dependencies now raise
DependencyNotFoundError(codeDEPENDENCY_NOT_FOUND) instead ofModuleLoadError(codeMODULE_LOAD_ERROR). Brings Python into compliance with PROTOCOL_SPEC §5.15.2 which has always mandatedDEPENDENCY_NOT_FOUND. Upgrade path: catchDependencyNotFoundErrorspecifically, or catch theModuleErrorbase class for any dependency-related failure. The error-code-based dispatch (viaErrorCodes.DEPENDENCY_NOT_FOUND) also works and is recommended for cross-language consumers. AsyncTaskManager.submitnow raisesTaskLimitExceededError, notRuntimeError. Callers catchingRuntimeError("Task limit reached …")must migrate toexcept TaskLimitExceededError:(or catchModuleErrorfor any task-manager failure).Registry.register()default version is now"1.0.0"instead of"0.0.0"for modules registered without an explicitversion=argument and without a class/instanceversionattribute. Callers that relied on"0.0.0"as an "unset" marker must passversion="0.0.0"explicitly.ModuleDescriptor.versionhas always defaulted to"1.0.0"— this aligns the internal state with the externally-visible view.- Malformed version constraint strings now raise
VersionConstraintErrorinstead of silently evaluating to False (which, via the degraded-parse-semver path, effectively became True). Callers that relied on silent no-op behavior must wrap in try/except or sanitize upstream. - Dependency upper bounds —
pydantic,pyyaml, andjsonschemaare now pinned to<3,<7,<5respectively. Prevents silent breakage from downstream major releases; raise the cap deliberately after a compatibility check.
DECLARATIVE_CONFIG_SPEC.mdv1.0 — Canonical spec for bindings, pipeline config, and entry-point YAML. Lives inapcore/docs/spec/. Defines cross-SDK YAML syntax parity, error model, configurable policy limits, and auto_schema semantics. All three SDKs (Python, TypeScript, Rust) now conform to this unified specification.auto_schema: true | permissive | strict— Explicit auto-schema mode selection.strictmode enforces OpenAI/Anthropic-compatible schemas (additionalProperties: false, all properties required, restricted type set). Incompatible features produceBindingStrictSchemaIncompatibleErrorat parse time.auto_schemaas implicit default — When no schema mode is specified in a binding entry, auto-schema inference is attempted automatically. This formalizes Python's existing behavior as cross-SDK spec.- Schema mode conflict detection — Specifying multiple schema modes (e.g.,
auto_schema+input_schema) now producesBindingSchemaModeConflictErrorat parse time. spec_versionfield in binding YAML files. Defaults to"1.0"with deprecation warning when absent (mandatory in spec 1.1).- New canonical error classes:
BindingSchemaInferenceFailedError,BindingSchemaModeConflictError,BindingStrictSchemaIncompatibleError,BindingPolicyViolationError. Each includes file path, line, module ID, and spec section reference. displayfield support onFunctionModuleand binding YAML entries. Surface overlay for CLI/MCP/A2A presentation perbinding.schema.json#/DisplayOverlay.documentation,annotations,metadatafields now round-trip throughBindingLoader→FunctionModule.- Cross-SDK conformance fixtures in
apcore/conformance/fixtures/:binding_yaml_canonical.yaml(YAML parse parity),binding_errors.json(error message parity).
Context.create(trace_parent=...)— strict input validation per PROTOCOL_SPEC §10.5. trace_ids that are all-zero or all-f (W3C-invalid) now trigger regeneration + WARN log, matching the other SDKs. No auto-normalization (dashed-UUID stripping or case folding) is performed atContext.create; such normalization is the caller's ContextFactory responsibility. Previously valid 32-hex inputs remain accepted verbatim. Covered by new conformance fixturecontext_trace_parent.json.BindingSchemaMissingErroris now a deprecated alias forBindingSchemaInferenceFailedError. Error code changed fromBINDING_SCHEMA_MISSINGtoBINDING_SCHEMA_INFERENCE_FAILED. Existingexcept BindingSchemaMissingErrorcatch clauses continue to work.BindingLoader._create_module_from_bindingrewritten to implement DECLARATIVE_CONFIG_SPEC.md §3.4 schema resolution: explicit schemas > schema_ref > explicit auto > implicit auto default. Replaces the previous if/elif chain.FunctionModule.__init__now acceptsdisplay: dict | Noneparameter.Annotationsinbinding.schema.jsonexpanded from 5 to 12 fields (addedstreaming,cacheable,cache_ttl,cache_key_fields,paginated,pagination_style,extra) to align withmodule-meta.schema.json.
- Implicit auto_schema as Python-specific behavior — this is now a cross-SDK spec-defined behavior, not a Python-only quirk.
APCoreunified client class (apcore.client.APCore) — High-level facade overRegistry+Executorproviding a single entry point for all module operations. Constructor accepts optionalregistry,executor,config, andmetrics_collector(auto-created whensys_modules.enabled). Public API surface:- Module management:
module()decorator,register(),list_modules(tags=, prefix=),discover(),describe() - Execution:
call(),call_async(),stream(),validate()— all acceptversion_hintfor semver negotiation (A14) - Middleware:
use(),use_before(),use_after(),remove()—use/use_before/use_afterreturnselffor chaining - Events:
eventsproperty,on(event_type, handler),off(subscriber)— requiressys_modules.events.enabledin config - Module toggle:
disable(module_id, reason=),enable(module_id, reason=)— wrappers aroundsystem.control.toggle_feature - Cross-language parity: matches apcore-typescript
APCoreclass and apcore-rustAPCorestruct public API surface
- Module management:
- Package-level global convenience functions (
apcore.call,apcore.call_async,apcore.stream,apcore.validate,apcore.register,apcore.describe,apcore.use,apcore.use_before,apcore.use_after,apcore.remove,apcore.discover,apcore.list_modules,apcore.on,apcore.off,apcore.disable,apcore.enable,apcore.module) — delegate to a module-level_default_client = APCore()instance for zero-setup usage (import apcore; apcore.call("math.add", {"a": 1, "b": 2})). Python-specific ergonomic; apcore-typescript and apcore-rust require explicit client construction. - Pipeline preset builders re-exported at package root —
build_standard_strategy,build_internal_strategy,build_testing_strategy,build_performance_strategy,build_minimal_strategyare now importable directly fromapcore. These functions existed inapcore.builtin_stepsbut were not previously inapcore.__all__. Parity with apcore-typescript (buildXxxStrategy) and apcore-rust (build_xxx_strategyat the crate root). TestRegisterInternalValidationtest class intests/registry/test_registry.py(6 parity tests covering empty rejection, pattern rejection, over-length rejection, reserved-word bypass, duplicate rejection, accept-at-max-length) plustest_pipeline_preset_builders_*intests/test_public_api.py.Registry.export_schema()— New method returning a module's schema definition as a dict, with optionalstrict=Truefor OpenAI/Anthropic strict schema compliance (additionalProperties: false). Aligned with apcore-rustRegistry::export_schema().
-
Executor.describe_pipeline()now returnsStrategyInfoinstead ofstr— Provides structured access tostep_count,step_names,name, anddescription.str(result)produces the original formatted string viaStrategyInfo.__str__. Aligned with apcore-typescriptdescribePipeline() -> StrategyInfo. -
Executor.call()andExecutor.call_with_trace()are now thin sync wrappers overcall_async()andcall_async_with_trace()via a shared_run_async_in_sync(coro, module_id)dispatcher. The cached-event-loop / thread-bridge logic that was previously inlined in three places lives in one helper. Sync semantics preserved: nested calls inside a running event loop still route through a background thread. -
Executor.call_async_with_trace()now uses the unified A11 error recovery path (_translate_abort+_recover_from_call_error+ middlewareon_errorchain). Previously it calledengine.runraw and letPipelineAbortErrorleak; behavior now matchescall_async. When a middlewareon_errorrecovers, the recovery dict is returned alongside a sentinelPipelineTrace(per-step trace detail is unavailable in the recovery branch — usecall_asyncif you don't need the trace, or attach a tracing middleware). -
BuiltinApprovalGatenow self-contains the full approval flow. Audit-log emission, span-event emission, and full status→error mapping (includingtimeoutand unknown-status warning) used to live on private methods ofExecutor, withBuiltinApprovalGatereaching into them viahasattr(executor, '_check_approval_async'). The reach-into-private cheat is gone;BuiltinApprovalGatedoes everything itself. Theexecutor=parameter onBuiltinApprovalGate.__init__is removed (was unused after consolidation). Approval audit logs are now emitted from loggerapcore.builtin_steps(wasapcore.executor) — update any log filters accordingly. -
BuiltinACLCheckandBuiltinApprovalGatenow expose publicset_acl()/set_handler()setters.Executor.set_aclandset_approval_handleruse the public setters instead of poking step._acl/._handler. Custom user-supplied ACL or approval steps without these setters are silently skipped — re-register the strategy if you need to swap providers on a custom step. -
Registry._discover_default()decomposed from a 153-line god method into a 23-line orchestrator + 9 named stage helpers (_scan_params,_scan_roots,_apply_id_map_overrides,_load_all_metadata,_resolve_all_entry_points,_validate_all,_resolve_load_order,_filter_id_conflicts,_register_in_order,_invoke_on_load). Pure refactor — no behavior change. Mirrors the structure ofapcore-typescript's_discoverDefault. -
ACL.check()andACL.async_check()consolidated via shared_snapshot()and_finalize_check()helpers. Audit-entry construction and debug-logging now live in exactly one place (was duplicated four times). Fixed_matches_rule_asyncto call_match_patterns()instead of inlining a variant that bypassed compound operators ($or/$not). -
ACL singular condition handler aliases removed (
identity_type,role,call_depth). Spec §6.1 only defines the plural forms (identity_types,roles,max_call_depth); the singular aliases were a python-only divergence. -
builtin_steps.pystrategy builders no longer useobject.__setattr__for thenamefield.ExecutionStrategywas never a frozen dataclass —s.name = Xalways worked. Cargo-cult code removed. -
ErrorCodesclass__setattr__/__delattr__traps dropped. The traps only fired on instance attribute mutation (ErrorCodes().X = ...), never on class attribute mutation (ErrorCodes.X = ...) which is howErrorCodesis actually used. Cargo-cult immutability that gave a false sense of protection. Aligned with apcore-typescript (Object.freeze) and apcore-rust (enum). -
Pydantic v1/v2/dataclass/constructor fallback cascade collapsed in
config.py. Previously maintained a 4-branch compatibility chain for Pydantic v1 → v2 migration. The project requires Pydantic v2 since 0.16.0; dead branches removed. -
Registry._handle_file_change()refactored — replaced fragiledir(mod)module-attribute discovery with explicit registry lookup. More predictable behavior on hot-reload events. -
Registry.register()/register_internal()now populate_module_metaat registration time, not lazily at firstget_definition()call. Consistent with_discover_defaultpath. -
31 pre-existing pyright type errors resolved across
executor.py,config.py,registry.py,builtin_steps.py, andacl.py. No runtime behavior change; strict type-checking now passes cleanly. -
MAX_MODULE_ID_LENGTHraised from 128 to 192 (apcore.registry.registry). Tracks PROTOCOL_SPEC §2.7 EBNF constraint #1 update — accommodates Java/.NET deep-namespace FQN-derived IDs while remaining filesystem-safe (192 + len('.binding.yaml') = 205 < 255-byte filename limit on ext4/xfs/NTFS/APFS/btrfs). Module IDs valid before this change remain valid; only the upper bound moved. Forward-compatible relaxation: older 0.17.x/0.18.x readers will reject IDs in the 129–192 range emitted by this version. -
Registry.register()andRegistry.register_internal()now share a_validate_module_id()helper that runs validation in canonical order (empty → EBNF pattern → length → reserved word per-segment). The reserved-word check is the only stepregister_internal()skips (so sys modules can use thesystem.*prefix); empty/pattern/length/duplicate now apply uniformly. Aligned cross-language with apcore-typescript and apcore-rust. -
register_internal()now enforces empty / pattern / length / duplicate checks. Previously bypassed every validation step. Production callers (apcore.sys_modules.*) all use canonical-shape IDs so no in-tree caller is broken; external adapters that usedregister_internalas a generic escape hatch should review. -
Duplicate registration error message canonicalized to
"Module ID '<id>' is already registered"(was"Module already exists: <id>"forregister_internal). Bothregister()andregister_internal()now emit the same message via the shared error path. Aligned with apcore-rust and apcore-typescript byte-for-byte.
FeatureNotImplementedErrorandDependencyNotFoundError— zero raise-sites across the codebase;grep -rnconfirmed no production or test code instantiated either class. Error codesGENERAL_NOT_IMPLEMENTEDandDEPENDENCY_NOT_FOUNDremain inErrorCodesfor use via the genericModuleErrorconstructor. Aligned with apcore-typescript (commit01ea84d).
Context.to_dict()andContext.from_dict()— superseded by the spec-compliantContext.serialize()andContext.deserialize()(shipped in v0.16.0). The two pairs were silently inconsistent (to_dictalways emittedredacted_inputseven whenNonewhileserializeomitted it;serializeincluded_context_version: 1,to_dictdid not), so mixing them produced divergent dicts. Migration:ctx.to_dict()→ctx.serialize()Context.from_dict(data, executor=x)→Context.deserialize(data); ctx.executor = x(theexecutor=parameter is removed; reassign directly on the returnedContext, which is non-frozen)
- Private
Executorapproval helpers removed as part of theBuiltinApprovalGateconsolidation. No public API impact unless your code reached intoExecutor._check_approval_async,_build_approval_request,_handle_approval_result,_emit_approval_event,_needs_approval,_check_approval_sync, the timeout-aware_run_async_in_sync(the new same-named method has a different(coro, module_id)signature),_async_cache, or_async_cache_lock. - Legacy event aliases removed. Per the §9.16 naming convention shipped in v0.15, the dual-emission transition period for
module_health_changedandconfig_changedended in this release (the original removal deadline was v0.16.0). Listeners that subscribed to these legacy names will no longer receive events. Migrate subscriptions to the canonical names:module_health_changed→apcore.module.toggled(fromsystem.control.toggle_feature) orapcore.health.recovered(fromPlatformNotifyMiddleware)config_changed→apcore.config.updated(fromsystem.control.update_config) orapcore.module.reloaded(fromsystem.control.reload_module)
- Renamed private method
_emit_config_changed→_emit_module_reloadedinsystem.control.reload_moduleto reflect the canonical event it emits. Private API, no public-surface impact.
- Global convenience functions
call(),call_async(),stream()missingversion_hintparameter — Theseapcore/__init__.pywrappers previously forwarded only(module_id, inputs, context)to theAPCoreclient, silently droppingversion_hint. Users callingapcore.call(..., version_hint=">=1.0.0")would have had the hint ignored. Now all three wrappers accept and forwardversion_hint: str | None = None, matching theAPCoreclass signature and cross-language SDKs. - Spec §4.13 annotation merge — YAML annotations are no longer silently dropped at registration. Two coupled bugs were repaired: (1)
registry/metadata.py:merge_module_metadatawas doing whole-replacement of theannotationsfield instead of the field-level merge mandated by §4.13 ("If YAML only definesreadonly: true, other fields must retain values from code or defaults."), and (2)registry/registry.py:get_definitionwas ignoring even that broken merge result and reading directly from the module's class attribute. The fix wires the previously-unwiredapcore.schema.annotations.merge_annotationsandmerge_examples(which were defined and unit-tested but never called from production) into the registry pipeline, and updatesget_definitionto consume the merged metadata. User-observable behavior change: modules that suppliedannotations:in their*_meta.yamlcompanion files were previously seeing those annotations silently ignored. Those annotations will now be honored. Modules that relied on the broken behavior should audit their*_meta.yaml. Adds 5 regression tests covering field-level merge, YAML-only, neither-defined, examples override, and an end-to-enddiscover() → get_definition()round-trip. ModuleAnnotations.from_dictprecedence inversion — Per PROTOCOL_SPEC §4.4.1 rule 7, when the same key appears both in a nestedextraobject and as a top-level overflow key, the nested value now wins (previously the top-level overflow would silently overwrite it). Behavior change is observable only in the pathological case where an input contains both forms of the same key — no conformant producer emits this. Top-level overflow keys are still tolerated and merged intoextrafor backward compatibility.
build_minimal_strategy()— 4-step pipeline (context → lookup → execute → return) for pre-validated internal hot paths. Registered as"minimal"in Executor preset builders.requires/providesonBaseStep— Optional advisory fields declaring step dependencies.ExecutionStrategyvalidates dependency chains at construction and insertion, emitting warnings for unmetrequires.
"minimal"added to preset builders —Executor(strategy="minimal")now works. Previously missing from_resolve_strategy_name()preset dict.- Executor docstrings updated — Constructor and
_resolve_strategy_namedocstrings now list all 5 presets (was missing"minimal").
- Step Metadata: Four declarative fields on
BaseStep:match_modules(glob patterns for selective execution),ignore_errors(fault-tolerant steps),pure(safe forvalidate()dry-run),timeout_ms(per-step timeout). - YAML Pipeline Configuration:
register_step_type(),unregister_step_type(),registered_step_types(),build_strategy_from_config()— configure pipeline steps viaapcore.yamlat startup. - PipelineContext fields:
dry_run,version_hint,executed_middlewaresfor pipeline-aware execution. - StepTrace:
skip_reasonfield for understanding why steps were skipped ("no_match", "dry_run", "error_ignored").
- Step order:
middleware_beforenow runs BEFOREinput_validation(was after). Middleware input transforms are now validated by the schema check. - Executor delegation:
call(),call_async(),validate(), andstream()fully delegate toPipelineEngine.run(). Removed ~300 lines of duplicated inline step code. - Renamed:
safety_checkstep →call_chain_guard(accurately describes call-chain depth/cycle/repeat checking). - Renamed:
BuiltinSafetyCheckclass →BuiltinCallChainGuard.
- Middleware input transforms were never re-validated against the schema (now validated after middleware runs).
validate()was hardcoded to 7 inline checks; now usesdry_run=Truepipeline mode — user-addedpure=Truesteps automatically participate.
- Config Bus:
env_style(auto/nested/flat),max_depth,env_prefixauto-derivation,env_map(namespace + global),Config.env_map(),CONFIG_ENV_MAP_CONFLICTerror. - Context:
ContextKey[T]typed accessor withget()/set()/delete()/exists()/scoped(). Built-in key constants (TRACING_SPANS,METRICS_STARTS, etc.).Context.serialize()/deserialize()with_context_version: 1. - Annotations:
extra: dict[str, Any]extension field onModuleAnnotations.pagination_stylechanged fromLiteraltostr.DEFAULT_ANNOTATIONSconstant.from_dict()classmethod with unknown key capture. - ACL:
SyncACLConditionHandler/AsyncACLConditionHandlerprotocols.ACL.register_condition().$or/$notcompound operators.async_check()method. Fail-closed for unknown conditions. - Pipeline:
Stepprotocol,BaseStepABC,StepResult,PipelineContext,PipelineTrace,ExecutionStrategy,PipelineEngine. 11BuiltinStepclasses. Preset strategies (standard/internal/testing/performance).Executor.strategyparameter.call_with_trace()/call_async_with_trace().register_strategy()/list_strategies()/describe_pipeline().
- Middleware data keys migrated from legacy names (
_metrics_startsetc.) to_apcore.mw.*convention using typedContextKey.
- Env prefix convention simplified — Removed the
^APCORE_[A-Z0-9]reservation rule fromConfig._validate_env_prefix(). Sub-packages now use single-underscore prefixes (APCORE_MCP,APCORE_OBSERVABILITY,APCORE_SYS) instead of the double-underscore form. Only the exactAPCOREprefix is reserved for the core namespace. - Built-in namespace env prefixes:
APCORE__OBSERVABILITY→APCORE_OBSERVABILITY,APCORE__SYS→APCORE_SYS.
Config.register_namespace(name, schema=None, env_prefix=None, defaults=None)— Class-level namespace registration. Any package can claim a named config subtree with optional JSON Schema validation, env prefix, and default values. Global registry is shared across allConfiginstances. Late registration is allowed; callconfig.reload()afterward to apply defaults and env overrides.config.get("namespace.key.path")— Dot-path access with namespace resolution. First segment resolves to a registered namespace; remaining segments traverse the subtree.config.namespace(name)— Returns the full config subtree for a registered namespace as a dict.config.bind(ns, type)/config.get_typed(path, type)— Typed namespace access;bindreturns a view of the namespace deserialized intotype,get_typeddeserializes a single dot-path value.config.mount(namespace, from_file=...|from_dict=...)— Attach external config sources to a namespace without a unified YAML file. Primary integration path for third-party packages with existing config systems.Config.registered_namespaces()— Class-level introspection; returns names of all registered namespaces.- Unified YAML with namespace partitioning — Single YAML file with namespace-keyed top-level sections. Automatic mode detection: legacy mode (no
apcore:key, fully backward compatible) vs. namespace mode (apcore:key present)._configis a reserved meta-namespace (strict,allow_unknown). - Per-namespace env override with longest-prefix-match dispatch — Each namespace declares its own
env_prefix. Apcore sub-packages useAPCORE_prefixed names (e.g.,APCORE_OBSERVABILITY,APCORE_SYS); the longest-prefix-match dispatch algorithm resolves any ambiguity with the coreAPCOREprefix. - Hot-reload namespace support —
config.reload()re-reads YAML, re-detects mode, re-applies namespace defaults and env overrides, re-validates, and re-reads mounted files. - New error codes —
CONFIG_NAMESPACE_DUPLICATE,CONFIG_NAMESPACE_RESERVED,CONFIG_ENV_PREFIX_CONFLICT,CONFIG_MOUNT_ERROR,CONFIG_BIND_ERROR
ErrorFormatterprotocol — Interface for adapter-specific error formatters. Implementations transformModuleErrorinto the surface-specific wire format (e.g., MCP camelCase, JSON-RPC code mapping).ErrorFormatterRegistry— Shared registry for surface-specific formatters:ErrorFormatterRegistry.register(surface, formatter)— register a formatter for a named surfaceErrorFormatterRegistry.get(surface)— retrieve a registered formatterErrorFormatterRegistry.format(surface, error)— format an error, falling back toerror.to_dict()if no formatter is registered for that surface
- New error code —
ERROR_FORMATTER_DUPLICATE
observabilitynamespace (APCORE_OBSERVABILITYenv prefix) — apcore pre-registers this namespace, promoting the existingapcore.observability.*flat config keys (tracing, metrics, logging, error_history, platform_notify) into a named subtree. Adapter packages (apcore-mcp, apcore-a2a, apcore-cli) should read from this namespace rather than independent logging defaults.sys_modulesnamespace (APCORE_SYSenv prefix) — apcore pre-registers this namespace, promoting the existingapcore.sys_modules.*flat keys into a named subtree.register_sys_modules()prefersconfig.namespace("sys_modules")in namespace mode withconfig.get("sys_modules.*")legacy fallback. Both registrations are 1:1 migrations of existing keys; there are no breaking changes.
- Canonical event names — Two confirmed event type collisions in apcore-python are resolved:
"module_health_changed"(previously used for both enable/disable toggles and error-rate recovery) split intoapcore.module.toggled(toggle on/off) andapcore.health.recovered(error rate recovery)"config_changed"(previously used for both key updates and module reload) split intoapcore.config.updated(runtime key update viasystem.control.update_config) andapcore.module.reloaded(hot-reload viasystem.control.reload_module)
- Naming convention —
apcore.*is reserved for core framework events. Adapter packages use their own prefix:apcore-mcp.*,apcore-a2a.*,apcore-cli.*. - Transition aliases — All four legacy short-form names (
module_health_changed,config_changed) continue to be emitted alongside the canonical names during the transition period.
- Middleware priority —
Middlewarebase class now acceptspriority: int(0-1000, default 0). Higher priority executes first; equal priority preserves registration order.BeforeMiddlewareandAfterMiddlewareadapters also acceptpriority. - Priority range validation —
ValueErrorraised for priority values outside 0-1000
- Middleware default priority changed from
0to100per PROTOCOL_SPEC §11.2. Middleware without explicit priority will now execute before priority-0 middleware.
- Rebrand: aipartnerup → aiperceivable
- Dict schema support — Modules can now define
input_schema/output_schemaas plain JSON Schema dicts instead of Pydantic model classes. A_DictSchemaAdaptertransparently wraps dict schemas at registration time so all internal code paths (executor, schema exporter,get_definition) work without changes.
get_definition()crash on dict schemas — Previously called.model_json_schema()on dict objects, causingAttributeError- Executor crash on dict schemas —
call(),call_async(), andstream()all called.model_validate()on dict objects
- File header docstrings — Enhanced docstrings for
errors.py,executor.py, andversion.py
0.13.0 - 2026-03-12
- Caching/pagination annotations —
ModuleAnnotationsgains 5 new fields:cacheable,cache_ttl,cache_key_fields,paginated,pagination_style(all optional with defaults, backward compatible) pagination_styleLiteral type — Typed asLiteral["cursor", "offset", "page"]instead of free-formstrsunset_date— New field onModuleDescriptorfor module deprecation lifecycle (ISO 8601 date)on_suspend()/on_resume()lifecycle hooks — Duck-typed optional hooks for state preservation during hot-reload; integrated intoReloadModuleModuleand registry watchdog- MCP
_metaexport — Schema exporter includescacheable,cacheTtl,cacheKeyFields,paginated,paginationStylein_metasub-dict - Suspend/resume tests —
tests/test_suspend_resume.pycovering state transfer, backward compatibility, error handling
- Rebranded — "module development framework" → "module standard" in pyproject.toml,
__init__.py, README, and internal docstrings ModuleProtocol —on_suspend/on_resumedeliberately kept OUT of Protocol (duck-typed viahasattr/callable)
0.12.0 - 2026-03-10
ExecutionCancelledErrornow extendsModuleError(was bareException) with error codeEXECUTION_CANCELLED, aligning with PROTOCOL_SPEC §8.7 error hierarchyErrorCodes— AddedEXECUTION_CANCELLEDconstant
0.11.0 - 2026-03-08
- Full lifecycle integration tests (
tests/integration/test_full_lifecycle.py) — 8 tests covering the complete 11-step pipeline with all gates (ACL + Approval + Middleware + Schema validation) enabled simultaneously, nested module calls, sharedcontext.data, error propagation, and ACL conditions.
Built-in system.* modules that allow AI agents to query, monitor
system.health.summary— Aggregate health status across all registered modules (healthy/degraded/unhealthy classification based on error rate thresholds).system.health.module— Per-module health detail including recent errors fromErrorHistory.system.manifest.module— Single module introspection (schema, annotations, tags, source path).system.manifest.full— Full registry manifest with filtering by tags/prefix.system.usage.summary— Usage statistics across all modules (call counts, error rates, avg latency).system.usage.module— Per-module usage detail with hourly trend data.system.control.update_config— Runtime config hot-patching 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.register_sys_modules()— Auto-registration wiring for all system modules.
ErrorHistory— Ring buffer tracking recent errors with deduplication and per-module querying.ErrorHistoryMiddleware— Middleware that recordsModuleErrordetails intoErrorHistory.UsageCollector— Per-module call counting, latency histograms, and hourly bucketed trend data.PlatformNotifyMiddleware— Threshold-based sensor that emits events on error rate spikes.
EventEmitter— Global event bus with async subscriber dispatch and thread-pool execution.EventSubscriberprotocol — Interface for event consumers.ApCoreEvent— Frozen dataclass for typed events (module lifecycle, errors, config changes).WebhookSubscriber— HTTP POST event delivery with retry.A2ASubscriber— Agent-to-Agent protocol event bridge.
APCore.on()/APCore.off()— Event subscription management via the unified client.APCore.disable()/APCore.enable()— Module toggle control via the unified client.APCore.discover()/APCore.list_modules()— Discovery and listing via the unified client.
ModuleDisabledError— Error class forMODULE_DISABLEDcode, raised when a disabled module is called.ReloadFailedError— Error class forRELOAD_FAILEDcode (retryable).SchemaStrategy— Enum for schema resolution strategy (yaml_first,native_first,yaml_only).ExportProfile— Enum for schema export profiles (mcp,openai,anthropic,generic).
- Module toggle — APCore client now supports
disable()/enable()for module toggling viasystem.control.toggle_feature, withModuleDisabledErrorenforcement and event emission. - Version negotiation —
negotiate_version()for SDK/module version compatibility checking.
WebhookSubscriber/A2ASubscribernow require optional dependencyaiohttp. Install withpip install apcore[events]. Core SDK no longer fails to import whenaiohttpis not installed.
aiohttphard import inevents/subscribers.pybroke core SDK import whenaiohttpwas not installed. Changed totry/except ImportErrorguard with clear error message at runtime.A2ASubscriber.on_eventImportErrorfor missingaiohttpwas silently swallowed by the broadexcept Exceptionblock. Moved guard before thetryblock to surface the error correctly.- README Access Control example now includes required
ExecutorandRegistryimports. pyproject.tomlrepository/issues/changelog URLs now point toapcore-python(was incorrectly pointing toapcore).- CHANGELOG
[0.7.1]compare link added (was missing from link references).
0.10.0 - 2026-03-07
APCore.stream()— Stream module output chunk by chunk via the unified client.APCore.validate()— Non-destructive preflight check via the unified client.APCore.describe()— Get module description info (for AI/LLM use).APCore.use_before()— Add before function middleware via the unified client.APCore.use_after()— Add after function middleware via the unified client.APCore.remove()— Remove middleware by identity via the unified client.
apcore.stream()— Global convenience for streaming module calls.apcore.validate()— Global convenience for preflight validation.apcore.register()— Global convenience for direct module registration.apcore.describe()— Global convenience for module description.apcore.use()— Global convenience for adding middleware.apcore.use_before()— Global convenience for adding before middleware.apcore.use_after()— Global convenience for adding after middleware.apcore.remove()— Global convenience for removing middleware.
FeatureNotImplementedError— New error class forGENERAL_NOT_IMPLEMENTEDcode (renamed fromNotImplementedErrorto avoid Python stdlib clash).DependencyNotFoundError— New error class forDEPENDENCY_NOT_FOUNDcode.
- APCore client and
apcore.*global functions now provide full feature parity withExecutor.
0.9.0 - 2026-03-06
PreflightCheckResult— New frozen dataclass representing a single preflight check result withcheck,passed, anderrorfields.PreflightResult— New dataclass returned byExecutor.validate(), containing per-check results andrequires_approvalflag. Duck-type compatible withValidationResultvia.validand.errorsproperties.- Full 6-check preflight —
validate()now runs Steps 1–6 of the pipeline (module_id format, module lookup, call chain safety, ACL, approval detection, schema validation) without executing module code or middleware.
- Step renumbering — Approval Gate renumbered from Step 4.5 to Step 5; all subsequent steps shifted +1 (now 11 clean steps).
validate()return type — Changed fromValidationResulttoPreflightResult. Backward compatible:.validand.errorsstill work identically for existing consumers (e.g., apcore-mcp router).validate()signature — Added optionalcontextparameter for call-chain checks;inputsnow defaults to{}.
- Exported
PreflightCheckResultandPreflightResultfromapcoretop-level package.
0.8.0 - 2026-03-05
- Dual-timeout model — Global deadline enforcement (
executor.global_timeout) alongside per-module timeout. The shorter of the two is applied, preventing nested call chains from exceeding the global budget. - Cooperative cancellation — On module timeout, the executor sends
CancelToken.cancel()and waits a 5-second grace period before raisingModuleTimeoutError. Modules that checkcancel_tokencan clean up gracefully. - Error propagation (Algorithm A11) — All execution paths (sync, async, stream) now wrap exceptions via
propagate_error(), ensuring middleware always receivesModuleErrorinstances with trace context. - Deep merge for streaming — Streaming chunk accumulation uses recursive deep merge (depth-capped at 32) instead of shallow merge, correctly handling nested response structures.
- ErrorCodeRegistry — Custom module error codes are validated against framework prefixes and other modules to prevent collisions. Raises
ErrorCodeCollisionErroron conflict. - VersionIncompatibleError — New error class for SDK/config version mismatches with
negotiate_version()utility. - MiddlewareChainError — Now explicitly
_default_retryable = Falseper PROTOCOL_SPEC §8.6.
guard_call_chain()— Standalone Algorithm A20 implementation for call chain safety checks (depth, circular, frequency). Executor delegates to this utility.propagate_error()— Standalone Algorithm A11 implementation for error wrapping and trace context attachment.normalize_to_canonical_id()— Cross-language module ID normalization (Python snake_case, Go PascalCase, etc.).calculate_specificity()— ACL pattern specificity scoring for deterministic rule ordering.parse_docstring()— Docstring parser for extracting parameter descriptions from function docstrings.
- Audit logging —
ACLconstructor accepts optionalaudit_loggercallback. All access decisions emitAuditEntrywith timestamp, caller/target IDs, matched rule, identity, and trace context. - Condition-based rules — ACL rules support
conditionsfor identity type, role, and call depth filtering.
- Full validation —
Config.validate()checks schema structure, value types, and range constraints. - Hot reload —
Config.reload()re-reads the YAML source and re-validates. - Environment overrides —
APCORE_*environment variables override config values (e.g.,APCORE_EXECUTOR_DEFAULT_TIMEOUT=5000). Config.from_defaults()— Factory method for default configuration.
- RetryMiddleware — Configurable retry with exponential/fixed backoff, jitter, and max delay. Only retries errors marked
retryable=True.
- ID conflict detection — Registry detects and prevents registration of conflicting module IDs.
- Safe unregister —
safe_unregister()with drain timeout for graceful module removal.
- Generic
servicestyping —Context[T]supports typed dependency injection via theservicesfield.
- Conformance test suite — JSON fixture-driven tests for error codes, call chain safety, ACL evaluation, pattern matching, specificity, ID normalization, and version negotiation.
- New unit tests — 17 new test files covering all added features.
_check_safety()now delegates to standaloneguard_call_chain()instead of inline logic.- Error handling wraps exceptions with
propagate_error()and re-raises withraise wrapped from exc. - Global deadline set on root call only, propagated to child contexts via
Context._global_deadline.
- Expanded
__all__inapcore.__init__with new exports:RetryMiddleware,RetryConfig,ErrorCodeRegistry,ErrorCodeCollisionError,VersionIncompatibleError,negotiate_version,guard_call_chain,propagate_error,normalize_to_canonical_id,calculate_specificity,AuditEntry,parse_docstring.
0.7.1 - 2026-03-04
- Module Protocol — Introduced
Moduleprotocol inapcore.modulefor standardized module typing. - Schema System — Exposed schema APIs (
SchemaLoader,SchemaValidator,SchemaExporter,RefResolver,to_strict_schema) to the top-levelapcoreexports. - Utilities — Exposed
match_patternutility to the top-levelapcoreexports.
0.7.0 - 2026-03-01
- ApprovalHandler Protocol - Async protocol for pluggable approval handlers with
request_approval()andcheck_approval()methods - ApprovalRequest / ApprovalResult - Frozen dataclasses carrying invocation context and handler decisions with
Literalstatus typing - Phase A (synchronous) - Handler blocks until approval decision; denied/timeout raise immediately
- Phase B (asynchronous) -
pendingstatus returns_approval_tokenfor async resume viacheck_approval() - Built-in handlers -
AlwaysDenyHandler(safe default),AutoApproveHandler(testing),CallbackApprovalHandler(custom logic) - Approval errors -
ApprovalError,ApprovalDeniedError,ApprovalTimeoutError,ApprovalPendingErrorwithresult,module_id, andreasonproperties - Audit events (Level 3) - Dual-channel emission:
logging.info()always + span events when tracing is active - Extension point -
approval_handlerregistered as a built-in extension point inExtensionManager - ErrorCodes - Added
APPROVAL_DENIED,APPROVAL_TIMEOUT,APPROVAL_PENDINGconstants
- Step 4.5 approval gate - Inserted between ACL (Step 4) and input validation (Step 5) in
call(),call_async(), andstream() - Executor.set_approval_handler() - Runtime handler configuration
- Executor.from_registry() - Added
approval_handlerparameter - Dict and dataclass annotations - Both
ModuleAnnotationsand dict-stylerequires_approvalsupported - Unknown status fail-closed - Unrecognized approval statuses treated as denied with warning log
- Approval errors re-exported from
apcore.approvalfor multi-language SDK consistency; canonical definitions remain inerrors.py ApprovalResult.statustyped asLiteral["approved", "rejected", "timeout", "pending"]per PROTOCOL_SPEC §7.3.2
0.6.0 - 2026-02-23
- ExtensionManager / ExtensionPoint - Added a unified extension-point framework for
discoverer,middleware,acl,span_exporter, andmodule_validator - Extension wiring - Added
apply()support to connect registered extensions intoRegistryandExecutor
- AsyncTaskManager - Added background task orchestration with status tracking, cancellation, concurrency limits, shutdown, and cleanup
- TaskStatus / TaskInfo - Added task lifecycle enum and metadata dataclass for async task management
- CancelToken / ExecutionCancelledError - Added cooperative cancellation primitives and integrated cancellation checks into executor flows
- TraceContext / TraceParent - Added W3C Trace Context utilities for
inject(),extract(), and strict parsing viafrom_traceparent() - Context.create(trace_parent=...) - Added distributed-tracing entry support by accepting inbound trace context
- OTLPExporter top-level export - Added OTLP exporter re-exports in observability and top-level public API
- Custom discoverer/validator hooks - Added
set_discoverer()andset_validator()integration paths - Module describe support - Added
Registry.describe()for human-readable module descriptions - Hot-reload APIs - Added
watch(),unwatch(), and file-change handling helpers for extension directories - Validation constants/protocols - Added
MAX_MODULE_ID_LENGTH,RESERVED_WORDS,Discoverer, andModuleValidatorexports
- Expanded top-level
apcoreexports to include cancellation, extensions, async task types, trace context types, additional registry protocols/constants, and new error classes
- Added
ModuleExecuteErrorandInternalErrorto the framework error hierarchy and exports - Extended
ErrorCodeswith additional constants used by newer execution/extension paths
- executor - Added recursive
_secret_key redaction for nested dictionaries - executor - Preserved explicit cancellation semantics by re-raising
ExecutionCancelledError
- Reduced import-coupling risk across middleware/observability/trace typing paths while preserving existing runtime behavior and public interfaces
0.5.0 - 2026-02-22
- decorator - Renamed
_generate_input_model/_generate_output_modeltogenerate_input_model/generate_output_modelas public API - context_logger - Renamed
formatparameter tooutput_formatto avoid shadowing Python builtin - registry - Renamed
_write_lockto_lockfor clearer intent
- decorator - Replaced bare
dictwithdict[str, Any]in_normalize_result,annotations,metadata,_async_execute,_sync_execute - bindings - Fixed
_build_model_from_json_schemaparameter type fromdicttodict[str, Any] - scanner - Fixed
rootsparameter type fromlist[dict]tolist[dict[str, Any]] - metrics - Fixed
snapshotreturn type fromdicttodict[str, Any] - executor - Removed redundant string-quoted forward references in
from_registry; fixedmiddlewaresparameter type tolist[Middleware] | None
- executor - Extracted
_convert_validation_errors()helper to eliminate 6 duplicated validation error conversion patterns - executor - Refactored
call_async()andstream()to use new async middleware manager methods - executor - Removed internal
_execute_on_error_asyncmethod (replaced byMiddlewareManager.execute_on_error_async) - loader - Use
self._resolver.clear_cache()instead of accessing private_file_cachedirectly - tracing - Replaced
print()withsys.stdout.write()inStdoutExporter - acl / loader - Changed hardcoded logger names to
logging.getLogger(__name__)
- ExtensionManager and ExtensionPoint for unified extension point management (discoverer, middleware, acl, span_exporter, module_validator) with
register(),get(),get_all(),unregister(),apply(),list_points()methods - AsyncTaskManager, TaskStatus, TaskInfo for async task execution with status tracking (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED), cancellation, and concurrency limiting
- TraceContext and TraceParent for W3C Trace Context support with
inject(),extract(), andfrom_traceparent()methods Context.create()now accepts optionaltrace_parentparameter for distributed trace propagation
- MiddlewareManager - Added
execute_before_async(),execute_after_async(),execute_on_error_async()for proper async middleware dispatch withinspect.iscoroutinefunctiondetection - RefResolver - Added
clear_cache()public method for cache management - Executor - Added
clear_async_cache()public method
- SchemaExporter - Added
streaminghint toexport_mcp()annotations fromModuleAnnotations
- context - Changed
Identity.rolesfrom mutablelist[str]to immutabletuple[str, ...]in frozen dataclass
- context_logger / metrics - Handle cases where
before()was never called inObsLoggingMiddlewareandMetricsMiddleware
- acl - Added explicit
encoding="utf-8"to YAML file open
0.4.0 - 2026-02-20
- Executor.stream() - New async generator method for streaming module execution
- Implements same 6-step pipeline as
call_async()(context, safety, lookup, ACL, input validation, middleware before) - Falls back to
call_async()yielding single chunk for non-streaming modules - For streaming modules, iterates
module.stream()and yields each chunk - Accumulates chunks via shallow merge for output validation and after-middleware
- Full error handling with middleware recovery
- Implements same 6-step pipeline as
- ModuleAnnotations.streaming - New
streaming: bool = Falsefield to indicate if a module supports streaming execution - Test coverage - Added 5 comprehensive tests in
test_executor_stream.py:- Fallback behavior for non-streaming modules
- Multi-chunk streaming
- Module not found error handling
- Before/after middleware integration
- Disjoint key accumulation via shallow merge
0.3.0 - 2026-02-20
- ErrorCodes - New
ErrorCodesclass with all framework error code constants; replaces hardcoded error strings - ContextFactory Protocol - New
ContextFactoryprotocol for creating Context from framework-specific requests (e.g., Django, FastAPI) - Registry constants - Exported
REGISTRY_EVENTSdict andMODULE_ID_PATTERNregex for consistent module ID validation - Executor.from_registry() - Convenience factory method for creating an Executor from a Registry with optional middlewares, ACL, and config
- Comprehensive schema system - Full implementation with loading, validation, and export capabilities
- Schema loading from JSON/YAML files
- Runtime schema validation
- Schema export functionality
- ErrorCodes class - Prevent attribute deletion to ensure error code constants remain immutable
- Planning documentation - Updated progress bar style in overview.md
0.2.3 - 2026-02-20
- ContextFactory Protocol - New
ContextFactoryprotocol for creating Context from framework-specific requests (e.g., Django, FastAPI) - ErrorCodes - New
ErrorCodesclass with all framework error code constants; replaces hardcoded error strings - Registry constants - Exported
REGISTRY_EVENTSdict andMODULE_ID_PATTERNregex for consistent module ID validation - Executor.from_registry() - Convenience factory method for creating an Executor from a Registry with optional middlewares, ACL, and config
- Module ID validation - Strengthened to enforce lowercase letters, digits, underscores, and dots only; no hyphens allowed. Pattern:
^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ - Registry events - Replaced hardcoded event strings with
REGISTRY_EVENTSconstant dict - Test fixtures - Updated registry test module IDs to comply with new module ID pattern
- .code-forge.json - Updated directory mappings:
basefromplanning/to./;inputfromfeatures/to../apcore/docs/features
- Better type hints and protocol definitions for framework integration
- Consistent error handling with standardized error codes
0.2.2 - 2026-02-16
- planning/features/ - Moved all feature specifications to
apcore/docs/features/for better organization with documentation - planning/implementation/ - Restructured implementation planning to consolidate with overall project architecture
- Implementation planning - Reorganized implementation plans to streamline project structure and improve maintainability
0.2.1 - 2026-02-14
- code-forge integration - Added
.code-forge.jsonconfiguration (v0.2.0 spec) with_toolmetadata, directory mappings, and execution settings - Feature specifications - 7 feature documents in
planning/features/covering all core modules: core-executor, schema-system, registry-system, middleware-system, acl-system, observability, decorator-bindings - Implementation plans - Complete implementation plans in
planning/implementation/for all 7 features, each containingoverview.md,plan.md,tasks/*.md, andstate.json - Project-level overview - Auto-generated
planning/implementation/overview.mdwith module dependency graph, progress tracking, and phased implementation order - Task breakdown - 42 task files with TDD-oriented steps, acceptance criteria, dependency tracking, and time estimates (~91 hours total estimated effort)
0.2.0 - 2026-02-14
- MiddlewareManager - Added internal locking and snapshot pattern;
add(),remove(),execute_before(),execute_after()are now thread-safe - Executor - Added lock to async module cache; use
snapshot()for middleware iteration incall_async()andmiddlewaresproperty - ACL - Internally synchronized;
check(),add_rule(),remove_rule(),reload()are now safe for concurrent use - Registry - Extended existing
RLockto cover all read paths (get,has,count,module_ids,list,iter,get_definition,on,_trigger_event,clear_cache)
- InMemoryExporter - Replaced unbounded
listwithcollections.deque(maxlen=10_000)and addedthreading.Lockfor thread-safe access
- TracingMiddleware - Added empty span stack guard in
after()andon_error()to log a warning instead of raisingIndexError - Executor - Set
daemon=Trueon timeout and async bridge threads to prevent blocking process exit
- apdev integration - Added
apdev[dev]as development dependency for code quality checks and project tooling - pip install support - Moved dev dependencies to
[project.optional-dependencies]sopip install -e ".[dev]"works alongsideuv sync --group dev - pre-commit hooks - Fixed
check-charsandcheck-importshooks to run as local hooks viaapdevinstead of incorrectly nesting underruff-pre-commitrepo
- Context.child() - Added docstring clarifying that
datais intentionally shared between parent and child for middleware state propagation
0.1.0 - 2026-02-13
- Schema-driven modules - Define modules with Pydantic input/output schemas and automatic validation
- @module decorator - Zero-boilerplate decorator to turn functions into schema-aware modules
- Executor - 10-step execution pipeline with comprehensive safety and security checks
- Registry - Module registration and discovery system with metadata support
- Access Control (ACL) - Pattern-based, first-match-wins rule system with wildcard support
- Call depth limits - Prevent infinite recursion and stack overflow
- Circular call detection - Detect and prevent circular module calls
- Frequency throttling - Rate limit module execution
- Timeout support - Configure execution timeouts per module
- Composable pipeline - Before/after hooks for request/response processing
- Error recovery - Graceful error handling and recovery in middleware chain
- LoggingMiddleware - Structured logging for all module calls
- TracingMiddleware - Distributed tracing with span support for observability
- YAML bindings - Register modules declaratively without modifying source code
- Configuration system - Centralized configuration management
- Environment support - Environment-based configuration override
- Tracing - Span-based distributed tracing integration
- Metrics - Built-in metrics collection for execution monitoring
- Context logging - Structured logging with execution context propagation
- Sync/Async modules - Seamless support for both synchronous and asynchronous execution
- Async executor - Non-blocking execution for async-first applications
- Type safety - Full type annotations across the framework (Python 3.11+)
- Comprehensive tests - 90%+ test coverage with unit and integration tests
- Documentation - Quick start guide, examples, and API documentation
- Examples - Sample modules demonstrating decorator-based and class-based patterns
- pydantic >= 2.0 - Schema validation and serialization
- pyyaml >= 6.0 - YAML binding support
- Python 3.11+