AIRI Runtime Kernel — Architecture Document

Overview

The AIRI daemon is evolving from a simple module host into a true agent runtime kernel. This document describes the task orchestration layer that enables the daemon to manage, schedule, track, and cancel units of work (tasks) across all modules.

Why Tasks Are Daemon-Owned

Tasks are owned by the daemon, not by individual modules, for several reasons:

  1. Centralized lifecycle management: The daemon is the single source of truth for task state. Modules request task creation; the daemon manages transitions.
  2. Cross-module visibility: Any module can observe any task's progress through the event bus.
  3. Graceful shutdown: The daemon can cancel all running tasks during shutdown, ensuring clean teardown.
  4. Client reconnection: The replay buffer allows clients to catch up on task state after a brief disconnect.
  5. Resource management: Concurrency limits and priority scheduling are enforced centrally.

Task Lifecycle State Machine

pending → queued → running → completed
                 ↘          ↗
                  failed
                  cancelled

States

State Description
pending Task created, not yet queued for execution.
queued Task waiting for an execution slot.
running Task currently being executed by an executor.
completed Task finished successfully.
failed Task encountered an unrecoverable error.
cancelled Task was cancelled before completion.

Valid Transitions

Invalid transitions are rejected by the TaskManager. This ensures deterministic lifecycle behavior.

Architecture Components

TaskManager (core/tasks/manager.ts)

The central task lifecycle manager.

Responsibilities:

Key design decisions:

TaskScheduler (core/tasks/scheduler.ts)

Dispatches queued tasks to executors based on priority and concurrency limits.

Responsibilities:

Selection order:

  1. Highest effective priority (with starvation boost).
  2. FIFO within same priority level.

TaskExecutor (core/tasks/executor.ts)

The interface that modules implement to execute tasks.

interface TaskExecutor {
  canExecute(task: Task): boolean
  execute(task: Task, ctx: TaskExecutionContext): Promise<TaskResult>
}

Execution context provides:

TaskReplayBuffer (core/tasks/replay-buffer.ts)

Bounded in-memory event history for reconnecting clients.

Responsibilities:

TaskMetrics (core/tasks/metrics.ts)

In-memory runtime diagnostics.

Tracks:

Cancellation Infrastructure (core/tasks/cancellation.ts)

Cooperative cancellation primitives.

Key concepts:

Cancellation is cooperative, not preemptive. Tasks must check the token at reasonable intervals. This is intentional — it avoids the complexity and unsafety of forced termination.

IPC Integration

The daemon exposes task operations via IPC request handlers:

Method Description
task.create Create a new task. Params: title, description?, priority?, moduleId?, metadata?
task.cancel Cancel a task. Params: taskId, reason?
task.list List tasks. Params: state?, moduleId?
task.get Get task details. Params: taskId

Task events are streamed to all connected clients:

Event Description
task.queued Task was queued for execution.
task.progress Task reported progress.
task.failed Task failed with an error.
task.cancelled Task was cancelled.

Reconnect Behavior

When a new client connects, the daemon sends a replay snapshot containing:

This allows clients to catch up after a brief disconnect without missing task state changes.

Module Integration

Modules participate in the task system by registering a TaskExecutor:

// In module activation:
const executor = new MyTaskExecutor()
taskManager.registerExecutor(moduleId, executor)

The executor's canExecute() method determines which tasks it handles. The scheduler uses this to find the right executor for each task.

Scheduler Behavior

Priority + FIFO

Tasks are dispatched in priority order: critical > high > normal > low. Within the same priority, tasks are dispatched FIFO (oldest first).

Concurrency Limits

The scheduler enforces a maximum number of concurrently running tasks (default 4). When all slots are full, queued tasks wait. When a slot frees up, the highest-priority queued task is dispatched.

Starvation Prevention

Tasks waiting longer than the starvation threshold (default 30 seconds) get their effective priority boosted by one level. This prevents low-priority tasks from being permanently blocked by a stream of high-priority tasks.

Future Directions

Persistence

Currently, all task state is in-memory. Future work could add:

Worker Processes

Currently, all task execution is in-process. Future work could add:

Task Dependencies

Future work could add:

Retry Policies

Future work could add:

Constraints

The current implementation deliberately does not include: