Skip to main content
AFK’s architecture is built on one principle: orchestration and execution are separate concerns. The runner (orchestration) manages the step loop, state, and policies. Adapters (execution) handle LLM calls, tool execution, and external communication. You can swap any adapter without touching your agent code.

Layered architecture

Module boundaries

ModuleResponsibilityDependencies
afk.agentsAgent definition, configuration, fail-safepydantic
afk.coreRunner, step loop, state management, policiesafk.agents, afk.llms, afk.tools, afk.memory
afk.llmsLLM runtime, provider adapters, retry/circuit breakingProvider SDKs
afk.toolsTool registry, execution, validation, hookspydantic
afk.memoryState persistence, checkpoints, compactionBackend drivers
afk.telemetryEvent pipeline, metrics, exportersOTEL SDK (optional)
afk.a2aAgent-to-agent protocol, auth, external adaptersafk.core
afk.evalsEval runner, assertions, reportingafk.core
Key rule: Modules in the Adapters layer never import from each other. afk.llms doesn’t know about afk.tools. Only afk.core wires them together.

Runtime sequence

What happens when you call runner.run(agent, user_message="..."):

Contract boundaries

Every boundary between modules uses a typed contract: All contracts are Pydantic models. This means:
  • Validation at boundaries — malformed data causes clear errors
  • Serializable — every contract can be logged, stored, or sent over the wire
  • Versionable — contracts have stable shapes for backward compatibility

Error isolation

Failures in one adapter don’t crash the others:
FailureImpactRunner behavior
LLM call failsNo response for this stepRetry (if retryable) or fail the run
Tool execution failsTool result is an error objectReturn error to LLM for self-correction
Memory backend failsState not persistedRun continues (degraded mode)
Telemetry failsEvents not exportedRun continues (events dropped silently)
Telemetry failures are always silent. A broken exporter should never block an agent run. If telemetry is critical to your use case, add a separate monitoring check.

Design principles

  1. Contracts first. Define the interface (Pydantic model), then implement the behavior. Never skip the contract.
  2. No cross-adapter imports. afk.llms doesn’t import afk.tools. Only afk.core wires modules together.
  3. Classify failures. Every error is retryable, terminal, or non-fatal. The runner uses this classification to decide what to do.
  4. Least privilege. Adapters get only the data they need. LLM adapters don’t see tool results until the runner decides to include them.

Next steps