liminallm is an experiment in what a chatgpt-like system looks like if you stop hard-coding product logic and let the model help evolve itself.
the core bet: small models, deeply adapted. a small self-hosted model with behavior baked into lora weights beats a small model begging through a long system prompt - weights survive context pressure, free the window for the user's actual content, and cost nothing per token. a frontier model can help as an offline teacher, but inference never depends on one.
it’s a small kernel wrapped around:
the code is just the glue. everything interesting lives as data.
the ui is one chat surface with a workspace beside it, plus a separate admin console. each image below was captured from a running instance driven through a real browser: a real server, a real postgres and redis, and a live model answering the questions shown.
sign in. email and password, with the access token held in session storage and the refresh token in an http-only cookie the page cannot read.

chat. the app rail down the left edge moves between sections; the pane beside it lists that section's items. conversations are titled from their first exchange. the thread keeps context across turns, so a follow-up that names nobody is still answered about the same subject. the bar carries the conversation's name and one action, with the knowledge context and workflow override behind its menu.

notes. a searchable vault. the pane lists and searches it, selecting a note opens it for editing, and sweep and graph operate across the whole set.

contexts. knowledge contexts group uploaded sources for retrieval. the pane lists them and the workspace shows the selected one. a conversation can be pointed at one from the chat screen's menu.

files. uploads are attached to a context, or kept private when no context is given. the same screen browses everything already uploaded.

artifacts. workflows, policies, adapters, and tools are all artifacts. the pane filters them by type and visibility; the workspace shows the selected artifact and its version history.

tools. the pane lists the registered tool specifications a workflow can call, and the workflows themselves. the workspace shows the selected one and invokes it.

insights. preference events are summarized here, so you can see what your feedback has shaped and which adapters it reached.

settings. account and session details, including the role and tenant the current session carries.

admin console. a separate page at /admin, guarded by the admin role.
it reviews configuration patch proposals, administers tenant users, and
inspects database objects.

