Extensions

Build your own on the platform.

An extension is a new face built right next to the data — a new UI, a new ingestor, a new domain — that runs in-process on the engine and reads the same one copy every other face reads. It owns no storage of its own, so there is nothing to sync and no second store to keep in step. This is the same Tier-3 pattern Semurg's own faces are built in.

Two ways to get one. Hand-build it — follow the pattern below; a working template ships in the tree to copy Live. Or Helix — describe what you want and a small swarm of role-agents builds, tests, and deploys the extension for you, inside the same guardrails Preview.
The pattern

A Tier-3 extension, defined.

An extension is a normal application in the engine's umbrella — it lives at apps/<your_name> — that depends on one thing: the engine. It is built in the same Elixir-over-Rust ("Octopus") pattern as Semurg itself, and it exposes its face with Phoenix LiveView. Four properties define it, and all four are how Semurg's own faces already work:

Where it lives

apps/<name> Live

A first-class app in the umbrella. Its only in-tree dependency is the engine app (substrate_core). It compiles, tests, and ships alongside the engine.

Its data layer

The substrate, and only the substrate Live

The extension holds handles the engine vends and calls the engine's data functions. It never opens a raw file or keeps a side store — handle, not open. One copy, read by every face.

Its direction

Inward only Live

It builds on the engine and never reaches sideways into another extension. The engine holds itself to this same strictly-inward rule, checked in CI (mix semurg.check_rings).

Its face

Phoenix LiveView Live

The UI is a LiveView, the same stack the console renders through (phoenix_live_view ~> 1.0). Server-rendered, live-updating, no separate front-end build to keep in step with the data.

Why "handle, not open". The storage, the memory, the page cache, the background upkeep — all of that is always the engine's job. An extension holds handles and calls functions; it does not manage bytes on disk. That is what makes "one copy, no second store" a structural fact and not a promise you have to keep by hand.
The five rules

The guardrails every extension follows.

Whether you hand-build it or Helix builds it for you, an extension is only a valid Semurg extension if it holds to these. They are the same rules Semurg's own faces obey.

1
The substrate is your only data layer. Read and write through the engine's data functions. No second database, no cache of your own, no side file — the engine owns storage.
2
Handle, not open. Take the handles the engine vends and call functions on them. Never open a raw path or reach under the engine.
3
Inward only. Depend on the engine, never sideways on another extension. One direction, enforced by the build.
4
Phoenix LiveView for the UI. Your face is a LiveView, so it renders next to the data and updates live with no separate front-end to sync.
5
The Octopus Elixir/Rust pattern. Coordination and the UI in Elixir/OTP (share-nothing processes that own their state); hot inner loops, when you need them, in a Rust NIF handed a batch — the same shape the engine is built in.
Anatomy

What the code looks like.

An extension is a small umbrella app. Its mix.exs declares exactly one in-tree dependency — the engine — which is what makes "the substrate is my only data layer" true at the build level, not just by intention:

# apps/my_extension/mix.exs
defp deps do
  [{:substrate_core, in_umbrella: true}]   # the engine — the ONLY data dependency
end

The extension's own module is a share-nothing OTP process that owns the handles the engine vends and answers calls by delegating to the engine's data functions. It never touches a file itself:

# apps/my_extension/lib/my_extension/store.ex — sketch, real engine functions
defmodule MyExtension.Store do
  use GenServer
  alias SubstrateCore.{Shard, Container}   # the engine's public data surface

  # this process OWNS the handles — share-nothing, no shared-mutable state
  def handle_call({:neighbors, id}, _from, s),
    do: {:reply, Shard.neighbors(s.nav, id), s}

  def handle_call({:put_vectors, pairs}, _from, s) do
    batch = Enum.map(pairs, fn {node_id, code} ->
      Container.vec(vec_id(node_id), code, 1)  # build 64-byte containers, hand the engine a BATCH
    end)
    {:reply, Shard.append(s.of_record, batch), s}
  end
end

SubstrateCore.Shard (graph reads, k-hop, vectors, append) and SubstrateCore.Container (the 64-byte content-addressed record) are the engine's public data surface — the same functions the shipping template calls. Your LiveView calls your store; your store calls the engine. Nothing else touches the data.

The template

A working extension already ships in the tree. Live

