Sentinel

An in-process autonomous dev agent for Elixir. Captures runtime errors, creates tracker issues, picks up board work, and runs Codex to investigate and fix.

Built in four layers that can be adopted incrementally -- use just error collection, add tracker integration, or go full autonomous with the board watcher and agent.

Installation

Add to your mix.exs:

def deps do
  [
    {:sentinel, "~> 0.1"},
    {:plug, "~> 1.15", optional: true},       # for Sentinel.Plug
    {:provenance, "~> 0.1", optional: true}    # enriches agent context
  ]
end

Sentinel depends on codex_app_server and linear_client (both pulled automatically).

Architecture

                     +------------------+
                     | Your Application |
                     +--------+---------+
                              |
               Logger errors / Sentinel.report/2
                              |
+-----------------------------v-----------------------------+
|  Layer 1: Error Intelligence                              |
|                                                           |
|  Collector ---------> Deduplicator ---------> ErrorBucket |
|  (Logger handler)     (GenServer)             (signature, |
|                                                samples)   |
+-----------------------------+-----------------------------+
                              |
               threshold reached / manual
                              |
+-----------------------------v-----------------------------+
|  Layer 2: Tracker Integration                             |
|                                                           |
|  TrackerServer -----> Tracker behaviour                   |
|  (create/update)      |                                   |
|                       +-> Tracker.Linear (Linear API)     |
|                       +-> (your custom adapter)           |
+-----------------------------+-----------------------------+
                              |
               issues on board labeled "sentinel"
                              |
+-----------------------------v-----------------------------+
|  Layer 3: Board Watcher                                   |
|                                                           |
|  BoardWatcher (GenServer, polls on interval)              |
|  - Fetches issues in pickup states                        |
|  - Self-assigns, transitions to "In Progress"             |
|  - Dispatches to Agent                                    |
+-----------------------------+-----------------------------+
                              |
                              v
+-----------------------------------------------------------+
|  Layer 4: Agent                                           |
|                                                           |
|  Agent ---------> CodexAppServer.run/3                    |
|  (dispatch)       (JSON-RPC to codex app-server)          |
|                                                           |
|  Prompt ---------> builds investigation/task prompts      |
|  (optional)        enriched with Provenance data          |
+-----------------------------------------------------------+

Configuration

Layer 1 -- Error Collection

Captures Logger errors and groups them by signature. No external dependencies.

# config/config.exs
config :sentinel,
  enabled: true,
  workspace: File.cwd!()

Install the Logger handler in your application startup:

# lib/my_app/application.ex
def start(_type, _args) do
  Sentinel.Collector.install()
  # ...
end

Or report errors manually:

Sentinel.report(error,
  stacktrace: __STACKTRACE__,
  metadata: %{request_id: conn.assigns[:request_id]}
)

Optional Plug integration for Phoenix/Plug apps:

# lib/my_app_web/endpoint.ex
plug Sentinel.Plug

Query collected errors:

Sentinel.error_buckets()
# => [%Sentinel.ErrorBucket{id: "eb:a1b2c3d4", signature: %{...}, count: 7, ...}]

Sentinel.status()
# => %{enabled: true, bucket_count: 3, tracker: :configured, agent: :disabled, ...}

Layer 2 -- Tracker Integration

Creates and updates issues in Linear (or any tracker implementing the Sentinel.Tracker behaviour).

# config/config.exs
config :sentinel,
  tracker: {Sentinel.Tracker.Linear,
    api_key: System.get_env("LINEAR_API_KEY"),
    team_id: "YOUR-TEAM-UUID",
    project_slug: "MY-PROJECT"
  }

Issues are created automatically when error buckets are dispatched. You can also create them manually:

bucket = Sentinel.Deduplicator.get_bucket("eb:a1b2c3d4")
Sentinel.TrackerServer.create_or_update(bucket)

Custom Tracker Adapter

Implement the Sentinel.Tracker behaviour:

defmodule MyApp.Tracker.GitHub do
  @behaviour Sentinel.Tracker

  @impl true
  def create_issue(bucket), do: ...

  @impl true
  def update_issue(ref, attrs), do: ...

  @impl true
  def find_existing(bucket), do: ...

  @impl true
  def fetch_available_issues(opts), do: ...

  @impl true
  def assign_issue(id, assignee), do: ...

  @impl true
  def transition_issue(id, state), do: ...

  @impl true
  def add_comment(id, body), do: ...
end
config :sentinel,
  tracker: {MyApp.Tracker.GitHub, repo: "org/repo", token: "..."}

Layer 3 -- Board Watcher

Polls the tracker board for issues labeled "sentinel", self-assigns them, and dispatches to the agent.

# config/config.exs
config :sentinel,
  board_watcher: [
    enabled: true,
    poll_interval_ms: 60_000,
    pickup_states: ["Todo", "Ready"],
    in_progress_state: "In Progress",
    done_state: "Done",
    labels: ["sentinel"],
    max_concurrent: 2,
    assignee: "me"                    # "me" resolves to the API key's user
  ]

The watcher runs as a supervised GenServer. For each eligible issue it:

  1. Assigns the issue to the configured assignee
  2. Transitions it to the in-progress state
  3. Dispatches to Sentinel.Agent.dispatch_board_issue/1
  4. Posts a comment with the result and transitions to done

Layer 4 -- Agent

Runs Codex via codex_app_server to investigate errors or execute board tasks.

# config/config.exs
config :sentinel,
  agent: [
    enabled: true,
    command: "codex app-server",
    approval_policy: "never",
    max_concurrent: 2,
    investigate_threshold: 1           # dispatch after N error occurrences
  ]

The agent builds a prompt from the error bucket or board issue, optionally enriched with provenance data (related modules, tables, dependency graph), and sends it to Codex for investigation.

# Dispatched automatically by Deduplicator when threshold is reached.
# Can also be triggered manually:
Sentinel.Agent.dispatch_error(bucket)
Sentinel.Agent.dispatch_board_issue(issue)

Provenance Integration

When provenance is installed, Sentinel enriches agent prompts with lineage data:

This gives the agent significantly more context for investigation. No additional configuration is needed -- Sentinel detects provenance automatically.

Full Configuration Example

# config/dev.exs
config :sentinel,
  enabled: true,
  workspace: File.cwd!(),

  tracker: {Sentinel.Tracker.Linear,
    api_key: System.get_env("LINEAR_API_KEY"),
    team_id: "YOUR-TEAM-UUID",
    project_slug: "MY-PROJECT"
  },

  board_watcher: [
    enabled: true,
    poll_interval_ms: 60_000,
    pickup_states: ["Todo", "Ready"],
    in_progress_state: "In Progress",
    done_state: "Done",
    labels: ["sentinel"],
    max_concurrent: 2,
    assignee: "me"
  ],

  agent: [
    enabled: true,
    command: "codex app-server",
    approval_policy: "never",
    max_concurrent: 2,
    investigate_threshold: 1
  ]
# config/prod.exs -- conservative defaults
config :sentinel,
  enabled: true,
  workspace: File.cwd!(),

  tracker: {Sentinel.Tracker.Linear,
    api_key: System.get_env("LINEAR_API_KEY"),
    team_id: "YOUR-TEAM-UUID",
    project_slug: "MY-PROJECT"
  },

  board_watcher: [enabled: false],
  agent: [enabled: false]

Environment Behavior

Capability dev staging prod
Error collection yes yes yes
Deduplication yes yes yes
Ticket creation yes yes yes
Board watching yes yes no (default)
Agent investigation yes yes no
Code changes / PRs yes configurable no

Supervision Tree

Sentinel starts the following processes under its application supervisor:

Sentinel.Collector is not auto-started. Call Sentinel.Collector.install/0 in your application's start/2 to attach the Logger handler.

Specification

Sentinel implements the Sentinel Specification (Draft v1), a language-agnostic contract for provenance IDs, error buckets, tracker adapters, and agent dispatch.

License

MIT -- see LICENSE.