Extend Loom
Loom has one registry per kind of thing: operations, workers, settings, providers, language lanes, extraction steps and tracker sources. To extend Loom, add a row to the right registry; HTTP, MCP, the SDKs and the generated reference follow.
Check first whether data alone does it. A tracker source, a record schema, a procedure or a history policy is configured, not programmed.
flowchart LR
subgraph Data["Configure, no code"]
T["tracker source<br/>tracker.source.add"]
R["record schema<br/>memory_schema.register"]
P["history policy<br/>policy.set"]
end
subgraph Code["Add a registry row"]
O["operation<br/>server/<domain>/api"]
W["worker<br/>server/workers/registry.py"]
L["language lane<br/>server/code/domain/extract/lang_*.py"]
S["pipeline step<br/>register_step"]
V["provider<br/>server/embedding, server/llm"]
M["table / column<br/>schemas/migrations"]
end
subgraph Clients["Outside the server"]
I["plugins, hooks, skills<br/>integrations/"]
end
Choose the extension point
| You want to… | Extension point | Where | Guide |
|---|---|---|---|
| Expose a new capability to callers | An operation | server/<domain>/api/ops_<group>.py, server/<domain>/app/<group>.py |
Add an operation |
| Do work in the background on a schedule | A worker | server/<domain>/workers/<name>.py, a row in server/workers/registry.py |
Add a worker |
| Store something new | A table or column | schemas/migrations/<NNN>_*.sql, server/<domain>/store/ |
Add a table |
| Index a new programming language | A code-index language lane | server/code/domain/extract/lang_<lang>.py, lang_detect.py, server/code/app/index/ingest.py |
Add language support |
| Change what extraction keeps for some callers | A pipeline step | register_step in server/memory/app/pipeline.py |
Extraction pipeline |
| Read a new issue tracker or CI history | A tracker source (data only) | tracker.source.add + tracker.source.set_mapping |
Tracker record contract |
| Hold facts with an identity and typed fields | A record schema (data only) | memory_schema.register, memory.record.write |
Writing memory |
| Restrict what agents may do based on their history | A history policy (data only) | policy.set |
Tokens and grants |
| Use another embedding or LLM provider | A provider client | server/embedding/, server/llm/ |
below |
| Add a setting | A settings field | The area's section of server/settings/catalog.py |
below |
| Capture sessions from another client, or ship agent guidance | An integration, hook or skill | integrations/, skills/ |
below |
What every extension shares
- The code is the registry. Transports iterate
ALL_OPERATIONS, the supervisor iterates_WORKERS, and the settings reference is generated fromserver/settings/. Each registry has a snapshot or census test that fails until you regenerate it. - Generated files.
make lintruns every generator with--check: the SDK op tables (scripts/codegen/sdk_py_gen.py,sdk_ts_gen.py), the reach manifest (op_reach_census.py), the reference pages (docs_reference.py,settings_reference.py) and the Codex plugin copies (agent_plugin_sync.py). See code generation. - Maturity. A new op declares
maturity="experimental"unless it replaces a stable one (adding an operation). - Measurement. A change to ranking, extraction or a provider default runs the relevant bench before and after (performance tuning).
- Foundational shapes are not extension points. The engine (ClickHouse), the identity tuple, per-row content-hash audit and the five belief-revision operations change only with a design rationale in the PR and in the architecture docs (system overview).
Add a provider
Embedders and LLMs are clients chosen by settings.
An embedder goes in server/embedding/ beside openai.py, gemini.py, local.py and fake.py. It shares the HTTP plumbing in server/embedding/_provider.py:
- retries on retryable statuses only, three attempts;
EmbedderInputTooLargeinstead of a silent provider truncation;MalformedEmbeddingfor an empty, wrong-width or non-finite vector.
Wire it in server/embedding/embedder.py: provider inference from LOOM_EMBEDDING_MODEL (_inferred_provider), the LOOM_EMBEDDING_PROVIDER values, and the key variables in KEY_ENVS. A missing key falls back to the fake embedder and logs an error.
An LLM goes in server/llm/ beside openai.py, anthropic.py and fake.py and implements LLMClient (server/llm/client.py). Wire it in _build_llm_client and in DEFAULT_MODEL_BY_PROVIDER, KEY_ENV_BY_PROVIDER and resolve_llm_provider (server/core/llm_provider.py).
Warning: a provider changes quality. The similarity gates are calibrated to
text-embedding-3-small. Runmake bench-quickagainst a new provider before making it a default. A new embedder needsloom reembedon every existing namespace.
Add a setting
A new field goes in its area's section of server/settings/catalog.py (MemorySearch, Code, Tracker, …), the one file every setting is declared in. The comment above it says what it controls and what its values mean; that comment becomes its row in the generated configuration reference. The environment name is LOOM_<FIELD>.
Read it as settings.<field>, never getattr(settings, "<field>", default). tests/unit/test_settings_attribute_access.py fails on the latter: a copied default drifts and hides the reader from whoever deletes the field. Regenerate with python scripts/codegen/settings_reference.py and python scripts/codegen/settings_schema.py.
To retire a flag, delete the setting, its branch and any per-request field that carried it. A client that still sends the field is served, with the field named in ignored_args.
Add an integration
| Integration | Where | Notes |
|---|---|---|
| A coding-agent plugin hook or skill | integrations/claude-code-plugin/ (hooks/, lib/, skills/) |
The source of truth; python scripts/codegen/agent_plugin_sync.py copies scripts, skills and the listing icon (assets/) into integrations/codex-plugin/. Wire a new hook into the Codex hooks.json by hand. Installed clients pick up a change when the plugin's version in its manifest changes. |
| A session-capture hook for another client | integrations/<client>/ (cursor/, generic/) |
Call loom ingest-transcript or conversation.append, and memory.set_from_messages for extraction. |
| A portable agent skill | skills/<name>/SKILL.md |
Installed by name into any Agent-Skills client; guidance only, no hooks. |
| A framework adapter | sdk/python/loom/integrations/, sdk/typescript/src/integrations/ |
Wrap the SDK in the framework's store or memory interface (LangGraph LoomStore and Strands LoomMemoryStore in Python, a Strands adapter in TypeScript). |
| An observability backend | server/observability/ |
Spans already carry loom.request_id and export through OTLP; prefer an OTLP collector over a new exporter. |
Integrations describes the shipped ones. Agent integration covers what any new client should write and read.