# AIRuntime Architecture

> **Philosophy:** Small Core. Powerful Capabilities.

AIRuntime is a local-first AI runtime for Unity and .NET applications.

It provides a stable orchestration layer for AI-powered software while hiding
the transport and data-format details of individual model providers.
Applications integrate with AIRuntime rather than coupling themselves directly
to Ollama, OpenAI, or another provider API.

The runtime is the product. Providers and capabilities are replaceable
extensions around it.

---

## Architectural Goals

AIRuntime is designed to be:

- Local-first
- Provider-agnostic
- UI-agnostic
- Explicitly configured
- Asynchronous and cancellation-aware
- Testable without a live model
- Observable
- Secure by default at capability boundaries
- Extensible through composition

The runtime should grow through new providers, capabilities, middleware, and
observers without introducing provider-specific decisions into the core.

---

## System Context

```text
Application
    |
    v
AIRuntime facade
    |
    +-- Agent layer
    +-- Conversation engine
    +-- Capability system
    +-- Provider coordination
    +-- Workspace filesystem
    +-- Run observers and results
            |
            v
      Provider adapter
            |
            v
       Provider API / LLM
```

The application owns the user experience and application state.

AIRuntime owns orchestration, execution policy, provider resilience,
conversation state, and capability dispatch.

Kanban delegation is outcome-sensitive and structurally enforced when
**Auto-decompose** is enabled. Broad new builds such as a complete game,
application, or system enter an orchestration-only run whose capability surface
contains only scoped Kanban graph tools. The parent creates the smallest useful
graph of independently owned implementation and verification outcomes,
coordinates dependencies and integration, and never duplicates child work.
Narrow cohesive tasks execute directly. A card can explicitly opt out of
decomposition, and operators can switch Auto-decompose off for a manual board.

The companion **Kanban Graph** window renders that live task structure on a
free pan-and-zoom canvas. Parent-child hierarchy links, declared dependencies,
and runtime blocker links use distinct edge colors; node colors reflect Kanban
status. Nodes can be positioned manually without changing board data, while
Auto Layout and Fit rebuild a readable dependency flow as decomposition grows.

Each Kanban card also owns a bounded persistent execution-memory journal. Agent
events record run boundaries, tool names and arguments, summarized results and
failures, and collapsed repeat counts. The editor saves journal updates during
the run and before assembly reload, displays them on the card, and injects the
journal into later implementation and review prompts so resumed agents continue
from observed state instead of rediscovering it. The journal retains at most 48
entries and 24,000 serialized characters; individual arguments and results are
bounded to keep card context usable.

PlayMode verification temporarily disables Unity's domain reload while a
`run_play_mode_tests` or owned `control_play_mode` session is active and
explicitly keeps scene reload enabled. Automated runs therefore receive normal
scene isolation without showing Unity's blocking warning for entering Play Mode
with scene reload disabled, while the awaiting agent and its completion callback
survive long enough to receive the result. The prior Enter Play Mode settings
are restored when the run completes or is cancelled. Without this scope, a test
entry can destroy the active Kanban run and auto-resume can repeatedly create
and delete Unity's `InitTestScene...` bootstrap scene.

Provider adapters own translation between AIRuntime's internal contracts and
provider-specific HTTP contracts.

The external model produces responses and requests tool execution. It never
executes application capabilities directly.

---

## Main Runtime Areas

```text
Assets/Runtime/
|
+-- AIRuntime.cs                 Public facade and composition root
+-- Core/                        Conversation state and orchestration loop
+-- Agents/                      Agent definitions, profiles, sessions, and policy
+-- Capabilities/                Capability contracts, schema, dispatch, and pipeline
+-- Filesystem/                  Workspace-contained filesystem implementation
+-- Models/                      Provider-independent conversation contracts
+-- Providers/                   Provider contracts, adapters, and resilience
+-- Services/                    Run context, results, events, and observers
+-- Tools/                       Direct tool-call parsing and execution
+-- Docs/                        Architecture, dependency rules, and roadmap
```

This is a logical module map, not a complete file listing. The runtime currently
ships as one Unity assembly, so these boundaries are architectural conventions
rather than assembly-enforced constraints.

---

## Public Facade

