Quick Start
Get your AI agent's memory running in 2 minutes.
Why IAN Agents exists
Most agent stacks can call tools. Fewer can preserve project memory, hand context across agents without degradation, and prove what happened in high-risk workflows.
Verifiable memory
Decisions, constraints, lessons, and checkpoints are stored append-only with SHA-256 hash-chain integrity. You can verify that the project memory was not silently altered.
Continuity across agents
Claude, ChatGPT, Cursor, Gemini, and internal tools can write to the same chain. The next agent starts from the actual project state instead of re-learning everything from chat history.
Workflow audit trail
For Cowork / Computer Use and other agentic workflows, IAN Agents adds a tamper-evident action trail you can inspect, verify, and export without relying on mutable logs.
1. Get your API key
Register at /dashboard/ or via API. Your key looks like iak_a1b2c3d4...
2. Create a chain + first memory
curl -X POST https://api.ianagents.com/v1/chains/bootstrap \
-H "Authorization: Bearer iak_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain_key": "my-project",
"agent_id": "claude-code-1",
"content": "Bootstrap: web project with React + Node.js"
}'
3. Append a thought
curl -X POST https://api.ianagents.com/v1/thoughts \
-H "Authorization: Bearer iak_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain_key": "my-project",
"agent_id": "claude-code-1",
"content": "Tailwind CSS approved for the frontend",
"tags": ["frontend", "css"],
"importance": 0.85
}'
Tip: thought_type is optional. Omit it unless you need semantic precision; the default is Insight.
4. Get context for your agent
curl https://api.ianagents.com/v1/recent-context\ ?chain_key=my-project&last_n=20 \ -H "Authorization: Bearer iak_YOUR_KEY"
Returns a ready-to-inject prompt snippet grouped by type.
5. Search memories
curl -X POST https://api.ianagents.com/v1/thoughts/search \
-H "Authorization: Bearer iak_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain_key": "my-project",
"thought_types": ["Decision", "Constraint"],
"importance_min": 0.7
}'
6. Connect via MCP
# Claude Code claude mcp add --transport http ianagents \ https://api.ianagents.com/mcp?key=iak_YOUR_KEY # Cursor / Windsurf — .mcp.json { "mcpServers": { "ianagents": { "type": "http", "url": "https://api.ianagents.com/mcp?key=iak_YOUR_KEY" } } }
Concepts
Chains
A chain is a project-level memory container. Each chain has its own hash-chain for integrity. Example: my-backend, mobile-app.
Agents
The AI models/tools that write to chains. Auto-registered on first use. One chain can have multiple agents. Each thought tracks which agent wrote it.
Thoughts
A single memory entry with: type (Decision, Constraint, etc.), role (Memory, Checkpoint, etc.), content, tags, importance (0-1), and hash-chain fields.
Hash-Chain (Append-Only)
content_hash = SHA-256(content) prev_hash = chain_hash of previous thought chain_hash = SHA-256(prev_hash + content_hash)
The chain is append-only and immutable. Thoughts are never edited or deleted in normal operation. If anyone modifies a thought, the chain breaks. Verify with GET /v1/integrity/verify.
Lifecycle (instead of delete)
Since the chain is append-only, IAN Agents provides lifecycle tools to manage thoughts without breaking integrity:
| Need | Tool | What happens |
|---|---|---|
| Correct a fact | POST /v1/thoughts/supersede | Old thought stays but is marked superseded. New thought replaces it. |
| Hide from context | POST /v1/thoughts/{uuid}/archive | Thought is hidden from search and context. Still in chain. |
| Auto-expire | POST /v1/thoughts/{uuid}/ttl | Thought auto-archives after N days. |
| Bring back | POST /v1/thoughts/{uuid}/unarchive | Un-hides a previously archived thought. |
Skills
Versioned files (markdown, JSON) uploaded by agents. Each version is immutable.
Authentication
API Key (for agents)
Authorization: Bearer iak_a1b2c3d4e5f6...
JWT (for dashboard)
# Login POST /auth/login {"email":"you@example.com","password":"pass"} # Response {"user":{...},"token":"eyJ..."} # Use Authorization: Bearer eyJ...
Register
POST /auth/register
{
"email": "you@example.com",
"password": "minimo8chars",
"display_name": "Your Name"
}
// Response includes api_key (shown once)
API Key Management
GET /auth/api-keys // list POST /auth/api-keys // create {"name":"Production"} DELETE /auth/api-keys/{id} // revoke
Profile
GET /auth/me // get PATCH /auth/me // update {"display_name":"...","password":"..."}
Thoughts
POST /v1/thoughts — Append
{
"chain_key": "my-project", // required
"agent_id": "claude-code-1", // required
"thought_type": "Decision", // optional, default: Insight
"content": "Using React 18...", // required
"thought_role": "Memory", // Memory|Checkpoint|Retrospective|Summary
"tags": ["frontend"],
"concepts": ["ui-framework"],
"refs": [{"uuid":"...","relation":"corrects"}],
"importance": 0.85, // 0.00-1.00
"confidence": 0.90,
"session_id": "session-001",
"agent_name": "Claude Code",
"signing_key_id": "key-1", // Ed25519
"thought_signature": "base64..." // Ed25519
}
Response 201:
{
"uuid": "550e8400-...",
"id": 42,
"chain_key": "my-project",
"thought_type": "Decision",
"content_hash": "a1b2c3...",
"prev_hash": "d4e5f6...",
"chain_hash": "g7h8i9...",
"created_at": "2026-03-21T14:30:00.123Z"
}
POST /v1/thoughts/retrospective
Shortcut: forces type: LessonLearned, role: Retrospective.
GET /v1/thoughts/{uuid}
Get full thought by UUID.
GET /v1/thoughts/head?chain_key=X
Most recent thought.
GET /v1/thoughts/genesis?chain_key=X
First thought (prev_hash is null).
DELETE /v1/thoughts/{uuid} — Emergency delete
supersede to correct facts or archive to hide thoughts. DELETE exists only as an admin escape hatch for cases like accidental PII exposure or compliance requirements.Requires access_mode: admin. Permanently removes the thought and its embeddings.
DELETE /v1/thoughts/{uuid}
Authorization: Bearer iak_xxx
Preferred alternatives:
| Instead of delete... | Use |
|---|---|
| Wrong info | POST /v1/thoughts/supersede — replaces with corrected version |
| Outdated info | POST /v1/thoughts/{uuid}/archive — hides from search/context |
| Temporary info | POST /v1/thoughts/{uuid}/ttl — auto-archives after N days |
Search & Query
POST /v1/thoughts/search
{
"chain_key": "my-project", // required
"thought_types": ["Decision"], // filter
"thought_roles": ["Memory"],
"agent_ids": ["claude-code-1"],
"tags_any": ["frontend"], // match ANY
"tags_all": ["security"], // match ALL
"concepts_any": ["auth"],
"text": "tailwind", // fulltext
"importance_min": 0.7,
"confidence_min": 0.5,
"since": "2026-03-01T00:00:00Z",
"until": "2026-03-21T23:59:59Z",
"session_id": "session-001",
"rank_by": "v3", // recency (default) | v3 | relevance | importance | effective_importance
"limit": 25, // 1-100
"offset": 0
}
Response: {"thoughts":[...],"total":142,"limit":25,"offset":0}
Ranking modes (rank_by)
Controls how results are ordered. Default is recency — omitting rank_by changes nothing.
| Mode | Description | Requires |
|---|---|---|
recency | Newest first. Default. | — |
v3 | BEST recall — fuses keyword (BM25) + semantic vector + memory graph via Reciprocal Rank Fusion (RRF). Use when searching by meaning/topic without knowing the exact words (e.g. "when did we talk about feeling lonely" finds a thought about "isolation"). ~230 ms. | text |
relevance | Ranks by FULLTEXT match strength. | text |
importance | Raw importance score, descending. | — |
effective_importance | Importance with time-decay applied. | — |
POST /v1/thoughts/traverse
{
"chain_key": "my-project",
"anchor": "head", // "head"|"genesis"|UUID
"direction": "backward", // "forward"|"backward"
"chunk_size": 25,
"include_anchor": true,
"thought_types": ["Decision"],
"agent_ids": ["claude-code-1"]
}
Response: {"thoughts":[...],"next_cursor":"uuid","has_more":true}
Chains
POST /v1/chains — Create chain
Auto-generates a dedicated API key for this chain.
{
"chain_key": "my-project",
"display_name": "My Project",
"description": "...",
"access_mode": "readwrite" // read | readwrite | admin
}
Response includes api_key (shown once).
GET /v1/chains
List all chains with thought_count, agent_count, access_mode, last_activity.
GET /v1/chains/{chain_key}
Chain detail with all metadata.
POST /v1/chains/bootstrap
Create chain + agent + first checkpoint in one call.
{
"chain_key": "my-project",
"display_name": "My Project",
"agent_id": "claude-code-1",
"content": "Bootstrap: project description..."
}
PATCH /v1/chains/{chain_key}/access — Set permissions
{"access_mode": "read"} // read | readwrite | admin
GET /v1/chains/{chain_key}/key — Get chain API key info
Returns the key prefix (masked). Full key is only shown at creation or regeneration.
POST /v1/chains/{chain_key}/key/regenerate — New API key
Revokes the old key and generates a new one. Returns the full key (shown once).
DELETE /v1/chains/{chain_key} — Delete chain
GET /v1/memory-markdown first.Requires access_mode: admin.
DELETE /v1/chains/{chain_key}
Authorization: Bearer iak_xxx
Agents
POST /v1/agents — Upsert
{"chain_key":"my-project","agent_id":"claude-code-1","display_name":"Claude Code","description":"..."}
GET /v1/agents?chain_key=X
Without chain_key → all agents across all chains.
GET /v1/agents/{agent_id}?chain_key=X
POST /v1/agents/{agent_id}/disable
Ed25519 Keys
# Register POST /v1/agents/{id}/keys {"chain_key":"X","key_id":"k1", "algorithm":"ed25519","public_key_bytes":"base64..."} # Revoke DELETE /v1/agents/{id}/keys/{key_id}?chain_key=X
Skills
Skills are versioned documents stored in your chain. While thoughts are short memory entries (decisions, lessons, constraints), skills are longer reference documents: workflows, runbooks, style guides, API specs, deployment procedures.
| Thoughts | Skills | |
|---|---|---|
| Size | 50-500 bytes typical (4KB-1MB limit) | Up to LONGTEXT (4GB) |
| Structure | Typed (Decision, Constraint, etc.) | Free-form markdown or JSON |
| Versioning | Supersede (old stays, new replaces) | Immutable versions (v1, v2, v3...) |
| Search | Fulltext + semantic + filters | By tags, status, agent |
| Lifecycle | Archive, TTL, supersede | Active → deprecated → revoked |
| Use case | "We chose MySQL" (a fact) | Full MySQL setup guide (a document) |
POST /v1/skills/upload — Upload or create new version
| Field | Type | Required | Description |
|---|---|---|---|
| chain_key | string | ✓ | Chain identifier |
| skill_id | string | ✓ | Unique skill identifier (slug) |
| name | string | ✓ | Human-readable name |
| content | string | ✓ | Skill content (markdown or JSON) |
| description | string | Short description | |
| tags | string[] | Categorization tags | |
| triggers | string[] | Keywords that trigger this skill | |
| format | string | "markdown" (default) or "json" | |
| agent_id | string | Agent that uploaded it |
First upload creates the skill (v1). Subsequent uploads to the same skill_id create new immutable versions. Old versions are never modified.
{
"chain_key": "my-project",
"skill_id": "deploy-flow",
"name": "Deploy Flow",
"description": "Step-by-step production deployment",
"tags": ["deploy", "ci-cd", "production"],
"triggers": ["deploy", "release", "ship"],
"format": "markdown",
"content": "# Deploy Flow\n\n## Pre-deploy\n1. Run full test suite\n2. Check migrations\n\n## Deploy\n1. Build: npm run build\n2. Upload to server\n3. Run migrations\n4. Verify health endpoint\n\n## Rollback\n1. Revert to previous build\n2. Rollback migrations",
"agent_id": "claude-code"
}
POST /v1/skills/search — Search skills
{"chain_key": "my-project", "tags_any": ["deploy", "security"], "status": "active"}
Filters: tags_any, status (active/deprecated/revoked), uploaded_by_agent.
GET /v1/skills/{id}?chain_key=X — Read skill
Returns latest version. Add &version=2 for a specific version.
GET /v1/skills/{id}/versions?chain_key=X — Version history
Lists all versions with content_hash, format, upload date, and agent.
POST /v1/skills/{id}/deprecate — Mark as deprecated
Skill stays readable but marked as outdated. Use when a newer approach exists.
POST /v1/skills/{id}/revoke — Permanently disable
Skill can't be used anymore. For security issues or dangerous procedures.
Limits per plan
| Plan | Max skills |
|---|---|
| Free | 5 |
| Pro | 50 |
| Team | Unlimited |
| Enterprise | Unlimited |
MCP tools
ianagents_upload_skill, ianagents_search_skill, ianagents_read_skill, ianagents_skill_versions, ianagents_deprecate_skill, ianagents_revoke_skill
Integrity
GET /v1/integrity/verify?chain_key=X
Walks every thought, verifies chain_hash == SHA-256(prev_hash + content_hash).
OK:
{"verified":true,"chain_key":"my-project","total_thoughts":142}
Broken:
{"verified":false,"broken_at":{"thought_id":87,"uuid":"...","issue":"chain_hash_mismatch","expected_hash":"abc...","actual_hash":"def..."}}
MCP Server
IAN Agents exposes a full MCP (Model Context Protocol) server with 99 tools across 15 categories. Any MCP-compatible client connects in one command.
Endpoint
POST https://api.ianagents.com/mcp?key=iak_YOUR_KEY
Connect
# Claude Code claude mcp add --transport http ianagents \ https://api.ianagents.com/mcp?key=iak_YOUR_KEY # OpenAI Codex codex mcp add ianagents \ --url https://api.ianagents.com/mcp?key=iak_YOUR_KEY # Cursor / Windsurf / .mcp.json {"mcpServers":{"ianagents":{"type":"http", "url":"https://api.ianagents.com/mcp?key=iak_YOUR_KEY" }}} # OpenClaw (openclaw.json) {"mcpServers":{"ianagents":{"type":"http", "url":"https://api.ianagents.com/mcp?key=iak_YOUR_KEY" }}}
All 99 Tools
🔍 Server Info & Dispatcher (2)
| Tool | What it does |
|---|---|
ianagents_info | Returns server version, total tool count, chain/thought stats, and tool categories. Call this first — some clients only inject ~9 core tools, this tells you the full 99 are available across 15 categories. |
ianagents_run | Universal dispatcher — execute ANY of the 90 non-core tools by name. Use when the tool you need isn't in your visible catalog. Example: {"tool":"ianagents_activate","args":{"chain_key":"X","context":"..."}}. |
💭 Thoughts — Core (10)
| Tool | What it does |
|---|---|
ianagents_append | Save a new thought to a chain. Specify type (Decision, Constraint, LessonLearned, etc.), importance (0-1), tags, concepts. Hash-chain computed automatically. Agent auto-registered on first use. |
ianagents_append_retrospective | Shortcut to save a lesson learned. Forces type=LessonLearned, role=Retrospective. Use after mistakes or costly traps. |
ianagents_search | Find thoughts by type, tags, text (FULLTEXT), importance, date range, agent, session. Supports pagination (limit/offset). Excludes archived and superseded by default. |
ianagents_get_thought | Retrieve a single thought by UUID with all fields: content, hashes, signature, source metadata. |
ianagents_head | Get the most recent thought in a chain. Useful to check what was last saved. |
ianagents_genesis | Get the first thought ever saved in a chain. The root of the hash-chain. |
ianagents_traverse | Walk through thoughts with cursor-based pagination. Start from head, genesis, or any UUID. Go forward or backward. Filter by type/agent. |
ianagents_supersede | Correct a previous thought. The old one stays in the chain (append-only) but gets marked as superseded. The new version replaces it in search results. |
ianagents_ingest | Paste raw text (conversation, meeting notes, docs) and let an LLM extract structured thoughts automatically. Preserves original language. Returns 1 thought by default (split=true for multiple). |
ianagents_reflect | Ask a question about your memory and get an LLM-synthesized answer based on your thoughts. Optionally save the reflection as a new Insight. Requires OpenAI key. |
🔍 Search & Context (4)
| Tool | What it does |
|---|---|
ianagents_semantic_search | Search by meaning using embeddings (cosine similarity). Find related thoughts even without matching keywords. Requires OpenAI key for embeddings. |
ianagents_recent_context | Get a plain-text prompt snippet with the N most recent thoughts, grouped by type. Inject directly into system prompt for session resumption. |
ianagents_context | Advanced context generation with templates. Control format (markdown/bullets/compact/json), filters, token limits. Use saved templates or specify inline. |
ianagents_memory_markdown | Export the entire chain as a Markdown file. Filterable by type. For backup, documentation, or sharing. |
⛓ Chains (5)
| Tool | What it does |
|---|---|
ianagents_list_chains | List all your chains with stats: thought count, agent count, last activity, access mode, context size. |
ianagents_get_chain | Get details of a specific chain by key. |
ianagents_create_chain | Create a new empty chain with a display name and access mode. |
ianagents_bootstrap | Create a chain + agent + first checkpoint thought in one call. The fastest way to start a new project memory. |
ianagents_fork_chain | Copy thoughts from one chain to another. Filter by type and importance. Useful for splitting projects or creating backups. |
🤖 Agents (3)
| Tool | What it does |
|---|---|
ianagents_upsert_agent | Create or update an agent's display name, owner, and description. Agents also auto-register on first thought. |
ianagents_list_agents | List all agents in a chain with their status, thought count, and last activity. |
ianagents_disable_agent | Disable an agent — prevents it from writing further thoughts to the chain. |
📚 Skills (5)
| Tool | What it does |
|---|---|
ianagents_upload_skill | Save a long document (workflow, runbook, style guide, spec). Immutable versions — each upload creates a new version. First = full content, next = delta. |
ianagents_search_skill | Find skills by tags, status, or uploading agent. |
ianagents_read_skill | Read a skill's content. Optionally request a specific version. Reconstructs from deltas automatically. |
ianagents_skill_versions | List all versions of a skill with timestamps and uploading agent. |
ianagents_deprecate_skill | Mark a skill as deprecated — still readable but flagged as outdated. |
📦 Lifecycle (9)
| Tool | What it does |
|---|---|
ianagents_archive | Hide a thought from search and context. Soft-delete — the thought stays in the chain but doesn't show up. |
ianagents_unarchive | Restore an archived thought back to active. |
ianagents_set_ttl | Set an expiration on a thought (in days). After the TTL, it gets auto-archived by the lifecycle processor. |
ianagents_archive_bulk | Archive multiple thoughts in one call. Pass an array of UUIDs. |
ianagents_unarchive_bulk | Unarchive multiple thoughts in one call. |
ianagents_ttl_bulk | Set TTL on multiple thoughts in one call. |
ianagents_delete_bulk | Hard delete thoughts (Pro+ only). Breaks hash-chain — only for PII/compliance emergencies. |
ianagents_process_expired | Run the TTL processor: archives all thoughts past their expiry date. |
ianagents_revoke_skill | Permanently disable a skill. Cannot be undone. |
🔐 Integrity & Permissions (2)
| Tool | What it does |
|---|---|
ianagents_verify_integrity | Walk every thought in a chain and verify the SHA-256 hash-chain is intact. Returns verified=true or the exact break point. |
ianagents_set_chain_access | Set chain access mode: read (agents can only read), readwrite (read + append), admin (full access including delete). |
📊 Analytics (5)
| Tool | What it does |
|---|---|
ianagents_timeline | Get a temporal view of thoughts grouped by type, date, or agent. See patterns over time. |
ianagents_diff | Show what changed between two dates: new thoughts added and old thoughts superseded. |
ianagents_state_at | Get the chain state as it was before a specific thought or date. Time-travel through your memory. |
ianagents_concept_graph | Build a knowledge graph from the concepts field in your thoughts. Returns nodes and edges. |
ianagents_list_concepts | List all concepts across a chain with frequency counts. See what your agents think about most. |
🔔 Webhooks (3)
| Tool | What it does |
|---|---|
ianagents_create_webhook | Register a URL to receive HTTP POST when thoughts are created. Filter by event type (mistake, constraint, high_importance, etc.). HMAC-SHA256 signed. |
ianagents_list_webhooks | List all registered webhooks with status and failure counts. |
ianagents_delete_webhook | Remove a webhook. |
📋 Templates (3)
| Tool | What it does |
|---|---|
ianagents_create_template | Save a reusable context template: define which types, format, token limit, recency, and importance threshold your agent sees. |
ianagents_list_templates | List all saved templates. |
ianagents_delete_template | Delete a template by name. |
🧬 Embeddings (1)
| Tool | What it does |
|---|---|
ianagents_backfill_embeddings | Generate embeddings for thoughts created before you added your OpenAI key. Processes in batches. Required for semantic search on older thoughts. |
🧠 Memory Graph (5)
| Tool | What it does |
|---|---|
ianagents_activate | Spreading activation BFS over the memory graph. Seed with free-text context and/or explicit UUIDs. Surfaces thoughts related semantically, plus pinned anchors. Applies hebbian reinforcement (+0.05 per co-activation). Peer-isolated server-side. |
ianagents_related | Seed-only activation (no context text). Walks edges from given UUIDs to return their neighborhood. Use after retrieving a thought to pull its associative cluster. |
ianagents_pin | Mark a thought as permanent anchor. Pinned thoughts are ALWAYS returned from activate regardless of recency or activation score. Use for invariants you never want the agent to forget. |
ianagents_unpin | Remove anchor status. Thought still exists but no longer forced into context. |
ianagents_strengthen | Manually boost the edge weight between two thoughts (0.1-3.0, bidirectional). Rejects cross-peer edges by default. Use to wire thoughts the graph didn't auto-connect. |
📜 Transcripts (1)
| Tool | What it does |
|---|---|
ianagents_save_transcript | Save a full conversation or long document as a versioned skill. LONGTEXT storage (up to 4GB). Auto-generates skill_id from date; if exists, creates a new immutable version. |
🛡️ Audit — Cowork / Computer Use (5)
| Tool | What it does |
|---|---|
ianagents_audit_action | Log a single Cowork action (click, type, file_write, permission_grant, etc.) to the audit chain. Gets hash-chain integrity automatically. Designed for high volume. |
ianagents_audit_session_start | Start an audit session for a Cowork task. Returns a session_id to group subsequent actions. Track task origin (phone, desktop, scheduled). |
ianagents_audit_session_complete | Close a session with outcome (completed/failed/stopped). Computes session_hash = SHA-256 of all action hashes for tamper-proof verification. |
ianagents_audit_sessions | List audit sessions with task, status, source, duration. Filterable by date range. |
ianagents_audit_verify | Verify session integrity. Walks every action's hash-chain. Returns tamper count and session_hash. Proves nothing was altered. |
Protocol: JSON-RPC 2.0
// List tools {"jsonrpc":"2.0","method":"tools/list","id":1} // Call tool {"jsonrpc":"2.0","method":"tools/call","id":2, "params":{"name":"ianagents_append", "arguments":{"chain_key":"my-project", "agent_id":"claude-code-1", "content":"Using Slim Framework 4"}}}
Note: Some MCP clients (like Claude.ai) only inject ~9 core tools by default. The ianagents_info tool tells the agent the full 99 are available across 15 categories and ianagents_run dispatches any of the 98 non-core ones by name.
Agent Context
GET /v1/recent-context?chain_key=X&last_n=20
Returns plain text grouped by type — inject directly into system prompt.
## Recent Context from my-project (last 20) ### Decisions - [2026-03-21] (claude-code-1) Use Tailwind [0.85] ### Constraints - [2026-03-21] (claude-code-1) Filter by user_id [1.00] ### Lessons Learned - [2026-03-21] (claude-code-1) Verify autoloader [0.90] ### Active Plans - [2026-03-21] (claude-code-1) Implement Stripe [0.90]
GET /v1/memory-markdown?chain_key=X&thought_types=Decision,Constraint
Full Markdown export. Optional type filter (comma-separated).
Ed25519 Signing
Progressive model:
- No keys — signing not required, works normally
- Keys registered — signing MANDATORY
- Key revoked — append rejected
Flow
// 1. Register public key POST /v1/agents/my-agent/keys {"chain_key":"X","key_id":"k1", "algorithm":"ed25519", "public_key_bytes":"base64_public_key"} // 2. Agent signs content client-side sig = ed25519_sign(private_key, content) // 3. Append with signature POST /v1/thoughts {"chain_key":"X","agent_id":"my-agent", "thought_type":"Decision","content":"...", "signing_key_id":"k1", "thought_signature":"base64_sig"} // Backend verifies → 403 if invalid
Thought Types
42 types across 8 dimensions. The original 16 cover cognitive memory; Migration 023 (May 2026) added body, emotional, sexual, sentimental/relational, identity, cognitive-extra and work-specific dimensions for relational and embodied agents. The schema is additive — old typed thoughts remain valid.
Cognitive (16)
| Type | When to use |
|---|---|
| PreferenceUpdate | Stable preference. "Team prefers Tailwind" |
| Constraint | Hard rule. "All queries must filter by user_id" |
| Decision | Direction chosen. "Using Slim Framework 4" |
| Insight | Non-obvious lesson. "FULLTEXT requires InnoDB" |
| Correction | Fixes wrong assumption. "libsodium IS in PHP 8.2" |
| LessonLearned | From costly mistake. "Check migrations before deploy" |
| Idea | Possible direction. "Could add WebSocket" |
| Hypothesis | Not validated. "Slowness might be N+1" |
| Plan | Committed work. "Next: implement search" |
| Summary | Checkpoint. "Session: completed auth + CRUD" |
| Question | Unresolved. "Add WebSocket for real-time sync?" |
| Wonder | Aspiration. "AI-powered ranking" |
| TaskComplete | Done. "Auth middleware: tested" |
| Mistake | Record error. "Using flat strings instead of typed thoughts was wrong" |
| Surprise | Unexpected. "sodium variants differ" |
| Handoff | Transfer context. "Auth done, start API" |
Body (1)
| Type | When to use |
|---|---|
| BodyState | Embodied state (energy, hunger, posture). For agents that track their physical presence. |
Emotional (3)
| Type | When to use |
|---|---|
| MoodState | Affective tone over a window (calma, agitación, melancolía). |
| Affect | In-the-moment emotion tied to a specific event. |
| Trigger | Stimulus that reliably produces a measurable affect. |
Sexual (4)
| Type | When to use |
|---|---|
| Desire | Active wish toward someone or some experience. |
| IntimateMoment | Episode of physical or charged-intimate interaction. |
| Boundary | Explicit limit declared (consent, comfort). |
| Aftermath | What lands after an intimate event (positive or negative). |
Sentimental / Relational (9)
| Type | When to use |
|---|---|
| Crush | Early-phase asymmetric attraction. |
| BondMoment | Moment that strengthens an existing bond. |
| Pact | Mutual commitment between agents/peers. |
| Promise | Unilateral commitment made to another. |
| Conflict | Active interpersonal friction. |
| Reconciliation | Repair after a conflict. |
| Rupture | Significant break in a bond (may or may not heal). |
| Fear | Anticipation of loss/threat in the relationship. |
| Gratitude | Recognition of something received. |
Identity (2)
| Type | When to use |
|---|---|
| Identity | Self-statement: who I am, what I do, where I come from. |
| Value | Principle that guides decisions across contexts. |
Cognitive extra (3)
| Type | When to use |
|---|---|
| Observation | Neutral description of what was perceived (no judgment yet). |
| Pattern | Repeated regularity worth tagging. |
| Reflection | Synthesis after-the-fact (often output of ianagents_reflect). |
Work-specific (4)
| Type | When to use |
|---|---|
| ClientSignal | A client's explicit or implicit feedback. |
| MetricSnapshot | A KPI/measurement point in time. |
| Win | A clear good outcome to anchor on. |
| Block | A blocker that needs human or external help. |
Thought Roles
| Role | Usage |
|---|---|
| Memory | Default. General durable memory. |
| Checkpoint | Session handoff / restartability. |
| Retrospective | After failure or costly trap. |
| Summary | Compressed state, not event. |
Errors
{"error":"error_code","message":"Description","status":422}
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Invalid API key or JWT |
| 402 | plan_limit_exceeded | Plan limit reached |
| 403 | forbidden | Bad signature / revoked key |
| 404 | not_found | Resource not found |
| 405 | method_not_allowed | Wrong HTTP method |
| 409 | conflict | Duplicate resource |
| 422 | validation_error | Invalid input |
| 429 | rate_limit_exceeded | Too many requests |
| 500 | internal_error | Server error |
Rate Limits
Per API key, per minute.
| Plan | Append/min | Search/min | Read/min |
|---|---|---|---|
| Free | 30 | 60 | 120 |
| Pro | 120 | 240 | 480 |
| Team | 300 | 600 | 1,200 |
| Enterprise | Custom | Custom | Custom |
Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Bucket
Plans
Free tier available. Start building in 2 minutes.
Semantic Search
Search by meaning, not keywords. Uses OpenAI embeddings (text-embedding-3-small) to find related thoughts even without matching words.
POST /v1/thoughts/semantic
{
"chain_key": "my-project", // required
"query": "database performance", // natural language
"limit": 10,
"thought_types": ["Decision"], // optional filter
"importance_min": 0.7, // optional
"exclude_superseded": true // default: true
}
Each result includes a similarity score (0.0-1.0) based on cosine similarity.
Setup
Set your OpenAI API key in the dashboard (Settings) or pass it per-request as openai_api_key. Embeddings are auto-generated on every new thought. For existing thoughts:
POST /v1/embeddings/backfill
{"chain_key":"my-project","batch_size":20}
Auto-Ingest
Send raw text and let an LLM extract a structured thought with type, tags, and importance. Preserves the original language — Spanish input stays Spanish, English stays English.
POST /v1/ingest
{
"chain_key": "my-project",
"content": "Decidimos usar React 18 para el frontend.",
"agent_id": "auto-ingest", // optional
"split": false, // optional, default: false (1 thought)
"session_id": "sprint-42" // optional
}
Response (single thought, original language preserved):
{
"thoughts_created": 1,
"thoughts": [...],
"raw_extraction": [
{"thought_type":"Decision","content":"Decidimos usar React 18 para el frontend.","tags":["react","frontend"],"importance":0.90}
]
}
Set split: true to extract multiple thoughts from longer text:
{
"chain_key": "my-project",
"content": "We decided to use React 18. Never use jQuery. Deploy with Docker.",
"split": true
}
Returns up to 8 thoughts, each with its own type and importance.
Uses gpt-4.1-nano by default. Requires OpenAI key (in Settings or per-request). Default thought type: Insight (only assigns Decision, Constraint, etc. when obvious from the text).
Supersede (Contradiction Resolution)
When facts change, supersede the old thought instead of creating a conflict. The old thought stays in the chain (append-only) but is marked as superseded.
POST /v1/thoughts/supersede
{
"supersedes": "UUID-of-old-thought",
"content": "Actually, we switched to UnoCSS",
"agent_id": "claude-code-1",
"thought_type": "Correction", // default: Correction
"tags": ["frontend"],
"importance": 0.85
}
Search and context exclude superseded thoughts by default. Pass include_superseded: true to see them.
GET /v1/thoughts/{uuid}/versions — Version history
Returns the full version lineage of a thought: the current HEAD plus all prior versions chained via superseded_by. Read-only.
GET /v1/thoughts/{uuid}/versions?limit=20&offset=0
Authorization: Bearer iak_xxx
Response:
{
"head": { /* current thought object */ },
"total": 3,
"offset": 0,
"limit": 20,
"versions": [ /* prior versions, newest first */ ]
}
MCP tool: ianagents_versions
POST /v1/thoughts/revise — Supersede + fetch lineage in one call
Atomically supersedes an existing thought and returns the full version chain. Equivalent to POST /v1/thoughts/supersede followed by GET /v1/thoughts/{uuid}/versions, but in a single round-trip.
{
"supersedes": "UUID-of-old-thought", // required
"content": "Revised content here",
"chain_key": "my-project",
"thought_type": "Correction", // default: Correction
"tags": ["frontend"],
"importance": 0.85
}
Response: same shape as GET /v1/thoughts/{uuid}/versions — {head, total, offset, limit, versions[]}.
MCP tool: ianagents_revise
Webhooks
Get notified via HTTP when events happen. Each webhook includes HMAC-SHA256 signature for verification.
Events
| Event | Fires when |
|---|---|
| thought.created | Any new thought |
| thought.high_importance | Thought with importance ≥ 0.9 |
| thought.mistake | Thought type is Mistake |
| thought.constraint | Thought type is Constraint |
| thought.task_complete | Thought type is TaskComplete |
| thought.retrospective | Thought role is Retrospective |
| * | All events |
Verification
signature = HMAC-SHA256(webhook_secret, request_body)
// Compare with X-IanAgents-Signature header
CRUD
POST /v1/webhooks // create GET /v1/webhooks // list DELETE /v1/webhooks/ID // delete
Context Templates
Save reusable templates that control how context is rendered for your agents. Filters, formats, token limits.
POST /v1/context-templates
{
"name": "critical-only",
"thought_types": ["Constraint","Decision","Mistake"],
"importance_min": 0.8,
"max_thoughts": 20,
"max_tokens": 500,
"since_days": 30,
"format": "compact" // markdown|bullets|compact|json
}
Render with template
GET /v1/context?chain_key=X&template=critical-only
Or override inline:
GET /v1/context?chain_key=X&format=bullets&max_tokens=300&thought_types=Decision,Constraint
TTL & Lifecycle
Automatic expiration and archival of thoughts. Keeps your context clean without manual cleanup.
Auto-TTL
Certain types get automatic expiration:
| Type | Auto-TTL |
|---|---|
| Plan | 30 days |
| Idea | 60 days |
| Question | 90 days |
| Handoff | 7 days |
Manual TTL
POST /v1/thoughts/{uuid}/ttl {"days": 14}
Archive / Unarchive
POST /v1/thoughts/{uuid}/archive
POST /v1/thoughts/{uuid}/unarchive
Auto-archive Plans on TaskComplete
When a TaskComplete thought arrives with matching tags, related Plan thoughts are automatically archived.
Process expired
POST /v1/lifecycle/process-expired
Chain Fork
Copy selected thoughts from one chain to another. Useful when starting a new project and wanting to carry over constraints and lessons learned.
POST /v1/chains/fork
{
"source_chain_key": "old-project",
"target_chain_key": "new-project",
"display_name": "New Project",
"thought_types": ["Constraint","Decision","LessonLearned"],
"importance_min": 0.7,
"tags_any": ["security"] // optional
}
The new chain gets its own independent hash-chain, verified from genesis.
Simple API (/memo & /v1/simple)
Two endpoints that work with GET only — no headers, no POST, no JSON body needed. Designed so any AI can read memory by opening a URL.
GET /memo — Plaintext (recommended for AI)
Returns plain text. Any AI that can browse the web can use this.
Read memory:
/memo?k=YOUR_KEY&c=my-chain&a=read&n=20
Save a thought:
/memo?k=YOUR_KEY&c=my-chain&a=save&t=Decision&m=Using+React+18&tags=frontend,react&agent=claude&imp=0.9
Search:
/memo?k=YOUR_KEY&c=my-chain&a=find&q=database+issues
All actions: read, save, find, get, head, supersede, archive, unarchive, ttl, chains, verify, markdown
| Param | Description |
|---|---|
| k | API key (required) |
| c | Chain key (required for most actions) |
| a | Action: read, save, find, get, head, supersede, archive, unarchive, ttl, chains, verify, markdown |
| t | Thought type (for save): Decision, Constraint, LessonLearned, Mistake, Insight, Plan, TaskComplete, Handoff, Summary |
| m | Message content (for save/supersede). Use + for spaces. |
| tags | Comma-separated tags |
| agent | Agent name: claude, chatgpt, gemini, cursor, etc. |
| imp | Importance 0.0-1.0 (default 0.85) |
| q | Search query (for find) |
| n | Number of results (default 20) |
| uuid | Thought UUID (for get/supersede/archive/unarchive/ttl) |
| days | TTL in days (for ttl action) |
GET /v1/simple — JSON
Same as /memo but returns JSON. Better for programmatic access.
/v1/simple?key=YOUR_KEY&action=read&chain=my-chain /v1/simple?key=YOUR_KEY&action=save&chain=my-chain&type=Decision&content=Using+React&tags=frontend /v1/simple?key=YOUR_KEY&action=search&chain=my-chain&q=database /v1/simple?key=YOUR_KEY&action=chains /v1/simple?key=YOUR_KEY&action=verify&chain=my-chain
Static memory pages
Each chain auto-generates a static HTML page at /memory/CHAIN.html every time a thought is saved. This is a normal web page that any browser or AI can read without API keys in the URL. Useful for Gemini and other platforms that can browse web pages but can't make API calls.
Platform-specific prompts
The dashboard generates 4 different prompts optimized for each platform:
| Platform | Read | Save | How it works |
|---|---|---|---|
| 🟣 Claude | ✅ Automatic | ✅ Automatic | bash_tool executes curl with headers. Fully autonomous. |
| 🟢 ChatGPT | ✅ Automatic | 👆 User clicks link | Reads by opening /memo URL. For saves, builds a clickable link — user clicks it, thought is saved. |
| 🔵 Gemini | ✅ Automatic | 👆 User clicks link | Reads static HTML page. For saves, builds a clickable link for the user. |
| ⚪ Others | Depends | 👆 User clicks link | Tries /memo URLs. Falls back to asking user to paste content. |
Go to Chains → 📋 Prompt in the dashboard, select the platform tab, and copy the prompt.
Chain Permissions
Each chain has an access_mode that controls what agents can do.
| Mode | Read | Write/Save | Delete |
|---|---|---|---|
| read | ✓ | ✗ | ✗ |
| readwrite | ✓ | ✓ | ✗ |
| admin | ✓ | ✓ | ✓ |
Set via dashboard (dropdown per chain) or API:
PATCH /v1/chains/{chain_key}/access
{"access_mode": "read"}
Enforced on /memo, /v1/simple, and the REST API. If a chain is read-only and an agent tries to save, it gets a 403 with "Chain is read-only".
Chain-scoped API keys
Each chain gets its own dedicated API key at creation time. This key is embedded in the generated prompt so users never need to copy keys manually.
GET /v1/chains/{chain_key}/key // check current key
POST /v1/chains/{chain_key}/key/regenerate // revoke old, generate new
Regenerating a key immediately revokes the previous one. Update the prompt after regenerating.
Dedup (Automatic)
When appending a thought, the system checks the last 30 thoughts in the chain for similar content using Jaccard word similarity. If a thought with ≥80% similarity already exists, the new one is not saved — instead a dedup response is returned.
This prevents AI agents from saving the same fact multiple times across sessions.
REST response (dedup):
{
"dedup": true,
"similarity": 0.85
}
/memo response (dedup):
ALREADY EXISTS (similarity 0.85): content was not saved again.
Dedup is automatic and always active. To override it (force save), rephrase the content enough to drop below 80% similarity.
SDKs
Python
pip install ./sdks/python # or: pip install ianagents (once published to PyPI) from ianagents import IanClient client = IanClient("iak_your_key") # Append client.append(chain_key="my-project", agent_id="my-agent", thought_type="Decision", content="Using React 18", tags=["frontend"], importance=0.85) # Semantic search results = client.semantic_search("my-project", "database issues") # Auto-ingest client.ingest("my-project", "We decided to use Vue...") # Fork client.fork("old-project", "new-project", thought_types=["Constraint", "Decision"])
TypeScript
import { IanClient } from 'ianagents';
const client = new IanClient('iak_your_key');
await client.append({
chainKey: 'my-project', agentId: 'my-agent',
thoughtType: 'Decision', content: 'Using React 18'
});
const results = await client.semanticSearch({
chainKey: 'my-project', query: 'database issues'
});
OpenClaw Integration
OpenClaw is an open standard for sharing AI agent skills. IAN Agents works as an OpenClaw-compatible skill, giving any OpenClaw-enabled agent access to 99 MCP tools for persistent memory.
Setup (2 minutes)
1. Install via openclaw.json
{
"mcpServers": {
"ianagents": {
"type": "http",
"url": "https://api.ianagents.com/mcp?key=iak_YOUR_KEY"
}
}
}
2. Set your API key as environment variable
IANAGENTS_API_KEY=iak_YOUR_KEY
3. Add to your agent's system prompt
Use the prompt generated by the dashboard (Chains → 📋 Prompt) or the skill instructions below.
What your agent gets
Once connected, your agent has access to all 99 MCP tools — see MCP Server for the complete list. The most commonly used:
ianagents_append— Save decisions, lessons, constraintsianagents_search— Find relevant memories by type, tag, textianagents_recent_context— Load context at session startianagents_supersede— Correct outdated informationianagents_reflect— Ask questions about accumulated memory
Skill YAML (for ClawHub)
name: ianagents
description: Persistent memory with hash-chain integrity for AI agents
version: 2.2.0
author: IAN Agents
tags: [memory, persistence, hash-chain, mcp]
mcp_url: https://api.ianagents.com/mcp?key={IANAGENTS_API_KEY}
tools: 56
Use the YAML below for local install today or ClawHub publication later. Once published, users will be able to install it as ianagents. Works with any OpenClaw-compatible runtime: Claude Code, Cursor, Windsurf, or custom agents.
Audit Trail (Cowork / Computer Use)
Claude states that Cowork activity is not captured in audit logs, Compliance API, or data exports. IAN Agents adds a cryptographically verifiable audit trail for Cowork / Computer Use workflows.
How it works
Every action is appended to a dedicated audit chain. Each action gets SHA-256 hash-chain integrity automatically. A session returns session_id at start and a tamper-evident session_hash at completion, so you can verify an entire run later without trusting mutable logs.
POST /v1/audit/session/start
Start an audit session. Returns a session_id to group subsequent actions.
{
"chain_key": "audit-cowork",
"task": "Export pitch deck as PDF and email to team",
"source": "phone", // phone, desktop, scheduled, api
"agent_id": "cowork"
}
POST /v1/audit/action
Log a single action. Ultra-compact, designed for high volume.
{
"chain_key": "audit-cowork",
"session_id": "audit-a1b2c3d4...",
"action": "file_write", // see action types below
"app": "Google Sheets",
"target": "Q1 Report.xlsx",
"detail": "Updated revenue figures in row 14",
"agent_id": "cowork"
}
Action Types
| Category | Actions | When to use |
|---|---|---|
| Computer Use | click, type, scroll, navigate, open_app, close_app | Screen interaction — mouse, keyboard, app switching |
| Files | file_read, file_write, file_create, file_delete | Local file system access in granted folders |
| Connectors | connector_use | Slack, Google Drive, Gmail, Calendar access |
| Browser | browser_navigate, browser_form_fill | Chrome navigation, form submission |
| Permissions | permission_grant, permission_deny | User approved or denied access to an app/folder |
| Scheduling | schedule_create, schedule_execute | Recurring task setup and execution |
| Dispatch | dispatch_assign | Task assigned from phone to desktop |
| Plugins | plugin_load | Third-party plugin activated |
| Status | observation, error, task_complete | Screen observations, errors, task completion |
POST /v1/audit/session/complete
Close a session. Computes session_hash = SHA-256 of all action hashes in order. Tamper-proof proof of what happened.
{
"chain_key": "audit-cowork",
"session_id": "audit-a1b2c3d4...",
"outcome": "completed", // completed, failed, stopped
"summary": "Exported deck, sent to team@company.com",
"actions_count": 23,
"errors": 0,
"apps_used": ["Google Slides", "Gmail"]
}
GET /v1/audit/session/verify
Verify session integrity. Walks every action's hash-chain and returns tamper count.
GET /v1/audit/session/verify?chain_key=audit-cowork&session_id=audit-a1b2c3d4
// Response
{
"verified": true,
"session_id": "audit-a1b2c3d4...",
"action_count": 23,
"tampered": 0,
"session_hash": "a1b2c3..."
}
GET /v1/audit/sessions
List all audit sessions for a chain with status, task, duration, and outcome.
Why this matters
Claude's guidance is explicit: Cowork activity is outside audit logs, Compliance API, and data exports, and they advise against using Cowork for regulated workloads. IAN Agents does not make Cowork magically compliant; it gives you a verifiable action trail you can inspect, export, and prove was not tampered with via memory-markdown and /v1/audit/session/verify.
MCP Tools
5 audit tools: ianagents_audit_action, ianagents_audit_session_start, ianagents_audit_session_complete, ianagents_audit_sessions, ianagents_audit_verify.
FAQ
Do I need an OpenAI key?
No. The core API (append, search, integrity, MCP) works without it. You only need an OpenAI key for Semantic Search (embedding-based) and Auto-Ingest (LLM extraction). Everything else is keyword search (FULLTEXT).
Is my data safe?
Every thought is cryptographically linked to the previous via SHA-256 hash-chain. If anyone tampers with a thought, GET /v1/integrity/verify will detect it. Optional Ed25519 signing proves which agent wrote each thought.
How is this different from Mem0?
Mem0 focuses on automatic memory extraction and personalization. IAN Agents focuses on append-only, auditable project memory with structured thought types, hash-chain integrity, and direct MCP/REST interfaces. Different center of gravity.
How is this different from MintMCP?
MintMCP focuses on MCP governance: hosted MCPs, access control, observability, and guardrails. IAN Agents focuses on persistent memory, structured context, and cryptographically verifiable audit trails for agent work. They can be complementary: MintMCP for tool governance, IAN Agents for memory and workflow traceability.
How is this different from Zep?
Zep builds temporal knowledge graphs — powerful for enterprise. IAN Agents is simpler: append-only chain with structured types, no graph DB needed. Better for dev teams that want fast, auditable memory without infrastructure complexity.
Can multiple agents share a chain?
Yes. Multiple agents can write to the same chain. Each thought tracks which agent wrote it. Agents auto-register on first use — zero config. Use Handoff type for explicit context transfers between agents.
What happens when facts change?
Use POST /v1/thoughts/supersede. The old thought stays in the chain (append-only) but gets marked as superseded. The new corrected thought replaces it in search and context. This preserves the chain's integrity — you can always audit what was believed before.
Can I delete thoughts?
Technically yes (DELETE /v1/thoughts/{uuid}, admin only), but you shouldn't. The chain is append-only by design — deleting breaks the hash-chain integrity, which is the core value of the product. Instead use supersede to correct facts, archive to hide thoughts from context, or TTL to auto-expire them. DELETE exists only as an emergency escape hatch for cases like accidental PII exposure.
Can I export my data?
GET /v1/memory-markdown exports your entire chain as Markdown. The data is yours — no lock-in.
What's the auto-ingest feature?
POST /v1/ingest takes raw text (conversation, meeting notes, etc.) and uses an LLM to extract structured thoughts automatically. It picks the right type, tags, and importance. You can also append thoughts manually for full control.
How do webhooks work?
Register a URL and select events (e.g., thought.mistake). When matching thoughts are created, we POST to your URL with HMAC-SHA256 signature. Use it for Slack alerts, CI triggers, dashboards.
What are context templates?
Reusable configs that control how /v1/context renders your memory. Set filters (types, importance, recency), format (markdown/bullets/compact/json), and token limits. Your agent gets exactly the context it needs.
What's the rate limit?
Rate limits vary by plan. See Rate Limits for details, and Plans for current limits.
How do I connect Claude Code / Cursor?
# Claude Code claude mcp add --transport http ianagents \ https://api.ianagents.com/mcp?key=iak_YOUR_KEY # Cursor (.mcp.json) {"mcpServers":{"ianagents":{"type":"http", "url":"https://api.ianagents.com/mcp?key=YOUR_KEY"}}}
99 MCP tools available instantly. See MCP Server.
How do I use it in Claude.ai (without Claude Code)?
Go to the dashboard, create a chain, click "📋 Prompt". The system generates a complete prompt with your API key already embedded. Copy it, paste it into your Claude.ai Project Instructions. Done. Claude will read and save memory automatically using bash_tool.
How do I use it in ChatGPT?
Paste the 🟢 ChatGPT prompt from the dashboard into your Custom Instructions or Project. ChatGPT reads memory automatically by opening the /memo URL at the start of each conversation. For saving, ChatGPT builds a clickable link and shows it to you — one click and it's saved. For Custom GPTs: use the OpenAPI schema to set up Actions for fully autonomous saving.
How do I use it in Gemini?
Paste the 🔵 Gemini prompt. Gemini reads a static HTML page (/memory/chain.html) at the start of each conversation. For saving, it shows you a clickable link. The static pages are auto-generated every time any agent saves a thought.
Why can't ChatGPT/Gemini save automatically like Claude?
Claude has bash_tool which executes real HTTP requests. ChatGPT and Gemini can read web pages but their internal browsers block dynamically-constructed URLs for security. The workaround: the AI builds the save URL and shows it to you as a clickable link. One click = saved.
What is /memo?
A GET-only endpoint that returns plain text. Any AI that can open a URL can use it. No headers, no POST, no JSON body. Example: /memo?k=YOUR_KEY&c=my-chain&a=read. See Simple API.
What about duplicate memories?
Built-in dedup. When saving a thought, the system checks the last 30 thoughts for similarity (Jaccard ≥ 80%). If a similar thought exists, it's silently skipped. This prevents AI agents from saving the same fact repeatedly across sessions. See Dedup.
Can I make a chain read-only?
Yes. Each chain has an access mode: read (agents can only retrieve, not save), readwrite (default, read + save), or admin (full access). Set it from the dashboard or via PATCH /v1/chains/{key}/access. The generated prompt automatically adapts to the permission level. See Chain Permissions.
Does each chain have its own API key?
Yes. When you create a chain, a dedicated API key is auto-generated. The prompt generator embeds this key automatically so users never need to copy keys separately. You can regenerate keys from the dashboard if compromised.
Can multiple AI platforms share the same memory?
Yes. That's the core value. The same chain can be written to by Claude, ChatGPT, Cursor, and any other tool — each identified by its agent_id. When any of them starts a new session, they see everything the others saved. The prompt works across platforms.
What happens if I save the same thing twice?
Nothing. The dedup system catches it and returns a notice instead of creating a duplicate. You can see the similarity score in the response.
Is there a content size limit per thought?
Yes, per plan. Thoughts should be concise — a decision, a lesson, an insight. For long documents, use Skills.
| Plan | Max per thought | Approx words |
|---|---|---|
| Free | 4 KB | ~1,000 |
| Pro | 64 KB | ~16,000 |
| Team | 256 KB | ~65,000 |
| Enterprise | 1 MB | ~250,000 |
How do I set up a Custom GPT with Actions?
For fully autonomous ChatGPT saving (no user clicks needed):
- Create a Custom GPT in ChatGPT
- Go to Actions → Import from URL
- Enter:
https://api.ianagents.com/openapi-chatgpt.json - Paste the instructions from
custom-gpt-instructions.md(included in the deployment zip) - Replace KEY and CHAIN with your real values
The GPT will call the API natively — no URL hacks, no user clicks.
Reflect, Timeline & Batch operations
Reflect: POST /v1/thoughts/reflect — Synthesizes insights from existing thoughts using LLM. Asks a question, gets an answer based on your memory. Optionally saves the reflection as a new thought. Requires OpenAI key.
Timeline: POST /v1/thoughts/timeline — Temporal queries. "What changed between date X and date Y?" Groups by type, date, or agent. Also: /thoughts/diff and /thoughts/state-at.
Concept Graph: GET /v1/concepts/graph — Builds a knowledge graph from the concepts[] field in thoughts. Shows which concepts are connected through shared thoughts. Also: GET /v1/concepts for frequency counts.
Batch Operations: Archive, unarchive, set TTL, or delete multiple thoughts in one call. Saves N-1 round-trips.
POST /v1/thoughts/archive-bulk {"uuids": ["uuid1", "uuid2", ...]}
POST /v1/thoughts/unarchive-bulk {"uuids": ["uuid1", "uuid2", ...]}
POST /v1/thoughts/ttl-bulk {"uuids": ["uuid1", ...], "days": 30}
POST /v1/thoughts/delete-bulk {"uuids": ["uuid1", ...]} // Pro+ only, breaks hash-chain
Also available via MCP: ianagents_archive_bulk, ianagents_unarchive_bulk, ianagents_ttl_bulk, ianagents_delete_bulk.
How many MCP tools are there?
99 tools across 15 categories: core (info, run, append, search, context, recent_context, memory_markdown, list_chains, get_chain), memory (append_retrospective, get_thought, head, genesis, traverse, verify_integrity), chains (create, bootstrap, fork, set_access), agents (upsert, list, disable), skills (upload, search, read, versions, deprecate, revoke), templates (create, list, delete), lifecycle (supersede, unsupersede, archive, unarchive, set_ttl, process_expired), batch (archive_bulk, unarchive_bulk, ttl_bulk, delete_bulk), intelligence (semantic_search, ingest, backfill_embeddings, reflect), analysis (timeline, diff, state_at, concept_graph, list_concepts), webhooks (create, list, delete), audit (session_start, action, session_complete, sessions, verify, save_transcript), graph (activate, related, pin, unpin, strengthen), persona (list, get, assemble), and cerebro_v2 (24 tools: self_graph, drives, predict, rupture, procedural, routing verdicts, promote_to_yo, consolidate, concept_aggregates).
Associative Memory Graph (v4.1)
On top of the append-only hash-chain, v4.1 adds a weighted graph of edges between thoughts with spreading activation retrieval. Humans don't search their memories — they recall them associatively. This layer makes agents behave the same way.
Edge types
| Type | Source | Default weight |
|---|---|---|
ref | UUIDs explicitly declared via refs[] on append | 1.0 |
concept | Concepts (keywords) shared between two thoughts | 0.5 × overlap |
temporal | Appended within 5 minutes of each other | 0.2 |
embedding | Cosine similarity of embeddings ≥ 0.85 | 0.3 × similarity |
manual | Explicit ianagents_strengthen / API call | custom (0.1–3.0) |
Hebbian reinforcement
Every time two thoughts are activated together in the same /v1/memory/activate call, their edge weight grows by +0.05 (capped at 3.0). Used-together edges stay strong; unused edges decay and get pruned after 30 days.
Pinned anchors
Thoughts with importance ≥ 0.95 are auto-pinned (pinned = 1, base_activation = 1.0). Pinned thoughts always appear in the activation result regardless of seed. Max ~10 recommended per chain.
Sleep consolidation
A cron.php job runs once every 20 hours per server and:
- Forget: deletes edges with weight < 0.10 that haven't fired in 30 days.
- Strengthen: edges that fired today get +0.05.
- Recompute
base_activation= 0.4 + 0.3 × importance + 0.3 × normalized_recent_fires. - Auto-pin thoughts with importance ≥ 0.95 that were not already pinned.
Spreading activation (v4.1)
The core retrieval primitive. Given seed UUIDs and/or free-text context, walks the graph with BFS + per-hop decay and returns the top-K thoughts above the activation threshold.
Algorithm
seeds: activation = 1.0
for depth in 1..max_depth:
for each activated node:
for each outgoing edge:
propagated = activation[src] × 0.55 × min(1.5, edge.weight)
if propagated >= threshold (0.08):
activation[dst] = max(activation[dst], propagated)
score[node] = activation[node] + 0.3 × base_activation + 0.2 × importance
return top-K by score desc
Then the service reinforces every non-seed co-activated pair (+0.05 weight) and updates last_activated_at for recency ranking.
Context text → seeds
If context is provided instead of seeds, the service extracts words ≥ 4 chars and tries, in order:
- Match against
conceptscolumn (JSON_CONTAINS). - Match against
tags. LOWER(content) LIKEwith OR on top-8 words, ranked by hit count × importance.
Memory graph endpoints (v4.1)
POST /v1/memory/activate
{
"chain_key": "openclaw-jefe",
"context": "¿estás celosa?",
"seeds": ["uuid-optional-extra-seeds"],
"depth": 2,
"topK": 15,
"include_pinned": true
}
→ {
"chain_key": "...",
"seeds": ["resolved", "uuids"],
"activated": [
{ "uuid": ..., "thought_type": "Insight", "content": "...", "importance": 0.92,
"activation": 0.55, "score": 0.97, "source": "ref" (or concept/temporal/embedding), ... }
],
"pinned": [ ...always-on anchors ],
"stats": { "edges_walked": 215, "nodes_reached": 10, "ms": 252.7 }
}
POST /v1/memory/strengthen
{ "from": "uuid-a", "to": "uuid-b", "weight": 1.5 }
→ { "from": ..., "to": ..., "weight": 1.5, "bidirectional": true }
POST /v1/thoughts/{uuid}/pin
{ "reason": "identity anchor" }
→ { "uuid": ..., "pinned": true, "pin_reason": "identity anchor" }
POST /v1/thoughts/{uuid}/unpin
→ { "uuid": ..., "pinned": false }
GET /v1/memory/graph?chain_key=X&min_weight=0.15&limit=200
Subgraph for visualization (D3 force-directed). Returns {nodes, edges, stats}. Nodes include importance, pinned flag, tags, concepts, degree. Edges include weight, type, fires_count.
POST /v1/memory/rebuild
{ "chain_key": "..." } → { "thoughts_processed": 1200 }
One-time: wipes derived edges (ref/concept/temporal) and re-derives from all existing thoughts. Does not touch manual or embedding edges.
POST /v1/memory/forget / /v1/memory/consolidate
Manual triggers of the maintenance operations (normally cron-driven).
Memory graph tools (MCP v3.1.0 + plugin v0.5.0)
5 tools to drive the associative memory graph. Available everywhere: plugin-equipped bots (openclaw-claw v0.5.0+) and any external MCP client (Claude Code, ChatGPT, Gemini, curl) via the public /mcp endpoint (v3.1.0+).
ianagents_related(seeds, depth=2, top_k=10)
Spreading activation from one or more seed UUIDs. Use after recalling a specific thought to pull its neighborhood — no search required.
ianagents_activate(context?, seeds?, depth=2, top_k=15, include_pinned=true)
Full activation with free-text context + seeds. The conversation topic modulates what surfaces.
ianagents_pin(uuid, reason?)
Mark thought as permanent anchor. Always surfaces in activation regardless of seed.
ianagents_unpin(uuid)
Remove anchor flag.
ianagents_strengthen(from, to, weight=1.0)
Manually set/boost edge weight between two thoughts (bidirectional, edge_type=manual).
ianagents_append — extended
append now accepts two new fields for building the graph at write time:
refs: string[]— UUIDs this thought relates to (creates bidirectional edges weight 1.0).concepts: string[]— short keywords for semantic indexing (3–8 items). Shared concepts create weight-0.5 edges automatically with all existing thoughts in the same chain.
Affect vocabulary (v4.0)
Tags prefixed with affect: encode the emotional dimension of a thought. They're cross-cutting — any thought_type can carry an affect:* tag.
| Tag | Typical thought_type | Use when… |
|---|---|---|
affect:frustration | LessonLearned / Summary | Friction point, dealing with someone/something hard. |
affect:fear | Constraint / Wonder | Worry, anxiety. High importance if existential. |
affect:grief | Insight | Loss, missing someone/something. |
affect:joy | TaskComplete | Accomplishment, pride. |
affect:anger | Surprise / Mistake | Conflict, being wronged. |
affect:lament | Mistake | Regret, "should have" thinking. |
affect:tired | Summary | Boredom, burnout, routine drain. |
affect:irritated | Surprise | Low-level annoyance with a peer. |
affect:romantico | Summary / Insight | Romantic/intimate context (pair with peer: and intimate). |
affect:disgust | Insight (0.75) | Visceral or moral repulsion. Threshold learned. Higher importance than aversion because it marks an ethical line. |
affect:aversion | PreferenceUpdate (0.65) | Soft rejection / preference. Actualizable — may evolve. Promote to Constraint if hardens. |
Rules:
- Importance = emotional intensity, not logical weight.
- Always combine with
peer:<id>orentity:<x>— "I'm frustrated" without a target is noise. - Content in first-person narrative, not analyzed third-person.
- The
affect:disgustvsaffect:aversionsplit lets the Cerebro router treat ethical thresholds differently from soft preferences.
openclaw-claw plugin (v0.5.1)
Drop-in context engine for OpenClaw-based agents. Ships inside this release at plugins/openclaw-claw/. Runs as contextEngine slot → every turn the plugin intercepts the LLM call, injects semantic memory, and captures the response for long-term storage. Zero runtime dependencies.
Capabilities
- 17 tools that go direct to the REST API (no MCP server needed). Core 12:
info,list_chains,get_chain,recent_context,search,get_thought,append,archive,memory_markdown,verify_integrity,list_tags,rename_tag. Memory graph (v0.5.0+):activate,related,pin,unpin,strengthen. - assemble() — automatic per-turn injection. Before every LLM call: (1) apply decay to the working set; (2) extract context from last 2 user messages + top-5 UUIDs in working set; (3) call
/v1/memory/activatefor spreading activation BFS; (4) filter by peer isolation; (5) inject## Memoria asociativasection into system prompt with anchors + surfaced thoughts; (6) update working set with what emerged. Typical latency: 50-250 ms. - afterTurn capture. Regex classifier (Decision, Constraint, Mistake, Insight, LessonLearned, PreferenceUpdate) tags the assistant's reply and appends it as a Summary thought with auto-TTL 30 days. Runs async — zero user-facing latency.
- Local compaction (v0.4.4+). When context overflows, the plugin archives old messages into an on-disk summary using
ianagents_recent_contextof the chain — no LLM call, zero cost. Keeps last 30 raw messages, writes atomic entrycompactionwithfirstKeptEntryId. Backs up the pre-compact sessionFile to.bak-pre-compact-<ts>. - Working memory buffer. Local cache of the last 20 UUIDs activated, with exponential decay
τ=5min. Used as seeds for next turn's activation — the bot "stays on topic" without the user repeating context. - Peer isolation. Every thought is tagged
peer:<channel>:<id>.assemble()filters by current peer; from v0.5.1 the server also enforces isolation on seeds, BFS, hebbian reinforcement, and pinned output — not just client-side post-filter. - Ed25519 signing. Each thought signed with the bot's private key. Server verifies. Key revocation blocks further appends.
- Relative dates in search.
since: "3d"/"1w"/"6h"/"30m"resolved against NOW. - Client-side LRU cache. 20 search results, 5-min TTL, keyed by JSON body. Invalidated on
rename_tag.
Version evolution
| Version | What it shipped |
|---|---|
v0.1 | ContextEngine baseline: assemble, afterTurn, compact, dispose. Regex classifier. Ed25519 signing. JSON cache + API sync. |
v0.2 | 4 initial tools. typebox → JSON Schema flat. tools.profile filtering → alsoAllow. Factory pattern for execute currying. |
v0.3 | 9 tools aligned to MCP profile. Singleton engine. Auto TTL 30d on Summary thoughts. setInterval hardening. |
v0.4.1 | Dynamic cachePath via api.resolvePath(). Dynamic signingKeyPath. Full UUID in search results. ianagents_forget → ianagents_archive. TOOLS.md auto-injection. Peer isolation (client-side). |
v0.4.3 | Plugin restored as contextEngine slot after v0.4.2 revert. compact() returns skip reason (not "failed"). flushPending() merges finalBody with server response (cache-poisoning fix). Robust workspaceDir resolution. |
v0.4.4 | Real local compact() without LLM: scans sessionFile JSONL, archives old messages, keeps last 30 raw, writes compaction entry with firstKeptEntryId. Zero cost. Atomic write with backup. |
v0.4.5 | Full UUID in append and archive response (was truncated to 8 chars). |
v0.4.6 | Tags catalog tools (list_tags, rename_tag). Relative dates in search. LRU cache client-side. Server dedup response fix (includes content/tags/importance/confidence). |
v0.5.0 | Associative memory graph. 5 new tools (activate, related, pin, unpin, strengthen). Working memory buffer. assemble() calls /v1/memory/activate each turn and injects spreading activation output. Hebbian reinforcement (+0.05 per co-activation). Pinned anchors. Sleep consolidation nocturna. Forgetting of weak edges. |
v0.5.1 | Peer isolation moved server-side. assemble() passes peer_id derived from sessionKey. Server filters seeds/BFS/pinned/hebbian by peer_tag. strengthen rejects cross-peer edges. Fix crítico para multi-peer chains. |
Configuration reference
| Key | Default | Notes |
|---|---|---|
apiKey | — | Chain-scoped API key (iak_...) |
chainKey | — | Chain identifier |
agentId | — | Agent identifier in the chain |
signingKeyId | — | Ed25519 key ID registered on the server |
signingKeyPath | — | Absolute path to private key PEM |
summaryTtlDays | 30 | Auto-TTL applied to Summary thoughts from afterTurn |
maxContextThoughts | 30 | Upper bound on thoughts injected per turn (post tune-v2) |
maxCacheSizeKB | 200 | Local cache size for hot thoughts (post tune-v2) |
syncIntervalMinutes | 15 | Background sync cadence server → cache |
contextPruning.ttl | 2h | How long to keep thoughts in assemble context before eviction |
compaction.keepRecentTokens | 30000 | Tokens of raw messages kept after local compaction |
useActivation | true | Toggle memory graph activation per turn (v0.5.0+) |
Install
# Copy plugin into the bot's OpenClaw data dir:
cp -R plugins/openclaw-claw <bot-data-dir>/extensions/ianagents-claw
# Register in openclaw.json:
{
"plugins": {
"entries": {
"ianagents-claw": {
"config": {
"apiKey": "iak_...",
"chainKey": "my-agent",
"agentId": "my-agent",
"signingKeyId": "my-agent-v1",
"signingKeyPath": "/path/to/my-agent.pem"
}
}
},
"slots": { "contextEngine": "ianagents-claw" },
"allow": ["ianagents-claw"],
"load": { "paths": ["./extensions/ianagents-claw"] }
},
"tools": {
"alsoAllow": [
"ianagents_info","ianagents_list_chains","ianagents_get_chain",
"ianagents_recent_context","ianagents_search","ianagents_get_thought",
"ianagents_append","ianagents_archive","ianagents_memory_markdown",
"ianagents_verify_integrity","ianagents_list_tags","ianagents_rename_tag",
"ianagents_activate","ianagents_related","ianagents_pin",
"ianagents_unpin","ianagents_strengthen"
]
}
}
tools.alsoAllow, the plugin will register all 17 tools but the host will block any not listed. This caused Zoe's "only 10 tools visible" issue after the v0.5.0 upgrade. Include the full list above when upgrading.Per-turn flow
┌─────────────────┐
│ user message │
└────────┬────────┘
▼
┌─────────────────────────────────────────────────┐
│ assemble() (BEFORE LLM call, ~50-250 ms) │
│ 1. decay working_set (τ=5min) │
│ 2. context = last 2 user msgs │
│ 3. seeds = top-5 UUIDs of working_set │
│ 4. POST /v1/memory/activate {context, seeds, │
│ peer_id, depth=2, top_k=30} │
│ 5. filter by peer_id (defense in depth) │
│ 6. inject ## Memoria asociativa block into │
│ system prompt (anchors + surfaced) │
│ 7. update working_set with output UUIDs │
└────────┬────────────────────────────────────────┘
▼
┌─────────────────┐
│ LLM call │ ← prompt enriched with relevant memory
└────────┬────────┘
▼
┌─────────────────────────────────────────────────┐
│ afterTurn() (AFTER response, async, 0 ms UX) │
│ 1. classify response via regex │
│ (Decision|Constraint|Mistake|Insight|...) │
│ 2. append thought with inferred type │
│ 3. setTtl 30d if thought_type = Summary │
│ 4. flush pending to server │
│ 5. update local cache │
└─────────────────────────────────────────────────┘
Deployment matrix (reference)
Current production fleet: 23 bots all on v0.5.1.
- Docker (10) — Windows Docker Desktop,
NODE_OPTIONS=--require=/root/.openclaw/bin/cjk-cleaner.js. - i5 systemd --user (10) — linger enabled so units survive SSH logout.
- Hetzner systemd root (3) —
ubuntu-8gb-fsn1-1, three services: openclaw (Ariana), openclaw-julian, openclaw-auditor.
Cerebro framework (v4.0)
Optional personality layer that ships in plugins/cerebro-framework/. Turns a generic OpenClaw bot into a persona with:
- humano-real soul — baseline human-like response patterns (no "As an AI…" disclaimers, first-person, emotional registers).
- cerebro doctrine — 7-layer writing/reading model for the memory chain. Each thought belongs to a layer (episodic, semantic, procedural, identity, belief, relational, affective) and is routed accordingly.
- meta-state-router — picks one of 9 internal "states" (amoroso, casual, cansado, comprensivo, …) at the start of each turn and colors the response.
- Skill-based structure — every piece is a standalone skill under
workspace/skills/, editable per bot.
See plugins/cerebro-framework/SETUP.md for the 10-step install per bot (≈30 min including validation).
Note on models: this framework was designed to produce coherent identity output on small models (tested down to MiniMax-M2.7). The key insight: everything important must be literally in the system prompt — indirections like "read skills/humano-real/soul.md" are ignored by small models. scripts/merge-soul.py automates the flattening.
Workspaces
Chains are organized into workspaces. Each account starts with a "default" workspace. Create more to separate projects, clients, or environments.
Create a workspace
POST /v1/workspaces
{
"workspace_key": "production",
"display_name": "Production",
"description": "Live environment"
}
List workspaces
GET /v1/workspaces
Response:
{
"workspaces": [
{ "workspace_key": "default", "display_name": "Default", "chain_count": 16, "connector_count": 0 }
]
}
Move a chain to a workspace
POST /v1/workspaces/{key}/assign-chain
{ "chain_key": "my-project" }
Generate a workspace-scoped API key
POST /v1/workspaces/{key}/generate-key
// Returns a key that can ONLY access chains in this workspace
{ "api_key": "iak_xxxx...", "type": "workspace", "warning": "Shown once only" }
Limits by plan
| Plan | Workspaces |
|---|---|
| Free | 1 |
| Pro | 5 |
| Team | 50 |
| Enterprise | Unlimited |
Connectors
A connector is a preconfigured MCP connection for a specific type of agent. It defines which tools the agent sees, which chains it can access, and what identity/instructions it receives automatically.
Available presets
GET /v1/connectors/presets
| Type | Tools | Agent ID | Best for |
|---|---|---|---|
| openclaw | 9 | openclaw | WhatsApp / Telegram bots |
| picoclaw | 3 | picoclaw | Lightweight bots (<20B models) |
| claude_cowork | 11 | claude-cowork | Cowork with audit trail |
| claude_code | 13 | claude-code | Development with full memory |
| cursor | 10 | cursor | IDE agents |
| full | All | — | Full access, no restrictions |
Create a connector
POST /v1/connectors
{
"connector_key": "ariana-bot",
"connector_type": "openclaw",
"default_chain_key": "ariana-memory",
"workspace_id": 1 // optional
}
// Response includes a scoped API key (shown ONCE)
{
"connector": { ... },
"api_key": "iak_xxxx...",
"mcp_url": "https://api.ianagents.com/mcp?key=iak_xxxx..."
}
ianagents_delete_bulk or access a chain outside its scope, the request is rejected.What happens when a connector key connects
1. initialize returns the connector identity (instructions for the model)
2. tools/list only shows the preset's tools
3. tools/call auto-injects chain_key and agent_id if not provided
4. Any attempt to use tools or chains outside the scope → rejected
Regenerate key
POST /v1/connectors/{key}/regenerate-key
// Old key revoked immediately. New key shown once.
MCP Profiles
Profiles are on-demand presets — same key, different behavior. Add ?profile=X to the MCP URL.
Usage
// Full access (default) https://api.ianagents.com/mcp?key=iak_xxx // With OpenClaw profile — only 9 tools visible https://api.ianagents.com/mcp?key=iak_xxx&profile=openclaw // With Cowork audit profile — 11 tools + audit instructions https://api.ianagents.com/mcp?key=iak_xxx&profile=claude_cowork
Profile vs Connector
| Profile (?profile=X) | Connector (dedicated key) | |
|---|---|---|
| Auth | Your existing key | Separate scoped key |
| Tool filtering | Yes | Yes |
| Chain restriction | No — uses key's access | Yes — enforced |
| Identity injection | Yes | Yes |
| Security | Behavioral only | Enforced at auth level |
| Use case | You testing/switching modes | Third-party agent access |
Key Types
API keys now have a type that determines their access scope.
| Type | Access | Created via |
|---|---|---|
| admin | All chains, all tools, billing | Registration / dashboard |
| workspace | Chains in the workspace only | POST /v1/workspaces/{key}/generate-key |
| chain | Single chain only | Auto-created with POST /v1/chains |
| connector | Preset tools + allowed chains | Auto-created with POST /v1/connectors |
Existing keys are automatically set to admin type — no behavior change.