general-agent-runtime
v0.0.4
Published
A small, composable general-purpose AI agent runtime for TypeScript.
Downloads
645
Maintainers
Readme
General Agent Runtime
A small, composable TypeScript runtime for building tool-using AI agents with streaming events, persistent sessions, hooks, and long-context compaction.
Status
The core v0 Runtime architecture is complete through Phase 11.
Current verification baseline:
npm run typecheck ✅
npm test ✅ 16 files / 140 tests
npm run test:integration ✅ 2 files / 2 tests
npm run build ✅
npm run test:package ✅
git diff --check ✅v0 is complete: the core Runtime, public-API examples, live integration tests, and minimal CLI have all been implemented and verified against a real OpenAI-compatible llama.cpp endpoint. MCP, Skills, Memory, Approval, and SubAgent are intentionally v1+.
Installation
npm install general-agent-runtimeThe package is ESM-only and requires Node.js 22 or newer.
Quick start
The Runtime does not discover configuration from files, environment variables, or user directories. The host application resolves those sources and passes explicit Runtime configuration to createAgent(config):
import { createAgent } from "general-agent-runtime";
const agent = await createAgent({
model: {
provider: "openai-compatible",
baseUrl: "https://api.openai.com/v1",
apiKey: "your-key",
model: "gpt-5.6",
contextWindow: 128_000,
},
});
for await (const event of agent.run("Explain what this project does in one paragraph.")) {
if (event.type === "message_delta") {
process.stdout.write(event.data.delta);
}
}createAgent(config) requires an explicit configuration object. The supplied partial configuration is merged over built-in Runtime defaults, validated as a complete RuntimeConfig, frozen, and passed into the default composition root. Configuration discovery belongs to the host layer, such as a CLI, TUI, desktop client, service, or test harness.
For the default OpenAI-compatible provider, model.contextWindow declares the model context window in tokens. Runtime projection budgeting and compaction use this value through ModelCapabilities.contextWindow. This is an explicit host-supplied capability declaration rather than automatic model metadata discovery. When a custom ModelProvider is injected through the Builder, that provider remains responsible for its own getCapabilities() result.
Agent API
The main SDK surface is intentionally small:
interface Agent {
run(input, options?): AsyncIterable<AgentEvent>;
resume(sessionId, options?): Promise<SessionProjection>;
abort(runId): void;
getState(runId): Readonly<AgentRunState> | undefined;
}Streaming events
agent.run() returns an AsyncIterable<AgentEvent>. Common events include:
run_start
turn_start
message_start
message_delta
tool_start
tool_end
compact_start
compact_end
message_end
turn_end
error
run_endThe same logical event sequence is visible to registered Event sinks and the Agent stream.
Session resume
A new run creates a JSONL Session automatically. Capture its sessionId from the event stream and pass it back later:
let sessionId;
for await (const event of agent.run(
"Remember that the project codename is Atlas.",
{ sessionMetadata: { cwd: process.cwd() } },
)) {
sessionId ??= event.sessionId;
}
const projection = await agent.resume(sessionId);
for await (const event of agent.run(
"What is the project codename?",
{ sessionId },
)) {
if (event.type === "message_delta") {
process.stdout.write(event.data.delta);
}
}resume() reopens and projects the persisted Session and returns that SessionProjection to the host. Continuing the conversation is still done through run(..., { sessionId }).
Session catalog
Session persistence details stay inside Runtime. Hosts that need session browsing or management should create the public Runtime facade and use its SessionCatalog:
import {
DEFAULT_RUNTIME_CONFIG,
createRuntime,
} from "general-agent-runtime";
const runtime = createRuntime({
config: {
...structuredClone(DEFAULT_RUNTIME_CONFIG),
session: {
...structuredClone(DEFAULT_RUNTIME_CONFIG.session),
directory: "./sessions",
},
},
cwd: process.cwd(),
});
const sessions = runtime.sessions;
const currentProject = await sessions.list({ cwd: runtime.cwd });
const descriptor = await sessions.get(sessionId);
await sessions.updateMetadata(sessionId, { title: "Atlas work" });
await sessions.delete(sessionId);SessionDescriptor exposes id, optional title / cwd, plus createdAt and updatedAt. Multiple session_meta entries are merged by Runtime, with later fields overriding earlier fields. JSONL entries, Store, Projector, serializer, path normalization, and Catalog implementations are not part of the package-root API.
Runtime does not generate titles. Title generation, rename UX, delete confirmation, fuzzy search, relative time formatting, and session pickers are client concerns. Hosts receive Runtime-normalized cwd from createRuntime() instead of importing path-normalization helpers.
Abort
Use an AbortSignal when the caller owns cancellation:
const controller = new AbortController();
const stream = agent.run("Do a long task", {
signal: controller.signal,
});
controller.abort("user_cancelled");When a runId is already known, agent.abort(runId) can cancel that active run directly.
Tools
The default builder registers four core tools:
read_filewrite_filelist_directoryrun_command
All model tool calls pass through ToolExecutor for input validation, Hook control, timeout/abort handling, bounded parallelism, error normalization, and output truncation.
Runtime implementation classes are intentionally not exported from the package root. Clients should use createAgent(config) for the Agent-only facade or createRuntime({ config, cwd }) when they also need Session management. Both entrypoints return frozen plain-object facades rather than concrete implementation instances, so clients do not receive internal constructors or dependency fields.
Store, Projector, RunManager, Builder, provider adapters, loop internals, serializer helpers, and cwd comparison helpers remain Runtime implementation details. New extension points should be added as explicit stable public contracts instead of exposing concrete internals.
Hooks
Hooks form the Runtime control plane. They can observe or control lifecycle points without placing policy inside the Loop.
Typical uses:
- modify or block a tool call with
before_tool; - inspect tool output with
after_tool; - modify the final model request with
before_model; - stop a run at lifecycle boundaries;
- observe compaction and errors.
before_model may modify messages, tools, temperature, max output tokens, and provider options, but it cannot change the resolved model.
Long-context handling
The Runtime separates projection from compaction:
Context sources
↓
Context snapshot
↓
Projection
↓
Budget evaluation
↓
Compaction when required
↓
ModelRequestCompaction supports deterministic compression, semantic compaction, durable checkpoints, and bounded reactive recovery from provider context-limit errors. Original Session history remains append-only.
Examples
examples/basic-chat.ts
examples/tool-call.ts
examples/resume-session.tsRun one with:
npm run dev -- examples/basic-chat.ts "Hello"
npm run dev -- examples/tool-call.ts
npm run dev -- examples/resume-session.tsCLI
From this repository, run the minimal interactive CLI with:
npm run cliAfter installing the npm package, run:
npx general-agent-runtimeContinue an existing Session:
npx general-agent-runtime --session <session-id>The CLI is a host application around the public Agent API. It maps AGENT_PROVIDER, AGENT_MODEL, AGENT_BASE_URL, AGENT_API_KEY, AGENT_CONTEXT_WINDOW, and AGENT_SESSION_DIR into explicit Runtime configuration before calling createAgent(config); the Runtime itself does not read environment variables.
Tests
Default tests are deterministic unit tests:
npm testLive OpenAI-compatible integration tests are separate:
npm run test:integrationThe live-test harness owns its configuration discovery. It optionally reads agent.config.json and AGENT_* environment variables, merges them in the test layer, and passes the resulting explicit configuration to the Runtime. If neither source is present, live tests skip.
Package verification
Before publishing, build a real tarball and verify it from a clean TypeScript consumer:
npm run test:packagetest:package creates the real tarball in a temporary directory, installs it into a clean consumer, checks TypeScript declaration resolution, verifies the root ESM import, and runs the installed CLI. prepack automatically runs a clean build, typecheck, and unit tests before npm creates the package. The published tarball is restricted to dist/, README.md, and npm metadata; tests, examples, local configuration, sessions, coverage, design documents, and release scripts are not published.
Publish with:
npm publishThe current package name is general-agent-runtime. Confirm the name is still available immediately before the first publish.
Architecture
The main ownership model is:
createAgent / createRuntime
↓
Agent
↓
RunManager
↓
AgentLoop
↙ ↓ ↘
Context Model Tools
↓
CompactionThe Loop owns orchestration and state transitions, not component algorithms. Session and Context integration are bridged through top-level adapters, preserving one-way core dependencies.
Design documents
ARCHITECTURE.mdINTERFACE_DESIGN.mdIMPLEMENTATION_PLAN.mdREFACTOR_PLAN.md