`AIRuntime` is the primary entry point for applications. It is both the public
facade and the current composition root.

It owns and wires:

- The workspace and filesystem implementation
- The capability registry and dispatcher
- The capability permission policy and approval handler
- Provider priority, retry, health, and cooldown services
- The conversation engine
- Agent observers
- Direct tool-call helpers

Applications normally create providers and capabilities, then register them
through the facade. They should not need to construct the internal registry,
dispatcher, pipeline, or conversation engine.

```csharp
using AIRuntime.Runtime.Capabilities.Permissions;
using AIRuntime.Runtime.Filesystem;
using AIRuntime.Runtime.Providers.Ollama;

var runtime =
    new AIRuntime.Runtime.AIRuntime(
        workspaceRoot);

runtime.UseProvider(
    new OllamaProvider(
        model: "qwen2.5:7b-instruct"));

runtime.AllowPermission(
    CapabilityPermission.ReadOnly);

runtime.RegisterCapability(
    new ReadFileCapability());

string response =
    await runtime.ChatAsync(
    "Read README.md and summarize it.");
```

The default constructor remains a convenience composition. Packages that need
to replace services use `AIRuntimeBuilder` and `IAIRuntimeModule`. A module can
replace the filesystem, provider failure classifier, or provider-tool builder;
add capability middleware and observers; disable default middleware/console
logging; and contribute ordinary runtime configuration without changing the
facade.

```csharp
var runtime =
    new AIRuntimeBuilder(workspaceRoot)
        .AddModule(new MyRuntimeModule())
        .Build();
```

Runtime configuration cannot be changed while a run is active. This protects a
run from observing partially changed providers, permissions, capabilities,
timeouts, prompts, or observers.

---

## Execution APIs

AIRuntime exposes three conversation-oriented execution modes.

### `ChatAsync`

Returns only the final response text and uses the persistent conversation.

Use it for simple chat-style integrations where structured execution metadata
is not required.

### `RunAsync`

Returns an `AgentRunResult` and uses the persistent conversation.

The result reports:

- Response text
- Run status
- Session identifier
- Number of capability calls executed
- Duration
- Failure information

An overload accepts an `AgentRuntimeContext` to apply an agent-specific
capability set, capability access policy, and provider request options.

### `RunIsolatedAsync`

Returns an `AgentRunResult` but executes against a new temporary conversation.
Messages from the isolated run are not added to the persistent conversation.

Use isolated runs for independent tasks that must not inherit or modify chat
history.

### Concurrency

A `ConversationEngine` accepts one active run at a time. A concurrent attempt is
rejected rather than sharing mutable conversation or configuration state.

Each structured run receives its own `AgentRunContext`, including a unique
session identifier, timing information, capability policy, and tool-call count.

---

## Conversation Engine

`ConversationEngine` owns the provider/capability loop. It coordinates
provider-independent services and contains no Ollama- or OpenAI-specific
branches.

Its responsibilities include:

- Persistent and isolated conversation state
- System-prompt application
- Provider requests
- Provider retries and fallback selection
- Provider health and cooldown tracking
- Bounded malformed tool-call correction
- Capability advertisement
- Iterative capability execution
- Maximum capability-round enforcement
- Run and provider timeouts
- Cancellation
- Conversation rollback after unsuccessful runs
- Run events and structured results

The high-level flow is:

```text
Application
    |
    v
AIRuntime
    |
    v
ConversationEngine
    |
    +--> Build provider-independent tool definitions
    |
    +--> Select an available provider
    |        |
    |        +--> Apply per-attempt timeout
    |        +--> Retry retryable failures
    |        +--> Fall back when appropriate
    |
    +--> Receive ChatCompletion
             |
             +-- Rejected tool calls
             |       |
             |       +--> Preserve bounded diagnostics
             |       +--> Request one schema correction
             |       +--> Stop an identical repeat
             |
             +-- No tool calls --> Store response and complete
             |
             +-- Tool calls
                    |
                    v
              Verify run scope
                    |
                    v
              CapabilityDispatcher
                    |
                    v
              Permission / approval
                    |
                    v
              Middleware pipeline
                    |
                    v
              Capability execution
                    |
                    v
              Add tool result to conversation
                    |
                    +--> Continue provider loop
```

