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 pgvector extension 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:

  1. Adds {:cairnloop, "~> 0.1.0"} to your mix.exs dependencies.
  2. Detects your Ecto repo via Igniter.Libs.Ecto.select_repo/1 and generates a create_cairnloop_tables migration that creates cairnloop_conversations and cairnloop_messages with 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.setup alias in mix.exs so 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"}
  ]
end

Then 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"})
end

The 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 set PGPORT if 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_id shown in quickstarts is a trap in production.
  • Troubleshooting — resolve common install, migration, pgvector, and mount-config errors.