Skip to content
Back to blog
Tutorial·Apr 12, 2026·AgentOS Team

Build a TypeScript AI Agent Runtime in 5 Minutes (AgentOS Quickstart)

From npm install to a TypeScript agent with a HEXACO personality, cognitive memory across sessions, web search and a claim-by-claim citation check. Five steps.
AgentOSTutorial Notes

Build a TypeScript AI Agent Runtime in 5 Minutes (AgentOS Quickstart)

April 12, 2026 · AgentOS Team

"Begin at the beginning, the King said gravely, and go on till you come to the end: then stop."

Alice in Wonderland, 1865

Five-minute tutorials are usually a lie. They omit the API key setup, they assume your network and your node_modules cooperate, and they end at "hello world" instead of at something you can use. This one tries not to be that. By the end of these five minutes you'll have a TypeScript agent with an opt-in HEXACO personality, cognitive memory that carries what a user told it into their next session, a web search tool, and a check that scores every claim in an answer against the sources it found. The full example is 67 lines of TypeScript. None of those lines are placeholder code.

If you are setting up your first AgentOS agent and run into trouble at any step, the Discord is the fastest way to unblock yourself; we monitor it. Runnable examples live in examples/ in the agentos repository. They import the built dist/, so run them from a clone after pnpm install && pnpm build. high-level-api.mjs is the closest match to this tutorial.

Zero to a working AI agent with personality, cognitive memory, web search, and citation checks. Five steps.

Step 1: Install

npm install @framers/agentos

Set your API key:

export OPENAI_API_KEY=sk-your-key
# or ANTHROPIC_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, etc.

AgentOS auto-detects which provider you have configured. It ships 13 LLM providers, each with the model it uses when you name none:

ProviderAPI Key VariableDefault model
OpenAIOPENAI_API_KEYgpt-4o
AnthropicANTHROPIC_API_KEYclaude-sonnet-4-6
Google GeminiGEMINI_API_KEYgemini-2.5-flash
OllamaOLLAMA_BASE_URL (no key)llama3.2
GroqGROQ_API_KEYllama-3.3-70b-versatile
OpenRouterOPENROUTER_API_KEYopenai/gpt-4o
RequestyREQUESTY_API_KEYopenai/gpt-4o
TogetherTOGETHER_API_KEYmeta-llama/Llama-3.3-70B-Instruct-Turbo
MistralMISTRAL_API_KEYmistral-large-latest
xAIXAI_API_KEYgrok-2
LiteLLMapiKey and baseUrl options (your proxy)None; set model to one your proxy serves
Claude Code CLIPATH detectionclaude-sonnet-4-6 via subscription
Gemini CLIPATH detectiongemini-3.5-flash via subscription

Step 2: Generate Text

1import { generateText } from '@framers/agentos';
2
3const result = await generateText({
4  provider: 'openai',
5  model: 'gpt-4o',
6  prompt: 'Explain the Monty Hall problem.',
7});
8
9console.log(result.text);

One LLM call, one response. Now let's add personality and memory.

Step 3: Add Personality and Memory

1import { agent } from '@framers/agentos';
2
3const assistant = agent({
4  runtime: 'gmi',            // cognitive memory runs on the GMI runtime
5  provider: 'openai',
6  model: 'gpt-4o',
7  instructions: 'You are a helpful research assistant.',
8  personality: {
9    openness: 0.9,           // explores ideas broadly
10    conscientiousness: 0.85, // thorough and organized
11    agreeableness: 0.7,      // warm, validates before offering alternatives
12  },
13  memory: true,              // embeds with text-embedding-3-small through OPENAI_API_KEY
14});
15
16// Monday: Sarah introduces herself
17const monday = assistant.session('monday', { userId: 'user-7f3a' });
18await monday.send('My name is Sarah and I study marine biology.');
19
20// Friday: a new session for the same user
21const friday = assistant.session('friday', { userId: 'user-7f3a' });
22const reply = await friday.send('What do I study, and what should I read next?');
23console.log(reply.text); // this turn's prompt carries what Sarah said on Monday
24
25await assistant.close();

Every session keeps its own message history, on either runtime. Monday's fact reaches Friday's session through cognitive memory. With runtime: 'gmi' and memory set, the agent builds one memory manager that all of its sessions share, files each message a user sends under their userId, and recalls it in any later session opened with the same id. Without runtime: 'gmi', agent() keeps session history only and logs a warning that it does not apply memory. Memory embeds through OpenAI, Gemini or Ollama. Anthropic, Groq and xAI have no embedding models in AgentOS, so an agent on one of those needs one of the three keys as well, or memory: { embedding: { provider, model } } naming the one to use.

How Personality Works

HEXACO personality traits, based on the six-factor model from personality psychology (Ashton & Lee, 2007), shape how the agent communicates. Each trait above 0.65 or below 0.35 adds one directive to the system prompt, and a value in between adds none; on the GMI runtime the same traits also tune seven of the eight memory mechanisms. Recent research shows that LLMs can reliably simulate HEXACO personality structures with coherent factor recovery.

