Get from a fresh clone to a running Cairnloop operator dashboard in a few minutes.
This guide follows the example app at examples/cairnloop_example/ as its reference.
Prerequisites
- Elixir 1.15+ / OTP 26+
- Postgres 16+ with the
pgvectorextension installed
The pgvector extension powers Cairnloop's Knowledge Base embeddings. If you need a
containerized Postgres with pgvector, the repository ships a docker-compose.yml at the
repo root. From the repo root run:
docker compose up -d db
Install
Cairnloop ships an Igniter installer that adds the dependency and generates the database migration for you.
First, fetch deps:
mix deps.get
Then run the installer:
mix cairnloop.install
The installer does two things:
- Adds
{:cairnloop, "~> 0.1.0"}to yourmix.exsdependencies. - Detects your Ecto repo via
Igniter.Libs.Ecto.select_repo/1and generates acreate_cairnloop_tablesmigration that createscairnloop_conversationsandcairnloop_messageswith the correct schema.
If no Ecto repo is found, the installer emits:
No Ecto repo found. Please create a migration manually for cairnloop tables.In that case, create a priv/repo/migrations/<timestamp>_create_cairnloop_tables.exs
migration by hand before running mix ecto.migrate.
Run migrations
After the installer has run, apply both your host app's migrations and the Cairnloop library's own migrations:
# Run host migrations (generated by the installer or written by hand)
mix ecto.migrate
# Run the Cairnloop library's own migrations
mix ecto.migrate --migrations-path deps/cairnloop/priv/repo/migrations
The library ships 15+ additional migrations (knowledge base, retrieval corpus, gap
candidates, article suggestions, outbound, and more) that mix ecto.migrate alone will
not apply. Skipping this second command will cause Postgrex.Error relation-not-found
errors the first time any non-chat feature is exercised.
Tip: Add both commands to your
ecto.setupalias inmix.exsso they always run together:"ecto.setup": ["ecto.create", "ecto.migrate", "ecto.migrate --migrations-path deps/cairnloop/priv/repo/migrations", "run priv/repo/seeds.exs"]
Manual install (without Igniter)
If you prefer not to use Igniter, add Cairnloop to your deps directly:
# mix.exs
def deps do
[
{:cairnloop, "~> 0.1.0"}
]
endThen create the create_cairnloop_tables migration manually.
Mount the Dashboard
Import the cairnloop_dashboard/2 macro and mount it under a scope in your router.
The example app uses /support:
# lib/my_app_web/router.ex
import Cairnloop.Router, only: [cairnloop_dashboard: 2]
scope "/support", MyAppWeb do
pipe_through :browser
cairnloop_dashboard("/", session: %{"host_user_id" => "demo_operator"})
endThe cairnloop_dashboard/2 macro mounts routes under whatever path you pass as its
first argument. With the /support scope above:
- Cockpit Home (the task-oriented landing) is at
/support - Inbox is at
/support/inbox - A conversation is at
/support/:id - Knowledge Base is at
/support/knowledge-base - Settings is at
/support/settings
This guide assumes /support as in the example app. Adjust paths to match your own
scope if you mount elsewhere.
Route convention: Always use the path you pass to
cairnloop_dashboard/2. The internal integration-test routes (not the shipped macro routes) will 404 for adopters.
Styling
Cairnloop ships a self-contained, themeable stylesheet in the package at
priv/static/cairnloop.css. It defines the design tokens (--cl-*) and the .cl-*
component classes the dashboard renders — no Tailwind or daisyUI required in your app.
Include it once.
If you bundle CSS (esbuild/Tailwind), import it from your own stylesheet:
@import "../../deps/cairnloop/priv/static/cairnloop.css";Or serve the packaged asset directly and link it from your root layout:
# endpoint.ex
plug Plug.Static, at: "/cairnloop", from: {:cairnloop, "priv/static"}, only: ["cairnloop.css"]<link rel="stylesheet" href="/cairnloop/cairnloop.css" />Theming: override any --cl-* token in CSS loaded after cairnloop.css to re-skin
the dashboard to your brand, and toggle dark mode by setting data-theme="dark" on <html>.
Boot
The commands below are for the example app. Switch into it first, then set up the database and start the server:
cd examples/cairnloop_example
mix setup
mix phx.server
Then visit http://localhost:4000 for the guided demo index — it frames the Trailmark scenario and links to every stage of the JTBD Walkthrough. Or jump straight to the operator inbox at http://localhost:4000/support.
You should see the Cairnloop operator inbox. The example app's seed places 20 conversations across all lifecycle states — including ones pre-positioned in each JTBD state — so every screen is live and clickable immediately.
Port already in use? Start the server with a different port:
PORT=4010 mix phx.server(and setPGPORTif your Postgres isn't on 5433). The example honors both.
Next Steps
- JTBD Walkthrough — walk the full Jobs-To-Be-Done lifecycle in the seeded example: inbox → conversation workspace → cmd+k search → AI draft approval → governed tool approval → resolve → outbound trigger → bulk recovery.
- Host Integration — implement the four host behaviour
contracts (
ContextProvider,Notifier,AutomationPolicy,SLAPolicyProvider) so Cairnloop knows your app's context and policy. - Auth & Operator Identity — wire your real
authenticated operator into the dashboard so the audit log attributes actions correctly.
Read this before going past the demo — the hardcoded
host_user_idshown in quickstarts is a trap in production. - Troubleshooting — resolve common install, migration, pgvector, and mount-config errors.