Short, unconditional action announcements without tool calls (for example,
"I'll now inspect the scene") receive at most two continuation prompts per run
when tools are available. Continuation retains the conversation, capability
scope, permissions, and duplicate-call tracking, and emits `ModelResponseRepair`
events. Repeated announcements fail with `ActionContinuationExhausted` rather
than publishing `RunCompleted`. This conservative English-language detector
does not establish task completion; Kanban acceptance and review gates still
own that decision. Questions, conditional offers, and substantive answers are
left to the normal completion path.

The loop stops when the provider returns another completion without tool calls or when
the run reaches cancellation, timeout, failure, or an explicitly configured
maximum number of capability rounds. The default value is zero, which imposes
no fixed round ceiling. Repeated reads execute again because they observe live
editor, compilation, and filesystem state that may change without different
arguments. Repeated successful mutations remain idempotently suppressed and
replay their prior result without removing other tools from the next turn. A
repeated failed mutation is replayed once, then stopped as no progress. Distinct
argument payloads also stop after the same recoverable capability error occurs
three times without an intervening successful mutation; changing JSON ordering
or guessing alternate values is not forward progress. Authoritative test
verification may converge from an already-finished result;
cancellation remains the operator-controlled escape hatch for a provider that
never progresses.

Provider content that still contains a textual tool invocation is never accepted
as a successful final answer. It is converted to a structured call when the tool
is available, or rejected as incomplete when it is not. Kanban review likewise
returns an implementation containing unexecuted tool markup to To Do rather than
parking it in an evidence-only wait state.

Malformed provider tool calls are also never executed and are not treated as
transient network failures. A provider adapter records them as bounded
`RejectedToolCall` values on `ChatCompletion`. `ConversationEngine` gives the
model one correction turn containing the rejected shape and the matching
offered capability schema. An identical malformed call, or a different
malformed call after that single correction, terminates the run without another
blind retry. The `ModelResponseRepair` event makes this correction visible in
Chat activity and persistent Kanban execution memory.

Provider adapters may deterministically normalize a non-standard encoding only
when its meaning is unambiguous. In particular, an OpenAI-compatible response
that places several JSON argument objects next to each other, or in one object
array, under a single function name is expanded into ordered internal calls
with unique IDs. Expansion is bounded, requires every value to be an object,
and emits `ModelResponseNormalized`. The resulting calls still pass through
ordinary schema validation, permissions, duplicate detection, and conservative
mutation batching. Partially valid sequences are rejected in full.

---

## Conversation State

`Conversation` is the single owner of message history. A separate
`ConversationHistory` abstraction is not used.

Successful persistent runs retain their messages for the next request.
Unsuccessful runs restore the conversation to its checkpoint so partial user,
assistant, tool-call, or tool-result messages do not contaminate later runs.

The configured system prompt is applied by the conversation engine but is not
counted as a persistent conversation message.

---

## Agent Layer

The agent layer adds identity and per-agent execution policy on top of the
general runtime.

Its main concepts are:

- `AgentDefinition` — agent identity, system prompt, profile, and capability policy
- `AgentProfile` — provider-neutral execution preferences
- `AgentRegistry` — explicit registration and lookup
- `AgentSession` and `AgentSessionManager` — execution session lifecycle
- `AgentExecutionContext` — immutable identity for one execution
- `AgentRuntimeContext` — resolved capabilities, policy, and provider options
- `AgentExecutor` — adapts a registered agent into an AIRuntime run

Before execution, the agent capability resolver filters the runtime's registered
capabilities. Only capabilities allowed for that agent are advertised to the
provider and accepted during the run.

Agent configuration must remain provider-neutral. Generic settings such as
temperature and maximum tokens are translated into `ProviderRequestOptions`;
provider adapters decide whether and how to support them. A maximum-token value
of zero leaves the field unspecified so the provider uses its native limit.

