Version: 2.0.0
Last updated: 2026-05-27
Status: Active
Related: Scope Dimension Model — full specification of the four scope dimensions, storage architecture, node character model, and write pattern.
- Overview
- Transport Layer
- Request and Response Envelope
- Authentication
- Error Codes
- Context References (id Parameters)
- IAM — Roles, Capabilities, and Scopes
- Ontology Architecture
- Harmonia Workspace and Rollback Model
- Method Reference
- 10.1 System Handshake
- 10.2 Authentication
- 10.3 Memory (Atoms)
- 10.4 Links
- 10.5 Metadata
- 10.6 Aliases
- 10.7 Ontology Inspection
- 10.8 Exploration
- 10.9 Sets
- 10.10 Notes
- 10.11 JCL — Job Control
- 10.12 Contexa
- 10.13 Jataka
- 10.14 Session
- 10.15 Associate
- 10.16 Scope State
- 10.17 Log
- 10.18 Whiteboard
- 10.19 Cross-Concept Intersection
- 10.20 Cockpit (→ concept-model-spec.md §10.1)
- 10.21 Survey (→ concept-model-spec.md §10.3)
- 10.22 Delegation & Donation Sets
- JCL Security Blocklist
- MCP Portal
- Group Management API
- CLI Shorthand Reference
- Web Application Development
AKASHA is a local-first semantic memory substrate. Knowledge is stored as atoms — hashed content nodes — connected by typed, weighted links. The result is a living knowledge graph (the cortex) that can be explored, annotated, and extended programmatically.
All operations are exposed through a single JSON-RPC 2.0 interface. The same method names work identically across every transport (stdio REPL, HTTP server, MCP). The kernel never raises exceptions to callers; every response is a well-formed JSON-RPC object with either a result or an error field.
Key architectural concepts:
- Atom — the fundamental unit of memory. A SHA-256-keyed content node with optional metadata and scope labels.
- Link — a directed, typed, weighted edge between two atoms. Relation types follow a
namespace:nameconvention (e.g.,sys:is_a,emo:joy). - Scope — a string tag attached to atoms that controls visibility (e.g.,
view:user_alice,scope:sys:universal). - Session — a per-client anchor node that tracks context (last-written atom, active note, focus position).
- Cortex — the local SQLite-backed graph database for each client cell.
Start the interactive shell with:
python akasha.pyThe REPL accepts shorthand CLI commands (see Section 14) or raw JSON-RPC payloads. A background HTTP portal is also launched automatically unless --stdio is passed.
Options:
| Flag | Default | Description |
|---|---|---|
--stdio |
— | CLI only; skip web portal |
--server uvicorn |
httpd |
Use FastAPI/uvicorn instead of stdlib httpd |
--host ADDR |
127.0.0.1 |
Bind address for the web portal |
--port N |
auto (8000+) | Port for the web portal |
Single-shot headless execution (exits after the command):
python akasha.py ping
python akasha.py w "The cat sat on the mat"Content-Type: application/json
Akasha ships two HTTP portals, and their route sets are not identical:
- ASGI / uvicorn portal (
api/portals/asgi.py,--server uvicorn) — the FastAPI stack. - stdlib
httpdportal (services/http_gateway.py, the default) — pure stdlib, no FastAPI. It fronts aSplitGateway(api/portals/cell_ipc.py): reads are served from the local engine, while writes/auth/session are forwarded over a local socket to the single writer daemon (preservingTRUST_NETWORKand one signing authority for tokens).
Start in HTTP-only server mode:
python akasha.py --server uvicorn --host 0.0.0.0 --port 8080 # ASGI portal
python akasha.py --host 0.0.0.0 --port 8080 # stdlib httpd portal (default)| Route | Method | ASGI | stdlib httpd | Description |
|---|---|---|---|---|
/rpc |
POST | ✅ | — | JSON-RPC 2.0 endpoint (ASGI only) |
/api/rpc |
POST | ✅ | ✅ | JSON-RPC 2.0 endpoint (API path — both portals) |
/mcp |
POST | ✅ | ✅ | MCP over HTTP (TRUST_NETWORK) |
/api/mcp |
POST | ✅ | ✅ | MCP over HTTP (API path) |
/api/readme |
GET | ✅ | ✅ | Served README |
/health |
GET | ✅ | — | Liveness check (wraps sys.ping; ASGI only) |
/docs |
GET | ✅ | — | Swagger UI (uvicorn mode) |
/openapi.json |
GET | ✅ | — | OpenAPI schema (uvicorn mode) |
⚠️ /rpcand/healthare ASGI-only. The stdlibhttpdportal does not serve them — use/api/rpcfor JSON-RPC there. Both portals serve/mcp,/api/mcp, and/api/readme.
Both portals support guest sessions: an anonymous caller is issued a read-only guest session
whose token is a gbk: binding key (the "prove-don't-assert" default; see Section 4).
CORS headers (Access-Control-Allow-Origin: *) are set on all responses.
The MCP portal (api/portals/mcp.py) exposes a subset of kernel operations as MCP tools for AI assistant integration (e.g., Claude Desktop, a local Ollama agent). It is a stdlib implementation — MCP is just JSON-RPC 2.0 with a fixed method set, so no mcp-python-sdk dependency is required. Both transports are shipped and live: stdio (python akasha.py --mcp, TRUST_LOCAL) and HTTP (POST /mcp on ASGI / POST /api/mcp on httpd, TRUST_NETWORK). See Section 12 for the full tool catalogue.
Every request must follow the JSON-RPC 2.0 envelope:
{
"jsonrpc": "2.0",
"method": "kernel.memory.write",
"params": {
"session_token": "<client_id>",
"data": {
"text": "Hello, Akasha."
}
},
"id": "req-001"
}| Field | Type | Required | Description |
|---|---|---|---|
jsonrpc |
string | Yes | Must be "2.0" |
method |
string | Yes | Dot-separated method name |
params.session_token |
string | Yes (post-auth) | Your client_id / session token |
params.data |
object | Yes | Method-specific parameters |
id |
string or number | Yes | Request correlation ID (any value; UUID recommended) |
{
"jsonrpc": "2.0",
"result": {
"key": "a3f9...",
"status": "written"
},
"id": "req-001"
}{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "write requires 'text'"
},
"id": "req-001"
}Client Kernel
| |
|-- kernel.auth.status -----------> |
|<- {initialized, akasha_name} ---- |
| |
|-- kernel.genesis_rite ----------> | (first boot only)
|<- {status: "bound"} ------------ |
| |
|-- kernel.auth.verify -----------> |
|<- {session_token, role} --------- |
| |
|-- kernel.memory.write ----------> | (session_token in every request)
|<- {key, status} ----------------- |
The following methods are accessible without a valid session_token:
sys.pingsys.statuskernel.auth.statuskernel.auth.verify
kernel.genesis_rite is local/internal-only — it is not reachable over the network and cannot be used as a network pre-auth call.
Trust level is set by the portal that received the request, never by the client:
| Level | Set by | Meaning |
|---|---|---|
TRUST_NETWORK |
ASGI / web / CGI portals (default) | Safe default. A bare client_id is only ever an anonymous GUEST. |
TRUST_LOCAL |
Physical stdio console | OS process boundary is the gate; a bare client_id may be asserted. |
TRUST_INTERNAL |
Kernel-originated callers (JCL worker, boot loader) | A bare client_id may be asserted. |
Only TRUST_LOCAL and TRUST_INTERNAL may assert a bare client_id. Over TRUST_NETWORK a bare id is never an identity — it resolves to an anonymous GUEST.
After kernel.auth.verify succeeds, the returned session_token is a signed, expiring akt: credential (HMAC-SHA256 over client_id|role|expires|epoch|nonce) that must be passed as params.session_token in every subsequent request. It is a credential, not your username and not equal to your user_id. It carries a TTL and expires; it is also revoked when the passphrase or role changes (a per-user token-epoch bump invalidates all outstanding tokens) or when sys.session.close is called.
On a fresh installation, kernel.auth.status returns {"initialized": false}. Call kernel.genesis_rite once to register the administrator identity. After that, passphrase verification is mandatory.
Python example:
import requests
BASE = "http://localhost:8000/rpc"
def rpc(method, data=None, token="guest"):
payload = {
"jsonrpc": "2.0",
"method": method,
"params": {"session_token": token, "data": data or {}},
"id": "1"
}
return requests.post(BASE, json=payload).json()
# Check initialisation state
status = rpc("kernel.auth.status")
print(status["result"]) # {"initialized": false, "akasha_name": "AKASHA"}
# First-boot ceremony (run once)
rpc("kernel.genesis_rite", {
"akasha_name": "MyAkasha",
"user_name": "alice",
"passphrase": "s3cr3t"
})
# Authenticate
auth = rpc("kernel.auth.verify", {"user_id": "alice", "passphrase": "s3cr3t"})
token = auth["result"]["session_token"] # opaque signed credential, e.g. "akt:9f2c1a7e...c83"
role = auth["result"]["role"] # "admin"curl example:
curl -s -X POST http://localhost:8000/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","method":"kernel.auth.verify",
"params":{"session_token":"guest","data":{"user_id":"alice","passphrase":"s3cr3t"}},
"id":"auth-1"
}'| Code | Constant | Meaning |
|---|---|---|
| -32700 | Parse error | Body is not valid JSON |
| -32600 | Invalid request | JSON-RPC envelope malformed (missing jsonrpc, method, etc.) |
| -32601 | Method not found | The method string is not recognised by the kernel |
| -32602 | Invalid params | Required field missing or wrong type in data |
| -32001 | Permission denied | IAM check failed: wrong role, insufficient capability, or quota exceeded |
| -32002 | Resource not found | Atom key, alias, or job ID does not exist (or is out of scope) |
| -32003 | Permission denied | Caller lacks the required role or scope for this specific operation |
| -32000 | Internal kernel error | Unhandled exception in the cognitive engine |
When an error object is returned, result is absent. The error.data field may contain additional context in some cases.
Any parameter named id (atom key) in a method's data block supports context references — symbolic shortcuts resolved at dispatch time against the active session state and graph.
| Syntax | Resolves to |
|---|---|
$it |
The atom last explicitly written by the client (session.last_written_id) |
$0 |
The most recent user-authored atom in session history |
$1, $2, $N |
Older history entries (0 = newest) |
$0:5 |
Returns a list of the first 5 history keys (slice syntax) |
set:name |
Expands to the full list of member keys in the named set |
alias_name |
Resolved via the alias registry (e.g., "Philosophy", "emo:joy") |
@here |
Atom pinned to the current GPS position (Jataka spatial context) |
@now |
Atom pinned to the current temporal anchor |
@2026 |
Year-era anchor (chrono:year:2026) |
alias.child |
All atoms linked outward from the resolved alias |
alias.parent |
All atoms linked inward to the resolved alias |
~emo:sadness |
Closest-match tensor search (semantic gravity; planned) |
| 64-char hex | Direct key lookup; bypasses the resolver |
Context references are resolved by lib/akasha/resolver.py:ContextResolver. They respect the caller's IAM scopes: atoms out of scope are silently excluded from list results.
| Role | Value | Description |
|---|---|---|
ADMIN |
"admin" |
Full system access including DNA memory and all sessions |
LIBRARIAN |
"librarian" |
Collective knowledge editor; cannot access other users' private atoms |
GROUP_ADMIN |
"group_admin" |
Manages one group; can write group shared knowledge |
USER |
"user" |
Standard authenticated client; private scope + assigned group scopes |
GUEST |
"guest" |
Unauthenticated; read-only access to public collective knowledge |
| Capability | Granted to | Governs |
|---|---|---|
READ |
All | explore, read, link.list, dive.*, sys.history |
WRITE |
USER and above | write, define, link.create, meta.set, note.*, alias, jataka.dream |
DELETE |
USER and above | drop, set.clear, set.rm |
COLLECTIVE_WRITE |
LIBRARIAN, ADMIN | Writing to scope:sys:universal |
GROUP_MANAGE |
GROUP_ADMIN, ADMIN | Adding/removing members, granting group librarian rights |
SIMULATE |
USER and above | link.reinforce |
SYNC_PULL / SYNC_PUSH |
LIBRARIAN, ADMIN | Network synchronisation |
FEDERATE |
LIBRARIAN, ADMIN | Knowledge verification and merge |
DELEGATE |
ADMIN | Issuing tokens; cancelling others' JCL jobs |
iam.manage |
ADMIN | All user management: user.add, user.ls, user.mod, user.passwd, user.rm |
TELEMETRY |
ADMIN | Swarm intelligence telemetry |
Full specification:
docs/scope-dimension-model.md
Scope tags are namespace:value strings attached to atoms. They belong to one
of four distinct dimensions. Mixing dimensions in a single SQL query is a
security invariant violation.
| Dimension | Prefixes | Storage | Evaluated by |
|---|---|---|---|
| Dim-1 — Access control | scope:, owner:, view: |
chunk_access table |
check_chunk_access_any() |
| Dim-2 — Capability flags | role:, write:, manage: |
Session only | authorize() |
| Dim-3 — Locale preference | lang:XX (session) |
session.locale |
list_leaf(locale_codes=...) |
| Calc-Dim — Semantic dimensions | leaf:, ns:, lang:XX (atom), user sets |
collections table |
set-theory queries |
Dim-1 access scopes (quick reference):
| Scope tag | Who can read |
|---|---|
scope:sys:universal |
Everyone |
scope:sys:dna |
ADMIN only |
view:public |
Everyone (including GUEST) |
owner:user_<id> |
ADMIN + <id> |
view:user_<id> |
ADMIN + <id> |
scope:group_<g> / view:group_<g> |
Group members + GROUP_ADMIN + ADMIN |
view:admin_override |
ADMIN |
Every authenticated session's scope list is computed once at session creation by
IdentityManager.get_allowed_scopes() and cached in session.active_scopes.
Locale preference (lang:en etc.) lives separately in session.locale and is
never included in access-control SQL queries.
| Role | Max explore depth | Max nodes |
|---|---|---|
| GUEST | 2 | 50 |
| USER | 10 | 1 000 |
| GROUP_ADMIN | 15 | 2 000 |
| LIBRARIAN | 20 | 5 000 |
| ADMIN | 99 | 9 999 |
Exceeding the depth quota raises error -32001 with the message "Quota exceeded: depth N > M".
AKASHA's knowledge base has two tiers of foundational knowledge that are loaded automatically at startup.
Defined in lib/akasha/dna.py:get_primal_sequence(). These atoms are bootstrapped into scope:sys:universal before any user session exists. They encode the kernel's "birth state" — the minimum cognitive framework needed to reason about any domain:
| Namespace | Content |
|---|---|
sys: |
Topology relations: sys:is_a, sys:part_of, sys:associated_with, sys:requires, sys:causes, sys:mapped_to, sys:mapped_from |
log: |
Fuzzy logic operators: log:not, log:and, log:or, log:implies, log:iff |
geo: |
Spatiotemporal axes: geo:at (GPS pin), geo:ref (affine reference) |
chrono: |
chrono:period (temporal era pin) |
nar: |
nar:perspective (cognitive narrative filter) |
emo: |
8 primary Plutchik/Keltner emotions + 7 compound emotions (awe, nostalgia, love, guilt, curiosity, despair, contempt) |
frame: |
Epistemological frames: dialectics (thesis/antithesis/synthesis), systems (feedback loop) |
DNA atoms are tagged scope:sys:dna in addition to scope:sys:universal. Only ADMIN can read the scope:sys:dna tag directly; however, all users can access the content through scope:sys:universal.
Ontology ships as .ak files organised into per-namespace package directories under ontology/<ns>/ (e.g. ontology/base1–3/ (the base packs), ontology/art/, ontology/film/), each with a PACK.json manifest. ontology/REGISTRY.json (version 2) lists every package and its autoload flag (base1, base2, base3, nutrition, recipe, and curation autoload at startup). The obsolete flat JSON .ak format loaded by bootstrap_ontology() is no longer used.
An .ak file is a flat sequence of loader commands, not JSON. The full command vocabulary is:
| Command | Purpose |
|---|---|
def "id" "description" |
Define an atom |
ln src dst rel |
Create a typed link |
al atom_id alias |
Register an alias |
set.add name="..." id="..." |
Add an atom to a named collection |
# comment |
Comment — ignored by the loader |
def "emo:joy" "Primary Emotion: Happiness, expansion, and presence."
def "emo:sadness" "Primary Emotion: Melancholy, contraction, and memory."
ln emo:joy emo:sadness log:not
al emo:joy joy
An .ak file is loaded via the shell command run <file>, which submits a JCL job to apply the steps. All bootstrapped concepts are pinned to scope:sys:universal and view:public. Atoms are content-addressed, so reloading a file that overlaps existing content is idempotent — duplicates are silently unified and existing aliases are never overwritten.
Harmonia (lib/harmonia/engine.py:HarmoniaEngine) is the transactional motor cortex. Every operation that produces new atoms — whether triggered by Contexa NLP mapping, a Jataka dream cycle, or a JCL job — runs inside a Harmonia workspace.
begin_workspace(cortex, label)
→ tx_id = "ws:<label>:<timestamp>"
→ writes sys:workspace_info atom to cortex
execute_with_evidence(cortex, tx_id, executor, data)
→ writes sys:action_evidence atom (audit trail)
→ runs the plugin function
→ each output is stored as a "pending" atom tagged with tx_id
→ the atom key is added to the tx_id set in the DB
commit_workspace(cortex, tx_id)
→ for each pending atom in set tx_id:
- sets status = "active", removes tx_id binding
- overwrites the chunk with status = "verified"
→ workspace is marked "committed"
rollback_workspace(cortex, tx_id)
→ for each pending atom in set tx_id:
- physically deletes the chunk from the DB
→ workspace is marked "rolled_back"
→ evidence atoms (sys:action_evidence, sys:workspace_info) are retained
JCL jobs (see Section 10.11) run each job as a single Harmonia workspace. If any step fails:
- The workspace is rolled back — no orphaned atoms remain.
- A
sys:jcl_failure_logatom is written permanently (it survives rollback for auditing). - Evidence atoms from completed steps survive rollback (audit trail is preserved).
Plugins are registered with harmonia.register_plugin(name, callable). At boot, the kernel registers:
| Plugin name | Purpose |
|---|---|
nlp.extract |
Multi-locale SpaCy-based trait extraction (lib/harmonia/plugins/nlp.py). Auto-installs SpaCy via Symbiosis on first use. Degrades gracefully to regex (T1) or CJK bigrams (T0) when no model is available. |
Consciousness liveness check. No authentication required.
Request:
{
"jsonrpc": "2.0",
"method": "sys.ping",
"params": {"session_token": "guest", "data": {}},
"id": "1"
}Response:
{
"jsonrpc": "2.0",
"result": {
"status": "kernel_online",
"series": "seeds",
"timestamp": 1716600000.123
},
"id": "1"
}When called with a valid authenticated session_token, the full ConsciousnessEngine.ping() state is returned (includes active context, session metadata).
curl:
curl -s -X POST http://localhost:8000/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"sys.ping","params":{"session_token":"guest","data":{}},"id":"1"}'Returns kernel subsystem availability. No authentication required.
Response schema:
{
"result": {
"status": "online",
"series": "seeds",
"harmonia": true,
"contexa": true,
"active_sessions": 2,
"timestamp": 1716600000.0
}
}Full self-awareness pulse for the authenticated session. Returns rich session state from ConsciousnessEngine.cogito().
Requires: Authentication.
Python:
resp = rpc("sys.cogito", token=token)
print(resp["result"])First-boot ceremony. Must be called exactly once to register the system administrator. After this call, passphrase authentication is enforced for all subsequent sessions.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
akasha_name |
string | No | Name for this AKASHA instance (default: "AKASHA") |
user_name |
string | Yes | Administrator identity (client_id) |
passphrase |
string | Yes | Administrator passphrase. The client presents a SHA-256 hash; the server stores a per-user-salted PBKDF2 derivation of it and compares with hmac.compare_digest. |
Response:
{
"result": {
"status": "bound",
"akasha_name": "MyAkasha",
"admin_name": "alice"
}
}Errors:
| Code | Condition |
|---|---|
| -32602 | user_name or passphrase missing |
Returns initialisation state. No authentication required. The response contains only initialized and akasha_name; the administrator username is a login identifier and is withheld pre-authentication.
Response:
{
"result": {
"initialized": true,
"akasha_name": "MyAkasha"
}
}Verify credentials and obtain a session token.
Params (data):
| Field | Type | Required |
|---|---|---|
user_id |
string | Yes |
passphrase |
string | Yes |
Response:
{
"result": {
"status": "authenticated",
"user_id": "alice",
"session_token": "akt:9f2c1a7e...c83",
"role": "admin"
}
}Errors:
| Code | Condition |
|---|---|
| -32001 | Invalid credentials or unknown user |
| -32602 | user_id or passphrase missing |
Python:
auth = rpc("kernel.auth.verify", {"user_id": "alice", "passphrase": "s3cr3t"})
token = auth["result"]["session_token"]curl:
curl -s -X POST http://localhost:8000/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"kernel.auth.verify",
"params":{"session_token":"guest","data":{"user_id":"alice","passphrase":"s3cr3t"}},
"id":"auth"}'Write a new atom (memory node) to the cortex.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
text |
string | Yes | Content to store |
meta |
object | No | Arbitrary metadata dict attached to the atom |
public |
boolean | No | If true, adds view:public scope (default: false) |
scope |
string | No | Write destination. "universal" routes the atom to the shared nucleus DB (visible to all users). Requires LIBRARIAN or ADMIN role. Omit for private writes to the local cell. |
alias |
string | No | Alias to register for the atom when scope="universal". Ignored for private writes. |
Two write modes:
| Mode | scope value |
Destination | Visibility | Required role |
|---|---|---|---|---|
| Private | omitted | Local cell DB | Owner only | USER and above |
| Universal | "universal" |
Nucleus DB | All users | LIBRARIAN, ADMIN |
Response:
{
"result": {
"key": "a3f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
"status": "written"
}
}The returned key is a 64-character hexadecimal SHA-256 digest of the content. Akasha also sets session.last_written_id to this key, making it immediately accessible as $it.
After writing, the text is asynchronously processed by the unified post-write Weaver pipeline: protoword links are woven and NLP word decomposition is queued as a sys.weaver.decompose JCL job (non-fatal; the write succeeds regardless).
Errors:
| Code | Condition |
|---|---|
| -32602 | text is empty or missing |
Python:
resp = rpc("kernel.memory.write", {"text": "The basilica of San Vitale dates from 547 AD."}, token=token)
key = resp["result"]["key"]curl:
curl -s -X POST http://localhost:8000/rpc \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"method\":\"kernel.memory.write\",
\"params\":{\"session_token\":\"alice\",\"data\":{\"text\":\"The basilica of San Vitale dates from 547 AD.\"}},
\"id\":\"w1\"}"Create a named concept hub — a special atom that serves as a named anchor for semantic clustering. Automatically assigns the name as an alias to the new atom.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable name (becomes the alias) |
description |
string | No | Optional description text |
Response:
{
"result": {
"key": "b4c5...",
"alias": "Byzantine Architecture",
"status": "defined"
}
}The hub atom's content is formatted as [Byzantine_Architecture]\n<description> and tagged type: hub.
Python:
resp = rpc("kernel.memory.define",
{"name": "Byzantine Architecture", "description": "Architectural tradition 330–1453 AD"},
token=token)
hub_alias = resp["result"]["alias"]Read an atom by key, alias, or context reference.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | No | Key, alias, or $-reference. Defaults to $it (last written). |
Response:
{
"result": {
"key": "a3f9...",
"content": "The basilica of San Vitale dates from 547 AD.",
"meta": {"type": "hub", "name": "Byzantine Architecture"},
"aliases": ["Byzantine Architecture", "byz-arch"]
}
}Errors:
| Code | Condition |
|---|---|
| -32002 | Atom not found or out of caller's scope |
| -32602 | No id and no active session context |
Python:
# By alias
resp = rpc("kernel.memory.read", {"id": "Byzantine Architecture"}, token=token)
# By $-reference
resp = rpc("kernel.memory.read", {"id": "$it"}, token=token)
# By direct key
resp = rpc("kernel.memory.read", {"id": "a3f9b2c1..."}, token=token)Delete an atom and all its associated aliases, links, and collection memberships.
Params (data):
| Field | Type | Required |
|---|---|---|
id |
string | Yes |
Response:
{
"result": {"status": "dropped", "key": "a3f9..."}
}Errors:
| Code | Condition |
|---|---|
| -32001 | Atom is out of the caller's write scope |
| -32002 | Atom not found |
Create a directed, typed, weighted link between two atoms.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
src |
string | Yes | Source atom (key, alias, or $-ref) |
dst |
string | Yes | Destination atom (key, alias, or $-ref) |
rel |
string | Yes | Relation type. If no namespace prefix (:) is present, @ is prepended automatically. |
w |
number | No | Link weight 0.0–1.0 (default: 1.0) |
Response:
{
"result": {
"status": "linked",
"src": "a3f9...",
"dst": "b4c5...",
"rel": "sys:is_a",
"w": 1.0
}
}Python:
rpc("kernel.memory.link", {
"src": "$it",
"dst": "Byzantine Architecture",
"rel": "sys:is_a",
"w": 0.95
}, token=token)curl:
curl -s -X POST http://localhost:8000/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"kernel.memory.link",
"params":{"session_token":"alice",
"data":{"src":"$it","dst":"Byzantine Architecture","rel":"sys:is_a","w":0.95}},
"id":"ln1"}'List all links (inbound and outbound) for an atom.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | No | Target atom. Defaults to $it. |
Response:
{
"result": {
"key": "a3f9...",
"links": [
{
"direction": "out",
"rel": "sys:is_a",
"key": "b4c5...",
"preview": "Byzantine Architecture..."
},
{
"direction": "in",
"rel": "sys:associated_with",
"key": "c5d6...",
"preview": "Ravenna mosaics..."
}
]
}
}Each entry in links:
| Field | Type | Description |
|---|---|---|
direction |
"out" or "in" |
Whether the link points away from or toward the focal atom |
rel |
string | Relation type |
key |
string | The other atom's key |
preview |
string | First 60 characters of the other atom's content |
Increase the weight of an existing link by a delta.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
src |
string | Yes | Source atom |
dst |
string | Yes | Destination atom |
rel |
string | Yes | Relation type |
delta |
number | No | Weight increment (default: 0.1) |
Response:
{
"result": {
"status": "reinforced",
"src": "a3f9...",
"dst": "b4c5...",
"rel": "sys:is_a",
"w": 1.1
}
}Set or update a single metadata key on an existing atom.
Params (data):
| Field | Type | Required |
|---|---|---|
id |
string | Yes |
key |
string | Yes |
value |
any | Yes |
Response:
{
"result": {"status": "meta_updated", "key": "a3f9..."}
}Python:
rpc("meta.set", {"id": "$it", "key": "source", "value": "UNESCO World Heritage"}, token=token)Assign a human-readable alias to an atom. An atom may have multiple aliases; an alias points to exactly one key.
Params (data):
| Field | Type | Required |
|---|---|---|
id |
string | Yes |
name |
string | Yes |
Response:
{
"result": {"status": "alias_set", "alias": "San Vitale", "key": "a3f9..."}
}Return all aliases registered in the cortex.
Params (data): (none)
Response:
{
"result": {
"aliases": [
{"alias": "Byzantine Architecture", "key": "b4c5..."},
{"alias": "San Vitale", "key": "a3f9..."}
]
}
}Search aliases by SQL LIKE pattern.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
pattern |
string | Yes | SQL LIKE pattern (e.g., "emo:%", "%arch%") |
Response:
{
"result": {
"aliases": [
{"alias": "emo:awe", "key": "c5d6..."},
{"alias": "emo:joy", "key": "d6e7..."}
]
}
}Python:
resp = rpc("kernel.identity.alias.find", {"pattern": "emo:%"}, token=token)
for a in resp["result"]["aliases"]:
print(a["alias"], "→", a["key"][:12])Commands for inspecting the live ontology graph and diagnosing alias collisions. Typically used during ontology development and after loading new files.
Multi-angle dump of ontology data. Supports six modes selectable via the mode
parameter. All modes support sort and limit; additional parameters apply
per mode.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
mode |
string | No | atoms (default) | links | antonyms | aliases | sets | namespaces |
ns |
string | No | Namespace prefix filter, e.g. "word:en". Applied in atoms and aliases modes. |
rel |
string | No | Relation type filter for links mode, e.g. "sys:antonym". |
collection |
string | No | Collection name for sets mode, e.g. "ontology.narrative_typology". |
sort |
string | No | alpha (default) | count | recent |
limit |
integer | No | Max items returned. Default 500, max 5000. |
pattern |
string | No | SQL LIKE pattern for aliases mode. Overrides ns. |
Mode details:
| Mode | Returns | Typical use |
|---|---|---|
atoms |
One entry per distinct atom: primary alias + content preview | Verify all terms loaded |
links |
All semantic links: src / rel / dst / weight | Check link integrity |
antonyms |
Shortcut: links where rel=sys:antonym |
Audit antonym pair symmetry |
aliases |
All registered aliases with their keys | Find namespace collisions |
sets |
Members of a named collection | Inspect hub membership |
namespaces |
Atom count per namespace prefix | Check namespace balance |
Response (example — atoms mode):
{
"result": {
"mode": "atoms",
"count": 42,
"items": [
{"alias": "word:en:courage", "key": "a3f7c2...", "preview": "Word: courage. The ability to do something that..."},
{"alias": "word:en:fear", "key": "b8c1d5...", "preview": "Word: fear. An unpleasant emotion caused by..."}
]
}
}Response (example — namespaces mode):
{
"result": {
"mode": "namespaces",
"count": 12,
"items": [
{"ns": "word", "count": 823},
{"ns": "sys", "count": 79},
{"ns": "nar", "count": 42}
]
}
}Python:
# Dump all English vocabulary atoms
resp = rpc("onto.dump", {"mode": "atoms", "ns": "word:en", "limit": 100}, token=token)
for item in resp["result"]["items"]:
print(item["alias"], "—", item["preview"][:60])
# Check antonym pairs
resp = rpc("onto.dump", {"mode": "antonyms"}, token=token)
for link in resp["result"]["items"]:
print(f"{link['src']} ↔ {link['dst']}")
# Namespace overview
resp = rpc("onto.dump", {"mode": "namespaces"}, token=token)
for ns in resp["result"]["items"]:
print(f" {ns['ns']:20s} {ns['count']:4d} atoms")Returns the alias collision log accumulated since the last call with clear=true
(or since server start). Printed automatically at login when the log is non-empty.
Two collision event types:
| Event | Meaning | Action |
|---|---|---|
overwrite |
Canonical alias (word:en:X) rebound to a different atom |
Bug — remove duplicate definition from later-loading file |
leaf_skipped |
Bare alias (X) already claimed; new registration skipped |
Normal — first-registered wins; no action needed |
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
since |
number | No | If provided, return only entries recorded after this epoch timestamp |
limit |
integer | No | Maximum number of entries to return |
clear |
boolean | No | If true, return the log and immediately clear it (default: false) |
Response:
{
"result": {
"overwrites": 1,
"leaf_skips": 2,
"entries": [
{"event": "overwrite", "alias": "word:en:comedy", "winner": "a3f7c2...", "loser": "88ca29..."},
{"event": "leaf_skipped", "alias": "comedy", "winner": "88ca29...", "loser": "a3f7c2..."},
{"event": "leaf_skipped", "alias": "rival", "winner": "c5d8e1...", "loser": "f2a091..."}
]
}
}Python:
resp = rpc("onto.report", {"clear": True}, token=token)
r = resp["result"]
print(f"Overwrites: {r['overwrites']} Leaf skips: {r['leaf_skips']}")
for e in r["entries"]:
if e["event"] == "overwrite":
print(f" ⚠ OVERWRITE '{e['alias']}' {e['loser'][:8]}→{e['winner'][:8]}")Filter/query tool for discovering atoms in the ontology — NOT a graph traversal.
It matches atoms by namespace, set membership, meta type, or alias pattern, ANDs the
filters together, and returns a numbered flat list (for dive navigation). Use
graph.tree or dive.look to walk links; explore finds the starting atoms.
Params (data): at least one filter is required.
| Field | Type | Required | Description |
|---|---|---|---|
ns |
string | * | Namespace prefix — aliases matching ns:* (e.g. word:en) |
set |
string | * | Set membership — atoms in this named set |
type |
string | * | meta.type filter |
pat |
string | * | Alias wildcard pattern (% and _ wildcards). A positional argument (id=) falls through to pat=. |
limit |
integer | No | Max results (default: 50) |
* At least one of ns / set / type / pat must be supplied, else -32602 is returned. Multiple filters are ANDed.
Response:
{
"result": {
"atoms": [
{"key": "a3f7c2...", "alias": "word:en:courage", "preview": "Word: courage. ..."},
{"key": "b8c1d5...", "alias": "word:en:fear", "preview": "Word: fear. ..."}
],
"count": 2,
"filters": {"ns": "word:en"}
}
}Python:
# All English vocabulary atoms
resp = rpc("explore", {"ns": "word:en", "limit": 100}, token=token)
for atom in resp["result"]["atoms"]:
print(f" {atom['alias']} — {atom['preview'][:50]}")
# Alias pattern
resp = rpc("explore", {"pat": "%architecture%"}, token=token)curl:
curl -s -X POST http://localhost:8000/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"explore",
"params":{"session_token":"alice","data":{"ns":"word:en","limit":50}},
"id":"exp1"}'Render a hierarchical link-tree by BFS link-traversal from an atom, set, or namespace,
following outgoing links. (CLI shorthand: tree.)
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
target |
string | No | Root: an atom alias/key, set:<name>, or ns:<prefix>. Defaults to $it. |
depth |
integer | No | Traversal depth (default: 2, capped 1–5) |
follow |
string | No | Relation-type filter (empty = all outgoing links) |
format |
string | No | rich (default) | ascii |
concept |
string | No | yes → walk the taxonomic sys:is_a concept lattice only (delegates to the concept navigator) |
Response:
{
"result": {
"root": "b4c5...",
"max_depth": 3,
"tree": {
"key": "b4c5...",
"preview": "Byzantine Architecture",
"depth": 0,
"children": [
{
"key": "a3f9...",
"preview": "The basilica of San Vitale...",
"depth": 1,
"rel": "sys:is_a",
"children": []
}
]
}
}
}Each node in the tree:
| Field | Type |
|---|---|
key |
string |
preview |
string (first 70 chars of content) |
depth |
integer |
rel |
string (relation label on the edge to this node; absent on root) |
children |
array of nodes |
Focused single-atom view: returns the atom's content, N-D Cosmos coordinates, and a rich list of signpost links for navigation.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | No | Atom to focus on. Defaults to the session's current focus. |
time |
string | No | Temporal filter tag attached to the response as active_time metadata (display hint for UI; does not filter graph content). |
Response:
{
"result": {
"type": "atom",
"focus": {
"key": "a3f9...",
"alias": "San Vitale",
"content": "The basilica of San Vitale dates from 547 AD.",
"meta": "{\"type\": \"hub\"}",
"cosmos_nd": [0.5, 0.2, 0.8, 0.1, 0, "#a0c4ff"]
},
"signposts": [
{
"index": 0,
"key": "b4c5...",
"alias": "Byzantine Architecture",
"rel": "sys:is_a",
"direction": "out",
"w": 1.0,
"type": "explicit",
"preview": "Byzantine Architecture...",
"branches_ahead": 3,
"cosmos_nd": [0.4, 0.3, 0.9, 0.2, 1, "#00ffcc"]
}
]
}
}focus fields:
| Field | Type | Description |
|---|---|---|
key |
string | 64-char SHA-256 hex key |
alias |
string | null | Primary alias if any |
content |
string | Full atom content |
meta |
string | JSON-encoded metadata |
cosmos_nd |
array | 6-element vector [x, y, z, T, layer, color] for the Cosmos Viewer. X/Y/Z are the real semantic position — a projection of the atom's self-owned semantic_vector (near in space ⇒ near in meaning; when a learned model exists it projects onto that model's principal SVD axes, else a distance-preserving random projection). T is reserved for the chrono axis (0 until the time layer feeds it), layer is the BFS depth from the focus, color is the emotion/sense aura hex. |
Signpost fields:
| Field | Type | Description |
|---|---|---|
index |
integer | Position in the signpost list |
key |
string | Neighbor atom key |
alias |
string | null | Neighbor's primary alias |
rel |
string | Link relation type |
direction |
"out" | "in" |
Link direction relative to the focal atom |
w |
number | Link weight 0.0–1.0 |
type |
"explicit" | "magnetic" |
Whether the link was user-defined or inferred |
preview |
string | First ~30 chars of neighbor content |
branches_ahead |
integer | Number of further links on the neighbor atom |
cosmos_nd |
array | 6-element [x, y, z, T, layer, color] vector for the neighbor (see focus cosmos_nd). |
Cosmos graph payload.
dive.lookalso returns acosmosobject ({nodes, links, axis}) for the 3-D viewer. Each node carries the real semantic position (x/y/z, scaled to force-graph units — a front-end can seed layout from them so proximity means similarity), a degree-based size (val), and the emotion/sense auracolor. Seedocs/developer/cosmos-frontend-requirements.md.
The signpost list is assembled by ConsciousnessEngine.generate_view(). It merges explicit links from the local cell DB with cross-store atoms (nucleus, group spaces) that the caller's IAM scopes allow.
Calling dive.look updates session.focus to the resolved atom. When a collection name is passed as id, a collection view is returned (type: "collection") listing all members as signposts.
Zoom out to a macro-level view centered on the current (or specified) atom. Returns a broader neighbourhood summary.
Params (data):
| Field | Type | Required |
|---|---|---|
id |
string | No |
Sets are named collections of atom keys. They serve dual purpose: as user-facing grouping tools and as the underlying mechanism for IAM scope resolution.
Add an atom to a named set.
Params (data):
| Field | Type | Required |
|---|---|---|
name |
string | Yes |
id |
string | Yes |
Response:
{"result": {"status": "added", "set": "my_reading_list", "key": "a3f9..."}}Remove an atom from a set.
Params (data):
| Field | Type | Required |
|---|---|---|
name |
string | Yes |
id |
string | Yes |
Response:
{"result": {"status": "removed", "set": "my_reading_list", "key": "a3f9..."}}List all members of a set (scoped to the caller's visible atoms).
Params (data):
| Field | Type | Required |
|---|---|---|
name |
string | Yes |
Response:
{
"result": {
"set": "my_reading_list",
"members": [
{"key": "a3f9...", "content": "The basilica...", "meta": {}}
],
"count": 1
}
}Remove all members from a set (does not delete the atoms themselves).
Params (data):
| Field | Type | Required |
|---|---|---|
name |
string | Yes |
Response:
{"result": {"status": "cleared", "set": "my_reading_list"}}Perform a set-algebra operation, storing the result in a new named set.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
op |
string | Yes | "union", "isect" (intersection), or "diff" (difference) |
result |
string | Yes | Name of the output set |
a |
string | Yes | First operand set name |
b |
string | Yes | Second operand set name |
Response:
{
"result": {
"result_set": "overlap",
"members": [{"key": "a3f9...", "content": "..."}]
}
}Python:
# Find atoms in both "ravenna_sites" AND "byzantine_art"
rpc("set.op", {"op": "isect", "result": "overlap", "a": "ravenna_sites", "b": "byzantine_art"}, token=token)Notes are hierarchical document structures built on top of atoms. note.new creates a root atom (stored in session as active_note_root); subsequent calls build the document's dual topology — a horizontal timeline of chunks and a vertical hierarchy of sections, chapters, and paragraphs.
The editing layer (M1) adds non-destructive revision, reordering, undo/redo, and rename. It operates on a two-namespace design:
- Input layer (
sys:top/next): the immutable write-order timeline — never touched after creation. - Edit layer (
edit:top/next,note:current): reorderable display layer; absent means fall back to input order.note:currentresolves an anchor's current content version.
All edits are recorded in note:edit_journal (a JSON atom) that drives undo/redo as a cursor over a history list.
Create a new note/document.
Params (data):
| Field | Type | Required |
|---|---|---|
title |
string | Yes |
Response:
{"result": {"status": "initialized", "note_id": "e7f8..."}}The new note is set as the session's active_note_root.
Append a paragraph atom to the active note.
Params (data):
| Field | Type | Required |
|---|---|---|
text |
string | Yes |
Response:
{"result": {"node_id": "f8a9...", "status": "added"}}Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. Call note.new first. |
Python:
rpc("note.new", {"title": "Field Notes — Ravenna 2026"}, token=token)
rpc("note.add", {"text": "Morning visit to San Vitale. Mosaics in excellent condition."}, token=token)
rpc("note.add", {"text": "Afternoon: Galla Placidia mausoleum. Notable sarcophagi."}, token=token)Add a section (or chapter) to the active note. Creates a container atom in the hierarchy and appends it to the timeline.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Section heading text |
role |
string | No | Structural role label (default: "section"; use "chapter" for top-level chapters) |
Response:
{"result": {"node_id": "a1b2...", "title": "Morning Observations", "role": "section", "status": "added"}}Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. Call note.new first. |
Add a paragraph container to the active note. The paragraph becomes the active_container_id in session; subsequent note.add calls deposit chunks inside it.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
category |
string | No | Paragraph category label (default: "body") |
Response:
{"result": {"node_id": "b2c3...", "category": "body", "status": "added"}}Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. |
Return the table of contents for the active note — all section/chapter/paragraph container atoms with their titles and hierarchy depth.
Params (data): (none)
Response:
{"result": {"toc": [
{"node_id": "a1b2...", "title": "Morning Observations", "role": "section", "depth": 1},
{"node_id": "c3d4...", "title": "Afternoon", "role": "section", "depth": 1}
]}}Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. |
Read the active note as sequential text — all content chunks in timeline order.
Params (data): (none)
Response:
{"result": [
{"id": "f8a9...", "role": "chunk", "content": "Morning visit to San Vitale. Mosaics in excellent condition.", "category": "body"},
{"id": "g9b0...", "role": "chunk", "content": "Afternoon: Galla Placidia mausoleum. Notable sarcophagi.", "category": "body"}
]}Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. |
Delete the active note (root atom) and clear active_note_root from session. Child atoms remain in the graph; only the root and its direct index links are removed.
Params (data): (none)
Response:
{"result": {"status": "deleted", "note_id": "e7f8..."}}Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. |
| -32003 | Insufficient privileges to delete this atom. |
Python (full lifecycle example):
rpc("note.new", {"title": "Field Notes — Ravenna 2026"}, token=token)
rpc("note.section", {"title": "Morning", "role": "section"}, token=token)
rpc("note.paragraph", {"category": "body"}, token=token) # note.para is CLI shorthand only
rpc("note.add", {"text": "San Vitale. Mosaics intact."}, token=token)
rpc("note.section", {"title": "Afternoon"}, token=token)
rpc("note.add", {"text": "Galla Placidia mausoleum."}, token=token)
toc = rpc("note.toc", {}, token=token)
txt = rpc("note.read", {}, token=token)
rpc("note.rm", {}, token=token)List all content chunks in the active note, in current display order (edit layer if active, otherwise input order). Returns a head preview of each chunk.
Params (data): (none)
Response:
{"result": {"chunks": [
{"id": "a1b2...", "version": "c3d4...", "head": "San Vitale. Mosaics intact.", "role": "chunk", "order": 0},
{"id": "e5f6...", "version": "e5f6...", "head": "Galla Placidia mausoleum.", "role": "chunk", "order": 1}
], "count": 2}}id is the stable anchor id (never changes). version is the current content atom (changes on edit). head is the first line, truncated to 80 characters.
Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. |
Replace the content of a chunk with a new version. The original content atom is preserved as history via note:revises. Supports undo.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
chunk_id |
string | Yes | Anchor id of the chunk to edit |
text |
string | Yes | New content text |
Response:
{"result": {"status": "edited", "chunk_id": "a1b2...", "version": "f7g8..."}}version is the newly created content atom. The anchor chunk_id is unchanged.
Errors:
| Code | Condition |
|---|---|
| -32002 | No active note. |
| -32003 | Chunk not accessible under current IAM scopes. |
Reorder a chunk to a new position. The input-order timeline (sys:top/next) is never modified; only the edit-layer order (edit:top/next) is updated. Supports undo.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
chunk_id |
string | Yes | Anchor id to move |
after |
string | No | Anchor id to insert after. Omit or null to move to the top. |
Response:
{"result": {"status": "moved", "chunk_id": "a1b2...", "after": "e5f6...", "order": ["e5f6...", "a1b2..."]}}Undo the last edit or reorder. Steps the journal cursor back one position and re-materialises the previous state.
Params (data): (none)
Response:
{"result": {"status": "undone", "cursor": 2}}Returns {"status": "nothing_to_undo"} if already at the beginning of history.
Redo the last undone edit or reorder.
Params (data): (none)
Response:
{"result": {"status": "redone", "cursor": 3}}Returns {"status": "nothing_to_redo"} if already at the latest state.
Discard all edit-layer overrides and return to the original input order with original content. History atoms are preserved (the note:revises chain is intact). The restore operation itself is undo-able.
Params (data): (none)
Response:
{"result": {"status": "restored_to_original"}}Set a mutable display name for the active note. Because root atoms are content-addressed and immutable, the display name is stored as a separate note:title pointer atom, which this method updates atomically.
Params (data):
| Field | Type | Required |
|---|---|---|
title |
string | Yes |
Response:
{"result": {"status": "renamed", "note_id": "e7f8...", "title": "Field Notes — Ravenna 2026 (revised)"}}Python (editing lifecycle example):
rpc("note.new", {"title": "Field Notes"}, token=token)
rpc("note.add", {"text": "Morning: San Vitale."}, token=token)
rpc("note.add", {"text": "Afternoon: Galla Placidia."}, token=token)
chunks = rpc("note.list", {}, token=token)["chunks"]
chunk_id = chunks[0]["id"]
# Edit a chunk
rpc("note.edit", {"chunk_id": chunk_id, "text": "Morning: San Vitale. Apse mosaics in fine condition."}, token=token)
# Move second chunk to top
rpc("note.move", {"chunk_id": chunks[1]["id"], "after": None}, token=token)
# Undo the move
rpc("note.undo", {}, token=token)
# Rename the note
rpc("note.rename", {"title": "Ravenna 2026 — Final"}, token=token)JCL (Job Control Language) allows clients to submit multi-step batch jobs that execute asynchronously under Harmonia transactional guarantees. Jobs run on a single background worker that schedules by priority at step granularity (the mainframe-initiator model): priority changes the order in which ready steps run, it never introduces parallelism.
Submit a batch job with one or more sequential steps. Restricted to ADMIN and
LIBRARIAN roles (the job.submit capability); ordinary users cannot submit JCL jobs.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
steps |
array | Yes | List of step objects (see below) |
label |
string | No | Human-readable job name |
Each step object:
| Field | Type | Required | Description |
|---|---|---|---|
method |
string | Yes | Kernel method to invoke (must not be blocked by the JCL step validator, a blocklist) |
params |
object | No | data dict passed to the method |
cmd |
string | No | Original CLI command (audit trail only) |
Response (immediate):
{
"result": {
"job_id": "job:4a7f3c2b1e09",
"status": "PENDING",
"step_count": 3,
"label": "import-ravenna"
}
}The call returns immediately. The job runs in the background. Poll with job.stat.
Errors:
| Code | Condition |
|---|---|
| -32001 | A step's method is blocked by the JCL step validator (a blocklist); JCL subsystem unavailable |
| -32602 | steps is empty or malformed |
Python:
job = rpc("job.submit", {
"label": "import-ravenna",
"steps": [
{"method": "kernel.memory.write", "params": {"text": "Ravenna — capital of Western Roman Empire 402–476 AD"}},
{"method": "kernel.memory.define", "params": {"name": "Ravenna", "description": "Italian city, UNESCO World Heritage"}},
{"method": "kernel.memory.link", "params": {"src": "$it", "dst": "Byzantine Architecture", "rel": "sys:associated_with"}}
]
}, token=token)
job_id = job["result"]["job_id"]curl:
curl -s -X POST http://localhost:8000/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","method":"job.submit",
"params":{"session_token":"alice","data":{
"label":"import-ravenna",
"steps":[
{"method":"kernel.memory.write","params":{"text":"Ravenna — capital of Western Roman Empire"}},
{"method":"kernel.memory.define","params":{"name":"Ravenna"}}
]
}},
"id":"job1"
}'List jobs. Non-ADMIN callers see only their own jobs.
Params (data): (none)
Response:
{
"result": {
"jobs": [
{
"job_id": "job:4a7f3c2b1e09",
"label": "import-ravenna",
"status": "DONE",
"step_done": 3,
"step_count": 3,
"error": null
}
],
"count": 1
}
}Job status values: PENDING, RUNNING, DONE, FAILED, CANCELLED.
Detailed status for a single job. Non-ADMIN callers can only inspect their own jobs.
Params (data):
| Field | Type | Required |
|---|---|---|
job_id |
string | Yes |
Response:
{
"result": {
"job_id": "job:4a7f3c2b1e09",
"label": "import-ravenna",
"owner": "alice",
"status": "DONE",
"step_done": 3,
"step_count": 3,
"tx_id": "ws:jcl:import-ravenna:1716600123000",
"elapsed_sec": 0.42,
"error": null
}
}Cancel a PENDING job. Once a job transitions to RUNNING it cannot be cancelled.
Params (data):
| Field | Type | Required |
|---|---|---|
job_id |
string | Yes |
Response:
{"result": {"job_id": "job:4a7f3c2b1e09", "status": "CANCELLED"}}Errors:
| Code | Condition |
|---|---|
| -32001 | Job is already RUNNING/DONE/FAILED, or not owned by caller |
| -32002 | Job not found |
Note: The four job methods above (
job.submit,job.ls,job.stat,job.cancel) are the complete JCL surface. There is nojob.logorsys.monitormethod. Thesys:jcl_failure_log/sys:action_evidence/sys:workspace_infoevidence atoms a job writes are ordinary atoms in the graph, readable withread/explorewhere scope allows.
Contexa is the client session's input side on the I/O pipe (lib/harmonia/pipeline.py);
Jataka (§10.13) is the output side, and Consciousness is the substrate both flow through
(auto-weave on input, generate_view on output — never a pipe endpoint). Contexa reads the
external world into the cortex: web fetch, and collected survey responses with macro
context-binding.
Fetch external content from Wikipedia or a URL and integrate it as atoms (ContexaWebSource).
Written atoms carry the provenance=external guardrail (trust score + provenance scopes).
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Wikipedia search query or direct URL (url= accepted) |
Response: the provider result dict plus atom_key/written. Triggers the same Weaver/NLP
post-write pipeline as any write.
Errors: -32001 ContexaEngine unavailable · -32602 query/url missing.
Read collected survey responses (a CSV/JSON file, or an inline upload) into the survey graph
with Contexa macro-binding (ResponseIngestSink): each response is linked ctx:answers →
its question and ctx:from → its respondent, and added to the per-question set — the
dialogue/context layer over the survey model's structural tri-links. The input half of the
survey round-trip. Write capability; disk reads (path=) are admin/librarian and honour the
io.allow list; inline uploads (text=) are open to any WRITE client.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
survey |
string | Yes* | Survey root id (survey_id= accepted; falls back to the active survey) |
path | text |
string | Yes | A permitted file path, or an inline upload payload |
format |
string | with text= |
csv / json (required for inline) |
respondent_col |
string | No | Column identifying the respondent (default: first column) |
map |
string | No | Column→question mapping col:qid[,col:qid] (qid = question id, alias, or 1-indexed position); else columns map to questions in order |
Response: {kind:"survey_ingest", survey, respondents, responses, errors, mapped_questions}.
Errors: -32602 missing survey/source or no mappable question column · -32001 disk read denied (allow-list).
Jataka is the client session's output side on the I/O pipe: it presents a graph selection
back out, read through the Consciousness substrate (generate_view / cosmos_nd). dream
is the kernel's asynchronous affinity-gap incubation.
Render a selection as a presentation, returned inline (READ-level; no graph write).
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
as |
string | No | table (default) | scatter | narrative |
survey | set | focus |
string | Yes | The selection to present (which one depends on as) |
format |
string | No | For as=table: also serialise to csv/json/md |
table(survey=|set=) — per-(question,answer) counts / listed rows.scatter(survey=|set=) — 2-D points positioned bycosmos_nd(the real semantic position).narrative(focus=|survey=) — prose fromgenerate_view. LLM-optional: with no LLM a deterministic structural template is emitted (never empty); an injected narrator lifts it.
Response: {kind:"present", format, …} (rows/columns for table, points for scatter, text for narrative).
Asynchronous affinity-gap incubation ("sleep on it") — deliberately unlike the fast explorers
(assoc fills 1-hop high-confidence gaps; sim/node.sim rank what is already near). It
searches for atoms near in meaning but far in the explicit graph and stages them as
tentative links a human confirms. Runs as a LOW-priority background JCL job.
Params (data): id (focus atom); optional boldness (0 conservative … 1 bold single
signal; default 0.2), reach (gap weight; default 0.5), again=yes (re-dream a completed
focus), threshold/limit/scan.
Flow:
dream id=<atom>submits a background job →{status:"dreaming", job_id, elapsed_s}.dream id=<atom>again for the same focus polls: still running →dreaming; done →{status:"ready", focus, candidates:[{dst, alias, score, rel, preview}], count}. Candidates are staged astent:calc:hidden_affinitylinks — the job never writes a real edge.dream.confirm dst=<atom> [src=<focus>]promotes one staged bridge to a realcalc:hidden_affinitylink.dream.forget [dst=|all=yes] [src=]drops the rest. Human approval is mandatory by design (no agent auto-approval). If JCL is unavailable,dreamfalls back to a synchronous run.
IAM: dream requires the WRITE capability (its method action is write) and always inherits the session's active_scopes.
Errors: -32002 focus not found / access denied · -32602 dream.confirm needs dst.
Return the 10 most recent user-authored atoms (excluding internal sys: nodes).
Params (data): (none)
Response:
{
"result": {
"history": [
{"key": "a3f9...", "preview": "The basilica of San Vitale dates from 547 AD."},
{"key": "b4c5...", "preview": "Byzantine Architecture"}
]
}
}List the last N user-authored atoms.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of atoms to return (default: 10, max: 100) |
Response:
{
"result": {
"atoms": [
{"idx": 0, "key": "a3f9...", "preview": "The basilica..."},
{"idx": 1, "key": "b4c5...", "preview": "Byzantine Architecture..."}
],
"count": 2
}
}Terminate the caller's session, releasing all in-memory state.
Params (data): (none)
Response:
{"result": {"status": "session_closed", "client_id": "alice"}}kernel.associate (CLI alias: assoc) is a gap-detection operator. It scans the focal
atom's one-hop outgoing links, determines which semantic axes are absent (the voids),
and — for each void — offers structural candidates drawn from peer atoms in the focal atom's
shared sets (no inference). Optionally it fills each void by writing the top candidate's link.
It answers "what links is this atom missing?", not "what is it near?".
Params (data):
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id |
string | No | $it (last-written) |
Focal atom. Supports all context references. |
axis |
string | No | null (all axes) |
Restrict the scan to one semantic axis (see Axis table). |
fill |
string | No | no |
yes → write the top candidate link for each void. Requires WRITE. |
Axis values (which axis a void belongs to):
axis |
Detected via |
|---|---|
emotion |
emo: targets |
color |
word:color:, calc:color |
sense |
word:sense:, calc:sense |
time |
chrono:, calc:time |
context |
calc:context, calc:associated_with |
story |
polti:, story: |
| omitted | all of the above (plus category-declared salient relations) |
Response:
{
"result": {
"focal": {"key": "<hex>", "alias": "San Vitale", "preview": "<60 chars>"},
"axis": "all",
"voids": [
{
"axis": "emotion",
"missing": "emo:",
"hint": "No 'emotion' links found.",
"candidates": [
{"key": "<hex>", "alias": "emo:awe", "rel": "calc:associated_with"}
]
},
{
"axis": "salient",
"missing": "cocktail:base",
"missing_full": "base spirit — the defining spirit of the cocktail",
"hint": "Salient relation 'base spirit' expected for this category but absent.",
"candidates": [],
"salient": true
}
],
"filled": []
}
}voids— one entry per absent axis (and per category-declared salient relation the atom lacks). Each carries the axis name, themissingrelation/prefix, a humanhint, and up to three structuralcandidates.filled— populated only whenfill=yes: one entry (axis,rel,dst,alias) per void that had a candidate written as a real link.
Errors:
| Code | Condition |
|---|---|
| -32602 | Missing id and no last-written atom in session. |
| -32002 | Atom not found or access denied. |
CLI examples:
assoc $it
assoc $it axis=emotion
assoc $it fill=yes
associate note.chunk1 axis=story
sys.scope.* manages the session-level scope state — persistent context keys a UI (e.g. the Cosmos Viewer sliders) can stash on the session and read back without resending them per call. They are stored via sys.scope.set and returned by sys.scope.get; dive.look surfaces active_time in its response metadata.
| Key | Type | Default | Description |
|---|---|---|---|
active_axis |
string | null | null |
Preferred semantic axis (UI hint; stored session state) |
active_scope |
integer | null | null |
Preferred traversal depth (UI hint; stored session state) |
active_time |
string | null | null |
Temporal filter tag returned in dive.look response metadata |
Note: These keys are session-stored UI preferences read back through
sys.scope.get; the currentexplore(filter search:ns/set/type/pat) andkernel.associate(gap detection:id/axis/fill) handlers do not silently inject them as parameter fallbacks. Pass the parameters explicitly on each call.
Set one or more session scope keys. Omitted keys are left unchanged.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
axis |
string | null | No | Set active_axis. Pass null to clear. |
scope |
integer | null | No | Set active_scope. Pass null to clear. |
time |
string | null | No | Set active_time. Pass null to clear. |
Result:
{
"status": "scope_updated",
"active_axis": "emotion",
"active_scope": 3,
"active_time": null
}Return the current session scope state.
Params: none
Result:
{
"active_axis": "emotion",
"active_scope": 3,
"active_time": null
}Clear all session scope keys back to null.
Params: none
Result:
{
"status": "scope_reset",
"active_axis": null,
"active_scope": null,
"active_time": null
}The Cosmos Viewer sends a sys.scope.set call whenever the user moves a slider, then fires kernel.associate without repeating the axis/scope arguments:
// Slider onChange
async function onAxisChange(axis) {
await rpc("sys.scope.set", { axis });
}
async function onScopeChange(depth) {
await rpc("sys.scope.set", { scope: depth });
}
// Associate button — no axis/scope needed; session state is inherited
async function onAssociate(nodeId) {
return rpc("kernel.associate", { id: nodeId });
}scope # alias for: scope get
scope get # sys.scope.get
scope reset # sys.scope.reset
scope axis=emotion scope=3 # sys.scope.set {axis: "emotion", scope: 3}
scope time=2026 # sys.scope.set {time: "2026"}
scope axis=null # sys.scope.set {axis: null} — clear axis filter
The scope command uses key=value token parsing instead of positional arguments. Multiple key=value pairs may be combined in a single call.
log.* records the process of exploration — which atoms were visited, under what scope conditions, and when. Unlike note.* (which records written content), LogConcept records the act of traversal itself.
Implemented in lib/akasha/concepts/log.py as a BaseConcept subclass (does not inherit NoteConcept).
| Key | Description |
|---|---|
active_log_root |
Root atom of the currently active Log |
active_log_container |
Current container in the Log hierarchy (updated on each checkpoint) |
Create a new Log and set it as active.
Params (data): name: str
Result:
{"log_id": "<hex>", "name": "Cosmos Exploration 2026-05-26", "status": "created"}Record the current session state as a checkpoint. Automatically captures session.focus, active_axis, active_scope, active_time (whiteboard-local when a board is active), and timestamp.
Params (data): note?: str (optional annotation)
Result:
{
"checkpoint_id": "<hex>",
"focal": "<key>",
"axis": "emotion",
"scope": 2,
"time": null,
"note": "Noticed strong resonance here",
"status": "recorded"
}Checkpoint atom meta structure:
{
"type": "log_checkpoint",
"role": "checkpoint",
"focal_key": "<hex>",
"focal_alias": "<alias | null>",
"active_axis": "emotion",
"active_scope": 2,
"active_time": null,
"whiteboard": "<wb_name | null>",
"note": "<optional text>",
"created_at": 1716600000.0
}Add a text annotation to the most recent checkpoint.
Params (data): text: str
Result:
{"annotation_id": "<hex>", "checkpoint_id": "<hex>", "status": "annotated"}Replay the Log — restore each checkpoint's focal atom and scope state sequentially into the session.
Params: none
Result:
{
"checkpoints": [
{"index": 0, "focal": "<key>", "alias": "Atlantis", "axis": "emotion", "scope": 2, "note": "...", "restored": true}
],
"count": 5,
"status": "replayed"
}Read the Log as a sequential list of checkpoints (timeline order).
Params: none
Result:
{
"log_id": "<hex>",
"name": "Cosmos Exploration 2026-05-26",
"checkpoints": [...],
"count": 5
}Delete the active Log and clear session context keys.
Params: none
Result: {"status": "deleted", "log_id": "<hex>"}
| Set | Members |
|---|---|
set:log:<root_id> |
All checkpoint and annotation atoms |
set:log:<root_id>:checkpoints |
Checkpoint atoms only |
set:concept:<root_id> |
Concept-word atoms ("log", "checkpoint") |
A Whiteboard is a named meaning session — a surface onto which Concept Models are pinned to define their intersection. When active, scope state and traversal context are scoped to that board.
Implemented in lib/akasha/concepts/whiteboard.py. No cortex atoms are created — all state lives in session context keys.
| Key | Description |
|---|---|
active_whiteboard |
Name of the currently active Whiteboard |
wb_names |
List of all known whiteboard names |
wb:<name>:pinned |
Ordered list of pinned concept model names |
wb:<name>:scope_axis |
Whiteboard-local active_axis |
wb:<name>:scope_scope |
Whiteboard-local active_scope |
wb:<name>:scope_time |
Whiteboard-local active_time |
When a whiteboard is active, sys.scope.set/get/reset operate on the board's local scope keys (wb:<name>:scope_*) instead of the session-global keys. Switching boards via wb.focus restores the target board's scope state automatically.
wb.focus "Story Exploration"
scope axis=emotion scope=3 # → stored in wb:"Story Exploration":scope_axis/scope_scope
wb.focus "Molecule Search"
scope axis=context scope=2 # → stored in wb:"Molecule Search":scope_axis/scope_scope
wb.focus "Story Exploration"
scope get # → {"axis": "emotion", "scope": 3, "time": null}
Create a new Whiteboard and make it active.
Params (data): name: str
Result: {"name": "Story Exploration", "pinned": [], "status": "created", "active": true}
Pin a Concept Model to the active Whiteboard.
Params (data): concept: str — valid values: note, log (extensible)
Result: {"whiteboard": "Story Exploration", "pinned": ["note"], "status": "pinned"}
Remove a Concept Model from the active Whiteboard.
Params (data): concept: str
Result: {"whiteboard": "Story Exploration", "pinned": ["log", "cosmos"], "status": "unpinned"}
Switch the active Whiteboard. All scope state reads/writes immediately route to this board's local keys.
Params (data): name: str
Result:
{
"active_whiteboard": "Molecule Search",
"pinned": ["log", "molecule"],
"scope": {"axis": null, "scope": 2, "time": null},
"status": "focused"
}List all Whiteboards in this session.
Params: none
Result:
{
"whiteboards": [
{"name": "Story Exploration", "pinned": ["note", "log", "cosmos"], "active": true},
{"name": "Molecule Search", "pinned": ["log", "molecule"], "active": false}
],
"count": 2
}Show the current state of the active Whiteboard.
Params: none
Result:
{
"name": "Story Exploration",
"pinned": ["note", "log", "cosmos"],
"scope": {"axis": "emotion", "scope": 3, "time": null},
"active_note": "<note_root_id | null>",
"active_log": "<log_root_id | null>",
"active_focus": "<focal_key | null>"
}Remove a Whiteboard from this session. Clears all wb:<name>:* context keys.
Params (data): name: str
Result: {"status": "deleted", "name": "Story Exploration"}
When a Whiteboard is active, kernel.associate appends a whiteboard_context field:
{
"focal": {...},
"whiteboard_context": {
"name": "Story Exploration",
"pinned": ["note", "log", "cosmos"]
},
"associations": [...],
"resonance": [...]
}The Cosmos Viewer uses whiteboard_context.pinned to apply the correct node colour scheme per concept.
sys.cross.* computes the set intersection of atoms across multiple active Concept Models. WhiteboardConcept uses sys.cross.query internally; direct use is available for scripting and JCL jobs.
Concept names are resolved to their active collection set names via session context:
"note"→set:note:<active_note_root>"log"→set:log:<active_log_root>
Return atoms present across the specified concept sets, weighted by coverage ratio.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
concepts |
array of strings | Yes | Concept model names to intersect |
id |
string | No | Focal atom for context. Defaults to $it. |
format |
string | No | "raw" (default) or "cosmos" |
Result (format=raw):
{
"focal": "<key | null>",
"concepts": ["note", "log", "cosmos"],
"intersection": [
{
"key": "<hex>",
"preview": "<60 chars>",
"present_in": ["note", "log"],
"weight": 0.6667
}
],
"count": 12
}weight is len(present_in) / len(concepts) — atoms present in all concepts have weight 1.0.
Return the semantic axes available across the specified concept sets, with a recommendation.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
concepts |
array of strings | Yes | Concept model names |
Result:
{
"concepts": ["note", "log"],
"available_axes": ["emotion", "time", "story", "context"],
"recommended": "time"
}recommended is the axis with the highest cross-concept coverage (present in the most distinct concept sets).
Full API reference:
docs/concept-model/concept-model-spec.md §10.1
The Cockpit concept model (lib/akasha/concepts/cockpit.py) is registered automatically
via the Concept Model Plugin Registry — no manual kernel dispatch is required.
| Method | Required params | Description |
|---|---|---|
cockpit.new |
name (str) |
Commission a new cockpit deck |
cockpit.ls |
— | List all cockpits owned by this user |
cockpit.open |
cockpit_id (str) |
Mount an existing cockpit as active |
cockpit.lock |
target (str) |
Set the focal point (session only) |
cockpit.tune |
axis? (str), scope? (int) |
Adjust dimensional lens filters |
cockpit.beacon |
note (str) |
Drop a beacon at current focal point |
cockpit.wake |
— | Read the chronological beacon trail |
cockpit.status |
— | Read instrument panel state |
cockpit.rm |
— | Decommission the active cockpit |
CLI aliases: cp.new, cp.ls, cp.open, cp.lock, cp.tune, cp.beacon, cp.wake, cp.status, cp.rm
Full API reference:
docs/concept-model/concept-model-spec.md §10.3
The Survey concept model (lib/akasha/concepts/survey.py) is registered automatically
via the Concept Model Plugin Registry — no manual kernel dispatch is required.
| Method | Required params | Description |
|---|---|---|
survey.new |
title (str) |
Create a new survey root |
survey.open |
survey_id (str) |
Mount an existing survey |
survey.ls |
— | List all accessible surveys |
survey.q.add |
text (str) |
Add a question to the active survey |
survey.opt.add |
question_id, label (str) |
Add an answer option to a question |
survey.res.add |
respondent_id (str) |
Register a respondent |
survey.ans |
question_id, respondent_atom, answer |
Record a tri-linked response |
survey.list |
— | Structural inventory of active survey |
survey.rm |
— | Delete the active survey root |
Delegation sets (dont:*) are named collections used to bundle atoms and transfer them to a shared space — either the nucleus (universal scope) or a group knowledge space. They serve both as a donation mechanism and as a provenance record: both origin and destination retain the set record with metadata about date, source, and destination.
Donation modes:
| Mode | open value |
Effect |
|---|---|---|
| Copy (default) | false |
Atom is physically copied to the target DB. Originals are unchanged. Group members can collaborate on the copy independently. |
| Open | true |
Original atom's scope is extended to include the target scope. No copy is made. |
Copy mode is preferred for group donations because it prevents group collaboration from modifying the original.
Create or update a named delegation set with provenance metadata.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Set name. The dont: prefix is added automatically if omitted. |
description |
string | No | Human-readable description of the set's purpose |
Response:
{
"result": {
"set": "dont:love_vocab",
"status": "created",
"meta": {
"type": "donation_set",
"created_by": "alice",
"created_at": 1748304000.0,
"description": "Vocabulary atoms for the emotion cluster",
"donations": []
}
}
}Add atoms to an existing delegation set.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Set name (with or without dont: prefix) |
targets |
string or array | Yes | Space-separated atom keys/aliases, or a JSON array. Supports $-references. |
Response:
{
"result": {
"set": "dont:love_vocab",
"added": 3,
"keys": ["a3f9...", "b4c5...", "c5d6..."]
}
}Donate all atoms in a delegation set to a shared space.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Delegation set name |
to |
string | Yes | Destination: "universal" or "group:<id>" |
open |
boolean | No | If true, extends scope instead of copying (default: false) |
Destination "universal": requires LIBRARIAN or ADMIN role. Atoms are written to the nucleus DB.
Destination "group:<id>": requires membership in the group (scope:group_<id> in caller's scopes).
Response:
{
"result": {
"status": "donated",
"set": "dont:love_vocab",
"to": "group:history_lab",
"mode": "copy",
"donated": 3,
"skipped": 0,
"donated_at": 1748304000.0
}
}After dont.send, both the origin set and the destination set record a provenance entry:
Origin metadata (donations[] array, appended):
{
"target": "group:history_lab",
"donated_at": 1748304000.0,
"atom_count": 3,
"mode": "copy"
}Destination metadata (receipt record in target DB):
{
"type": "donation_receipt",
"source_cell": "alice",
"source_set": "dont:love_vocab",
"donated_at": 1748304000.0,
"atom_count": 3,
"mode": "copy"
}Errors:
| Code | Condition |
|---|---|
| -32602 | name or to missing |
| -32002 | Set is empty, not found, or group space not loaded |
| -32003 | Destination requires librarian role or group membership |
Convenience shorthand: dont.send with open=true. Extends the original atom's scope to include the destination instead of copying.
Params (data): identical to dont.send (the open flag is forced to true).
List delegation sets and their donation history.
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | If provided, returns detail for a single set (including member count). If omitted, lists all sets. |
Response (no name — list all):
{
"result": {
"donation_sets": [
{"name": "dont:love_vocab", "description": "Vocabulary atoms...", "atom_count": 3}
]
}
}Response (with name — single set detail):
{
"result": {
"set": "dont:love_vocab",
"atom_count": 3,
"meta": {
"type": "donation_set",
"created_by": "alice",
"created_at": 1748304000.0,
"description": "Vocabulary atoms for the emotion cluster",
"donations": [
{"target": "group:history_lab", "donated_at": 1748304000.0, "atom_count": 3, "mode": "copy"}
]
}
}
}Python workflow example:
# 1. Create a delegation set
rpc("dont.create", {"name": "emotion_vocab", "description": "Core emotion vocabulary"}, token=token)
# 2. Add atoms (by alias or $-reference)
rpc("dont.add", {"name": "emotion_vocab", "targets": "emo:love emo:joy emo:sadness"}, token=token)
# 3. Donate to the group space
rpc("dont.send", {"name": "emotion_vocab", "to": "group:history_lab"}, token=token)
# 4. Inspect provenance
rpc("dont.ls", {"name": "emotion_vocab"}, token=token)job.submit validates every step's method against a hard-coded blocklist (validate_steps, defined in lib/akasha/jcl/validator.py). Submitting a step whose method matches the blocklist returns error -32001. Everything not on the blocklist is permitted.
Blocked methods (may not run inside a JCL step):
| Category | Methods |
|---|---|
| Job control | job.* (prevents recursion and job introspection/cancellation from inside a job) |
| Privilege escalation | sys.su |
| User & group management | user.*, grp.* |
| Session | session.* |
| Authentication | auth.*, kernel.auth.* |
| First-boot ceremony | kernel.genesis_rite |
| Destructive ontology | onto.reset, onto.genesis.redo, onto.scope.drop |
Everything else is permitted — including rec.*, table.*, lens*, quadrant.*, weave operations, and any future concept models. A blocklist is used deliberately: with an allowlist, every newly added concept model would silently become un-runnable inside a job until someone remembered to register it; a blocklist keeps new models runnable by default while still barring the sensitive control-plane methods.
Each step runs as a normal kernel.dispatch() call under the submitter's own session_token, and is re-authenticated as the job owner (the blocklist is defense-in-depth on top of that). Scope isolation is therefore enforced naturally: a USER can only write to their private scope from a JCL step, exactly as they would in an interactive session.
The MCP portal (api/portals/mcp.py) exposes Akasha to any MCP-speaking LLM client (Claude
Desktop, a local Ollama agent, an editor plugin) as a set of MCP tools. It is a stdlib
implementation — MCP is just JSON-RPC 2.0 with a fixed method set (initialize, tools/list,
tools/call, ping, notifications/*), so there is no mcp-python-sdk dependency (it stays
installable on the seeds/edge tier).
Both transports are shipped and verified end-to-end:
| Transport | Launch / route | Trust | Identity |
|---|---|---|---|
| stdio (local) | python akasha.py --mcp (run_mcp_stdio) — newline-delimited JSON-RPC over stdin/stdout, spawned as a subprocess |
TRUST_LOCAL |
Acts as an authenticated local client (read + own-scope write) |
| HTTP (remote) | POST /mcp (ASGI) / POST /api/mcp (httpd) via mcp_http_process |
TRUST_NETWORK |
Anonymous → guest session (read-only); Authorization: Bearer akt:<token> → that identity's scopes (scoped write) |
Both transports call the same AkashaMCPPortal bridge → gw.dispatch(...). The kernel is the
capability gate — a guest calling a write tool is denied there, not in the portal. Trust is set
by the transport, never the client.
Each tool maps to one existing kernel JSON-RPC method. access documents the underlying
capability the kernel enforces (read tools work for a guest session; write tools need an
authenticated akt: token or the local stdio route).
| MCP Tool | Kernel Method | Access | Purpose |
|---|---|---|---|
akasha_ping |
sys.ping |
read | Liveness check |
akasha_read |
read |
read | Read an atom by id/alias/$-ref |
akasha_explore |
explore |
read | Filter search (by query/ns) |
akasha_concept |
thesaurus.concept |
read | Concept page: neighbourhood, synonyms/broader/narrower, refs |
akasha_search |
semantic.search |
read | Semantic (meaning) nearest-neighbour search |
akasha_gap_scan |
gap.scan |
read | Find important-but-thin concepts (ontology gaps) |
akasha_write |
kernel.memory.write |
write | Write a new atom |
akasha_link |
kernel.memory.link |
write | Link two atoms with a typed relation |
akasha_fetch |
contexa.fetch |
write | Fetch external context (Wikipedia/URL) into the mesh |
akasha_command |
sys.cli_exec |
write | Run any Akasha CLI command as a raw string (CLI-equivalent escape hatch; kernel gates per command) |
akasha_society_new |
society.new |
write | Create a dialogue space (channel) in a group |
akasha_society_say |
society.say |
write | Speak as your avatar into a dialogue space |
akasha_society_feed |
society.feed |
read | Read a dialogue space's timeline |
akasha_society_roster |
society.roster |
read | List avatars present in a space |
akasha_society_turn |
society.turn |
read | Whose turn it is (last speaker + suggested next) |
AkashaMCPPortal.list_tools() returns the name / description / inputSchema for each; the
authoritative schemas live in MCP_TOOLS in api/portals/mcp.py.
The tool-dispatch method is call_tool(name, arguments, session_token) — it dispatches the
tool's kernel method and wraps the result as MCP tool content ({"content": [...], "isError": …}).
from api.gateway import create_gateway
from api.portals.mcp import AkashaMCPPortal
gw = create_gateway(series="seeds", base_dir="data")
portal = AkashaMCPPortal(gw, client_id="claude") # stdio/local default trust
# List available tools
tools = portal.list_tools()
# Resolve a session token (LOCAL → the configured client; NETWORK → a fresh guest), then call.
token = portal.resolve_session(None)
result = portal.call_tool("akasha_write",
{"text": "Theodoric the Great ruled 493–526 AD."}, token)
print(result) # {"content": [{"type": "text", "text": "..."}], "isError": false}Full MCP messages (initialize / tools/list / tools/call) are handled by
portal.handle(msg, session_token); run_mcp_stdio(gw) and mcp_http_process(gw, body, ...)
are the two transport drivers.
Groups provide shared knowledge spaces for collaborative research teams. Each group has its own dedicated SQLite database (data/groups/{group_id}/g_space.db) managed by a GroupEngine instance. Group atoms are isolated from the nucleus and from other groups.
- Each group has a unique string identifier (e.g.,
"architects","history_lab"). - Group knowledge lives in a separate per-group DB (
data/groups/{group_id}/g_space.db), not in the nucleus or any user's local cell. - Atoms in a group space are tagged
scope:group_<id>. - Only group participants can read group atoms;
GROUP_ADMINand group-level LIBRARIANs can write. - A
GROUP_ADMINcannot read private atoms of individual group members. - The kernel loads all group DBs for a user's groups automatically at session creation (
AkashaSession.group_enginesdict).
| Method | Required Role | Description |
|---|---|---|
grp.new |
ADMIN | Create a group and assign its administrator |
grp.ls |
GROUP_ADMIN, ADMIN | List current members of a group |
grp.add |
GROUP_ADMIN, ADMIN | Add a user to a group |
grp.rm |
GROUP_ADMIN, ADMIN | Remove a member (cannot remove the GROUP_ADMIN) |
grp.lib |
GROUP_ADMIN, ADMIN | Grant or revoke group-level LIBRARIAN rights |
grp.del |
ADMIN | Delete a group |
Params (data):
| Field | Type | Required |
|---|---|---|
group_id |
string | Yes |
admin_id |
string | Yes |
Response: {"status": "created", "group_id": "history_lab", "admin": "alice"}
Params (data):
| Field | Type | Required |
|---|---|---|
group_id |
string | Yes |
Response: {"group_id": "history_lab", "members": [...], "count": 3}
Params (data):
| Field | Type | Required |
|---|---|---|
group_id |
string | Yes |
member_id |
string | Yes |
Response: {"status": "added", "group_id": "history_lab", "member": "bob"}
Params (data):
| Field | Type | Required |
|---|---|---|
group_id |
string | Yes |
member_id |
string | Yes |
Response: {"status": "removed", "group_id": "history_lab", "member": "bob"}
Params (data):
| Field | Type | Required | Description |
|---|---|---|---|
group_id |
string | Yes | Target group |
action |
string | Yes | "grant" or "revoke" |
member_id |
string | Yes | Target user |
Response: {"status": "librarian_granted", "group_id": "history_lab", "member": "carol"}
Params (data):
| Field | Type | Required |
|---|---|---|
group_id |
string | Yes |
Response: {"status": "deleted", "group_id": "history_lab"}
When a user is added to a group, their scope list automatically includes:
scope:group_<id> — read access to group atoms
view:group_<id> — explicit read grant
A group-level LIBRARIAN additionally receives write:group_<id> (Dim-2 capability flag), granting write access to the group space without elevating them to the global LIBRARIAN role.
Writing atoms to a group space is done via the Delegation & Donation Sets API (see §10.21). A user collects atoms into a named delegation set and then sends them to "group:<id>". The GroupEngine copies (or scope-extends) the atoms into the group DB and records provenance metadata on both sides.
Direct group writes (atoms created directly in the group space) are possible for users holding write:group_<id> via the standard kernel.memory.write method — add a scope field of "group:<id>" (planned; currently use the donation API).
iam = kernel_dispatcher.iam
# Create a group with alice as its admin
iam.create_group("history_lab", "alice")
# Add bob as a member
iam.add_group_member("history_lab", requester_id="alice", new_member_id="bob")
# Grant bob group-level librarian rights
iam.grant_group_librarian("history_lab", requester_id="alice", target_id="bob")The interactive REPL translates shorthand commands to JSON-RPC 2.0 payloads via api/router.py:CommandRouter. Every shorthand is equivalent to its full method name.
| Shorthand | Method | Arguments |
|---|---|---|
w <text> |
kernel.memory.write |
text |
def <name> |
kernel.memory.define |
name |
r <id> |
kernel.memory.read |
id |
rm <id> |
kernel.memory.drop |
id |
ln <src> <dst> <rel> |
kernel.memory.link |
src, dst, rel |
ln.ls [id] |
link.list |
id |
ln.+ <src> <dst> <rel> |
link.reinforce |
src, dst, rel |
meta <id> <key> <value> |
meta.set |
id, key, value |
al <id> <name> |
kernel.identity.alias |
id, name |
al.ls |
kernel.identity.alias.list |
— |
al.find <pattern> |
kernel.identity.alias.find |
pattern |
onto.dump [mode] [ns=..] [rel=..] [collection=..] [sort=..] [limit=..] |
onto.dump |
mode, ns, rel, collection, sort, limit |
onto.report [clear=true] |
onto.report |
clear |
exp [ns=..] [set=..] [type=..] [pat] |
explore |
ns, set, type, pat, limit |
tree <target> [depth] |
graph.tree |
target, depth, follow, format, concept |
look [id] |
dive.look |
id |
d [id] |
dive.look |
id (alias for look) |
out [id] |
dive.out |
id |
s.add <name> <id> |
set.add |
name, id |
s.rm <name> <id> |
set.rm |
name, id |
s.ls <name> |
set.ls |
name |
s.clear <name> |
set.clear |
name |
s.op <op> <result> <a> <b> |
set.op |
op, result, a, b |
n.new <title> |
note.new |
title |
n.ls |
note.ls |
— |
n.open <note_id> |
note.open |
note_id |
n.add <text> |
note.add |
text |
n.sec <title> |
note.section |
title |
n.chap <title> [role] |
note.section |
title, role |
n.para [category] |
note.paragraph |
category |
n.toc |
note.toc |
— |
n.read |
note.read |
— |
n.rm |
note.rm |
— |
n.list |
note.list |
— |
n.edit <chunk_id> <text> |
note.edit |
chunk_id, text |
n.move <chunk_id> [after] |
note.move |
chunk_id, after |
n.undo |
note.undo |
— |
n.redo |
note.redo |
— |
n.restore |
note.restore |
— |
n.rename <title> |
note.rename |
title |
cp.new <name> |
cockpit.new |
name |
cp.ls |
cockpit.ls |
— |
cp.open <cockpit_id> |
cockpit.open |
cockpit_id |
cp.lock <target> |
cockpit.lock |
target |
cp.tune <axis> <scope> |
cockpit.tune |
axis, scope |
cp.beacon <note> |
cockpit.beacon |
note |
cp.wake |
cockpit.wake |
— |
cp.status |
cockpit.status |
— |
cp.rm |
cockpit.rm |
— |
job.ls |
job.ls |
— |
job.stat <job_id> |
job.stat |
job_id |
job.cancel <job_id> |
job.cancel |
job_id |
associate <id> [axis=X] [fill=yes] |
kernel.associate |
id, axis, fill |
assoc <id> [axis=X] [fill=yes] |
kernel.associate |
id, axis, fill |
scope / scope get |
sys.scope.get |
— |
scope reset |
sys.scope.reset |
— |
scope [key=val ...] |
sys.scope.set |
axis, scope, time (key=value pairs) |
log.new <name> |
log.new |
name |
log.cp [note] |
log.checkpoint |
note (optional) |
log.ann <text> |
log.annotate |
text |
log.replay |
log.replay |
— |
log.read |
log.read |
— |
log.rm |
log.rm |
— |
wb.new <name> |
wb.new |
name |
wb.pin <concept> |
wb.pin |
concept |
wb.unpin <concept> |
wb.unpin |
concept |
wb.focus <name> |
wb.focus |
name |
wb.ls |
wb.ls |
— |
wb.show |
wb.show |
— |
wb.rm <name> |
wb.rm |
name |
cross <c1> <c2> [...] |
sys.cross.query |
concepts (space-separated) |
cross.axes <c1> <c2> [...] |
sys.cross.axes |
concepts (space-separated) |
dream |
jataka.dream |
— |
fetch <query> |
contexa.fetch |
query |
ping |
sys.ping |
— |
cog |
sys.cogito |
— |
hist |
sys.history |
— |
ls [limit] |
sys.ls |
limit |
grp.new <group_id> <admin_id> |
grp.new |
group_id, admin_id |
grp.ls <group_id> |
grp.ls |
group_id |
grp.add <group_id> <member_id> |
grp.add |
group_id, member_id |
grp.rm <group_id> <member_id> |
grp.rm |
group_id, member_id |
grp.lib <group_id> <grant|revoke> <member_id> |
grp.lib |
group_id, action, member_id |
grp.del <group_id> |
grp.del |
group_id |
dont.create <name> [desc] |
dont.create |
name, description |
dont.add <name> <targets...> |
dont.add |
name, targets (space-separated) |
dont.send <name> <to> |
dont.send |
name, to |
dont.open <name> <to> |
dont.open |
name, to |
dont.ls [name] |
dont.ls |
name (optional) |
Arguments are parsed with shlex.split. The last declared argument in a command absorbs all remaining tokens, so w This is a full sentence. works as expected.
AKASHA's HTTP portal (http_portal, default port 8000) serves both the JSON-RPC API and static web application files from a single process. Sub-applications are plain HTML/JS single-page apps stored under services/static/ and accessed via path-based routing.
http://host:8000/ → services/static/index.html (Cosmos 3D)
http://host:8000/note → services/static/note/index.html (Note UI)
http://host:8000/<name> → services/static/<name>/index.html (any app)
BaseWebHandler.translate_path() maps directory paths directly to their index.html without issuing a redirect, which ensures correct routing through reverse-proxy and port-forwarding environments (e.g. GitHub Codespaces).
All kernel access from the browser goes through POST /api/rpc with standard JSON-RPC 2.0 payloads.
Place a single HTML file in services/static/<name>/index.html. No other configuration is required.
services/static/
<name>/
index.html ← entire app lives here
The app is immediately accessible at http://host:8000/<name>.
Additional static assets (CSS, JS, images) can be placed in the same subdirectory and referenced with relative paths.
Every web app must implement the two-phase auth flow. The browser never handles passphrases directly — the kernel performs all credential verification.
Phase 1 — Login (pre-auth, no session token required)
const resp = await fetch('/api/rpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'kernel.auth.verify',
params: { data: { user_id: user, passphrase: pass } },
id: 'auth'
})
});
const { result, error } = await resp.json();
if (error) { /* show error */ return; }
// result.status === 'authenticated'
const sessionToken = result.session_token; // store this
const currentUser = result.user_id;Phase 2 — All subsequent calls (session token required)
async function rpc(method, data = {}) {
const res = await fetch('/api/rpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0', method,
params: { session_token: sessionToken, data },
id: Date.now()
})
});
const json = await res.json();
if (json.error) throw new Error(json.error.message);
return json.result;
}Rules:
session_tokenis the opaque signedakt:credential returned bykernel.auth.verify— not youruser_id.- Every call except pre-auth methods must include
session_tokeninparams. - Never hardcode a default username or pass
client_id— both are legacy patterns that bypass the IAM layer. - The login form must have no
value=default on the username field.
All RPC calls from the browser use the standard envelope defined in §3. The data object contains the method-specific parameters.
{
"jsonrpc": "2.0",
"method": "kernel.memory.write",
"params": {
"session_token": "<token>",
"data": { "text": "Hello, Akasha." }
},
"id": 1748304000000
}The id field can be any unique value; using Date.now() is conventional for browser clients.
If an application requires API endpoints beyond /api/rpc, use the sub-service launcher.
Step 1 — Create the route handler file
services/routes/<name>.py
# services/routes/<name>.py
from api.gateway import gateway
def my_handler(req_data: dict) -> dict:
# session_token is pre-validated by the HTTP layer
session_token = req_data.get("session_token", "")
result = gateway.dispatch({
"jsonrpc": "2.0",
"method": "some.kernel.method",
"params": {"session_token": session_token, "data": {}},
"id": "req",
})
return result.get("result", result)
ROUTES = {
"/api/<name>/action": ("POST", my_handler),
}ROUTES is a dict mapping URL path → (HTTP_METHOD, handler_function). Only "POST" handlers are supported for custom endpoints (they receive a validated session_token).
Step 2 — Launch with app_server.py
python -m services.app_server --app <name> --port 8081app_server.py auto-discovers services/routes/<name>.py and registers its routes. If the file does not exist, the service starts with /api/rpc and static files only.
Via svc commands (admin only):
svc restart <name>
The sub-service runs on a separate port as a subprocess managed by the Harmonia Supervisor
(lib/harmonia/supervisor.py) — the one service registry, persisted in the run-dir
(data/central/run/*). ServiceManager has been retired.
services/
app_server.py ← universal sub-service launcher
http_gateway.py ← BaseWebService / BaseWebHandler
routes/
cosmos.py ← custom routes for Cosmos (example)
<name>.py ← optional; omit if /api/rpc is sufficient
static/
index.html ← Cosmos 3D UI (served at /)
note/
index.html ← Note UI (served at /note)
<name>/
index.html ← any new app (served at /<name>)
- No hardcoded credentials. Never embed
value="admin"or any default identity in login forms. - session_token in every call. All methods except the pre-auth whitelist (§4.2) require a valid
session_token. Calls without one return-32001 Authentication failed. - Custom endpoints are authenticated.
_execute_custom_handlerrejects requests missingsession_tokenwith HTTP 401 before the handler function is invoked. - No
client_idin params. Theclient_idfield is a legacy pattern. The kernel derives identity exclusively fromsession_token. - XSS prevention. Always escape user-supplied content before inserting into the DOM. Use
escHtml()or equivalent for all dynamic text rendering.
This document is generated from the AkashicTree source at /home/user/AkashicTree. Refer to lib/akasha/kernel.py for the authoritative method dispatcher and lib/akasha/identity.py for the IAM layer.