"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:
| Provider | API Key Variable | Default model |
|---|---|---|
| OpenAI | OPENAI_API_KEY | gpt-4o |
| Anthropic | ANTHROPIC_API_KEY | claude-sonnet-4-6 |
| Google Gemini | GEMINI_API_KEY | gemini-2.5-flash |
| Ollama | OLLAMA_BASE_URL (no key) | llama3.2 |
| Groq | GROQ_API_KEY | llama-3.3-70b-versatile |
| OpenRouter | OPENROUTER_API_KEY | openai/gpt-4o |
| Requesty | REQUESTY_API_KEY | openai/gpt-4o |
| Together | TOGETHER_API_KEY | meta-llama/Llama-3.3-70B-Instruct-Turbo |
| Mistral | MISTRAL_API_KEY | mistral-large-latest |
| xAI | XAI_API_KEY | grok-2 |
| LiteLLM | apiKey and baseUrl options (your proxy) | None; set model to one your proxy serves |
| Claude Code CLI | PATH detection | claude-sonnet-4-6 via subscription |
| Gemini CLI | PATH detection | gemini-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.
| Trait | High Value Effect | Low Value Effect |
|---|---|---|
| Openness | Explores tangential ideas, creative responses | Stays focused, conservative |
| Conscientiousness | Organized, thorough, detailed | Casual, brief |
| Agreeableness | Warm, accommodating | Direct, challenging |
| Extraversion | Enthusiastic, verbose | Reserved, concise |
| Emotionality | Empathetic, supportive | Analytical, detached |
| Honesty-Humility | Transparent, admits limitations | Confident, 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:
- Ebbinghaus decay: the decay model under all eight. Memories fade exponentially over time (R = e^(-t/S)), replicated and validated by Murre & Dros (2015). Emotionally intense events resist decay: above an intensity of 0.8 a memory encodes as a flashbulb memory, with five times the stability.
- Reconsolidation: retrieved memories drift toward the agent's current emotional context (Nader et al., 2000).
- Retrieval-induced forgetting: retrieving one memory suppresses competing memories (Anderson, Bjork & Bjork, 1994).
- Memory types: traces are episodic, semantic, procedural, prospective or relational, and a slot-based working memory holds the ones in focus.
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:
| Guardrail | Detection Method | What It Catches |
|---|---|---|
| PII Redaction | Regex patterns, an NLP prefilter and a local NER model; an LLM judge is optional and off by default | Names, emails, SSNs, credit cards, addresses |
| ML Classifiers | A local ONNX model (Xenova/toxic-bert); when it can't load, an LLM judge (if you pass one) and then keyword patterns | Toxicity, NSFW content, threats; prompt injection only on the fallbacks, since the ONNX model has no injection label |
| Topicality | Embedding-based topic matching on user input, with LLM and keyword fallbacks | Off-topic messages, scope violations |
| Code Safety | OWASP pattern scanning | Command injection, XSS, SQL injection in generated code |
| Grounding Guard | NLI-based claim verification | Hallucinated facts not supported by RAG sources |
| Content Policy | Keyword pre-filter on the stream, LLM judge on the final reply | Policy 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
- Deploy to messaging channels: 37 adapters including Telegram, WhatsApp, Discord, Slack
- Add voice: 11 STT providers, 10 TTS providers, VAD, and telephony via Twilio, Telnyx, Plivo
- Build multi-agent teams: 6 coordination strategies with emergent tool forging
- Graph-based orchestration:
workflow(),AgentGraph, andmission()for complex pipelines - Full documentation: guides, API reference, and architecture
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.
