Current section

Files

Jump to
nous mix.exs
Raw

mix.exs

defmodule Nous.MixProject do
  use Mix.Project

  @version "0.17.2"
  @source_url "https://github.com/nyo16/nous"

  def project do
    [
      app: :nous,
      version: @version,
      elixir: "~> 1.18",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      docs: docs(),
      aliases: aliases(),
      description: "AI agent framework for Elixir with multi-provider LLM support",
      source_url: @source_url,
      package: package(),
      elixirc_paths: elixirc_paths(Mix.env()),
      # hackney, net_runner and tyrex are optional backends (see deps); without
      # this, apps that depend on nous without them get ":hackney is not
      # available" / "NetRunner is not available" / "Tyrex is not available"
      # warnings when compiling the dep. The NetRunner call sites that matter
      # fail closed at runtime (see Nous.Hook.Runner and Nous.Tools.Bash);
      # Nous.CodeRuntime.JS.Session calls Tyrex directly and is only reachable
      # once a code runtime is configured.
      elixirc_options: [no_warn_undefined: [:hackney, :hackney_pool, NetRunner, Tyrex]],
      # Coverage ratchet. `mix test --cover` defaults to a 90% threshold this
      # project has never met, and no CI job ran it, so the gate was purely
      # decorative. 66 is the current measured floor (67.75%, :llm/:llama
      # excluded as in CI) with ~1.75pp of headroom, enforced by the `coverage`
      # job in .github/workflows/ci.yml. Raise it as coverage improves; never
      # lower it. History: 56.80% at the audit, 57 after the perf wave, 59 once
      # the provider/web_fetch/prompt_template suites landed, 66 after the
      # 2026-09 remediation (ghost backends + Store.Results + Code Mode gates).
      #
      # :threshold MUST be nested under :summary — a bare
      # `test_coverage: [threshold: n]` is silently ignored and you keep the
      # 90% default (Mix.Tasks.Test.Coverage `get_threshold(true)`).
      test_coverage: [summary: [threshold: 66]],
      # `mix hex.audit` acknowledgements. Two cowlib advisories have no patched
      # release (2.20.0 fixed only CVE-2026-43971):
      #   CVE-2026-43966  structured-fields header CRLF injection
      #   CVE-2026-43969  cookie request-header injection (GHSA-g2wm-735q-3f56)
      # Exposure is test-only: cowlib reaches this project exclusively through
      # bypass -> plug_cowboy -> cowboy, and bypass is `only: :test`. hex.audit
      # warns when an entry no longer matches the lock, so a patched cowlib
      # bump surfaces the stale ignore by itself. The `deps.audit` CI step
      # carries the same two ids (see .github/workflows/ci.yml). Never add a
      # runtime dep here — that is exactly what the gate exists to catch.
      hex: [ignore_advisories: ["CVE-2026-43966", "CVE-2026-43969"]],
      dialyzer: [
        plt_file: {:no_warn, "priv/plts/dialyzer.plt"},
        plt_add_apps: [:mix, :ex_unit]
      ]
    ]
  end

  def application do
    [
      extra_applications: [:logger],
      mod: {Nous.Application, []},
      # hackney's :default pool starts automatically when the :hackney
      # application starts; ensuring it's listed here is just defensive.
      included_applications: []
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  defp deps do
    [
      # YAML (for evaluation framework)
      {:yaml_elixir, "~> 2.9"},

      # Validation
      {:ecto, "~> 3.11"},

      # HTTP clients for all LLM providers.
      # Finch/Req is the default backend for both one-shot and streaming.
      # Hackney is an opt-in alternative for SSE/long-streaming bodies — its
      # `:async, :once` mode is truly pull-based, so the consumer paces the
      # producer and a slow consumer can't OOM us via mailbox accumulation.
      # (Finch.stream/5's callback is push-based; a fast LLM + slow consumer =
      # unbounded mailbox growth — see review M-12.) To use the hackney
      # backend, declare `{:hackney, "~> 4.0"}` in your app's deps and select
      # it via `NOUS_HTTP_BACKEND=hackney` (or the streaming variant).
      {:finch, "~> 0.19"},
      # req 0.7+: `finch:` takes options (`[name: ...]`) — the bare-atom shape
      # our backends used through 0.6 is deprecated with a per-request IO.warn
      # and slated for removal, so the call sites use the new shape and the
      # constraint floor moves with them.
      {:req, "~> 0.7"},
      # Direct dep, not just transitive via finch: the backends match
      # `%Mint.TransportError{}` (Nous.HTTP.Backend.Req, Nous.Providers.HTTP),
      # and finch's own `~> 1.8` floor still admits vulnerable releases:
      # 1.9.3 (CVE-2026-82729 / CVE-2026-82728, HTTP/1 response-parsing DoS)
      # and 1.10.0 (CVE-2026-82672, chunk-size response smuggling on pooled
      # connections) — both reachable through Nous.Tools.WebFetch from a
      # hostile server. This floor is what makes a consumer's resolver refuse
      # the vulnerable line.
      {:mint, "~> 1.10 and >= 1.10.1"},
      {:hackney, "~> 4.0", optional: true},

      # Google Cloud auth for Vertex AI (optional — add to your app's deps to unlock)
      {:goth, "~> 1.4", optional: true},

      # HTML parsing (for web content extraction in research tools)
      {:floki, "~> 0.36", optional: true},

      # Code Mode's JS runtime: an embedded Deno isolate via Rustler NIFs
      # (optional — add to your app's deps to unlock `Nous.CodeRuntime.JS`).
      # 0.4 is the floor, not a preference: it is the first version with an
      # execution interrupt that terminates a `while(true){}` guest, an opt-in
      # `:apply` allowlist rather than an always-on arbitrary-module gateway,
      # and a `:max_heap_mb` cap. Code Mode needs all three, so an earlier
      # tyrex is not "mostly fine" — it is unusable for model-authored code.
      {:tyrex, "~> 0.4", optional: true},

      # Memory / decisions store backends. optional: true keeps the NIFs out
      # of downstream builds unless the app opts in; declaring them here is
      # what lets Nous compile AND test the code it ships for them — before
      # this they were `if Code.ensure_loaded?` ghosts that never built
      # in-repo (audit D-M2). CI runs them in the `optional_deps` job
      # (`mix test --only sqlite --only duckdb --only prom_ex`).
      {:exqlite, "~> 0.27", optional: true},
      {:duckdbex, "~> 0.5", optional: true},
      # Prometheus metrics via PromEx (`Nous.PromEx.Plugin`). Same story. plug
      # rides along because prom_ex 1.12's `PromEx.Plug` compiles
      # unconditionally against `Plug.Conn` even though prom_ex declares plug
      # optional — without it prom_ex fails to build in :dev (test already
      # has plug via bypass -> plug_cowboy).
      {:prom_ex, "~> 1.11", optional: true},
      {:plug, ">= 1.16.0", optional: true},

      # Local LLM inference via llama.cpp NIFs (optional — add to your app's deps
      # to unlock the LlamaCpp provider). optional: true keeps it out of
      # downstream apps' builds unless they opt in, while still being available
      # for Nous's own dev/test (e.g. the tagged llamacpp smoke test).
      {:llama_cpp_ex, "~> 0.8", optional: true},

      # Process execution for command hooks and the Bash tool. A NIF-based
      # runner is used (over System.cmd/Port) for fine-grained process-tree
      # control and reliable kill-on-timeout of child processes. `~> 1.0` (not
      # the tighter `~> 1.0.4`) so downstream apps can pick up 1.x fixes without
      # waiting on a nous release. optional: true keeps the NIF out of
      # downstream builds that use neither the Bash tool nor :command hooks;
      # both paths fail closed (refuse to run, never execute unconfined) when
      # net_runner is absent — see Nous.Hook.Runner and Nous.Tools.Bash.
      {:net_runner, "~> 1.0", optional: true},

      # Telemetry
      {:telemetry, "~> 1.2"},

      # Note: For Prometheus metrics, users can add {:prom_ex, "~> 1.11"} and {:plug, "~> 1.18"}
      # to their deps. The Nous.PromEx.Plugin will automatically be available.

      # Dev/Test
      # >= 0.34 for `mix docs --warnings-as-errors`, which the `docs` CI job runs.
      {:ex_doc, "~> 0.34", only: :dev, runtime: false},
      {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      # Security audit of mix.lock against the GitHub-sourced advisory DB —
      # run by the deps_audit CI job (`mix deps.audit`, alongside the built-in
      # `mix hex.audit` retirement check).
      {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
      # optional (not only: :test) so the `~> 2.1` constraint reaches downstream
      # resolvers — Nous.PubSub integrates with phoenix_pubsub at runtime (guarded
      # by Code.ensure_loaded?), and apps that bring their own copy should see a
      # compatible-version requirement rather than a hidden test-only pin.
      {:phoenix_pubsub, "~> 2.1", optional: true},
      # Bypass = in-test HTTP server for exercising the streaming pipeline
      # without hitting real LLM endpoints. `only: :test`: nothing in :dev uses
      # Bypass, and it drags in plug_cowboy/cowboy/ranch — currently carrying
      # HIGH advisories (see `mix hex.audit`) — which would otherwise be
      # compiled and loadable while `Nous.Application` runs in :dev.
      {:bypass, "~> 2.1", only: :test},
      # Benchee = the bench/ scripts. Also in :test because
      # `bench/http_backend.exs` starts an in-process plug_cowboy server, and
      # plug_cowboy now only reaches the build through Bypass in :test:
      #
      #     MIX_ENV=test mix run bench/http_backend.exs
      {:benchee, "~> 1.3", only: [:dev, :test]}
    ]
  end

  defp docs do
    [
      main: "readme",
      extras: [
        "README.md",
        "CHANGELOG.md",
        {"AGENTS.md", title: "AI Agent Guide (AGENTS.md)"},
        {"CONTRIBUTING.md", title: "Contributing"},

        # Getting Started
        {"docs/README.md", filename: "docs_index", title: "Documentation Hub"},
        {"docs/getting-started.md", filename: "getting_started", title: "Getting Started Guide"},

        # Examples Overview
        {"examples/README.md", filename: "examples_overview", title: "Examples Overview"},
        {"docs/guides/README.md", filename: "guides_index", title: "All Guides"},

        # Livebook notebooks (ex_doc renders .livemd natively)
        {"notebooks/01_intro_to_nous.livemd",
         filename: "notebook_intro", title: "Livebook: Intro to Nous"},
        {"notebooks/02_tools_and_agents.livemd",
         filename: "notebook_tools", title: "Livebook: Tools & Agents"},
        {"notebooks/03_rag_knowledge_base.livemd",
         filename: "notebook_rag", title: "Livebook: RAG & Knowledge Base"},

        # Production Guides
        {"docs/guides/skills.md", filename: "skills", title: "Skills Guide"},
        {"docs/guides/hooks.md", filename: "hooks", title: "Hooks Guide"},
        {"docs/guides/code_mode.md", filename: "code_mode", title: "Code Mode Guide"},
        {"docs/guides/liveview-integration.md",
         filename: "liveview_integration", title: "Phoenix LiveView Integration"},
        {"docs/guides/best_practices.md",
         filename: "best_practices", title: "Production Best Practices"},
        {"docs/guides/tool_development.md",
         filename: "tool_development", title: "Tool Development Guide"},
        {"docs/guides/troubleshooting.md",
         filename: "troubleshooting", title: "Troubleshooting Guide"},
        {"docs/guides/migration_guide.md", filename: "migration_guide", title: "Migration Guide"},
        {"docs/guides/evaluation.md",
         filename: "evaluation", title: "Evaluation Framework Guide"},
        {"docs/guides/structured_output.md",
         filename: "structured_output", title: "Structured Output Guide"},
        {"docs/guides/workflows.md", filename: "workflows", title: "Workflow Engine Guide"},
        {"docs/guides/memory.md", filename: "memory", title: "Memory System Guide"},
        {"docs/guides/context.md", filename: "context", title: "Context & Dependencies Guide"},
        {"docs/guides/transcript.md", filename: "transcript", title: "Transcripts & Compaction"},
        {"docs/guides/knowledge_base.md",
         filename: "knowledge_base", title: "Knowledge Base Guide"},
        {"docs/guides/permissions.md",
         filename: "permissions", title: "Permissions & Guardrails Guide"},
        {"docs/guides/observability.md",
         filename: "observability", title: "Observability & Telemetry Guide"},

        # Orchestration & Multi-Agent
        {"docs/guides/teams.md", filename: "teams", title: "Multi-Agent Teams Guide"},
        {"docs/guides/decisions.md", filename: "decisions", title: "Decision Graph Guide"},
        {"docs/guides/research.md", filename: "research", title: "Deep Research Guide"},

        # Providers & Backends
        {"docs/guides/providers.md", filename: "providers", title: "Providers Overview"},
        {"docs/guides/custom_providers.md",
         filename: "custom_providers", title: "Custom Providers Guide"},
        {"docs/guides/http_backends.md", filename: "http_backends", title: "HTTP Backends Guide"},
        {"docs/guides/vertex_ai_setup.md",
         filename: "vertex_ai_setup", title: "Vertex AI Setup Guide"},
        {"docs/guides/fallback.md", filename: "fallback", title: "Fallback Chains Guide"},

        # Design Documents
        {"docs/design/llm_council_design.md",
         filename: "council_design", title: "LLM Council Design (Proposal)"},
        {"docs/benchmarks/http_backend.md",
         filename: "http_backend_benchmark", title: "HTTP Backend Benchmark"}
      ],
      source_ref: "v#{@version}",
      source_url: @source_url,
      # Internal modules (`@moduledoc false`). AGENTS.md's "What NOT to use"
      # section and older CHANGELOG entries name them in code style; without
      # this, ExDoc tries to autolink each one and warns "references module
      # ... but it is hidden". This list is the single place the hidden set is
      # spelled out — if you hide a module and mention it in prose, add it
      # here; if you un-hide one, remove it and the warning tells you where
      # the stale reference is.
      skip_code_autolink_to: [
        "Nous.AgentRunner.IterationLoop",
        "Nous.AgentRunner.PromptAssembly",
        "Nous.AgentRunner.RequestDispatch",
        "Nous.AgentRunner.Streaming",
        "Nous.AgentRunner.ToolExecution",
        "Nous.AgentRunner.ToolInvocation",
        "Nous.Application",
        "Nous.CodeRuntime.JS.Bridge",
        "Nous.CodeRuntime.JS.Prelude",
        "Nous.CodeRuntime.JS.Session",
        "Nous.JSON",
        "Nous.OutputSchema.UseMacro",
        "Nous.Persistence.ETS.TableOwner",
        "Nous.Util",
        "Nous.Workflow.Checkpoint.ETS.TableOwner",
        "Nous.Workflow.Engine.Executor",
        "Nous.Workflow.Engine.ParallelExecutor",
        "Nous.Workflow.Engine.StateMerger"
      ],
      groups_for_extras: [
        "Getting Started": [
          "readme.html",
          "docs_index.html",
          "getting_started.html",
          "examples_overview.html",
          "guides_index.html",
          "notebook_intro.html",
          "notebook_tools.html",
          "notebook_rag.html"
        ],
        "Production Guides": [
          "skills.html",
          "hooks.html",
          "liveview_integration.html",
          "best_practices.html",
          "tool_development.html",
          "troubleshooting.html",
          "migration_guide.html",
          "evaluation.html",
          "structured_output.html",
          "memory.html",
          "context.html",
          "transcript.html",
          "knowledge_base.html",
          "permissions.html",
          "observability.html"
        ],
        "Orchestration & Multi-Agent": [
          "teams.html",
          "decisions.html",
          "research.html",
          "workflows.html"
        ],
        "Providers & Backends": [
          "providers.html",
          "custom_providers.html",
          "http_backends.html",
          "vertex_ai_setup.html",
          "fallback.html"
        ],
        Design: [
          "council_design.html",
          "http_backend_benchmark.html"
        ],
        Reference: [
          "changelog.html",
          "agents.html",
          "contributing.html"
        ]
      ],
      groups_for_modules: [
        "Core API": [
          Nous,
          Nous.Agent,
          Nous.Agent.Context,
          Nous.Agent.Behaviour,
          Nous.Agent.Callbacks,
          Nous.ReActAgent,
          Nous.Transcript,
          Nous.LLM,
          Nous.RunContext
        ],
        "Agent Implementations": [
          Nous.Agents.BasicAgent,
          Nous.Agents.ReActAgent
        ],
        "Agent Execution": [
          Nous.AgentRunner,
          Nous.AgentServer
        ],
        "Model Configuration": [
          Nous.Model,
          Nous.ModelDispatcher,
          Nous.Fallback
        ],
        Providers: [
          Nous.Provider,
          Nous.Providers.HTTP,
          Nous.Providers.HTTP.JSONArrayParser,
          Nous.Providers.OpenAI,
          Nous.Providers.OpenAICompatible,
          Nous.Providers.Anthropic,
          Nous.Providers.Custom,
          Nous.Providers.Gemini,
          Nous.Providers.Mistral,
          Nous.Providers.LMStudio,
          Nous.Providers.VertexAI,
          Nous.Providers.LlamaCpp,
          Nous.Providers.VLLM,
          Nous.Providers.SGLang
        ],
        "Messages & Streaming": [
          Nous.Message,
          Nous.Message.ContentPart,
          Nous.Messages,
          Nous.Messages.OpenAI,
          Nous.Messages.Anthropic,
          Nous.Messages.Gemini,
          Nous.StreamNormalizer,
          Nous.StreamNormalizer.OpenAI,
          Nous.StreamNormalizer.Anthropic,
          Nous.StreamNormalizer.Gemini,
          Nous.StreamNormalizer.LlamaCpp,
          Nous.StreamNormalizer.ToolCallAccumulator
        ],
        "HTTP Backends": [
          Nous.HTTP.Backend,
          Nous.HTTP.Backend.Req,
          Nous.HTTP.Backend.Hackney,
          Nous.HTTP.StreamBackend,
          Nous.HTTP.StreamBackend.Req,
          Nous.HTTP.StreamBackend.Hackney,
          Nous.HTTP.Buffer
        ],
        "Tool System": [
          Nous.Tool,
          Nous.Tool.Behaviour,
          Nous.Tool.ContextUpdate,
          Nous.Tool.Validator,
          Nous.Tool.Registry,
          Nous.Tool.Schema,
          Nous.ToolSchema,
          Nous.ToolExecutor,
          Nous.ToolCall
        ],
        "Structured Output": [
          Nous.OutputSchema,
          Nous.OutputSchema.Validator
        ],
        "Research Tools": [
          Nous.Tools.WebFetch,
          Nous.Tools.Summarize,
          Nous.Tools.SearchScrape,
          Nous.Tools.TavilySearch,
          Nous.Tools.BraveSearch,
          Nous.Tools.ResearchNotes,
          Nous.Tools.Search.Common
        ],
        "Coding Tools": [
          Nous.Tools.Bash,
          Nous.Tools.FileRead,
          Nous.Tools.FileWrite,
          Nous.Tools.FileEdit,
          Nous.Tools.FileGlob,
          Nous.Tools.FileGrep,
          Nous.Tools.TodoTools,
          Nous.Tools.Env
        ],
        "Utility Tools": [
          Nous.Tools.DateTimeTools,
          Nous.Tools.StringTools,
          Nous.Tools.ReActTools
        ],
        Testing: [
          Nous.Tool.Testing
        ],
        Templates: [
          Nous.PromptTemplate
        ],
        "Data Types": [
          Nous.Types,
          Nous.Usage,
          Nous.Usage.Pricing
        ],
        Infrastructure: [
          Nous.Telemetry,
          Nous.PromEx.Plugin,
          Nous.Errors,
          Nous.Errors.Base,
          Nous.Errors.RetryInfo,
          Nous.Errors.ConfigurationError,
          Nous.Errors.ModelError,
          Nous.Errors.ProviderError,
          Nous.Errors.ToolError,
          Nous.Errors.ToolTimeout,
          Nous.Errors.ValidationError,
          Nous.Errors.UsageLimitExceeded,
          Nous.Errors.MaxIterationsExceeded,
          Nous.Errors.ExecutionCancelled
        ],
        Evaluation: [
          Nous.Eval,
          Nous.Eval.Suite,
          Nous.Eval.TestCase,
          Nous.Eval.Runner,
          Nous.Eval.Evaluator,
          Nous.Eval.Evaluators.Contains,
          Nous.Eval.Evaluators.ExactMatch,
          Nous.Eval.Evaluators.FuzzyMatch,
          Nous.Eval.Evaluators.LLMJudge,
          Nous.Eval.Evaluators.Schema,
          Nous.Eval.Evaluators.ToolUsage,
          Nous.Eval.Metrics,
          Nous.Eval.Metrics.Summary,
          Nous.Eval.Result,
          Nous.Eval.SuiteResult,
          Nous.Eval.YamlLoader,
          Nous.Eval.Config,
          Nous.Eval.Reporter,
          Nous.Eval.Reporter.Console,
          Nous.Eval.Reporter.Json,
          Nous.Eval.Optimizer,
          Nous.Eval.Optimizer.Parameter,
          Nous.Eval.Optimizer.SearchSpace,
          Nous.Eval.Optimizer.Strategy,
          Nous.Eval.Optimizer.Strategies.Bayesian,
          Nous.Eval.Optimizer.Strategies.GridSearch,
          Nous.Eval.Optimizer.Strategies.Random
        ],
        "Hooks System": [
          Nous.Hook,
          Nous.Hook.Registry,
          Nous.Hook.Runner
        ],
        "Skills System": [
          Nous.Skill,
          Nous.Skill.Loader,
          Nous.Skill.Registry,
          Nous.Plugins.Skills,
          Nous.Skills.CodeReview,
          Nous.Skills.TestGen,
          Nous.Skills.Debug,
          Nous.Skills.Refactor,
          Nous.Skills.ExplainCode,
          Nous.Skills.CommitMessage,
          Nous.Skills.DocGen,
          Nous.Skills.SecurityScan,
          Nous.Skills.Architect,
          Nous.Skills.TaskBreakdown,
          Nous.Skills.PhoenixLiveView,
          Nous.Skills.EctoPatterns,
          Nous.Skills.OtpPatterns,
          Nous.Skills.ElixirTesting,
          Nous.Skills.ElixirIdioms,
          Nous.Skills.PythonFastAPI,
          Nous.Skills.PythonTesting,
          Nous.Skills.PythonTyping,
          Nous.Skills.PythonDataScience,
          Nous.Skills.PythonSecurity,
          Nous.Skills.PythonUv
        ],
        "Plugin System": [
          Nous.Plugin,
          Nous.Plugins.HumanInTheLoop,
          Nous.Plugins.InputGuard,
          Nous.Plugins.InputGuard.Strategy,
          Nous.Plugins.InputGuard.Result,
          Nous.Plugins.InputGuard.Policy,
          Nous.Plugins.InputGuard.Strategies.Pattern,
          Nous.Plugins.InputGuard.Strategies.LLMJudge,
          Nous.Plugins.InputGuard.Strategies.Semantic,
          Nous.Plugins.Memory,
          Nous.Plugins.SubAgent,
          Nous.Plugins.Summarization,
          Nous.Plugins.LoopGuard,
          Nous.Plugins.Decisions,
          Nous.Plugins.TeamTools
        ],
        "Multi-Agent (Teams)": [
          Nous.Teams,
          Nous.Teams.Supervisor,
          Nous.Teams.Coordinator,
          Nous.Teams.Role,
          Nous.Teams.Comms,
          Nous.Teams.SharedState,
          Nous.Teams.RateLimiter
        ],
        "Decision Graph": [
          Nous.Decisions,
          Nous.Decisions.Node,
          Nous.Decisions.Edge,
          Nous.Decisions.ContextBuilder,
          Nous.Decisions.Store,
          Nous.Decisions.Store.ETS,
          Nous.Decisions.Store.DuckDB
        ],
        Memory: [
          Nous.Memory,
          Nous.Memory.Entry,
          Nous.Memory.Scope,
          Nous.Memory.Store,
          Nous.Memory.Store.ETS,
          Nous.Memory.Store.SQLite,
          Nous.Memory.Store.DuckDB,
          Nous.Memory.Store.Results,
          Nous.Memory.Store.Conformance,
          Nous.Memory.Scoring,
          Nous.Memory.Search,
          Nous.Memory.Tools,
          Nous.Memory.Embedding,
          Nous.Memory.Embedding.OpenAI,
          Nous.Memory.Embedding.Local
        ],
        PubSub: [
          Nous.PubSub,
          Nous.PubSub.Approval
        ],
        Persistence: [
          Nous.Persistence,
          Nous.Persistence.ETS
        ],
        Supervision: [
          Nous.AgentRegistry,
          Nous.AgentDynamicSupervisor
        ],
        Research: [
          Nous.Research,
          Nous.Research.Coordinator,
          Nous.Research.Planner,
          Nous.Research.Searcher,
          Nous.Research.Synthesizer,
          Nous.Research.Reporter,
          Nous.Research.Finding,
          Nous.Research.Report
        ],
        Session: [
          Nous.Session,
          Nous.Session.Config,
          Nous.Session.Guardrails,
          Nous.Session.Log,
          Nous.Session.Event,
          Nous.Session.Inbox,
          Nous.Session.Invariant,
          Nous.Session.Invariant.Violation,
          Nous.Session.Recovery
        ],
        "Code Mode": [
          Nous.CodeMode,
          Nous.CodeMode.RunCodeTool,
          Nous.CodeMode.Scheduler,
          Nous.CodeMode.Sdk,
          Nous.CodeRuntime,
          Nous.CodeRuntime.JS,
          Nous.CodeRuntime.Binding,
          Nous.CodeRuntime.Request,
          Nous.CodeRuntime.Result,
          Nous.CodeRuntime.Failure
        ],
        "Permissions & Security": [
          Nous.Permissions,
          Nous.Permissions.Policy,
          Nous.PathGuard,
          Nous.UrlGuard,
          Nous.Sandbox,
          Nous.Sandbox.Policy,
          Nous.Sandbox.Confined,
          Nous.Sandbox.RunnerFailureRule,
          Nous.Sandbox.Seatbelt,
          Nous.Sandbox.Bwrap,
          Nous.Sandbox.Unavailable
        ],
        "Tool Result Spill": [
          Nous.Spill,
          Nous.Spill.Local,
          Nous.Spill.Locator
        ],
        "Knowledge Base": [
          Nous.KnowledgeBase,
          Nous.KnowledgeBase.Entry,
          Nous.KnowledgeBase.Document,
          Nous.KnowledgeBase.Link,
          Nous.KnowledgeBase.Store,
          Nous.KnowledgeBase.Store.ETS,
          Nous.KnowledgeBase.Tools,
          Nous.KnowledgeBase.Workflows,
          Nous.KnowledgeBase.HealthReport,
          Nous.KnowledgeBase.Prompts,
          Nous.Plugins.KnowledgeBase,
          Nous.Agents.KnowledgeBaseAgent
        ],
        "Workflow Engine": [
          Nous.Workflow,
          Nous.Workflow.Graph,
          Nous.Workflow.Node,
          Nous.Workflow.Edge,
          Nous.Workflow.State,
          Nous.Workflow.Compiler,
          Nous.Workflow.Engine,
          Nous.Workflow.Mermaid,
          Nous.Workflow.Trace,
          Nous.Workflow.Checkpoint,
          Nous.Workflow.Checkpoint.Store,
          Nous.Workflow.Checkpoint.ETS,
          Nous.Workflow.Scratch,
          Nous.Workflow.Telemetry
        ],
        "Mix Tasks": [
          Mix.Tasks.Nous.Eval,
          Mix.Tasks.Nous.Optimize
        ]
      ]
    ]
  end

  defp package do
    [
      # Explicit files list — exclude `priv/plts/` (dialyzer artifacts) which
      # the default Hex package set would otherwise bundle, bloating the
      # tarball by 100MB+.
      files: ~w(lib .formatter.exs mix.exs README.md LICENSE CHANGELOG.md AGENTS.md),
      licenses: ["Apache-2.0"],
      links: %{
        "GitHub" => @source_url,
        "Changelog" => "#{@source_url}/blob/master/CHANGELOG.md"
      }
    ]
  end

  defp aliases do
    [docs: ["docs", &copy_images/1]]
  end

  defp copy_images(_) do
    File.mkdir_p!("doc/images")
    File.cp!("images/header.jpeg", "doc/images/header.jpeg")
    File.cp!("images/logo.jpeg", "doc/images/logo.jpeg")
  end
end