nyaasu
v0.1.0
Published
A clean, modular AI agent framework with zero runtime dependencies
Readme
Nyaasu
A clean, modular AI agent framework with zero runtime dependencies.
Nyaasu provides a harness-first architecture for building AI agents and orchestrating them through finite state machine workflows. It handles the execution loop, tool calls, memory, structured output, and safety controls — while staying entirely provider-agnostic.
Features
- Zero runtime dependencies — only Node.js built-ins (
node:sqlite,node:crypto) - Harness-first execution — agents never call LLMs directly; everything flows through a controlled loop
- Provider-agnostic — implement the
LLMinterface for any model provider - Type-safe workflows — FSM orchestration with generic state and compile-time guarantees
- Result types — predictable error handling via
Result<T, E>instead of exceptions - Config cascade — system → workflow → agent configuration inheritance
- Built-in memory — SQLite + FTS5 persistent memory per agent
- Structured output — JSON schema validation with automatic retry
- Safety controls — budget guards, input/output guardrails, abort propagation
- Execution patterns — sub-agents, reflection loops, iterative execution
- Strict validation — definition-time integrity checks with actionable error messages
- Test utilities — mock providers and isolated test runners shipped with the package
Requirements
- Node.js >= 22.5.0 (required for
node:sqlite) - TypeScript 5.x (strict mode, ESM-only)
Installation
npm install nyaasuQuick Start
Define an Agent
import { createAgent } from "nyaasu";
import type { Tool } from "nyaasu";
const searchTool: Tool = {
name: "web_search",
description: "Search the web for information",
inputSchema: {
type: "object",
properties: {
query: { type: "string" },
},
required: ["query"],
},
execute: async (input) => {
const { query } = input as { query: string };
return { output: `Results for: ${query}` };
},
};
const researcher = createAgent({
id: "researcher",
role: { system: "You are a research assistant. Use tools to find information." },
tools: [searchTool],
skills: [],
});Run an Agent (Testing)
import { testAgent } from "nyaasu/testing";
const result = await testAgent(researcher, {
input: "Find information about TypeScript 5.x features",
provider: [
{
text: "",
toolCalls: [{ id: "1", name: "web_search", input: { query: "TypeScript 5.x features" } }],
stopReason: "tool_use",
usage: { inputTokens: 100, outputTokens: 50 },
},
{
text: "TypeScript 5.x includes decorators, const type parameters, and more.",
toolCalls: [],
stopReason: "end",
usage: { inputTokens: 200, outputTokens: 100 },
},
],
});
console.log(result.output); // "TypeScript 5.x includes..."Build a Workflow
import { createWorkflow } from "nyaasu";
interface PipelineState {
topic: string;
research: string | null;
summary: string | null;
}
const pipeline = createWorkflow<PipelineState>({
id: "research-pipeline",
initialState: { topic: "AI agents", research: null, summary: null },
nodes: [
{
id: "research",
start: true,
agent: { definition: researcher },
run: async (ctx) => {
const result = await ctx.runAgent(ctx.state.topic);
return {
status: "done",
output: result.output,
stateUpdates: { research: result.output as string },
};
},
},
{
id: "summarize",
end: true,
run: async (ctx) => {
return {
status: "done",
stateUpdates: { summary: `Summary of: ${ctx.state.research}` },
};
},
},
],
transitions: [{ from: "research", to: "summarize" }],
});Structured Output
const analyzer = createAgent({
id: "analyzer",
role: { system: "Analyze sentiment. Return structured JSON." },
tools: [],
skills: [],
outputSchema: {
schema: {
type: "object",
properties: {
sentiment: { type: "string", enum: ["positive", "negative", "neutral"] },
confidence: { type: "number" },
},
required: ["sentiment", "confidence"],
},
maxRetries: 2,
},
});Architecture
┌─────────────────────────────────────────────────────┐
│ Workflow Layer (FSM Orchestrator) │
│ createWorkflow() → nodes + transitions │
├─────────────────────────────────────────────────────┤
│ Agent Layer │
│ createAgent() → definition + access control │
├─────────────────────────────────────────────────────┤
│ Harness Layer (Execution Engine) │
│ runLoop() → LLM invoke → tools → repeat │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Patterns │ │ Control │ │ Structured │ │
│ │ sub-agent│ │ budget │ │ Output │ │
│ │ reflect │ │ guardrail│ │ validation │ │
│ │ loop-iter│ │ abort │ │ + retry │ │
│ └──────────┘ └──────────┘ └───────────────┘ │
├─────────────────────────────────────────────────────┤
│ Infrastructure │
│ Provider (LLM interface) │ Config │ Memory │ Types │
└─────────────────────────────────────────────────────┘Subpath Exports
Import only what you need:
import { createAgent, runLoop } from "nyaasu"; // Core API
import { runLoop } from "nyaasu/harness"; // Harness only
import { createSqliteStore } from "nyaasu/memory"; // Memory system
import { createWorkflow } from "nyaasu/workflow"; // Workflow engine
import { createMockProvider, testAgent } from "nyaasu/testing"; // Test utilitiesProvider Interface
Nyaasu is provider-agnostic. Implement the LLM interface to connect any model:
import type { LLM, LLMResponse, StreamChunk } from "nyaasu";
const myProvider: LLM = {
async invoke(messages, options) {
// Call your LLM API here
return {
text: "response",
toolCalls: [],
stopReason: "end",
usage: { inputTokens: 0, outputTokens: 0 },
};
},
async *stream(messages, options) {
yield { type: "text", text: "response" };
yield {
type: "done",
response: {
text: "response",
toolCalls: [],
stopReason: "end",
usage: { inputTokens: 0, outputTokens: 0 },
},
};
},
};Memory System
Each agent gets its own persistent SQLite memory store with full-text search:
import { createSqliteStore, createMemoryTools } from "nyaasu/memory";
const store = createSqliteStore({
agentId: "my-agent",
path: ".nyaasu/memory/my-agent.db",
});
// Memory tools are automatically injected when memory.enabled = true
const tools = createMemoryTools({ store, agentId: "my-agent" });
// Provides: memory_store, memory_search, memory_list, memory_deleteConfig Cascade
Configuration merges across three levels with field-level override:
import { defineConfig } from "nyaasu";
const systemConfig = defineConfig({
provider: { model: "gpt-4o", temperature: 0.7, maxTokens: 4096 },
budget: { maxTokens: 100_000, maxIterations: 50, timeout: 120_000 },
context: { strategy: "full" },
});
// Workflow config overrides system → agent config overrides workflowSafety & Controls
Budget Guard
Enforces token, iteration, and time limits per agent run:
const agent = createAgent({
id: "bounded",
role: { system: "..." },
tools: [],
skills: [],
config: {
budget: { maxTokens: 10_000, maxIterations: 5, timeout: 30_000 },
},
});Guardrails
Validate input/output with optional retry:
import type { Guardrail } from "nyaasu";
import { ok, err } from "nyaasu";
const noSecrets: Guardrail = {
name: "no-secrets",
validate: (content) =>
content.includes("SECRET") ? err("Output contains secrets") : ok(undefined),
retryable: true,
maxRetries: 2,
};Abort / Cancellation
Full abort signal propagation from workflow down to provider calls:
const controller = new AbortController();
const result = await workflow.run({ signal: controller.signal });
// Cancel at any time
controller.abort();Testing
Nyaasu ships test utilities for deterministic agent testing:
import { createMockProvider, testAgent, testWorkflow } from "nyaasu/testing";
// Mock provider returns scripted responses in order
const mock = createMockProvider([
{ text: "Hello!", toolCalls: [], stopReason: "end", usage: { inputTokens: 10, outputTokens: 5 } },
]);
// Isolated agent test
const result = await testAgent(myAgent, {
input: "Hi",
provider: mock,
});
// Full workflow E2E test
const wfResult = await testWorkflow(myWorkflow, {
providers: { researcher: mock },
});Project Templates
Agent-First (single agent)
my-agent/
├── src/
│ ├── agent.ts # createAgent() definition
│ ├── tools/ # Agent tools
│ └── main.ts # Entry point
├── nyaasu.config.ts # System config
└── package.jsonWorkflow-First (multi-step orchestration)
my-project/
├── src/
│ ├── nodes/ # Workflow nodes (one folder each)
│ ├── agents/ # Reusable agent definitions
│ ├── workflows/ # Workflow definitions
│ ├── tools/ # Shared tools
│ └── main.ts
├── nyaasu.config.ts
└── package.jsonDesign Principles
- Zero runtime deps — only Node.js built-ins
- Harness-first — controlled execution loop for all agent interactions
- Result over exceptions — predictable error handling
- Strict validation — fail fast at definition time with clear messages
- Config cascade — sensible defaults with layer-by-layer override
- Provider-agnostic — adapters are external packages
- YAGNI — ship what's needed, defer what's not
Scripts
pnpm build # Build with tsup
pnpm test # Run tests (vitest)
pnpm test:watch # Watch mode
pnpm lint # Check with biome
pnpm lint:fix # Auto-fix lint issues
pnpm typecheck # Type check with tscLicense
MIT