TraitHigh Value EffectLow Value Effect
OpennessExplores tangential ideas, creative responsesStays focused, conservative
ConscientiousnessOrganized, thorough, detailedCasual, brief
AgreeablenessWarm, accommodatingDirect, challenging
ExtraversionEnthusiastic, verboseReserved, concise
EmotionalityEmpathetic, supportiveAnalytical, detached
Honesty-HumilityTransparent, admits limitationsConfident, promotional

How Memory Works

Cognitive memory goes beyond chat history. 8 mechanisms modeled on published memory research run on top of a decay model that every trace follows:

This approach mirrors the ACT-R cognitive architecture used by recent AI memory systems like Memory Bear and CortexGraph, which also integrate Ebbinghaus decay with activation scheduling.

Step 4: Add Tools

1import { agent } from '@framers/agentos';
2import { WebSearchService, TavilyProvider } from '@framers/agentos/cognition/web-search';
3
4// Needs OPENAI_API_KEY and TAVILY_API_KEY in the environment.
5const search = new WebSearchService();
6search.registerProvider(new TavilyProvider(process.env.TAVILY_API_KEY!));
7
8const researcher = agent({
9  provider: 'openai',
10  model: 'gpt-4o',
11  instructions: 'You are a research assistant with access to web search.',
12  tools: {
13    web_search: {
14      description: 'Search the web. Returns titles, URLs and snippets.',
15      parameters: {
16        type: 'object',
17        properties: { query: { type: 'string' } },
18        required: ['query'],
19      },
20      execute: async ({ query }) => search.search(query, { maxResults: 5 }),
21    },
22  },
23});
24
25const result = await researcher.generate(
26  'What are the latest developments in room-temperature superconductors?'
27);
28console.log(result.text);
29// The model calls web_search and answers from the results,
30// within maxSteps (5 by default) model calls.

A tool is a name mapped to a description, a JSON Schema for its arguments and an execute function; agent() also takes an ExternalToolRegistry or prompt-only tool schemas. WebSearchService sends each query to every provider you register (Firecrawl, Tavily, Serper and Brave) in parallel and fuses their rankings. The agentos-extensions registry holds more than 100 curated extension packs, among them news search, image search, browser automation and deep research. Their tools are ITool objects, and agent() accepts those in the same tool map.

Step 5: Check Answers Against Sources

1import { agent, embedText } from '@framers/agentos';
2// `search` is the WebSearchService from step 4.
3
4const sources = async (query: string) =>
5  (await search.search(query, { maxResults: 5 })).map((r) => ({
6    content: r.content ?? r.snippet,
7    title: r.title,
8    url: r.url,
9  }));
10
11const checked = agent({
12  provider: 'openai',
13  model: 'gpt-4o',
14  instructions: 'You are a research assistant. Answer in short, factual sentences.',
15  verifyCitations: {
16    retrieve: sources,
17    embedFn: async (texts) =>
18      (await embedText({ provider: 'openai', input: texts })).embeddings,
19  },
20});
21
22const answer = await checked.generate('What did the latest IPCC synthesis report conclude?');
23console.log(answer.text);
24for (const claim of answer.grounding?.claims ?? []) {
25  console.log(claim.verdict, claim.confidence.toFixed(2), claim.text);
26}

With verifyCitations, generate() sends the prompt to retrieve for sources, splits the answer into sentence-sized claims, and scores each claim by the cosine similarity of its embedding to the closest source: CitationVerifier marks it supported at 0.6 or above, unverifiable below 0.3, and weak in between. Pass an nliFn as well and a supported claim that the NLI model reads as contradicted is marked contradicted. The check runs after the answer is written and never holds it back; when retrieval fails, grounding stays unset. It runs on generate(). On the default runtime session.send() runs it too, and session.stream() sets grounding to a promise that settles once the text completes; stream() and sessions on the GMI runtime skip it.

Where Guardrails Run

agent() accepts guardrails and security options without applying them: it logs a warning that names them, and its capability contract lists both as accepted but deferred. The 6 guardrail packs run on the full AgentOS runtime (AgentOS.create()), which evaluates every guardrail registered with it against the user's input and the agent's output:

GuardrailDetection MethodWhat It Catches
PII RedactionRegex patterns, an NLP prefilter and a local NER model; an LLM judge is optional and off by defaultNames, emails, SSNs, credit cards, addresses
ML ClassifiersA local ONNX model (Xenova/toxic-bert); when it can't load, an LLM judge (if you pass one) and then keyword patternsToxicity, NSFW content, threats; prompt injection only on the fallbacks, since the ONNX model has no injection label
TopicalityEmbedding-based topic matching on user input, with LLM and keyword fallbacksOff-topic messages, scope violations
Code SafetyOWASP pattern scanningCommand injection, XSS, SQL injection in generated code
Grounding GuardNLI-based claim verificationHallucinated facts not supported by RAG sources
Content PolicyKeyword pre-filter on the stream, LLM judge on the final replyPolicy violations, blocked or rewritten

