A secure-by-default personal AI assistant built on the BEAM. OTP-native, fault-tolerant, and distributed by design.
A Claw clone that utilizes BeamAgent to allow for TOS-compliant, subscription-based account usage. A cautionary tale proving that technical adherence to the letter of a request is the most effective way to subvert its intended restrictions. "Be careful what you wish for..."
The AI assistant product category is proven, but existing solutions have catastrophic security postures. MonkeyClaw delivers the same capabilities with security built into the platform, not patched on top.
- Secure by default — Default-deny policy, process isolation, no implicit system access. Security comes from the BEAM, not patches.
- OTP supervision — Every agent is a supervised process. Crashes are isolated, recovered, and audited automatically.
- Distributed — Single user, multiple nodes. Run agents across machines with encrypted BEAM distribution.
- Extensible — Plug-based extension model for application-level capabilities, plus agent-level MCP, skills, and plugins via BeamAgent.
- Streaming — Real-time token-by-token response delivery through the full stack, from BeamAgent through to LiveView progressive rendering.
- Multi-agent — Five AI backend adapters (Claude, Codex, Gemini, OpenCode, Copilot) via BeamAgent, with unified session management.
┌─────────────────────────────────────────────────────┐
│ Workflow Layer │
│ MonkeyClaw.Workflows — conversation recipes │
├─────────────────────────────────────────────────────┤
│ Product Layer │
│ MonkeyClaw — assistants · workspaces · experiments │
│ scheduling · user modeling · webhooks · notifs · │
│ channels · vault · model registry │
├─────────────────────────────────────────────────────┤
│ Extension Layer │
│ Plug pipelines — hooks · contexts · pipelines │
├─────────────────────────────────────────────────────┤
│ Agent Bridge │
│ Backend behaviour · Session GenServer · Telemetry │
└────────────────────────┬────────────────────────────┘
│
┌────────────────────────▼────────────────────────────┐
│ Elixir API │
│ beam_agent_ex — sessions · threads · memory │
└────────────────────────┬────────────────────────────┘
│
┌────────────────────────▼────────────────────────────┐
│ Runtime Substrate │
│ BeamAgent — orchestration · audit · transports │
└─────────────────────────────────────────────────────┘
MonkeyClaw is the product layer. BeamAgent is the runtime substrate. Clean separation of concerns, connected through a public Elixir API.
| Concept | Module | Purpose |
|---|---|---|
| Assistant | MonkeyClaw.Assistants.Assistant |
AI persona — name, model, system prompt, provider |
| Workspace | MonkeyClaw.Workspaces.Workspace |
Project container, maps 1:1 to a BeamAgent session |
| Channel | MonkeyClaw.Workspaces.Channel |
Conversation thread within a workspace |
| Plug | MonkeyClaw.Extensions.Plug |
Extension behaviour — init/1 + call/2 on a context |
| Context | MonkeyClaw.Extensions.Context |
Data struct flowing through extension pipelines |
| Pipeline | MonkeyClaw.Extensions.Pipeline |
Compiled, ordered chain of plugs for a hook point |
| Workflow | MonkeyClaw.Workflows.Conversation |
Product-level orchestration recipe |
| Experiment | MonkeyClaw.Experiments.Experiment |
Bounded optimization loop with strategy-driven iteration |
| Recall | MonkeyClaw.Recall |
Cross-session history search and context injection |
| Skills | MonkeyClaw.Skills |
Reusable procedures extracted from successful experiments with FTS5 search and effectiveness scoring |
| Scheduling | MonkeyClaw.Scheduling |
Timed experiment runs — once or recurring — with status lifecycle and run tracking |
| UserModeling | MonkeyClaw.UserModeling |
Privacy-aware observation of user interactions, topic extraction, and injectable context for personalized queries |
| Webhooks | MonkeyClaw.Webhooks |
Multi-source webhook ingress (16 built-in sources) with source-specific signature verification, replay detection, rate limiting, and async agent dispatch |
| Notifications | MonkeyClaw.Notifications |
Event-driven notification system — routes telemetry events to user-facing alerts via PubSub (real-time) and email (async), with workspace-scoped rules, severity thresholds, and ETS-cached routing |
| Channels | MonkeyClaw.Channels |
Bi-directional platform adapters — Slack, Discord, Telegram, WhatsApp, Web — with adapter behaviour, message recording, webhook verification, and async agent dispatch |
| Vault | MonkeyClaw.Vault |
Encrypted secret and OAuth token storage with @secret:name opaque references — model never sees plaintext; AES-256-GCM encryption at rest with HKDF-derived keys |
| ModelRegistry | MonkeyClaw.ModelRegistry |
Backend-keyed model registry — GenServer with per-backend probes, SQLite persistence, ETS read-through cache, and configurable refresh intervals per (backend, provider) pair |
Contexts (MonkeyClaw.Assistants, MonkeyClaw.Workspaces, MonkeyClaw.Webhooks, MonkeyClaw.Notifications, MonkeyClaw.Channels, MonkeyClaw.Vault) provide the
public CRUD API. MonkeyClaw.AgentBridge translates domain objects into
BeamAgent session and thread configurations. MonkeyClaw.Workflows
composes these into user-facing operations.
Plug-based extension system for application-level capabilities. Plugs
use the init/1 + call/2 pattern on a context struct — the same
contract as Plug.Conn, applied to MonkeyClaw lifecycle events instead
of HTTP requests. Extensions do not replace agent-level MCP, skills, or
plugins, which flow through BeamAgent.
Sixteen hook points span queries, sessions, workspaces, channels,
and experiments.
Global plugs run on every event; hook-specific plugs run only on their
declared hook. Pipelines are compiled once at application start and
cached in :persistent_term for zero-overhead runtime lookups.
Workflows are product-level recipes that compose domain entities, agent
sessions, and extension hooks into cohesive user-facing operations. The
Conversation workflow implements the canonical "talk to an agent" flow:
- Load workspace and assistant from the database
- Ensure a BeamAgent session is running
- Find or create the conversation channel and thread
- Fire
:query_preextension hooks (plugs can halt or enrich) - Send the query through AgentBridge
- Fire
:query_postextension hooks - Return the result
Workflows are pure function modules — no processes. They orchestrate existing APIs; generic mechanics stay in BeamAgent.
The Conversation workflow also provides stream_message/4, which
replaces step 5 with AgentBridge.stream_query/3 and delivers
response chunks progressively to the caller:
{:stream_chunk, session_id, chunk}— A response fragment{:stream_done, session_id}— Stream completed successfully{:stream_error, session_id, reason}— Stream failed
Post-hooks run after the caller has accumulated the full response.
Autonomous, bounded iteration loops for code optimization and other strategy-driven tasks. The engine runs evaluate-decide cycles over a BeamAgent session, with full rollback safety and human override gates.
Three-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Strategy | MonkeyClaw.Experiments.Strategy |
Domain logic — state, prompts, evaluation, decisions |
| Runner | MonkeyClaw.Experiments.Runner |
Control flow — iteration loop, time budget, persistence |
| BeamAgent | MonkeyClaw.AgentBridge.Backend |
Execution — runs, tools, checkpoints |
Each experiment is a state machine (created → running → evaluating → accepted/rejected/halted/cancelled) driven by a strategy behaviour.
Strategies define how to prepare iterations, build prompts, evaluate
results, and decide whether to continue, accept, reject, or halt.
Features include async execution via Task.Supervisor.async_nolink
(GenServer stays responsive for timeouts and cancellation), mutation
scope enforcement (strategy declares allowed files, Runner rejects
out-of-scope changes), optional human decision gates (non-blocking),
automatic BeamAgent checkpoint save/rewind on rollback, configurable
per-experiment time budgets, full telemetry instrumentation, and
defense-in-depth secret scrubbing of strategy state, evaluation
results, and the final experiment result before persistence.
A lifecycle API (start_experiment/3, stop_experiment/1,
cancel_experiment/1, experiment_status/1) provides atomic
create-and-start with cleanup on failure, graceful stop, immediate
cancel, and live-or-persisted status queries. Extension hooks fire at
four lifecycle boundaries (:experiment_started, :iteration_started,
:iteration_completed, :experiment_completed), and PubSub broadcasts
on "experiment:#{id}" topics enable real-time LiveView observation.
The final evaluation result is persisted as experiment.result on
completion.
Automatic injection of relevant past conversation context into new agent queries. The recall system searches across all sessions in a workspace using FTS5, formats matching messages into context blocks, and prepends them to the agent's prompt.
Three-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Recall | MonkeyClaw.Recall |
Query sanitization, search orchestration, result assembly |
| Formatter | MonkeyClaw.Recall.Formatter |
Session-grouped text blocks with character budgets |
| Plug | MonkeyClaw.Recall.Plug |
Extension plug for :query_pre automatic injection |
The recall plug hooks into the existing extension pipeline at
:query_pre. When a user sends a query, the plug:
- Extracts keywords from the prompt (sanitized for FTS5)
- Searches past sessions via FTS5 with temporal/role filtering
- Formats matches into a context block (grouped by session)
- Sets
:effective_promptwith the recalled context prepended
Configuration is via application config alongside other query_pre plugs (see User Modeling section for the full pipeline configuration).
The search layer supports temporal filtering (:after/:before),
role filtering (:roles), session exclusion (:exclude_session_id),
and configurable result limits. All functions are pure (database I/O
aside) — no processes, no state.
Reusable procedure library built from accepted experiments. The skills system extracts proven strategies from the experiment engine, indexes them for natural language discovery, and injects relevant skills into agent queries automatically.
Three-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Skills | MonkeyClaw.Skills |
Skill CRUD, FTS5 search, effectiveness scoring |
| Extractor | MonkeyClaw.Skills.Extractor |
Auto-extraction from accepted experiments |
| Plug | MonkeyClaw.Skills.Plug |
Extension plug for :query_pre automatic injection |
When an experiment is accepted, the extractor derives a named skill
from the strategy and result, stores it with an initial effectiveness
score, and indexes it in FTS5 for full-text search. The skills plug
hooks into :query_pre alongside recall:
- Searches the skill library using FTS5 against the incoming prompt
- Ranks candidates by effectiveness score
- Prepends matching skills as reusable context in
:effective_prompt
An ETS hot cache holds workspace skill sets for non-query contexts (dashboards, listing). Query-time injection always uses FTS5 search for relevance. Effectiveness scores update on each use — accepted outcomes increment the score, rejected outcomes decrement it — so the library self-selects toward what actually works over time.
Configuration is via application config alongside other query_pre plugs (see User Modeling section for the full pipeline configuration).
All functions are pure (database and ETS I/O aside) — no processes, no state beyond the cache.
Pure OTP scheduling for timed experiment runs. Schedule entries define when and how often to create experiments — no external cron or Quantum dependency.
Two-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Scheduling | MonkeyClaw.Scheduling |
Schedule entry CRUD, status transitions, run tracking, due-entry queries |
| Scheduler | MonkeyClaw.Scheduling.Scheduler |
GenServer poll loop — wakes on interval, fires due entries |
Schedule types:
:once— Fires a single time atnext_run_at, then transitions to:completed.:interval— Fires everyinterval_msmilliseconds, starting atnext_run_at. Optionally bounded bymax_runs.
Status lifecycle: active → paused → active (toggle), active → completed (done or max_runs reached), active → failed (error).
Terminal states (:completed, :failed) cannot transition further.
The Scheduler GenServer polls every 15 seconds (configurable via
:scheduler_poll_interval_ms) for active entries whose next_run_at
has passed. For each due entry, it loads the workspace, creates an
experiment from the entry's config, and records the run. Individual
entry failures are logged but do not affect other entries in the same
poll cycle. trigger_poll/0 forces an immediate poll for testing or
when a newly created entry is already due.
Privacy-aware observation of user interactions for personalized agent queries. The user modeling system tracks topic frequencies and behavioral patterns from conversations, then injects relevant context into prompts to improve agent response quality.
Four-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| UserModeling | MonkeyClaw.UserModeling |
Profile CRUD, topic extraction, pattern merging, context generation |
| Observer | MonkeyClaw.UserModeling.Observer |
GenServer batching — accumulates observations in memory, flushes to DB on timer |
| ObservationPlug | MonkeyClaw.UserModeling.ObservationPlug |
Extension plug for :query_post — sends observations to the Observer |
| InjectionPlug | MonkeyClaw.UserModeling.InjectionPlug |
Extension plug for :query_pre — prepends personalized context to prompts |
Privacy levels control what gets recorded:
:full— Records topic frequencies and behavioral patterns (query count, average prompt length, active hours):limited— Records topic frequencies only (no patterns):none— Skips all observation recording
The Observer GenServer decouples observation collection from
persistence, accumulating observations in a buffer keyed by workspace
ID and flushing them to the database every 30 seconds (configurable
via :observer_flush_interval_ms). This prevents observation
recording from blocking the query pipeline.
Topic extraction downcases text, filters stopwords and short words,
and counts frequencies. All accumulation is bounded: topics capped
at the top 100 by frequency (individual counts capped at 1,000),
query counts capped at 1,000,000, active hour counts capped at
100,000. The injection context summarizes the user's top interests
and explicit preferences, gated by the profile's injection_enabled
flag.
Configuration is via application config alongside other plugs:
config :monkey_claw, MonkeyClaw.Extensions,
hooks: %{
query_post: [
{MonkeyClaw.Vault.SecretScannerPlug, []},
{MonkeyClaw.UserModeling.ObservationPlug, []}
],
query_pre: [
{MonkeyClaw.Vault.SecretScannerPlug, []},
{MonkeyClaw.Recall.Plug, max_results: 10, max_chars: 4000},
{MonkeyClaw.Skills.Plug, max_skills: 5, max_chars: 2000},
{MonkeyClaw.UserModeling.InjectionPlug, min_query_length: 10}
]
}
Secure HTTP endpoint for receiving external webhook deliveries and routing them to agent workflows. Every incoming request passes through a defense-in-depth security pipeline before reaching the agent.
Four-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Webhooks | MonkeyClaw.Webhooks |
Endpoint CRUD, delivery tracking, replay detection, secret management |
| Security | MonkeyClaw.Webhooks.Security |
Shared crypto utilities, verifier dispatch via verifier_for/1 |
| Verifiers | MonkeyClaw.Webhooks.Verifiers.* |
Source-specific signature verification (16 built-in sources) |
| Dispatcher | MonkeyClaw.Webhooks.Dispatcher |
Async agent dispatch via Conversation.send_message/4 |
Built-in webhook sources:
| Source | Scheme | Headers |
|---|---|---|
:generic |
HMAC-SHA256 with timestamp (Stripe-style) | X-MonkeyClaw-Signature |
:github |
HMAC-SHA256 body-only | X-Hub-Signature-256 |
:gitlab |
Plain token comparison (constant-time) | X-Gitlab-Token |
:slack |
Versioned HMAC-SHA256 with timestamp | X-Slack-Signature |
:discord |
Ed25519 public-key signatures | X-Signature-Ed25519 |
:bitbucket |
HMAC-SHA256 body-only | X-Hub-Signature |
:forgejo |
HMAC-SHA256 body-only (Forgejo/Gitea/Codeberg) | X-Forgejo-Signature |
:stripe |
HMAC-SHA256 with timestamp | Stripe-Signature |
:twilio |
HMAC-SHA1 URL-based (Base64) | X-Twilio-Signature |
:linear |
HMAC-SHA256 body-only | Linear-Signature |
:sentry |
HMAC-SHA256 re-serialized JSON | Sentry-Hook-Signature |
:pagerduty |
HMAC-SHA256 body-only | x-pagerduty-signature |
:vercel |
HMAC-SHA1 body-only | x-vercel-signature |
:netlify |
JWS/HS256 with body hash claim | X-Webhook-Signature |
:circleci |
HMAC-SHA256 body-only | circleci-signature |
:mattermost |
Plain token in request body | (body token field) |
Each source implements the MonkeyClaw.Webhooks.Verifier behaviour
(verify/3, extract_event_type/1, extract_delivery_id/1).
Security pipeline (in order):
- Endpoint lookup — Active endpoints only; missing, paused, and revoked return identical 404s (anti-enumeration)
- Content-Type — JSON only (415 for anything else)
- Source-dispatched verification — Each source uses its own signing
scheme;
Security.verifier_for/1routes to the correct module - Replay detection — Delivery IDs checked against delivery history; replays return 202 without reprocessing
- Rate limiting — ETS-backed per-endpoint sliding window with atomic counters; 429 with Retry-After header
- Event filtering — Optional allowed-events map per endpoint
Signing secrets are encrypted at rest with AES-256-GCM using a key
derived from secret_key_base with a domain-specific separator.
Each encryption uses a random 96-bit IV, so ciphertext differs even
for identical secrets. Secrets can be rotated but are never shown to
the user again after creation.
The CacheBodyReader preserves raw request bytes in conn.private
for HMAC verification, since Plug.Parsers consumes the body during
JSON decoding. Verified webhooks are dispatched asynchronously via
Task.Supervisor — the controller returns 202 immediately while the
agent processes the event in a dedicated "webhook:<endpoint_name>"
channel.
All error responses are deliberately opaque — no endpoint IDs, stack traces, or distinguishing details. Telemetry counters track received, rejected, rate-limited, and dispatched events.
Event-driven notification system that routes telemetry events to user-facing alerts. Connects the existing instrumentation pipeline (webhooks, experiments, agent sessions) to real-time and email delivery surfaces.
Four-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Notifications | MonkeyClaw.Notifications |
Notification and rule CRUD, status transitions, PubSub, query helpers |
| Router | MonkeyClaw.Notifications.Router |
GenServer — telemetry handler attachment, ETS rule cache, event → notification pipeline |
| EventMapper | MonkeyClaw.Notifications.EventMapper |
Pure event → notification attribute translation with workspace resolution |
MonkeyClaw.Notifications.Email |
Pure Swoosh email builder for notification delivery |
Notification categories:
:webhook— Webhook received, rejected, or dispatched events:experiment— Experiment completed or rolled back:session— Agent session or query exceptions:system— System-level events
Each workspace configures notification rules that map telemetry event
patterns to delivery channels (:in_app, :email, or :all) with a
minimum severity threshold (:info < :warning < :error). Rules
are cached in an application-owned ETS table for zero-overhead lookups
on every telemetry event, with periodic refresh and on-demand refresh
after rule mutations.
The Router GenServer attaches to telemetry events on startup. Handlers run in the caller's process and immediately cast to the GenServer to avoid blocking webhook requests, experiment runners, or agent sessions. The GenServer then maps the event, matches rules, checks severity, and creates the notification with delivery:
- In-app — PubSub broadcast to
"notifications:{workspace_id}". The ChatLive LiveView subscribes and forwards to the NotificationLive component, which renders a real-time notification bell with unread count badge and dropdown panel. - Email — Async delivery via
Task.Supervisorusing Swoosh. Email subjects include severity prefixes ([MonkeyClaw ERROR],[MonkeyClaw Warning]).
REST API endpoints provide programmatic access for listing notifications, marking read/dismissed, bulk mark-all-read, and full CRUD for notification rules. All endpoints are workspace-scoped with opaque 404s on workspace mismatch to prevent enumeration.
Bi-directional messaging between external platforms (Slack, Discord, Telegram, WhatsApp) and BeamAgent-backed workflows. Each platform is a stateless adapter implementing a common behaviour — no persistent WebSocket connections required.
Four-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Channels | MonkeyClaw.Channels |
Channel config CRUD, message recording, PubSub events |
| Adapter | MonkeyClaw.Channels.Adapter |
Behaviour contract — validate_config/1, send_message/2, verify_request/3, parse_inbound/2 |
| Adapters | MonkeyClaw.Channels.Adapters.* |
Platform-specific implementations (Slack, Discord, Telegram, WhatsApp, Web) |
| Dispatcher | MonkeyClaw.Channels.Dispatcher |
Inbound routing (platform to agent) and outbound delivery (agent to platform) |
Supported adapters:
| Adapter | Inbound | Outbound | Verification |
|---|---|---|---|
| Slack | Events API webhook | chat.postMessage |
HMAC-SHA256 signing secret |
| Discord | Interactions endpoint | REST API | Ed25519 public key |
| Telegram | Webhook updates | Bot API sendMessage |
Secret token header |
| Cloud API webhook | Graph API messages |
HMAC-SHA256 app secret | |
| Web | LiveView events | PubSub broadcast | Session authentication |
Inbound flow: webhook controller receives HTTP POST, adapter verifies request signature, adapter parses platform-specific payload, dispatcher records the message and dispatches to BeamAgent, agent response is sent back through the adapter. Challenge/verification handshakes (Slack URL verification, Discord PING, WhatsApp webhook verification) are handled transparently.
Outbound flow: agent produces output, dispatcher resolves enabled channels for the workspace, each adapter sends the message to its platform via supervised async tasks.
Global notifications ensure the user sees agent activity regardless of current page — a dedicated PubSub topic broadcasts all channel events to the notification system, visible from both the chat interface and the dashboard.
Channel configurations are workspace-scoped with adapter-specific config maps (API tokens, channel IDs, signing secrets). The web adapter is the default channel for every workspace, requiring no external credentials. A LiveView management interface provides CRUD for channel configs with adapter-specific form fields and enable/disable toggling.
Encrypted storage for API keys and OAuth tokens with opaque references
that prevent the AI model from ever seeing plaintext secret values.
Configuration references secrets via @secret:name strings; resolution
to plaintext occurs only at HTTP call boundaries in the process making
the external API call.
Four-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| Vault | MonkeyClaw.Vault |
Secret and token CRUD, encryption, resolution |
| Crypto | MonkeyClaw.Vault.Crypto |
AES-256-GCM encrypt/decrypt with HKDF key derivation from BEAM cookie |
| Reference | MonkeyClaw.Vault.Reference |
@secret:name validation, extraction, recursive resolution |
| SecretScanner | MonkeyClaw.Vault.SecretScanner |
14 regex patterns detecting leaked secrets in prompts and responses |
Security design:
- Encryption at rest — AES-256-GCM with random 96-bit IVs. Keys
derived from the BEAM cookie via HKDF-SHA256 and cached in
:persistent_term. - Opaque references — The model sees
@secret:anthropic_key, never the plaintext. Resolution happens exclusively inVault.resolve_secret/2. - Secret scanning — Extension plugs scan both inbound prompts
(
query_pre) and outbound responses (query_post) for 14 secret patterns (AWS, GitHub, Slack, Stripe, OpenAI, Anthropic, etc.), redacting matches before they reach the model. - OAuth tokens — Auto-encrypted via
EncryptedFieldcustom Ecto type with expiry tracking and upsert semantics (one token per provider per workspace).
Secrets are data entities, not processes. The vault context is a stateless Ecto-backed module — no GenServer overhead. The secret scanner runs as extension plugs in the compiled pipeline.
A LiveView management interface at /vault provides three tabs:
Secrets (create, list, delete — values never displayed after creation),
Tokens (list with active/expired status, delete), and Models (browse
cached models grouped by provider, trigger refresh).
Unified model cache keyed on (backend, provider) with per-backend
probes, SQLite persistence, and ETS read-through for low-latency reads.
Five-layer architecture:
| Layer | Module | Owns |
|---|---|---|
| ModelRegistry | MonkeyClaw.ModelRegistry |
GenServer — ETS table lifecycle, tick scheduler, per-backend probe dispatch, serialized writes via single upsert funnel |
| CachedModel | MonkeyClaw.ModelRegistry.CachedModel |
Ecto schema — (backend, provider) unique key, embedded model list, trust-boundary changeset validation |
| Baseline | MonkeyClaw.ModelRegistry.Baseline |
Boot seed loader — reads baseline model entries from runtime.exs, cold-start availability |
| EtsHeir | MonkeyClaw.ModelRegistry.EtsHeir |
ETS crash survival — heir process reclaims the table when the registry crashes and re-transfers on restart |
| Provider | MonkeyClaw.ModelRegistry.Provider |
HTTP fetching via Req for Anthropic, OpenAI, and Google APIs; called by the BeamAgent backend adapter |
Four independent writers populate the cache: Baseline (boot seed),
Probe (periodic per-backend tasks via TaskSupervisor),
Session hook (authenticated cast from AgentBridge.Session), and
on-demand refresh (refresh/1, refresh_all/0). All four funnel
through a single validated upsert path with conditional precedence on
(refreshed_at, refreshed_mono).
Graceful degradation: probe failures trigger exponential backoff (5s initial, 5min cap) and preserve stale cache. Baseline guarantees a floor of known models at boot even if SQLite is unavailable. The GenServer never crashes on refresh failure. The LiveView handles a missing ModelRegistry process (disabled in test config) by showing an empty state.
Runtime reconfiguration via ModelRegistry.configure/1 allows changing
backends, intervals, and backend configs without restarting the process.
Landing page at / with real-time system visibility, refreshing
every 5 seconds. Four panels cover BEAM VM health (memory, processes,
run queue, uptime), active agent sessions (clickable rows navigate
to the session's chat), extension and hook status, and recent
workspaces. Backend badges link directly to a new chat pre-configured
with that backend.
Phoenix LiveView chat interface at /chat with real-time streaming,
markdown rendering, and per-message token stats. Features include
multi-conversation management (sidebar with create/switch/delete),
session history (past conversations persisted in SQLite with
full-text search via FTS5), collapsible thinking blocks, code copy
buttons, runtime model selection across all supported backends, and
runtime permission mode control (:default, :accept_edits,
:bypass_permissions, :plan, :dont_ask).
SQLite3 via ecto_sqlite3. Embedded and zero-ops — a natural fit
for single-user self-hosted deployments. Tables use STRICT mode and
WITHOUT ROWID for type safety and clustered UUID primary key lookups.
- Erlang/OTP 27+
- Elixir 1.17+
- BeamAgent cloned as a
sibling directory (
../beam-agent/beam_agent_ex)
mix setupThis runs deps.get, creates the database, runs migrations, and builds
assets.
# Start the Phoenix server
mix phx.server
# Or inside IEx
iex -S mix phx.servermix monkey_claw.gen.certs # Generate CA + server + client certs
mix monkey_claw.gen.certs --san my.domain # Add custom SANs
mix monkey_claw.gen.certs --output-dir /path/to # Custom output directoryGenerates a self-signed CA, server certificate with SANs, client certificate, and a PKCS#12 bundle for browser import — all pure Elixir, no external dependencies.
mix compile --warnings-as-errors # Zero-warning compilation
mix format --check-formatted # Code formatting
mix test # Full test suite
mix credo --strict # Static analysis
mix dialyzer # Type checkingAll five gates run in CI. The mix precommit alias runs compile
(warnings-as-errors), deps.unlock --unused, format, and test in
sequence for quick local checks.
Private. All rights reserved.