Agnara Historical Reference Application #002 Demonstrating runtime-owned dependency injection, provider registration, compile-time DAG compilation, and caller parameter isolation with
agnara==0.1.0a2on Python 3.14+.
Agnara Dependency Intelligence is a software-task planning reference application designed to teach the dependency-injection model of the Agnara capability framework. It illustrates how a capability accepts business input from a caller while receiving underlying repositories and analyzers directly from the runtime.
In the Agnara ecosystem curriculum, reference applications form a structured learning progression:
- #001 — Agnara Task Intelligence: Capabilities, schema inference, execution plans, direct invocation, and input validation.
- #002 — Agnara Dependency Intelligence (this repository): Separating public caller inputs from runtime-owned dependencies, provider registration (
@provider), registry binding (DIRegistry), static graph compilation (ExecutionPlan.compile), and container resolution (DIContainer). - #003 — Agnara Secure Operations: Scopes, principals, risk governance, interactive policies, and execution confirmation.
From #001, you learned how capabilities declare schemas, how ExecutionPlan compiles input validation, and how invocations return canonical Success and Failure values. #002 builds directly on this foundation by introducing runtime-owned dependencies into the capability signature.
In Agnara, dependency injection separates caller-owned business inputs from runtime-owned dependencies. The compiled execution plan identifies dependency-backed parameters as protected runtime parameters, while the runtime resolves registered dependencies through the DI graph. In agnara==0.1.0a2, attempts to supply protected runtime-owned parameters through Invocation.payload are rejected before the capability handler executes.
The caller provides only business data via Invocation.payload. In our primary capability:
payload = {"task": "Add OAuth login to the admin portal"}Agnara automatically resolves and injects runtime-owned dependencies into the capability handler:
repository: TaskRepository(bound toInMemoryTaskRepository)analyzer: RiskAnalyzer(configured withRiskRules)
The caller never instantiates these dependencies. Attempts to supply protected runtime-owned parameters through Invocation.payload are rejected.
A provider is a factory function decorated with @provider(scope=...) from agnara.core.di. It defines how to instantiate a dependency and assigns it a lifecycle scope (Scope.SINGLETON or Scope.INVOCATION).
DIRegistry is the registry that maps abstraction types (e.g. TaskRepository) to their corresponding ProviderDefinition.
ExecutionPlan.compile(definition, registry) compiles the dependency graph ahead of invocation time. It performs cycle detection (compile_dag), verifies that all provider parameters can be resolved, divides handler parameters into protected_parameters vs input_schemas, and compiles input schemas for the caller-owned parameters.
- If a capability handler parameter is not bound in
DIRegistry, Agnara assumes it is a caller input; when compiling schemas,StandardSchemaAdapterrejects the custom interface and raises a compile-timeSchemaError. - If a provider requires an argument that is not registered,
compile_dagimmediately raisesDependencyResolutionErrorat compilation time. - If a caller attempts to supply a runtime-owned parameter inside
Invocation.payload, Agnara detects a protected parameter violation and raisesInvocationErrorbefore the handler is ever invoked.
This repository is a frozen historical reference application. It demonstrates what was possible using the public agnara==0.1.0a2 release published on PyPI. It will not be upgraded to newer framework releases, preserving an accurate historical artifact.
def prepare_task_plan(
task: str,
repository: TaskRepository,
analyzer: RiskAnalyzer,
) -> TaskPlan: ...| Parameter | Ownership | Origin | Validation / Resolution |
|---|---|---|---|
task: str |
Caller-Owned | Invocation.payload["task"] |
Validated by StandardSchemaAdapter |
repository: TaskRepository |
Runtime-Owned | Injected via DIRegistry |
Provided by provide_task_repository (Singleton) |
analyzer: RiskAnalyzer |
Runtime-Owned | Injected via DIRegistry |
Provided by provide_risk_analyzer (Invocation Scope) |
flowchart TD
subgraph 1. Composition["1. Composition Root"]
App["Agnara('dependency_intelligence')"]
CapDecl["@app.capability\nprepare_task_plan(task, repository, analyzer)"]
PRepo["@provider(scope=SINGLETON)\nprovide_task_repository() -> TaskRepository"]
PRules["@provider(scope=SINGLETON)\nprovide_risk_rules() -> RiskRules"]
PAnalyzer["@provider(scope=INVOCATION)\nprovide_risk_analyzer(rules: RiskRules) -> RiskAnalyzer"]
Reg["DIRegistry()\n.bind(TaskRepository, ...)\n.bind(RiskRules, ...)\n.bind(RiskAnalyzer, ...)"]
end
subgraph 2. Compilation["2. Compilation Phase"]
Freeze["app.compile() -> FrozenCapabilityRegistry"]
DAG["compile_dag(registry, [handler])\nResolves provider graph\nDetects cycles & unbound dependencies"]
Plan["ExecutionPlan.compile(definition, registry)\nExtracts protected_parameters\nCompiles input_schemas for caller inputs"]
end
subgraph 3. Runtime["3. Runtime Execution"]
Invoc["Invocation(payload={'task': '...'}, metadata={})"]
Cont["DIContainer(registry)\nSingleton & Invocation caches"]
Ctx["ExecutionContext(invocation, container)"]
Guard["Protected Parameter Guard\nRejects payload if it supplies runtime-owned keys"]
Val["_validate_inputs(plan, payload)\nValidates caller input types"]
Res["DIContainer.resolve_dependencies()\nInstantiates & caches dependencies"]
Call["prepare_task_plan(task=..., repository=..., analyzer=...)"]
Out["TaskPlan / CanonicalResult"]
end
CapDecl --> App
App --> Freeze
PRepo --> Reg
PRules --> Reg
PAnalyzer --> Reg
Freeze --> Plan
Reg --> DAG
DAG --> Plan
Invoc --> Ctx
Cont --> Ctx
Plan --> Guard
Ctx --> Guard
Guard --> Val
Val --> Res
Res --> Call
Call --> Out
- CPython 3.14 or newer.
- Git.
git clone https://github.com/agnara-project/agnara-dependency-intelligence.git
cd agnara-dependency-intelligence
python3.14 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install pytest>=9.0.0 ruff>=0.16.0git clone https://github.com/agnara-project/agnara-dependency-intelligence.git
cd agnara-dependency-intelligence
py -3.14 -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
pip install pytest>=9.0.0 ruff>=0.16.0Execute the reference scenarios directly:
python app.py=== Agnara Historical Reference Application #002 ===
Topic: Dependency Injection & Runtime-Owned Dependencies
Target Framework: agnara==0.1.0a2 (Python >=3.14)
[Compilation Inspection]
Capability ID: dependency_intelligence.prepare_task_plan
Direct dependencies: ['TaskRepository', 'RiskAnalyzer']
Protected parameters: ['analyzer', 'repository']
Required inputs: ['task']
Input schemas: ['task']
[Scenario A] Input: 'Add OAuth login to the admin portal'
Complexity: medium
Risk: medium
Estimated steps: 4
Requires review: False
Related tasks: ['HT-101', 'HT-102']
Recommendation: Follow standard authentication integration guidelines.
[Scenario B] Input: 'Replace production authentication and migrate customer credentials'
Complexity: high
Risk: critical
Estimated steps: 8
Requires review: True
Related tasks: ['HT-101', 'HT-102', 'HT-103', 'HT-105']
Recommendation: Requires multi-party review and staged migration window.
[Security/Integrity Demonstration: Rejected Runtime-Owned Parameter]
Rejected as expected: invocation payload supplies runtime-owned parameter(s): repository
[Schema Validation Demonstration: Invalid Caller Input]
Canonical Failure Code: invalid_input
Canonical Failure Message: expected str, got int
Error Path Details: {'path': ('task',)}
All demonstration scenarios executed successfully.
Execute the full test suite with pytest:
python -m pytest tests/ -vAll 24 tests verify domain rules, provider decorators, registry bindings, compile-time DAG validation, cycle detection, scope caching, and payload isolation.
Check formatting and linting with Ruff:
ruff format --check .
ruff check .- Architecture Specification: Full execution pipeline and design principles.
- Dependency Injection Deep Dive: In-depth guide to providers, scopes, registries, and resolution.
- Capability Specification: Formal definition of
prepare_task_plan. - Development Guide: Environment setup, tools, and workflows.
- Historical Context & Baseline: Positioning within the Agnara roadmap.
- Testing Strategy: Overview of invariants and test coverage.
- Agent Handbook: Instructions for AI coding assistants and agents.
This repository intentionally preserves the Agnara 0.1.0a2 dependency-injection baseline.
Its purpose is to demonstrate the separation between caller-owned capability inputs and runtime-owned dependencies, provider registration, dependency graph compilation, and dependency resolution.
It is not intended to evolve into a showcase of every future Agnara capability. More advanced execution-governance concepts are demonstrated by subsequent Historical Reference Applications.
- Exact-Type Matching:
DIRegistrybinds providers to exact Python types (dict[type, ProviderDefinition]). Subtype or protocol resolution is resolved through explicit registration. - Provider Direct Payloads Prohibited: Providers cannot accept request payloads directly; all provider arguments must themselves be bound providers in
DIRegistry. - Transport-Neutral In-Memory: As an execution kernel proof-of-concept,
0.1.0a2excludes network transport adapters (HTTP, MCP), which reside in future ecosystem layers.
This project is licensed under the Apache-2.0 License. See LICENSE for details.