You do not start from a blank page. The two-way database ingestorapps/db_ingest_substrate — is a real, tested extension that imports relational and graph databases (Postgres, MySQL, SQLite, Neo4j) into the one substrate and reads them back out. It is the canonical pattern to copy:

  • Its mix.exs depends only on the engine — nothing sideways.
  • Its store is a share-nothing process that owns the engine's handles and calls Shard.neighbors, Container.vec, k-hop and append — it keeps no second copy of what it imports.
  • It ships with golden tests (including a "no sidecar" test that proves it adds no side store).
Read it as the reference. Everything above is demonstrated by a component that is already compiled, tested, and shipping — not a hypothetical. When you get the extension kit, this is the example you start from.
The boundary

Inward-only, held by the toolchain.

Semurg's own faces are arranged in strictly-inward rings — the UI ring depends on the intelligence ring, which depends on the engine, which depends on a shared base, and nothing points outward. That is not a convention people remember to follow; it is a property the build checks:

# runs first in CI, before compile — a violation is a RED build, not a code review
mix semurg.check_rings
✓ semurg.check_rings: inward DAG intact

Your extension holds to the same discipline: it depends only on the engine app, so it can never entangle itself with another extension. The engine app declares no web framework at all — Phoenix lives only at the face — so the data layer stays clean of the UI.

Honest scope. The automated ring-check gates the engine's four core apps today. An extension keeps the same inward-only rule by depending only on the engine, and the shipping template shows exactly how; a one-command per-extension boundary gate is a small, planned addition to the same tool Coming.
Honest status

What you can do today, and what's coming.

CapabilityStatusDetail
The Tier-3 extension patternLiveapps/<name>, engine-only data layer, handle-not-open, inward-only, no side store — real and in production use by Semurg's own faces.
A working template to copyLiveThe two-way database ingestor ships in the tree, tested, with a no-sidecar proof.
Phoenix LiveView UILiveThe console and demos render through LiveView 1.0; your extension's face uses the same stack.
Octopus Elixir/Rust patternLiveThe engine is built this way; the Rust NIF toolchain and share-nothing OTP model are the same you build in.
In-process extension developmentLiveOffered to design partners and licensees today — talk to us and we get you the kit.
Public self-serve kit + one-command scaffoldComingA downloadable kit and a generator that stamps out apps/<name> with the store, LiveView, and tests wired.
Per-extension automated boundary gateComingThe same ring-check, pointed at your app, as a one-command guard.
Helix — the swarm builds it for youPreviewIn development; see below for the honest status.
Helix Preview · in development

Describe it. A swarm builds the extension.

Helix is the shortcut to everything above. You write a short spec — what the extension should do — and a small team of role-agents takes it from there: one develops it, one reviews it, a manager coordinates the work through an approval gate, and a security reviewer checks it. They iterate, run the tests, and deploy the finished extension onto the platform.

Role

Developer

Turns the spec into an apps/<name> extension: the store over the engine, the LiveView face, the tests.

Role

Reviewer

Reads the diff for correctness and for the five rules — engine-only data, handle-not-open, inward-only — and sends it back until it holds.

Role

Manager

Coordinates the loop and carries the work through an approval gate. It coordinates; it does not silently override the others.

Role

Security

Checks the extension against the boundary and safety rules before anything is deployed. Nothing ships that fails this gate.

The cap — what Helix is, and is not

Helix is deliberately narrow. It is not a general coding tool. It builds one kind of thing: extensions and apps that run on the platform, and every one it produces must clear the same guardrails as a hand-built extension.

  • It only builds extensions and apps on the platform — not arbitrary software.
  • Every extension it builds must use the substrate as its data layer — no side store.
  • Every UI it builds must be Phoenix LiveView.
  • Every extension must follow the Octopus Elixir/Rust pattern.
  • Nothing deploys until the review and security gates pass.
Honest status — Preview. Helix is in development. The extension pattern, the in-tree template, and the LiveView face it targets are all live today; the role-agents that take a spec all the way to a built, tested, deployed extension without a hand on the wheel are not general-availability yet. We are previewing the end-to-end loop, not claiming it is finished. If a step still needs a human, we will say so rather than pretend otherwise.
Want to see it? Extension development — by hand or with Helix — is opened to design partners and licensees first. Ask for the Helix preview, or start hand-building today with the pattern above and the Develop guide.