Kanban execution separates planning authority from implementation authority.
AIRuntime classifies broad multi-outcome cards before execution. With
Auto-decompose enabled, those cards run as restricted orchestrators: they can
read their scoped task context and create child cards, but they cannot inspect
or mutate project assets. The completed child batch is published atomically
only after deterministic attachment, artifact ownership, mutable-resource, and
dependency-graph validation. A dependency releases downstream implementation
only after it reaches `Done` through successful review. Cards are reviewed as
they finish during the implementation phase; this prevents downstream work from
consuming an artifact that a later review sends back for correction. After every
descendant is reviewed and `Done`, the root enters a separate integration review.
Review gaps create focused follow-up children rather than reopening direct
implementation on the root.

Ownership also determines evidence scope before worker preflights and system
instructions are built. A child owning only source artifacts, with no mutable
resources or consumed scene/prefab, is checked against all its declared source
outputs and current compilation. Runtime API language in its prose does not
require an instantiated scene component. Existing scene/prefab inspection must be
an explicit consumed input; scene mutation must have an explicit owner or exact
resource grant. Scene assembly and integrated behavior evidence belong to their
ordered integration cards. Source-only authority cannot mutate live Unity
objects or project settings, even through capabilities without a file argument.
Rejected mutations identify the declared sibling owner when available.

Queued implementation and review completions persist their resulting workflow
state independently of the manual-edit Auto Save preference. The existing
revision/fingerprint conflict checks still prevent stale writers from replacing
newer board state.

Planning commits validate a detached candidate snapshot, including draft cards,
before changing the live graph. Candidate dependency resolution excludes omitted
siblings. Empty, incomplete, cyclic, conflicting, or unchanged kicked-back plans
leave existing cards and descendants intact. Only a valid complete replacement
retires omitted branches and promotes accepted drafts, in one synchronous
main-thread transition before saving. Completion repeats validation before any
retirement in its fallback path. Failed commit diagnostics retain the actual
validation error rather than claiming the planner never attempted a commit.

Compiler recovery feeds exact owned-file diagnostics into compilation rework.
Workers may read individual artifacts from completed transitive dependencies in
their sibling graph to compare upstream APIs; this never expands write access
or grants recursive directory reads. Compiler snapshots survive assembly reload
within the current Editor session. If Unity reports failure with no captured
errors, the diagnostic capability requests one recapture compilation and reports
the missing evidence explicitly. A fingerprint covers all errors regardless of
query filters. Three reads without a source change or newly revealed diagnostics
stop a scoped worker with `CompilationNoProgress`; unrelated and identical writes
do not reset that guard. The stop is terminal, preserves diagnostic context, and
is displayed as a controlled failure rather than an editor crash.

Execution-memory updates are durable diagnostics, but they are not all progress.
Tool starts and failures refresh run activity only; successful tool completions
refresh both activity and progress. The Editor reclaims a tracked run after
fifteen minutes with no provider, capability, or journal activity. Provider
timeouts and semantic no-progress guards remain the faster protections for
hung requests and circular capability calls.

Versioned board recovery and schema migrations are persisted synchronously even
when normal board-edit auto-save is disabled. Maintenance can invoke
`AIRuntimeKanbanWindow.MigrateDefaultBoardForCommandLine` through Unity's
`-executeMethod` path, so recovery does not depend on a visible window's
`OnEnable` timing.

The agent layer does not replace `ConversationEngine`. It prepares an
agent-specific execution context and delegates the actual conversation loop to
AIRuntime.

---

## Capabilities

Capabilities represent executable application actions.

Examples include:

- Reading or writing a workspace file
- Listing a directory
- Calling an application service
- Running a build
- Navigating a browser

A capability must not depend on:

- A concrete provider
- Provider transport DTOs
- Conversation history
- Provider HTTP behavior

Every capability exposes:

- `Name`
- `Description`
- `Permission`
- `Parameters`
- `ArgumentType`
- `ExecuteObjectAsync(arguments, context)`

Most capabilities should inherit from `CapabilityBase<TArguments>`. The base
class supplies the untyped runtime bridge while the capability implements typed
business logic.

Capability arguments use the standard schema and validation attributes in
`Capabilities/Attributes`, including:

- `Required`
- `Description`
- `DefaultValue`
- Length and range constraints
- File and workspace path constraints

`SchemaGenerator` is the canonical argument-schema generator.
`CapabilitySchemaExporter` converts capability descriptors into the
provider-independent tool representation or an OpenAI-compatible JSON form.

