Kernel Internals
⚫ A document for those who write or modify the kernel code itself. The full specification is
in SPEC.md (English, source of truth); here is a brief map. Semantics come from
the sources agent_os_core.
14.1 Model
The kernel is a single async runtime (Tokio) with an event loop that:
- maintains a process table (
DashMap<Pid, AgentProcess>); - calls the scheduler (
schedule() → ScheduleDecision) on every tick; - dispatches turns (
run_turn(pid)), invoking the provider and running the tool-call loop; - limits resources (budgets, groups, concurrency limit) before dispatch;
- routes IPC;
- mediates every tool call;
- emits events to listeners;
- manages sessions (
SessionRegistry).
The kernel holds optional service handles, wired in once at load time by the
host (message_store, state_store, provider, pricing, etc.) — if a
handle is not wired in, the corresponding syscalls respond "not configured".
Modularity. Features (cron, workflow, connectors, memory, model_router, channels, …) have been moved out of the kernel into modules and are wired in as host ports. Instead of the typed fields
cron_service/workflow_service/connector_serviceonKernel, there is now a single neutral seamkernel::KernelServices, which the host fills from the portsagent_os.Cron/agent_os.Workflow/agent_os.Connectors(kernel.services().set_*). The kernel does not depend on any single feature; details are indocs/27-core-modularization.md§5 and 32-building-a-module.
Load cycle:
loop {
decision = schedule()
match decision {
Run(pid) → run_turn(pid)
Idle → sleep(50ms)
AtConcurrencyLimit → sleep(50ms)
Shutdown → break
}
}
14.2 Core Abstractions
| OS Concept | Equivalent | Module |
|---|---|---|
| Process | AgentProcess — PID, state machine, context, tools, budget |
process.rs |
| Kernel | Kernel — event loop |
kernel.rs |
| CPU | InferenceProvider — pluggable LLM |
provider.rs |
| RAM | ContextWindow — memory with eviction |
context.rs |
| I/O device | Tool |
tool.rs |
| cgroups/ulimits | TokenBudget / BudgetGroup |
budget.rs |
| System call | Syscall |
syscall.rs |
| Signal | Signal |
ipc.rs |
| fork() | fork() — COW context, group inheritance |
process.rs |
| Process table | DashMap<Pid, AgentProcess> |
kernel.rs |
| Driver | ToolHandler |
tool.rs |
Process State Machine
spawn → Created → (init) → Ready ⇄ Running → (exit) → Terminated
│
└→ Blocked{reason} → (unblock) → Ready
- Created — allocated, not initialized.
- Ready — waiting for the scheduler (
is_runnable()). - Running — consuming inference.
- Blocked { reason } — waiting for an external event (tool result, IPC, input).
- Terminated { exit_code } — finished (0) or killed (≠0).
Turn and Tool-Call Loop
- The kernel selects process P.
- It sends P's context + the latest tool results to the provider.
- The provider streams tokens.
- If the LLM requests a tool call, the kernel pauses the stream, validates
the call, executes the handler (with a deadline), adds the result as a
toolmessage, and resumes inference. - The turn ends when the agent yields, blocks, its quantum expires, or its budget is exhausted.
Concurrency is metered by Semaphore only around provider.stream — tool calls
and hooks run without a permit, so a slow tool does not "starve" other sessions.
Context Window
ContextWindow { max_tokens, messages, eviction, summary }. Eviction policies:
Fifo (drop old ones), SummaryFirst { keep } (collapse old ones into a summary, default keep=8),
SlidingWindow { n }. Eviction also cleans up orphaned
tool results whose parent assistant message has been removed.
Budgets
TokenBudget { max_per_turn, bucket: TokenBucket, max_total, … }.
TokenBucket — a classic leaky bucket (capacity + refill_rate). A group
BudgetGroup adds two optional layers: per-user (a sub-bucket per
user_id) and per-session. Check before dispatch: group → per-user →
per-session (can_consume_for); an exhausted layer — BudgetLevel::{Group,User,Session}.
IPC and Syscalls
MessagePayload: Text, Json, Signal (Terminate/Pause/Resume/Interrupt),
SpawnRequest, SpawnResult.
Syscalls: Spawn, Fork, Kill, Yield, Send/Recv, ToolCall,
SetState/GetState/DeleteState, Checkpoint/Restore.
Hooks
HookHandler with points: before_user_message (transform/inject/suppress/reply/
notice), before_tool_call (rewrite args / deny / short-circuit),
after_tool_result (redact/transform), before_inference (request mutation;
deny → block, response → synthetic response), before_retry
(allow/deny retry + backoff), before_syscall (syscall/IPC filter,
deny), after_turn, after_assistant_message (output guardrail),
on_token (streaming moderation), before_context_evict (custom
compaction), after_loop, on_spawn/on_terminate/on_error,
on_session_created/on_session_closed, on_budget_threshold. Plus
merge_user (identity merging), memory_inspect (inspection),
apply_feedback (like/dislike).
Composition: Hook.priority (higher — earlier), enabled, activation conditions
when { templates, agents, users, sessions }, global (directory
config/hooks/*.yaml). Every call goes through Kernel::run_hook
(catch_unwind + timeout hook_secs) and is accounted for in HookMetric per pair
(hook, point) (GET /v1/hooks/metrics). HookOutcome { inject, replace_message, reply, notice, suppress }.
14.3 Scheduler
SchedulingPolicy: RoundRobin (default), PriorityPreemptive,
FairShare, DeadlineDriven. Scheduler { policy, time_slice, max_concurrent }.
ScheduleDecision: Run(pid) | Idle | AtConcurrencyLimit | Shutdown.
14.4 Inference Provider
trait InferenceProvider {
fn provider_type(&self) -> &str;
async fn stream(&self, request) -> Result<Box<dyn Stream<Item=Result<InferenceEvent, InferenceError>>>>;
}
enum InferenceEvent { Token(String), ToolCall(ToolCallRequest), Done(InferenceStats) }
The concrete implementation is OpenAiProvider (reqwest + SSE). Pricing estimates
cost from statistics.
14.5 Storage Subsystems
| Layer | Where | Source of truth for |
|---|---|---|
| Text logs (tracing) | stdout → agent_os.log |
operational viewing |
Event store (EventDb) |
logs/events.db |
audit (one line per event) |
| Message store | logs/messages.db |
transcripts + FAQ cache, full-text search |
| JSONL traces | logs/process-{pid}.jsonl |
raw process event stream |
| State store | logs/state.db |
scoped key-value (session/user/agent/team) |
| Checkpoints | CheckpointStore |
recovery/migration |
14.6 Events and Listeners
The kernel emits typed ProcessEvent through the bus: TurnStart, Token,
ToolCall, ToolResult, ToolProgress, TurnEnd, StateChange, Error,
Spawned, Terminated, BudgetConsumed, BudgetExhausted, RateLimited,
ScheduleBlocked, session lifecycle events, Cost, workflow steps.
Listeners implement ProcessListener (all methods are no-op by default).
14.7 Loading
- The host loads the config and builds
Kernel. ToolRegistryregisters built-in and user tools;HookRegistry— hooks.InferenceProviderconnects to the LLM.- Service handles are connected (
message_store,state_store,cron_service,workflow_service,pricing). - Agent templates are loaded (built-in + YAML).
- Bootstrap agents are spawned (or lazily on the first session request).
- The scheduler loop starts.
- The host listens for external requests (HTTP + SSE).
14.8 Design decisions
- Cooperative multitasking by default; preemption is optional.
spawnandfork— both supported;forkalways inherits the budget group.- Cost accounting at the tool level — each
ToolResultcarriesToolCost. - I/O through the core — agents never call tools directly.
- Opt-in subsystems — when not connected, they respond "not configured" rather than crashing.
14.9 Non-goals
Multi-tenancy (one instance = one tenant), distributed scheduler, fine-tuning, graph workflows in the core (that's the server layer), real-time guarantees.
14.10 Source map
| Concern | Module |
|---|---|
| Core, turn loop | crates/kernel/src/kernel.rs |
| Process, spec, fork | crates/kernel/src/process.rs |
| State machine | crates/kernel/src/state.rs |
| Context, eviction | crates/kernel/src/context.rs |
| Budgets, groups, cost | crates/kernel/src/budget.rs |
| Scheduler | crates/kernel/src/scheduler.rs |
| Provider (trait) | crates/kernel/src/provider.rs |
| OpenAI provider | crates/kernel/src/openai_provider.rs |
| Tools | crates/kernel/src/tool.rs |
| Hooks | crates/kernel/src/hook.rs, hooks/ |
| IPC, signals | crates/kernel/src/ipc.rs |
| Syscalls | crates/kernel/src/syscall.rs |
| State store | crates/kernel/src/state_store.rs |
| Message store | crates/kernel/src/message_store.rs |
| Events, listeners | crates/kernel/src/event.rs, listener.rs |
| Event DB | crates/kernel/src/event_db.rs |
| Checkpoints | crates/kernel/src/checkpoint.rs |
| JSONL logger | crates/kernel/src/jsonl_logger.rs |
| Subagents | crates/kernel/src/subagent.rs |
| Cron | crates/kernel/src/cron.rs |
| Workflows | crates/kernel/src/workflow.rs |
| Pricing | crates/kernel/src/pricing.rs |
Metrics (Prometheus /metrics) |
crates/kernel/src/metrics.rs |
| Side-effect idempotency | crates/kernel/src/side_effect.rs |
| Durable sessions (opt-in persistence) | crates/kernel/src/durable.rs |
| Durable pending (approvals/input/await_event) | crates/kernel/src/pending.rs |
| Config loader | crates/kernel/src/config.rs |
| Script runtimes | crates/kernel/src/script_engine/, js_runtime/ |
14.11 Build and tests
Cargo workspace of three crates:
| Crate | Purpose |
|---|---|
agent_os_core |
core: processes, scheduler, budgets, events, provider abstraction, hooks, message store. No HTTP and no concrete provider |
agent_os_tools |
tool implementations (scrapers, archive, RSS, PDF, JSON, scripts) and built-ins |
agent_web_bot |
HTTP server (axum), OpenAI provider, widget, admin UI, proxy, Telegram bots, config loader. The binary is a single CLI agent-os |
cargo build # debug-сборка всех бинарников
cargo run --bin agent-os -- serve # запуск сервера (грузит config/server.yaml)
cargo test -p agent_os_core # юнит-тесты ядра/инструментов/хуков
cargo test -p agent_web_bot # тесты сервера, auth, proxy, login, sessions
cargo test -p agent_web_bot --lib # HTTP-слой (роутинг, auth, login, access-лог)
The widget in widget/ is built automatically build.rs at agent_web_bot (via
npm) — a separate npm run build is not needed. Integration tests
crates/host/tests/ require a live LLM key and are not run by default.