Getting started
Jennah provides persistent memory infrastructure for AI agents, combining semantic recall, knowledge graphs, and execution history behind a unified API. This guide covers account setup, CLI configuration, memory architecture, and running the memchat reference implementation.
Memory architecture: substrate and cognition
Jennah exposes two API surfaces for managing agent memory:
- Substrate (
memory:queryandmemory:commit): The direct, low-latency data plane. Callers explicitly manage and query vector chunks, knowledge graph nodes and edges, execution log steps, and metadata. Substrate operations execute deterministically within milliseconds, giving callers full control over record IDs, chunk boundaries, and supersession lifecycles. Use the substrate when an agent framework already implements chunking and entity extraction, or when deterministic control over stored records is required. - Cognition / Forming (
memory:form): The automated extraction and reconciliation layer. Callers submit raw conversation turns and tool traces. Jennah uses model inference to extract candidate facts and relationships, reconciles them against existing workspace memory to detect contradictions and temporal supersessions, and commits the resulting mutations to the substrate. Processing requires several seconds due to inference, removing the need for external extraction and reconciliation pipelines.
| Dimension | Substrate (memory:query / memory:commit) |
Cognition (memory:form) |
|---|---|---|
| Input | Pre-chunked vectors, graph nodes/edges, log steps | Raw conversation turns and tool traces |
| Extraction | Client-managed chunking and entity structuring | Automated LLM fact and relationship extraction |
| Reconciliation | Client-specified supersessions | Automated contradiction detection and supersession |
| Latency | Milliseconds | Seconds (model inference) |
| Identifiers | Client-provided IDs | Deterministic, content-derived IDs |
| Use cases | Structured ingestion, custom pipelines, low write latency | Automated conversation memory without custom pipelines |
Both surfaces target the same storage engine and enforce identical enterprise and workspace isolation boundaries. The cognition engine writes through substrate primitives, allowing manual commits and automated formation to coexist within the same workspace.
1. Authentication and enterprise setup
Access to Jennah requires an enterprise account and an API key. Initial sign-in automatically provisions an enterprise organization, assigning the authenticated user as the root administrator. Teammates can be invited subsequently.
- Authenticate with Google or GitHub.
- An enterprise workspace is provisioned automatically upon initial sign-in.
- New enterprises start with a 30-day trial at Pro allowances. When the trial ends, the enterprise continues on the Free plan: nothing is paused and existing memory stays in place.
Plans and limits
Every allowance applies to the whole enterprise, not to a key or a member. Formation allowances are counted in formation units.
| Free | Trial (30 days) | Pro | Scale | Team | |
|---|---|---|---|---|---|
| Formation units | 10 per UTC day | 550 for the whole trial | 550 / month | 1,800 / month | 6,500 / month |
| Formation overage | not available | not available | on AWS Marketplace plans, up to a limit you set | on AWS Marketplace plans, up to a limit you set | on AWS Marketplace plans, up to a limit you set |
| Formations in progress | 1 | at least 8 | at least 8 | at least 16 | at least 32 |
| Largest formation submission | 8,192 tokens | 16,384 tokens | 16,384 tokens | 16,384 tokens | 16,384 tokens |
| Agent workspaces / subject scopes | 5 / 5 | 25 / 25 | 1,000 / 100,000 | 5,000 / 500,000 | 25,000 / unlimited |
| Chunks per workspace or scope | 25,000 | 100,000 | 100,000 | 250,000 | 500,000 |
| Formation output tokens | 50,000 per UTC day | no daily limit | no daily limit | no daily limit | no daily limit |
| Memory and data queries | 1,000 per UTC day, 5 per second | 20,000 per UTC day, 20 per second | no daily limit | no daily limit | no daily limit |
| Memory writes | 1,000 items per UTC day, 2 per second | 50,000 items per UTC day, 20 per second | no daily limit | no daily limit | no daily limit |
| Largest memory item | 8,192 bytes (log steps 32,768) | no limit | no limit | no limit | no limit |
| Application datastore | not included | included | included | included | included |
| API keys / members | 5 / 5 | 5 / 5 | 25 / 25 | 50 / 50 | 100 / 100 |
On Free:
- A request past a limit is refused with
RESOURCE_EXHAUSTEDand a message naming the limit and when it resets. Nothing is charged as overage. - Formation runs at the model's default reasoning level, so extracted memory can differ from what the same conversation produced during the trial.
- Formations run one at a time. A formation sent while another is in progress can be refused with
RESOURCE_EXHAUSTEDand a retry delay. The client SDKs do not retry this automatically, so retry it yourself once the delay has passed. - Each formation model call may produce up to 32,768 output tokens, the model's reasoning included, and formations may produce 50,000 output tokens a day in total. A formation that reaches what is left of the day's output stops, writes nothing, and still uses its formation units; the next one is refused until the allowance resets.
- Memory writes (
memory:commit, and superseding a chunk or an edge) count one item for each chunk, node, edge, log step, and supersession a request carries, including rewrites of items that already exist. A request that would pass the day's allowance is refused whole. The memory a formation writes does not count. - A chunk's raw content, a node's label and properties, an edge's relationship type and properties, and their metadata may total at most 8,192 bytes per item; a precomputed embedding does not count. A log step's thought process, tool used, tool input, and tool output, with its metadata, may total at most 32,768 bytes. A request carrying a larger item is refused whole with
INVALID_ARGUMENTnaming the item. - Each agent workspace and subject scope may hold at most 25,000 chunks, superseded chunks included, so a Free enterprise holds at most 250,000. A write that would add chunks past a scope's ceiling is refused whole with
RESOURCE_EXHAUSTEDnaming the scope, its current count and the limit; rewriting chunks the scope already holds is not refused. See the per-scope volume ceiling. - Inspecting memory is limited by the per-second rate but does not count against the daily query allowance, and the queries a formation makes on its own do not count either.
- Datasets created during the trial can still be read, queried, and deleted, but not written to, and no new datasets can be created.
- A Free enterprise with no activity for 90 days is permanently deleted, with every agent workspace, its memory, and its datasets. Owners and administrators are emailed 30 days and 7 days before, and any use of the account (a sign-in, or any request with one of its API keys) cancels the deletion.
Generate an API key
API keys authenticate requests from the CLI, client SDKs, and agent runtimes. Keys are scoped to the active enterprise. Generating an API key requires the iam.apikeys:create permission, held by the root and admin roles or granted through a custom role.
A new key has no access to agent workspaces by default. The examples below grant access to demo.* because the demo agents create workspaces under that prefix.
Navigate to Settings → API keys, enter a label (such as memchat), enter demo.* under Agent access, optionally select permission scopes, then select Create key and copy the generated secret key.
After installing the CLI and signing in with jnh login:
Creating an API key requires an interactive login. If JENNAH_API_KEY is set in your environment, unset it first.
The generated secret key (prefixed with jennah_sk_) is displayed only once upon creation.
Secret key retention
Jennah stores only a one-way cryptographic hash of the API key. If the secret key is lost, the key must be revoked and recreated.
2. Install the CLI
jnh supports browser-based OAuth (Google and GitHub) and device-code flows for headless or SSH sessions, communicating over standard HTTP/JSON:
Authentication and basic usage
Run the following commands to authenticate and verify access:
jnh version
# Google OAuth (default); use --provider github to switch
jnh login
# Headless/SSH: outputs a one-time verification code
jnh login --device
# Display authenticated user identity and active enterprise
jnh whoami
Authentication tokens are cached locally and refreshed automatically. Once authenticated, manage agent workspaces and inspect stored memory:
jnh agents list
jnh agents create my-agent
jnh agents memory query my-agent --text "..."
# Update jnh binary to the latest release
jnh upgrade
Version pinning and custom paths
Export JENNAH_VERSION=jnh-v1.2.3 before running the installation script to pin a specific release version, or set JENNAH_INSTALL_DIR to specify a custom target directory.
Upgrading Homebrew installations
When jnh is installed via Homebrew, use brew upgrade jnh rather than jnh upgrade so Homebrew tracks updates.
3. Reference implementation: memchat
memchat is a reference CLI application demonstrating persistent agent memory. It is available in three implementations with the same behavior: a Python version built on the Python SDK, a TypeScript version built on the TypeScript SDK (both over gRPC, authenticated with an API key or a jnh login session), and a Go version that calls the HTTP/JSON API with an API key (jennah_sk_).
By default, memchat uses the cognition layer (memory:form), submitting raw conversation exchanges to Jennah for automated fact extraction, relationship mapping, and contradiction reconciliation. The local chat model only generates conversational responses.
Passing the --authored flag (-authored in the Go version) switches execution to the substrate layer (memory:commit). In this mode, the client performs local fact extraction via model tool calling (remember_fact) and explicitly commits graph nodes, edges, and vector records.
Prerequisites
- A valid Jennah API key (from Step 1) on an active enterprise. The Python and TypeScript versions also accept a
jnh loginsession. - Access to an LLM provider:
- Anthropic:
ANTHROPIC_API_KEY - Claude on Amazon Bedrock: an AWS profile allowed to call
bedrock:InvokeModelon theglobal.anthropic.claude-sonnet-5-5inference profile - Google Gemini via Vertex AI: Google Cloud project configured with Application Default Credentials (
gcloud auth application-default login) - Google Gemini via AI Studio:
GEMINI_API_KEY(orGOOGLE_API_KEY)
- Anthropic:
- Python 3.10 or later, Node.js 22.12 or later, or Go 1.21 or later.
Setup and execution
Install or build memchat, then set your Jennah API key:
git clone https://github.com/nightblue-io/jennah-memchat-ts/
cd jennah-memchat-ts/
# Install dependencies and build
npm install
# Put memchat-ts on your PATH
npm install -g .
# API key from Step 1
export JENNAH_API_KEY=jennah_sk_...
To try it without cloning, npx github:nightblue-io/jennah-memchat-ts runs it directly.
Configure one model provider:
# Provider option A: Anthropic
export ANTHROPIC_API_KEY=sk-ant-...
# Provider option B: Claude on Amazon Bedrock
# Nothing to export; when you run it, add:
# --provider bedrock --aws-profile my-profile
# Provider option C: Gemini via Vertex AI (GCP project with ADC)
gcloud auth application-default login
export GOOGLE_GENAI_USE_VERTEXAI=true
export GOOGLE_CLOUD_PROJECT=my-gcp-project
# Optional; defaults to "global"
export GOOGLE_CLOUD_LOCATION=us-central1
# Provider option D: Gemini via Google AI Studio
export GEMINI_API_KEY=... # or GOOGLE_API_KEY
Then run it:
The --provider auto flag (default) selects Anthropic when ANTHROPIC_API_KEY is set, falling back to Gemini if Google Cloud or Gemini credentials are detected. It never selects Bedrock. Provider selection can be forced using --provider anthropic, --provider bedrock, or --provider gemini. With Bedrock, --aws-region defaults to ap-northeast-1, and --aws-profile names the AWS profile that signs requests. Use it rather than AWS_PROFILE: when AWS_ACCESS_KEY_ID is also exported, it silently takes precedence over AWS_PROFILE, and the calls run in that key's account instead. At startup, the application logs the active model configuration (for example, chat model: anthropic/claude-sonnet-5-5 or chat model: bedrock/global.anthropic.claude-sonnet-5-5). The TypeScript version's command is memchat-ts, with the same flags. The Go version takes the same flags with a single dash (-provider, -verbose, -authored).
Memory formation trace
Running with --verbose outputs the detailed formation receipt for each dialogue turn, showing extracted candidate records and reconciliation decisions:
you> Hi, I'm Kenji and I lead the platform team at Nightblue.
I live in Sapporo.
memo> Hi Kenji, great to meet you! Leading the platform team at
Nightblue from Sapporo sounds like an exciting setup.
forming memory from 2 turn(s), key frm_sess_0c0409bf_1 ...
new Kenji leads the platform team at Nightblue.
new Kenji lives in Sapporo.
new Kenji works at Nightblue
new Kenji lives in Sapporo
formed: log=1 vec=2(+0 superseded) nodes=4 edges=2(+0 superseded)
In this exchange, the input produces candidates across two storage primitives: - Facts: Stored as text chunks in vector index space for semantic recall. - Relationships: Stored as nodes and directed edges in the knowledge graph.
A single statement can yield both fact chunks and graph relationships. Each candidate receives an explicit evaluation decision: new, revised, known, or rejected. Without --verbose, the interface outputs a summary count (for example, [formed: 4 new]).
Because formation runs model inference and candidate retrieval prior to committing, writes require several seconds to complete, compared to millisecond execution times for direct memory:commit operations. See Candidate decisions for the complete decision taxonomy.
Cross-session recall and provenance
Terminating the process (/exit or Ctrl+D) and restarting it demonstrates persistent memory across sessions. State persistence is maintained by preserving agent_instance_id in memchat-state.json (removing this file resets state to an uninitialized agent instance):
you> recalled 2 fact(s), 2 past snippet(s):
· Kenji works at Nightblue
· Kenji lives in Sapporo
~ Kenji lives in Sapporo. [formed by frm_19fb..., turn(s) 0]
The [formed by ...] annotation indicates record provenance. Memory produced through memory:form attaches reserved, immutable jennah.* metadata attributes identifying the originating formation ID and conversation turn index. These system attributes cannot be overwritten by client callers. See Provenance.
Contradiction reconciliation and temporal history
When new input contradicts established records, the reconciliation pipeline marks the earlier assertion as superseded (retired) and records the updated fact:
you> Actually I moved to Tokyo last month.
known Kenji leads the platform team at Nightblue.
revised Kenji lives in Tokyo, having moved there from Sapporo.
formed: log=1 vec=0(+1 superseded) nodes=0 edges=0(+0 superseded)
[memory] 1 earlier assertion(s) retired by a correction in this turn
During reconciliation, a revised decision records the ID of the assertion it supersedes (for example, (retired fct_8b2bb70a...)), known identifies redundant existing data, and rejected records any proposed candidate discarded during validation, along with the reason.
Superseded records are retired rather than permanently deleted. Each record maintains an active validity window; superseding a record closes its validity timestamp range while opening a new validity window for the replacement.
Standard queries return only currently valid records, so asking where Kenji lives now matches the Tokyo record. To see the retired one, query as of an instant before the correction; the table then shows each match's validity window:
jnh scopes memory query <agent-id> --limit 1 \
--text "where does Kenji live" --as-of-valid 2026-09-01 \
--fields content,validAt,invalidAt
CONTENT VALID FROM VALID TO
Kenji lives in Sapporo. 2026-08-20T03:12:09Z 2026-10-07T02:41:55Z
as of valid 2026-09-01T14:59:59.999999999Z
A match with a VALID TO has been retired since the instant you asked about. memory:query requests accept the same point-in-time and historical retrieval parameters. See Temporal memory.
Substrate direct mode
Running memchat with the --authored flag bypasses automatic cognition and directly exercises the substrate layer. The application uses tool calling (remember_fact) to perform local extraction, constructs graph nodes, edges, and vectors with client-managed identifiers, and commits records using memory:commit:
stored fact: user plays shakuhachi
stored fact: user has teacher Watanabe-sensei
committed: log=1 vec=1 nodes=2 edges=2 @ 2026-09-22T03:35:47Z
Substrate writes execute with millisecond latency and deterministic record control, placing responsibility for entity extraction, normalization, and supersession logic on the client application.
Schema alignment with vocabularies
Without schema constraints, formation produces ad-hoc entity names and relation labels based on model completion. Declaring a vocabulary injects structured entity classes and relationship definitions into the extraction prompt. Vocabularies guide extraction schemas without strictly rejecting out-of-vocabulary entities.
Declaring a vocabulary requires administrative credentials (such as an authenticated CLI session). To configure a workspace with a predefined vocabulary and test it with memchat (the example vocabulary.yaml ships in the Go repository and works with every version):
jnh agents create demo.memchat_vocab --wait
jnh vocabulary declare --scope demo.memchat_vocab \
--from-file vocabulary.yaml
memchat --verbose --agent demo.memchat_vocab
# TypeScript: memchat-ts --verbose --agent demo.memchat_vocab
# Go: ./memchat -verbose -agent demo.memchat_vocab
This vocabulary.yaml specifies 6 entity classes and 9 relationship types. Applying this configuration changes graph topology during extraction:
| Metric | Without vocabulary | With vocabulary.yaml |
|---|---|---|
| Nodes | 5 | 6 (identifies platform team) |
| Edges | 4 | 6 (identifies LEADS and PART_OF) |
| Classifications | None | All nodes classified |
Without a vocabulary, the statement "I lead the platform team at Nightblue" collapses to Kenji -WORKS_AT-> Nightblue, omitting the team entity. Declaring Team as a distinct class allows the extraction pipeline to preserve intermediate organizational entities and their relations:
Subsequent graph queries can filter traversals and lookups by assigned entity class (for example, Person, Organization, Team, Place, Interest).
Vocabulary read permissions
Printing the active vocabulary during startup requires the agent.vocabulary:read permission on the API key. If the key lacks this permission, memchat displays an informational notice while extraction continues to apply the declared vocabulary.
Next steps
- Consult the API Reference for endpoint specifications.
- See Forming memory for details on conversation ingestion, candidate generation, and reconciliation logic.
- See Content chunking and Retrieval channels for configuring vector chunks and substrate queries.
- See Memory vocabulary for schema configuration across entity classes and relationship types.
- Review the memchat source (Python, TypeScript, Go) for implementation examples of
memory:formand directmemory:query/memory:commitoperations.