Capabilities are registered explicitly. AIRuntime does not scan assemblies or
silently auto-register actions.

---

## Capability Execution Pipeline

`CapabilityDispatcher` is responsible for lookup and authorization.
`CapabilityExecutor` is responsible for argument conversion, context creation,
pipeline execution, and result serialization.

The default pipeline is:

```text
ExceptionMiddleware
    |
    v
LoggingMiddleware
    |
    v
TimingMiddleware
    |
    v
ValidationMiddleware
    |
    v
Capability
```

The pipeline provides cross-cutting behavior without placing it inside each
capability:

- Exception reporting
- Execution logging
- Timing
- Argument validation
- Cancellation propagation

`CapabilityContext` supplies the deserialized arguments, raw arguments,
capability metadata, cancellation token, and the workspace-contained
`IFileSystem`.

---

## Capability Security

Capability security has two distinct layers.

### Runtime permission policy

Each capability declares a broad `CapabilityPermission`. The runtime policy
decides whether that category is:

- Allowed
- Denied
- Allowed only after approval

Approval-required calls are sent to the configured
`ICapabilityApprovalHandler`. Without a handler, approval-required capabilities
cannot execute.

### Agent capability policy

An agent has a name-level allow/deny policy. This controls which registered
capabilities the agent can see and invoke.

Both layers must allow a call. Agent filtering is not a replacement for runtime
permission enforcement.

Malformed, unknown, unadvertised, or unauthorized provider tool calls are
rejected explicitly rather than silently ignored.

---

## Filesystem and Workspace Containment

Filesystem capabilities execute through `IFileSystem`, not directly through
`System.IO`.

`Workspace` defines the root boundary, and `LocalFileSystem` resolves requested
paths inside that boundary. Filesystem operations must not escape the workspace
through absolute paths, parent traversal, or equivalent path tricks.

The built-in filesystem capability set includes:

- Append file
- Copy file
- Create directory
- Delete directory
- Delete file
- Check file existence
- Get file information
- List a directory with compact structured entry metadata
- List a bounded, structured directory tree
- Move file
- Read file
- Write file

Filesystem capability implementations live together under
`Filesystem/Capabilities`. Their argument and result contracts live under
`Filesystem/Models`.

---

## Providers

Providers translate between AIRuntime's internal contracts and an external API.

The runtime currently contains adapters for:

- Ollama
- OpenAI
- Nous Research Portal
- OpenRouter
- NVIDIA Developer API

Other provider folders may reserve future work and must not be interpreted as
implemented support.

The base provider contract is:

```csharp
public interface IProvider
{
    string Name { get; }

    Task<ChatCompletion> ChatAsync(
        IReadOnlyList<ConversationMessage> messages,
        IReadOnlyList<ProviderTool> tools,
        CancellationToken cancellationToken = default);
}
```

`IProviderWithRequestOptions` is the optional extension used by providers that
support generic request settings.

A provider adapter is responsible for:

- Translating internal messages to provider request DTOs
- Translating provider responses into `ChatCompletion`
- Translating provider-independent tool definitions
- Serializing and deserializing provider JSON
- Validating provider response structure
- Applying bounded, unambiguous provider-protocol normalization
- Preserving malformed tool calls as bounded, non-executable diagnostics

A provider adapter is not responsible for:

- Conversation ownership
- Capability execution
- Capability permission decisions
- Application state
- Agent identity
- Memory or planning
- Retry and fallback policy

Provider instances should not hold mutable conversation state.

---

## Provider Transport

Each concrete provider owns a low-level client interface and implementation.

```text
Providers/
|
+-- Ollama/
|   +-- IOllamaClient.cs
|   +-- OllamaClient.cs
|   +-- OllamaProvider.cs
|   +-- OllamaDtos.cs
|
+-- OpenAI/
    +-- IOpenAIClient.cs
    +-- OpenAIClient.cs
    +-- OpenAIProvider.cs
    +-- OpenAIDtos.cs
```

Responsibilities are separated as follows:

- **Client** — HTTP transport, endpoint configuration, headers, and HTTP errors
- **Provider** — mapping between internal contracts and provider DTOs
- **DTOs** — transport-only request and response shapes