agency() reports the guardrail ids it is given as not enforced, so on that path too the packs need the full runtime.

Full Example: 67 Lines

1import { agent, embedText } from '@framers/agentos';
2import { WebSearchService, TavilyProvider } from '@framers/agentos/cognition/web-search';
3
4// Needs OPENAI_API_KEY and TAVILY_API_KEY in the environment.
5const search = new WebSearchService();
6search.registerProvider(new TavilyProvider(process.env.TAVILY_API_KEY!));
7
8const sources = async (query: string) =>
9  (await search.search(query, { maxResults: 5 })).map((r) => ({
10    content: r.content ?? r.snippet,
11    title: r.title,
12    url: r.url,
13  }));
14
15const myAgent = agent({
16  // LLM, on the GMI runtime so that memory applies
17  runtime: 'gmi',
18  provider: 'openai',
19  model: 'gpt-4o',
20  instructions: `You are a knowledgeable research assistant.
21    You search the web for current information
22    and cite your sources.`,
23
24  // Personality (HEXACO six-factor model)
25  personality: {
26    openness: 0.9,
27    conscientiousness: 0.85,
28    agreeableness: 0.7,
29    extraversion: 0.6,
30    emotionality: 0.3,
31    honestyHumility: 0.95,
32  },
33
34  // Cognitive memory shared by this agent's sessions
35  memory: true,
36
37  // Tools
38  tools: {
39    web_search: {
40      description: 'Search the web. Returns titles, URLs and snippets.',
41      parameters: {
42        type: 'object',
43        properties: { query: { type: 'string' } },
44        required: ['query'],
45      },
46      execute: async ({ query }) => sources(query),
47    },
48  },
49
50  // Claim-by-claim check on generate() answers
51  verifyCitations: {
52    retrieve: sources,
53    embedFn: async (texts) =>
54      (await embedText({ provider: 'openai', input: texts })).embeddings,
55  },
56});
57
58// A session: history, memory and tools
59const session = myAgent.session('briefing', { userId: 'user-7f3a' });
60const reply = await session.send('What happened in tech this week?');
61console.log(reply.text);
62
63// generate(): one turn without memory, plus the citation check
64const answer = await myAgent.generate('Which chips launched this week?');
65console.log(answer.grounding?.supportedRatio);
66
67await myAgent.close();

What's Next

npm install @framers/agentos

FAQ

What's the minimum Node version?

Node 22 LTS or later. AgentOS uses native fetch, top-level await, and the structured-clone-friendly Worker APIs that all stabilized by 22.

Can I use this without an OpenAI API key?

Yes. AgentOS auto-detects whichever provider key you set. Set ANTHROPIC_API_KEY for Claude, GEMINI_API_KEY for Gemini, or run Ollama locally with no key at all (set OLLAMA_BASE_URL). The runtime resolves models the same way regardless of source. Cognitive memory is the one piece that needs an embedding model, from OpenAI, Gemini or Ollama.

How does cognitive memory persist between sessions?

In process memory. With runtime: 'gmi' and memory set, the agent keeps one memory manager, over an in-memory vector store and knowledge graph, and every session it opens shares it. A session opened with the same userId recalls what that user said in earlier sessions; a session opened without one recalls only its own. The memory ends when the process exits or you call agent.close(). To keep memory across restarts, give the agent a memoryProvider backed by your own store in place of memory: agent() calls its getContext() before each model call and its observe() after each turn. See the cognitive memory docs for the full memory architecture.

Is HEXACO required, or opt-in?

Opt-in. Omit the personality block entirely and the agent runs with no trait shaping. When provided, every trait defaults to 0.5 (neutral) so partial profiles work too.

Can I run this fully local with Ollama?

Yes, with OLLAMA_BASE_URL set (http://localhost:11434 for a default install). Set provider: 'ollama' and model to any tag you have pulled (the default is llama3.2), and skip the API key entirely. Personality and tools work against local models, tools on models that support tool calling. With runtime: 'gmi', memory embeds with nomic-embed-text, so pull that too. Guardrails need the full runtime, local or not. Latency depends on your hardware.

What's the difference between AgentOS and other TypeScript agent frameworks?

The runtime ships cognitive memory, HEXACO personality, emergent tool forging, and guardrails as first-class primitives, not bolt-ons. Most TypeScript agent frameworks expose graph orchestration over an LLM and stop there. AgentOS treats the agent as a stateful runtime with measured memory and personality behavior. The framework comparison writes the same agent in AgentOS, LangGraph, CrewAI, Mastra and VoltAgent and says where each one is the better pick.


AgentOS is built by Frame. See Wilds.ai for AI game worlds powered by AgentOS.

Comments