User Feedback → Embeddings → Clustering → Skill Discovery
↑ ↓
Router Updates ← Eval Gate ← Adapter Training ← Prompt-Mode Skill
chatgpt-like web ui
deep behavioral memory (the adapter ladder)
latest pointer, the newest directory - none of them mean an adapter graduated.natural factual memory
/users/{id}/files)a notes vault with a witness
[[title]]; links become a graph you can seenote_search) and cite what you once wrotetesseract-ocr + pip install 'liminallm[ocr]' - technically optional, practically requiredcontext that fits the model you actually run
model_context_window overrides when discovery guesses wrongan openai-compatible responses api for agents
POST /v1/responses speaks the responses dialect, so any agent framework can point its base url here and get the kernel's whole enrichment stack - personas, skill adapters, hybrid rag, notes, memory - behind what looks like a plain model endpoint. a weak local model plus this kernel presents as a much richer model; the caller changes nothing but the base url.previous_response_id continues the conversation server-side; pass context_id (a liminallm extension) on the first turn to ground the whole thread in a knowledge contextstream: true returns sse response.* events (created → tool items as they run → text deltas → completed), with the reply's id stable from the first event to the persisted messagefile_search_call/web_search_call output items; grounding snippets, the full tool trace and active adapters ride under a namespaced liminallm key; usage includes reasoning/cached token details when the upstream reports them, and real totals from our own tokenizer on the local jax pathsk-liminal-…): mint, list, and revoke from the settings tab in the web ui, or at /v1/auth/api-keys with a logged-in session. keys are valid only on the agent surfaces (/v1/responses, /v1/mcp) - a leaked key can chat and search and nothing else, and in particular cannot mint or revoke keys. only a sha-256 of the key is stored; the plaintext is shown exactly once, at mint time.<tool_call> channel - so agents get the same grounded answers on every backendan mcp server for everyone else's agents
POST /v1/mcp speaks the model context protocol (streamable http, revision 2025-06-18): initialize, list tools, call tools - stateless, json responses, batching rejected as the spec now requiresnote_search over the notes vault and knowledge_search over knowledge contexts, the exact services the internal agent loop usessmall kernel, big data
adapter.loraworkflow.chatpolicy.routingtool.speccontext.knowledgeemergent domains & skills
DEBUGGING, WRITING, whateverrouter as data, not code
policy.routing) with a tiny expression language:
0 means the adapter is absent from the turn - no weights, no prompt, nothing sent to a provider, nothing in the kv cache key, and nothing claimed in what the turn reports it used. above zero it scales where scaling is defined; prompt text has no half-measure, so it goes in once, unchanged.llm as architect (under guardrails)
language / runtime
pip install -e ".[train]"config.json + *.safetensors straight from the model directory (no torch, no flax). incremental decode is tested to reproduce a full recompute, and a lora adapter at B=0 is tested to change nothing. with no checkpoint on disk it falls back to a synthetic stand-in and says so in the log - that path moves tokens, it does not answer questions. training uses that same forward pass, so an adapter is fitted to the model that will serve it: the loss is taken over the real decoder with the lora matrices inside its attention projections, and weights only load onto the base they declare.cached_tokens in usagestorage
/shared/models – frozen base model weights/users/{id}/files – user docs/users/{id}/adapters – per-user lora weight files/users/{id}/artifacts – generated notebooks, exports, etc.services (logically)
for v1 these can all live in one python app with clear module boundaries.
See INSTALL.md - Docker on Linux, Linux without Docker, or OpenBSD.
Before QA begins, verify:
| Criterion | How to Verify |
|---|---|
| Health check | curl http://localhost:8000/healthz returns {"status": "healthy"} |
| Chat UI loads | Open http://localhost:8000/ in browser |
| User signup | Sign up via UI or POST /v1/auth/signup |
| User login | Log in via UI or POST /v1/auth/login |
| Send message | Create conversation and send via /v1/chat |
| Admin protected | Regular user gets 403 on /v1/admin/settings |
| Admin access | Admin user gets 200 on /v1/admin/settings |
| Tests pass | make test-fast-xdist passes on fresh install |
| Bootstrap works | python scripts/bootstrap_admin.py creates admin |
Run the automated smoke test:
./scripts/smoke_test.sh http://localhost:8000
docs/DEPLOYMENT.mddocs/CONFIGURATION.mdTESTING.mdworkflow.chat graphserror.code, ownership-enforced artifact and conversation access (including workflows/tools), adapter checksum + path validation, and email verification flows/v1/auth/request_email_verification and /v1/auth/verify_emailnote: this is intentionally vague; exact commands depend on how you wire the codebase.
model_backend to local_gpu_lora in the admin console to target the local JAX+LoRA path instead of external API fine-tune IDs; leave the default to use the OpenAI-style plug. The JAX backend (LocalJaxLoRABackend in liminallm/service/model_backend.py) loads adapters from the filesystem, tokenizes prompts, runs a JAX forward pass, and enforces conservative shapes; it requires a JAX runtime and optionally a Transformers tokenizer for decode parity. Provider keys are admin settings, with <PROVIDER>_API_KEY as an environment fallback./frontend and is served by the FastAPI app at /, with static assets mounted at /static/*. It is a three-band shell: a 48px rail for sections, a contextual pane listing that section's items, and the workspace beside them./v1/auth/login. The access token is held in session storage and sent as a bearer token; the refresh token and session id are http-only cookies the page cannot read./admin and is guarded by the admin role (FastAPI enforces the role before serving the HTML). It surfaces config patch proposal/approval flows backed by /v1/config/* endpoints, tenant-scoped user administration (list/add/delete, role changes), adapter visibility, and a read-only inspector for database objects.Router policies pick an adapter; the inference backend decides whether that means applying LoRA weights locally, swapping to a remote fine-tuned model ID, or injecting distilled prompt instructions on top of a black-box API.
Each adapter.lora artifact carries a backend field describing where inference happens:
{
"kind": "adapter.lora.remote",
"provider": "zhipu",
"backend": "api",
"base_model": "glm-4-air",
"remote_model_id": "glm-4-air-ft-2025-11-01-u123-debug",
"region": "cn-beijing",
"cluster_id": "…",
"applicability": {
"natural_language": "u123: kernel panic debugging skill on GLM-4-Air",
"embedding_centroid": []
}
}
{
"kind": "adapter.lora.local",
"backend": "local",
"provider": "aliyun",
"base_model": "qwen2.5-32b-instruct",
"cephfs_dir": "/users/u123/adapters/{id}",
"rank": 8,
"layers": [0, 1, 2, 3],
"matrices": ["attn_q", "attn_v"],
"cluster_id": "…"
}
{
"kind": "adapter.lora.prompt",
"backend": "prompt",
"provider": "api_only",
"base_model": "glm-4-air",
"prompt_instructions": "for kernel issues: reproduce → bisect → log inspection; keep replies terse",
"cluster_id": "…",
"applicability": {
"natural_language": "prompt-distilled skill for kernel debugging",
"embedding_centroid": []
}
}
Remote adapters send requests to OpenAI-compatible fine-tuned model IDs (e.g., Zhipu BigModel or Alibaba DashScope). Local adapters resolve to filesystem-backed LoRA weights and are composable. Prompt-distilled adapters inject behavior as system messages without changing model IDs so you can still steer API-only providers.
“Model-ID adapters” (fine-tuned endpoints) map 1:1 to model strings on providers like OpenAI/Azure (fine-tuned deployments), Vertex AI Gemini, or Bedrock custom models. Switching behavior = switching the model string; composition happens at routing time, not inside a single call.
“Adapter-ID adapters” (multi-LoRA / adapter servers) surface adapter_id parameters on Together AI Serverless Multi-LoRA, LoRAX-style servers, or SageMaker adapter inference components. The backend keeps the base model string and passes adapter_id for one-or-more adapters per request when supported.
Hybrid patterns (local adapter-enabled “controller” + external API “executor”) flow through the same artifacts: the controller uses a local LoRA backend to plan, then the API backend executes with prompt or remote-model adapters.
configure env - one variable
DATABASE_URL – postgres dsn. that is the configuration.four others exist and none of them are settings you tune: BUILD_SHA
(stamped by the build), TEST_MODE (the test harness),
EMBEDDING_VECTOR_DIM (a property of the schema you applied, shared with
scripts/migrate.sh), and EXTRACT_READER_PLUGINS (imports python
modules, so making it settable from a web form would mean remote code
execution).
everything else - the model, credentials, rate limits, ttls, cors, smtp,
the signing key - lives in the database and is edited from the admin
console at /admin, applied to every replica without a restart.
changing an smtp password should not require redeploying the app. for
declarative deploys, seed on first boot with
INSTANCE_SETTINGS_JSON='{"model_backend": "stub"}'.
migrate db
${SHARED_FS_ROOT}/state/training_pg.json after upgrading to purge legacy MFA secrets (secrets are now sourced solely from the user_mfa_secret table).4a. preference_event → adapter dataset → tokenized batches
preference_event rows (positive feedback) capture context_embedding, score, and optional context_text; they are clustered per-user to build adapter personas.${SHARED_FS_ROOT}/users/{user_id}/adapters/{adapter_id}/jobs/{job_id}/dataset.jsonl.${SHARED_FS_ROOT}/users/{user_id}/adapters/{adapter_id}/v####/.start services
open the web ui
MIT
TESTING.md documents the lanes in full. The common ones:
make test-fast-xdist # the default edit-loop lane, about two minutes
make test-xdist # every test but the browser lane, in parallel
make test-browser # the browser lane; needs a Chromium binary
The suite starts its own throwaway Postgres and Redis (tests/harness.py) and
applies sql/schema.sql, so tests exercise the same store and cache the app
runs against. Set TEST_DATABASE_URL / TEST_REDIS_URL to point at existing
services instead.
CI runs the same selection on Python 3.10, 3.11 and 3.12. The wall-clock performance tests run in their own serial pass, so four parallel workers are not competing with the thing being timed.
For a full integration run against containers:
docker compose -f docker-compose.test.yml up --build
./scripts/smoke_test.sh
Key endpoints (Bearer access token required):
POST /v1/auth/signup → returns session + signed access/refresh tokensPOST /v1/auth/login → returns tokens, with MFA gating when enabledPOST /v1/auth/refresh → rotates refresh tokensPOST /v1/chat → creates conversation + LLM replyPOST /v1/responses → the same turn in OpenAI's Responses shape, for agents (api key or session auth; stream: true for SSE)POST /v1/mcp → MCP server (note_search + knowledge_search) for MCP-speaking agentsPOST /v1/auth/api-keys → mint an agent-surface api key (list with GET, revoke with DELETE /v1/auth/api-keys/{id})GET /v1/artifacts → lists data-driven workflows/policiesGET /v1/admin/settings → admin-only system settingsAdmin endpoints (/v1/admin/*, /v1/config/*) require admin role.
max_upload_bytes to prevent unbounded in-memory readsPython
91.1%
JavaScript
4.8%
HTML
1.5%
CSS
1.3%
liminallm is an experiment in what a chatgpt-like system looks like if you stop hard-coding product logic and let the model help evolve itself.
the core bet: small models, deeply adapted. a small self-hosted model with behavior baked into lora weights beats a small model begging through a long system prompt - weights survive context pressure, free the window for the user's actual content, and cost nothing per token. a frontier model can help as an offline teacher, but inference never depends on one.
it’s a small kernel wrapped around:
the code is just the glue. everything interesting lives as data.
the ui is one chat surface with a workspace beside it, plus a separate admin console. each image below was captured from a running instance driven through a real browser: a real server, a real postgres and redis, and a live model answering the questions shown.
sign in. email and password, with the access token held in session storage and the refresh token in an http-only cookie the page cannot read.

chat. the app rail down the left edge moves between sections; the pane beside it lists that section's items. conversations are titled from their first exchange. the thread keeps context across turns, so a follow-up that names nobody is still answered about the same subject. the bar carries the conversation's name and one action, with the knowledge context and workflow override behind its menu.

notes. a searchable vault. the pane lists and searches it, selecting a note opens it for editing, and sweep and graph operate across the whole set.

contexts. knowledge contexts group uploaded sources for retrieval. the pane lists them and the workspace shows the selected one. a conversation can be pointed at one from the chat screen's menu.

files. uploads are attached to a context, or kept private when no context is given. the same screen browses everything already uploaded.

artifacts. workflows, policies, adapters, and tools are all artifacts. the pane filters them by type and visibility; the workspace shows the selected artifact and its version history.

tools. the pane lists the registered tool specifications a workflow can call, and the workflows themselves. the workspace shows the selected one and invokes it.

insights. preference events are summarized here, so you can see what your feedback has shaped and which adapters it reached.

settings. account and session details, including the role and tenant the current session carries.

admin console. a separate page at /admin, guarded by the admin role.
it reviews configuration patch proposals, administers tenant users, and
inspects database objects.

User Feedback → Embeddings → Clustering → Skill Discovery
↑ ↓
Router Updates ← Eval Gate ← Adapter Training ← Prompt-Mode Skill
chatgpt-like web ui
deep behavioral memory (the adapter ladder)
latest pointer, the newest directory - none of them mean an adapter graduated.natural factual memory
/users/{id}/files)a notes vault with a witness
[[title]]; links become a graph you can seenote_search) and cite what you once wrotetesseract-ocr + pip install 'liminallm[ocr]' - technically optional, practically requiredcontext that fits the model you actually run
model_context_window overrides when discovery guesses wrongan openai-compatible responses api for agents
POST /v1/responses speaks the responses dialect, so any agent framework can point its base url here and get the kernel's whole enrichment stack - personas, skill adapters, hybrid rag, notes, memory - behind what looks like a plain model endpoint. a weak local model plus this kernel presents as a much richer model; the caller changes nothing but the base url.previous_response_id continues the conversation server-side; pass context_id (a liminallm extension) on the first turn to ground the whole thread in a knowledge contextstream: true returns sse response.* events (created → tool items as they run → text deltas → completed), with the reply's id stable from the first event to the persisted messagefile_search_call/web_search_call output items; grounding snippets, the full tool trace and active adapters ride under a namespaced liminallm key; usage includes reasoning/cached token details when the upstream reports them, and real totals from our own tokenizer on the local jax pathsk-liminal-…): mint, list, and revoke from the settings tab in the web ui, or at /v1/auth/api-keys with a logged-in session. keys are valid only on the agent surfaces (/v1/responses, /v1/mcp) - a leaked key can chat and search and nothing else, and in particular cannot mint or revoke keys. only a sha-256 of the key is stored; the plaintext is shown exactly once, at mint time.<tool_call> channel - so agents get the same grounded answers on every backendan mcp server for everyone else's agents
POST /v1/mcp speaks the model context protocol (streamable http, revision 2025-06-18): initialize, list tools, call tools - stateless, json responses, batching rejected as the spec now requiresnote_search over the notes vault and knowledge_search over knowledge contexts, the exact services the internal agent loop usessmall kernel, big data
adapter.loraworkflow.chatpolicy.routingtool.speccontext.knowledgeemergent domains & skills
DEBUGGING, WRITING, whateverrouter as data, not code
policy.routing) with a tiny expression language:
0 means the adapter is absent from the turn - no weights, no prompt, nothing sent to a provider, nothing in the kv cache key, and nothing claimed in what the turn reports it used. above zero it scales where scaling is defined; prompt text has no half-measure, so it goes in once, unchanged.llm as architect (under guardrails)
language / runtime
pip install -e ".[train]"config.json + *.safetensors straight from the model directory (no torch, no flax). incremental decode is tested to reproduce a full recompute, and a lora adapter at B=0 is tested to change nothing. with no checkpoint on disk it falls back to a synthetic stand-in and says so in the log - that path moves tokens, it does not answer questions. training uses that same forward pass, so an adapter is fitted to the model that will serve it: the loss is taken over the real decoder with the lora matrices inside its attention projections, and weights only load onto the base they declare.cached_tokens in usagestorage
/shared/models – frozen base model weights/users/{id}/files – user docs/users/{id}/adapters – per-user lora weight files/users/{id}/artifacts – generated notebooks, exports, etc.services (logically)
for v1 these can all live in one python app with clear module boundaries.
See INSTALL.md - Docker on Linux, Linux without Docker, or OpenBSD.
Before QA begins, verify:
| Criterion | How to Verify |
|---|---|
| Health check | curl http://localhost:8000/healthz returns {"status": "healthy"} |
| Chat UI loads | Open http://localhost:8000/ in browser |
| User signup | Sign up via UI or POST /v1/auth/signup |
| User login | Log in via UI or POST /v1/auth/login |
| Send message | Create conversation and send via /v1/chat |
| Admin protected | Regular user gets 403 on /v1/admin/settings |
| Admin access | Admin user gets 200 on /v1/admin/settings |
| Tests pass | make test-fast-xdist passes on fresh install |
| Bootstrap works | python scripts/bootstrap_admin.py creates admin |
Run the automated smoke test:
./scripts/smoke_test.sh http://localhost:8000
docs/DEPLOYMENT.mddocs/CONFIGURATION.mdTESTING.mdworkflow.chat graphserror.code, ownership-enforced artifact and conversation access (including workflows/tools), adapter checksum + path validation, and email verification flows/v1/auth/request_email_verification and /v1/auth/verify_emailnote: this is intentionally vague; exact commands depend on how you wire the codebase.
model_backend to local_gpu_lora in the admin console to target the local JAX+LoRA path instead of external API fine-tune IDs; leave the default to use the OpenAI-style plug. The JAX backend (LocalJaxLoRABackend in liminallm/service/model_backend.py) loads adapters from the filesystem, tokenizes prompts, runs a JAX forward pass, and enforces conservative shapes; it requires a JAX runtime and optionally a Transformers tokenizer for decode parity. Provider keys are admin settings, with <PROVIDER>_API_KEY as an environment fallback./frontend and is served by the FastAPI app at /, with static assets mounted at /static/*. It is a three-band shell: a 48px rail for sections, a contextual pane listing that section's items, and the workspace beside them./v1/auth/login. The access token is held in session storage and sent as a bearer token; the refresh token and session id are http-only cookies the page cannot read./admin and is guarded by the admin role (FastAPI enforces the role before serving the HTML). It surfaces config patch proposal/approval flows backed by /v1/config/* endpoints, tenant-scoped user administration (list/add/delete, role changes), adapter visibility, and a read-only inspector for database objects.Router policies pick an adapter; the inference backend decides whether that means applying LoRA weights locally, swapping to a remote fine-tuned model ID, or injecting distilled prompt instructions on top of a black-box API.
Each adapter.lora artifact carries a backend field describing where inference happens:
{
"kind": "adapter.lora.remote",
"provider": "zhipu",
"backend": "api",
"base_model": "glm-4-air",
"remote_model_id": "glm-4-air-ft-2025-11-01-u123-debug",
"region": "cn-beijing",
"cluster_id": "…",
"applicability": {
"natural_language": "u123: kernel panic debugging skill on GLM-4-Air",
"embedding_centroid": []
}
}
{
"kind": "adapter.lora.local",
"backend": "local",
"provider": "aliyun",
"base_model": "qwen2.5-32b-instruct",
"cephfs_dir": "/users/u123/adapters/{id}",
"rank": 8,
"layers": [0, 1, 2, 3],
"matrices": ["attn_q", "attn_v"],
"cluster_id": "…"
}
{
"kind": "adapter.lora.prompt",
"backend": "prompt",
"provider": "api_only",
"base_model": "glm-4-air",
"prompt_instructions": "for kernel issues: reproduce → bisect → log inspection; keep replies terse",
"cluster_id": "…",
"applicability": {
"natural_language": "prompt-distilled skill for kernel debugging",
"embedding_centroid": []
}
}
Remote adapters send requests to OpenAI-compatible fine-tuned model IDs (e.g., Zhipu BigModel or Alibaba DashScope). Local adapters resolve to filesystem-backed LoRA weights and are composable. Prompt-distilled adapters inject behavior as system messages without changing model IDs so you can still steer API-only providers.
“Model-ID adapters” (fine-tuned endpoints) map 1:1 to model strings on providers like OpenAI/Azure (fine-tuned deployments), Vertex AI Gemini, or Bedrock custom models. Switching behavior = switching the model string; composition happens at routing time, not inside a single call.
“Adapter-ID adapters” (multi-LoRA / adapter servers) surface adapter_id parameters on Together AI Serverless Multi-LoRA, LoRAX-style servers, or SageMaker adapter inference components. The backend keeps the base model string and passes adapter_id for one-or-more adapters per request when supported.
Hybrid patterns (local adapter-enabled “controller” + external API “executor”) flow through the same artifacts: the controller uses a local LoRA backend to plan, then the API backend executes with prompt or remote-model adapters.
configure env - one variable
DATABASE_URL – postgres dsn. that is the configuration.four others exist and none of them are settings you tune: BUILD_SHA
(stamped by the build), TEST_MODE (the test harness),
EMBEDDING_VECTOR_DIM (a property of the schema you applied, shared with
scripts/migrate.sh), and EXTRACT_READER_PLUGINS (imports python
modules, so making it settable from a web form would mean remote code
execution).
everything else - the model, credentials, rate limits, ttls, cors, smtp,
the signing key - lives in the database and is edited from the admin
console at /admin, applied to every replica without a restart.
changing an smtp password should not require redeploying the app. for
declarative deploys, seed on first boot with
INSTANCE_SETTINGS_JSON='{"model_backend": "stub"}'.
migrate db
${SHARED_FS_ROOT}/state/training_pg.json after upgrading to purge legacy MFA secrets (secrets are now sourced solely from the user_mfa_secret table).4a. preference_event → adapter dataset → tokenized batches
preference_event rows (positive feedback) capture context_embedding, score, and optional context_text; they are clustered per-user to build adapter personas.${SHARED_FS_ROOT}/users/{user_id}/adapters/{adapter_id}/jobs/{job_id}/dataset.jsonl.${SHARED_FS_ROOT}/users/{user_id}/adapters/{adapter_id}/v####/.start services
open the web ui
MIT
TESTING.md documents the lanes in full. The common ones:
make test-fast-xdist # the default edit-loop lane, about two minutes
make test-xdist # every test but the browser lane, in parallel
make test-browser # the browser lane; needs a Chromium binary
The suite starts its own throwaway Postgres and Redis (tests/harness.py) and
applies sql/schema.sql, so tests exercise the same store and cache the app
runs against. Set TEST_DATABASE_URL / TEST_REDIS_URL to point at existing
services instead.
CI runs the same selection on Python 3.10, 3.11 and 3.12. The wall-clock performance tests run in their own serial pass, so four parallel workers are not competing with the thing being timed.
For a full integration run against containers:
docker compose -f docker-compose.test.yml up --build
./scripts/smoke_test.sh
Key endpoints (Bearer access token required):
POST /v1/auth/signup → returns session + signed access/refresh tokensPOST /v1/auth/login → returns tokens, with MFA gating when enabledPOST /v1/auth/refresh → rotates refresh tokensPOST /v1/chat → creates conversation + LLM replyPOST /v1/responses → the same turn in OpenAI's Responses shape, for agents (api key or session auth; stream: true for SSE)POST /v1/mcp → MCP server (note_search + knowledge_search) for MCP-speaking agentsPOST /v1/auth/api-keys → mint an agent-surface api key (list with GET, revoke with DELETE /v1/auth/api-keys/{id})GET /v1/artifacts → lists data-driven workflows/policiesGET /v1/admin/settings → admin-only system settingsAdmin endpoints (/v1/admin/*, /v1/config/*) require admin role.
max_upload_bytes to prevent unbounded in-memory readsPython
91.1%
JavaScript
4.8%
HTML
1.5%
CSS
1.3%