Provider DTOs must not escape their concrete provider implementation.
Transport failures use `ProviderHttpException` so runtime resilience logic can
reason about status codes without parsing exception text.

OpenAI-compatible services reuse `OpenAIClient`, the shared DTO contracts, and
`OpenAICompatibleProvider`. `NousPortalProvider`, `OpenRouterProvider`, and
`NvidiaDeveloperProvider` supply stable provider identities, official default
endpoints, and service-specific configuration without duplicating tool-call
mapping or response validation.

Streaming termination is provider-declared rather than inferred by the HTTP
loop. `ProviderStreamTerminationPolicy` is an immutable protocol description
selected when a client is constructed. The download handler owns SSE framing,
text-delta delivery, cancellation, and raw tracing; the policy owns only the
terminal markers that prove the provider finished the response.

The standard OpenAI-compatible policy accepts `data: [DONE]` and the native
boolean `done` marker used by local compatible services. Nous Portal selects a
separate policy that additionally accepts its terminal `lastOne` metadata
frame. A non-null `finish_reason` is deliberately not sufficient on its own,
because providers may send final usage or other metadata afterward. Terminal
signals are retained in `ProviderTransportRecord` and shown in the Provider
Debug window so transport completion remains observable.

New compatible-provider plugins should declare their terminal JSON paths when
constructing the client. They must not add provider-name conditionals to the
shared transport loop.

---

## Provider Resilience

Provider resilience belongs to runtime orchestration rather than concrete
provider adapters.

The provider chain contains one primary provider and an ordered list of
fallbacks. For each request, the conversation engine:

1. Skips providers currently in cooldown.
2. Applies a timeout to each provider attempt.
3. Classifies failures.
4. Retries failures configured as retryable.
5. Updates provider health.
6. Moves to the next fallback when appropriate.

Default same-provider retries are reserved for transient transport categories
such as timeout, unavailability, and rate limiting. `InvalidResponse` is not
retried by replaying the same request. Structurally malformed tool calls use the
conversation-level schema-correction path instead, because only a new model
turn can explain the protocol error and ask for corrected arguments.

The run timeout covers the entire operation, including provider attempts,
retries, approvals, and capability execution. It is distinct from the
per-provider timeout.

Provider configuration, retry rules, health policy, and cooldown policy are
locked while a run is active.

---

## Internal Models and Terminology

AIRuntime deliberately separates its domain language from provider transport
language.

| AIRuntime concept | Provider/API concept |
|---|---|
| Capability | Tool or function |
| Capability invocation | Tool call or function call |
| `ConversationMessage` | Provider message DTO |
| `ChatCompletion` | Provider chat response |
| `ProviderTool` | Provider-specific tool definition |

Core models are provider-independent. Concrete provider DTOs remain private to
their provider folders.

---

## Observability

Structured runs publish lifecycle events through `IAgentObserver`.

Events include run and tool-call activity and carry the run session identifier.
Observers are registered explicitly on the runtime. The console observer is the
default implementation.

Editor integrations compose through `IAIRuntimeEditorRuntimePlugin`. The
runtime-plugin registry attaches a snapshot of registered observers and other
run-scoped integrations, isolates plugin failures, and exposes a revision so a
long-lived host rebuilds its composition after a plugin is added or removed.

The **Agent Activity** window projects these events into objective, stage,
milestone, retry, capability, review, completion, and error updates. It is an
observable-work view, not a chain-of-thought viewer: raw provider payloads,
hidden reasoning, full source bodies, and secret-bearing fields are excluded or
redacted. Large payloads take a length-only fast path rather than being parsed
on the agent thread.

`AgentRunContext` records per-run state, while `AgentRunResult` is the stable
result returned to the caller.

Logging is diagnostic behavior. Applications must not rely on log text as an
execution contract. Request and response body logging should eventually be
governed by an explicit logging and redaction policy.

---

## Direct Tool Execution

AIRuntime also supports direct execution outside the model conversation loop:

- `ExecuteCapabilityAsync` invokes a registered capability by name.
- `ExecuteToolCallAsync` parses and executes a serialized tool-call request.

Direct execution uses the same dispatcher, permission checks, approval rules,
validation, middleware, filesystem boundary, and cancellation behavior as
model-requested capability execution.

---

## Dependency Rules

The intended dependency direction is:

```text
Application
    |
    v
AIRuntime composition root
    |
    +--> Agent layer
    +--> Core orchestration
    +--> Capability system
    +--> Provider coordination
    +--> Filesystem
    +--> Services

Core orchestration
    +--> Internal models
    +--> Provider contracts and resilience
    +--> Capability contracts and dispatch
    +--> Run services
    +--> Agent capability policy

Concrete provider
    +--> Provider contracts
    +--> Internal models
    +--> Provider transport DTOs

Filesystem capability
    +--> Capability contracts
    +--> IFileSystem
    +--> Filesystem models
```

The following rules are mandatory:

1. Core must not reference a concrete provider.
2. Concrete providers must not reference application code or filesystem modules.
3. Capabilities must not reference concrete providers or provider DTOs.
4. Provider DTOs must not leave their provider implementation.
5. Filesystem capabilities must access files through `IFileSystem`.
6. Applications should enter through `AIRuntime` unless they are extending or
   testing an internal subsystem.
7. Capability and provider registration must remain explicit.

Because the runtime currently uses one assembly definition, code review and
tests enforce these rules. Splitting stable modules into separate assemblies is
a possible future hardening step, not a current requirement.

---

## Extension Points

AIRuntime is extended by implementing or registering:

- `IProvider` or `IProviderWithRequestOptions`
- `ICapability`, normally through `CapabilityBase<TArguments>`
- `ICapabilityMiddleware`
- `ICapabilityApprovalHandler`
- `IAgentObserver`
- `IFileSystem`
- Agent definitions and profiles
- `AIRuntimeBuilder` / `IAIRuntimeModule` for composition-time service and
  runtime registration
- `ICapabilityExecutionSemantics` for capability-owned batching or convergent
  repeated-call behavior

The Unity Editor has a separate composition root,
`AIRuntimeEditorPluginCatalog`, for provider, capability, and agent plugins.
Chat and Kanban query the same catalog; they do not own provider/capability
factory switches. Provider plugins may additionally implement
`IAIRuntimeEditorProviderConfigurationPlugin` to own optional fields,
validation, warnings, and agent-option ranges. Catalog registrations return
identity-bound `IDisposable` handles, metadata is snapshotted at registration,
duplicate execution identities are rejected, and capability creation always
requires a concrete host. Persisted missing plugin IDs remain visible rather
than silently falling back to a different implementation.

Capabilities that need per-run application state implement
`IAIRuntimeEditorScopedCapabilityPlugin` and resolve narrow services from an
`AIRuntimeEditorCapabilityCreationContext`. Kanban task reads and child-card
commands use this path; they are constructor-injected into the capability for
one run instead of crossing a static `AsyncLocal` bridge.

New extensions should depend on internal contracts, not on another concrete
extension.

---

## Local provider diagnostics

The Editor enables `ProviderTransportDebug` file logging automatically. Journals
are append-only UTF-8 JSON Lines files in `Logs/ProviderDiagnostics`, named
`provider-YYYY-MM-DD-PID.jsonl` using the UTC date and editor process ID. Each
event is appended and closed immediately. Editor reloads and the transport
viewer's **Clear view** action preserve these files. The existing Git ignore
rule excludes `Logs`; logging does not automatically delete old journals.

Requests include full prompts, history, tool schemas, model settings, endpoint,
and sanitized headers. Responses include status, headers, transport errors,
timing, raw bodies, stream chunks and termination markers. HTTP failures are
recorded before throwing; transport cleanup also records partial response data
on cancellation. OpenAI-compatible adapter input is recorded before completion
conversion, preserving evidence behind `EmptyResponse` failures. Attempt
outcomes and retry decisions explain provider fallback.

Streaming HTTP 200 responses can still contain provider error frames. The
stream adapter preserves those as `ProviderStreamErrorException` with the
provider's code, type and original error object, and emits `STREAM ERROR`.
Fallback classification uses the embedded provider code without changing the
recorded HTTP status. A 503 overload therefore remains an availability failure
instead of becoming an empty completion. An error after partial text or tool
deltas rejects the response before a partial tool batch can be dispatched.

