Ingot is a composable Phoenix LiveView labeling feature module that can be embedded in any Phoenix application. Built on top of Forge and Anvil, it provides a portable labeling interface with pluggable backends.
In a world already full of perfectly good data labeling tools, Ingot is the one that runs on the BEAM and can be mounted in your existing Phoenix app with a single router macro.
Ingot is a composable feature library providing labeling UI for Elixir/Phoenix applications. It consists of:
- Forge integration – creates and manages samples via pipelines
- Anvil integration – manages human labeling queues, assignments, labels, and agreements
- Host-agnostic LiveViews – portable labeling interface using
Phoenix.LiveViewdirectly - Backend behaviour – pluggable data layer (use Anvil, Ecto, HTTP API, or in-memory)
- Router macro – mount labeling routes with
labeling_routes/2
Ingot's job is to:
- Provide portable labeling UIs that work in any Phoenix app
- Render labeling interfaces from label schemas
- Enable custom sample/form rendering via component behaviours
- Stay out of the way of your business logic
Think of it as a feature module you can drop into any Phoenix app, similar to phoenix_live_dashboard or oban_web.
Add to your mix.exs:
def deps do
[
{:ingot, "~> 0.2.0"}
]
enddefmodule MyApp.LabelingBackend do
@behaviour Ingot.Labeling.Backend
@impl true
def get_next_assignment(queue_id, user_id, opts) do
# Your implementation
end
@impl true
def submit_label(assignment_id, label, opts) do
# Your implementation
end
@impl true
def get_queue_stats(queue_id, opts) do
# Your implementation
end
endOr use the Anvil adapter:
# Use Ingot.Labeling.AnvilClientBackend if you have Anvil configureddefmodule MyAppWeb.Router do
use MyAppWeb, :router
import Ingot.Labeling.Router
scope "/" do
pipe_through [:browser, :require_authenticated]
labeling_routes "/labeling",
on_mount: [MyAppWeb.AuthLive],
root_layout: {MyAppWeb.Layouts, :app},
config: %{
backend: MyApp.LabelingBackend,
default_queue_id: "my-queue"
}
end
end- Visit
/labelingfor the dashboard - Visit
/labeling/queues/:queue_id/labelto start labeling
See example/ for a complete working example.
- Schema-driven UI from Anvil label definitions
- Real-time updates via LiveView and PubSub
- Keyboard-first labeling (minimal mouse usage)
- Skip / flagging for problematic samples
- Optional per-sample timing and basic analytics
- Queue and pipeline status pages
- Labeler activity and velocity
- Agreement metrics surface (Cohen/Fleiss/… via Anvil)
- Export hooks for downstream training / analysis
- Stateless HTTP on the edge, supervised processes underneath
- Backed by Forge samples and Anvil queues
- Plays nicely with your existing Elixir stack, telemetry, and job runners
You might want Ingot if:
- You already use or want to use Forge and Anvil
- You want a self-hosted, Elixir-native labeling UI
- You prefer LiveView over a separate SPA frontend
- You enjoy the perverse satisfaction of saying “yes, our data labeling platform is written in Elixir” and having it be factually correct
You probably don’t want Ingot if you just need a generic labeling SaaS in five minutes. There are plenty of those.
- Elixir 1.15+
- Erlang/OTP 26+
- Node.js 18+ (for asset compilation)
forgeandanvilfrom Hex (~> 0.1.0)
git clone https://github.com/North-Shore-AI/ingot.git
cd ingotInstall dependencies:
mix setupStart the Phoenix server:
mix phx.server
# or
iex -S mix phx.serverThen open http://localhost:4000.
Application configuration lives under config/.
Key options:
# config/config.exs
config :ingot,
# Session timeout in milliseconds
session_timeout: :timer.hours(2),
# Maximum samples per labeling session
max_samples_per_session: 500,
# Enable/disable keyboard shortcuts
keyboard_shortcuts_enabled: trueYou’ll also need to configure Forge and Anvil (pipelines, queues, label schemas) in your umbrella or host application.
Ingot talks to Forge/Anvil through configurable client adapters:
-
HTTP (default) – In
config/config.exsandprod.exs, adapters are set toIngot.ForgeClient.HTTPAdapter/Ingot.AnvilClient.HTTPAdapter. You must run Forge and Anvil with their Plug/Cowboy servers enabled (Forge defaults to port4102, Anvil to4101) and setforge_base_url/anvil_base_urland timeouts. -
Elixir (in-VM) – To call the libraries directly inside the same BEAM, include the deps as runtime (remove
optional: true, runtime: falseforforge_ex/anvil_exin your app’smix.exs) and set:# config/runtime.exs config :ingot, forge_client_adapter: Ingot.ForgeClient.ElixirAdapter, anvil_client_adapter: Ingot.AnvilClient.ElixirAdapter
In this mode you don’t need the HTTP URLs, but you do need to start the Forge/Anvil apps in the same VM.
-
Mock – In tests,
config/test.exspoints to mock adapters; no services required.
Pick one path per environment and ensure the corresponding services (HTTP) or deps/apps (Elixir) are available.
In your Elixir app (outside Ingot):
- Use Forge to define pipelines and produce samples.
- Use Anvil to define label schemas and queues referencing those samples.
Ingot doesn’t care about the domain; it just talks to Forge/Anvil.
By default:
- Navigate to
/label - Ingot fetches the next assignment from Anvil
- LiveView renders the form derived from the label schema
- Labeler completes fields / ratings (keyboard shortcuts where enabled)
- Submit → Ingot writes labels via Anvil → next assignment
Typical keyboard shortcuts (configurable):
1–5– quick rating on focused dimensionTab– move between fieldsEnter– submitS– skip sampleQ– end session
Navigate to /dashboard to:
- Inspect queue depth and throughput
- See active labelers and recent activity
- View basic agreement metrics exposed by Anvil
- Trigger or link out to exports for downstream pipelines
ingot/
├── lib/
│ ├── ingot/
│ │ └── application.ex # Application supervisor
│ └── ingot_web/
│ ├── components/
│ │ └── core_components.ex # Shared UI components
│ ├── live/
│ │ ├── labeling_live.ex # Labeling interface
│ │ ├── dashboard_live.ex # Admin dashboard
│ │ └── components/
│ │ ├── sample_component.ex
│ │ ├── label_form_component.ex
│ │ └── progress_component.ex
│ ├── router.ex # Routes
│ └── endpoint.ex # Phoenix endpoint
├── assets/ # Frontend assets
├── docs/
│ └── adrs/ # Architecture decision records
└── test/
└── ingot_web/
└── live/ # LiveView tests
Run the test suite:
mix testWith coverage:
mix test --coverPrecommit-style checks (format, compile, test) if wired:
mix precommitDetailed decisions live in docs/adrs/, including:
- Thin wrapper architecture over Forge/Anvil
- LiveView-first UI design
- Session management and time tracking
- Real-time updates via PubSub
- Integration boundaries with Forge and Anvil
The short version: Phoenix only does presentation and request orchestration; the actual thinking happens in the libraries.
- Phoenix – Web framework
- Phoenix LiveView – Real-time UI
- Forge (
~> 0.1.0via Hex) – Sample generation and pipelines - Anvil (
~> 0.1.0via Hex) – Labeling queues, labels, agreements
If you, too, feel that the universe needed a BEAM-native data labeling UI:
- Fork the repo
- Create a branch:
git checkout -b feature/thing - Make changes
- Run tests:
mix precommit(or at leastmix test) - Open a PR
Copyright (c) 2025 North Shore AI
This project is part of the North Shore AI organization.
- Ingot: https://github.com/North-Shore-AI/ingot
- Forge: https://github.com/North-Shore-AI/forge
- Anvil: https://github.com/North-Shore-AI/anvil
- Phoenix: https://www.phoenixframework.org/
- Phoenix LiveView: https://hexdocs.pm/phoenix_live_view/
For questions or support, please open an issue on GitHub.