`SCHEDULER STATE` events explain stops before a provider request is dispatched.
They record the Run agents flag, remaining single-step budget, Unity lifecycle
gate, active run count, and each unfinished card's workflow, assigned execution
agent and eligibility blocker. The scheduler checks for changes at most once
per second and appends only changed snapshots. Its current global gate is also
shown in the Kanban status bar. Turning on Run agents explicitly restores
continuous scheduling after a bounded single step. Managed-domain reloads
preserve previously authorized scheduling even when only Review cards remain;
opening a window cold does not start agents.

The board's collapsible Agent status widget shows each configured agent's
current or next card, a live provider-cooldown countdown, and a clickable
dependency when work is waiting on another card. Active runs take precedence
over queued work. Card links open the existing task details window.

Failed script compilation does not reload Unity's managed domain. Once
compilation/import has settled and cancelled runs have drained, the scheduler
releases its reload-preparation guard and supplies compiler feedback to paused
implementation cards. It preserves Run agents intent and emits
`COMPILATION FAILURE RECOVERY`; a successful compilation still waits for the
normal assembly reload. This recovery must already be loaded in the editor to
operate when generated code fails compilation.

`RequestId` joins events within one provider attempt; `SessionId` joins attempts
and agent events within a run. Agent events retain full tool arguments/results
and lifecycle messages, with card correlation ID, agent, objective and stage in
their payloads. Activity reports retain full orchestration details. These
diagnostics supplement the compact activity view rather than changing its
bounded summaries. Bodies are intentionally unbounded; authentication header
values are redacted and registered API keys are replaced when found in logged
text. These files are local diagnostic data, not an export intended for sharing.

Use **Open log folder** in the provider transport viewer to inspect the journal.
Disk write failures appear there and emit one warning without failing the
provider request. Non-Editor hosts can opt in with
`ProviderTransportDebug.ConfigureFileLogging(directory)`; passing null disables
file logging. Previously discarded responses cannot be recovered retroactively.

## Current Status

Implemented runtime foundations include:

- AIRuntime facade
- Persistent and isolated conversation execution
- Structured run contexts and results
- Capability registry, schema generation, validation, and middleware
- Capability permissions and approval flow
- Workspace-contained filesystem capabilities
- Ollama and OpenAI providers
- Provider retries, fallback, health, cooldown, and structured HTTP errors
- Cancellation, provider timeouts, and whole-run timeouts
- Agent definitions, profiles, sessions, capability filtering, and built-in agents
- Run observers and events
- Fake providers and broad EditMode test coverage
- Unity Editor chat console for interactive, tool-capable provider and configured-agent conversations
- Unity Editor Kanban board for persistent task planning and agent assignment
- Unity Editor Kanban graph for live hierarchy, dependency, and blocker visualization
- Unity Editor Agent Activity window for safe, structured work visibility
- Unity Editor provider, capability, agent, and run-integration plugin registries
- Capability-owned batching and repeated-call execution semantics
- Runtime builder/module composition for filesystem, middleware, observers,
  failure classification, and provider-tool construction

Areas still expected to evolve include:

- Streaming responses
- Additional providers
- Memory and planning
- Extracting the Kanban domain model, persistence, migrations, and dispatcher
  from `AIRuntimeKanbanWindow` into independently testable services
- An append-only task-transition and attempt ledger with typed terminal reasons,
  explicit leases/heartbeats, and crash-safe replay
- Package discovery for plugin registrations
- Logging configuration and sensitive-data redaction
- Samples and package documentation
- Stronger compile-time module boundaries

The detailed delivery sequence belongs in `Roadmap.md`; this document describes
the architecture and current boundaries.

---

## Guiding Questions

When adding a feature, ask:

1. Is this application behavior?
2. Is this conversation or run orchestration?
3. Is this agent identity or execution policy?
4. Is this provider coordination or provider-specific translation?
5. Is this a capability?
6. Is this an internal contract or a transport DTO?
7. Does this cross a permission, workspace, or provider trust boundary?

If ownership is unclear, define the boundary before adding the dependency.

AIRuntime should remain a small, stable orchestration core surrounded by
interchangeable providers, capabilities, policies, and observers.
