# `Wardrik`

Agents, governed: the agents your app runs, the policies they're held to, and the operations
people and programs reach them through.

    defmodule MyApp.AgentRuntime do
      use Wardrik, otp_app: :my_app
    end

    children = [MyApp.AgentRuntime]

    scope = MyApp.AgentRuntime.as("acme/@maria", roles: [:owner], resources: ["acme/@maria/*"])
    Wardrik.Agent.stream(scope, "acme/@maria/assistant", "What's on today?") |> Enum.each(&IO.write/1)

## Options

Each place overrides the one before it:

    # 1. config/runtime.exs
    config :my_app, MyApp.AgentRuntime, store: {WardrikStore.EKV, data_dir: "/var/lib/my_app"}

    # 2. The supervision tree
    children = [{MyApp.AgentRuntime, deployment: "production"}]

    # 3. init/1, for what's only known at runtime
    @impl Wardrik
    def init(config), do: {:ok, Keyword.put(config, :keys, [System.fetch_env!("WARDRIK_KEY")])}

See `start_link/1` for every option.

# `principal`

```elixir
@type principal() :: WardrikCore.Principal.t()
```

Who acts through Wardrik, or whom an agent works for. Build one with `principal/2`.

# `t`

```elixir
@type t() :: %Wardrik{
  authorizer: {module(), term()},
  backend: {module(), keyword()},
  capabilities: %{required(String.t()) =&gt; WardrikCore.Context.entry()},
  channels: %{required(String.t()) =&gt; {module(), keyword()}},
  declarations: {module(), term()} | nil,
  defaults: map(),
  deployment: String.t(),
  egress: (String.t() -&gt; :ok | {:error, String.t()}),
  idle_timeout: pos_integer() | :infinity,
  keys: [String.t()],
  listeners: [module() | {module(), keyword()}],
  mcp: keyword() | nil,
  middleware: [WardrikCore.Context.entry()],
  name: atom(),
  notify: %{
    targets: %{required(String.t()) =&gt; {module(), keyword()}},
    retries: keyword()
  },
  oauth: keyword() | nil,
  orgs: {module(), term()} | nil,
  policies: %{required(String.t()) =&gt; WardrikPolicy.t()},
  policy: WardrikPolicy.t() | nil,
  prices: %{required(String.t()) =&gt; WardrikCore.Usage.price()},
  retention: pos_integer() | :infinity,
  store: WardrikStore.t(),
  triggers: keyword(),
  trusted_workers: boolean(),
  workspace: %{
    required(String.t()) =&gt; %{read: [String.t()], write: [String.t()]}
  }
}
```

# `init`

```elixir
@callback init(config :: keyword()) :: {:ok, keyword()}
```

Fills in options only known at runtime, before the module starts.

    @impl Wardrik
    def init(config), do: {:ok, Keyword.put(config, :keys, [System.fetch_env!("WARDRIK_KEY")])}

# `as`

```elixir
@spec as(atom() | t(), WardrikCore.Principal.t() | String.t(), term()) ::
  Wardrik.Scope.t()
```

Returns the scope `principal` acts in on `instance`.

`grants` is a list of `Wardrik.Statement`s, or the options for one. Nothing is allowed unless
it's granted.

    scope = Wardrik.as(MyApp.AgentRuntime, "acme/@maria", roles: [:reader], resources: ["acme/agent/*"])

# `authenticate`

```elixir
@spec authenticate(atom() | t(), String.t()) ::
  {:ok, Wardrik.Scope.t()} | {:error, Wardrik.Error.t()}
```

Returns the scope of the API token `secret` on `instance`.

Returns an `unauthorized` error if the token is unknown or has expired.

    {:ok, scope} = Wardrik.authenticate(MyApp.AgentRuntime, secret)

# `check_policy`

```elixir
@spec check_policy(map()) :: :ok | {:error, String.t()}
```

Checks a definition's `policy`.

Returns `:ok`, or the error defining the agent would give.

    Wardrik.check_policy(%{"judge" => %{"questions" => [%{"question" => "Publish?"}]}})
    #=> {:error, "a question decides nothing; give it deny, ask or allow"}

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `client_document`

```elixir
@spec client_document(atom() | t()) :: map()
```

Returns Wardrik's OAuth Client ID Metadata Document.

The sign-in pages serve it at `/client`. Use it if your app serves the document itself.

# `cursor`

```elixir
@spec cursor(%{required(String.t()) =&gt; non_neg_integer()}) :: String.t()
```

Returns a cursor for `events.list` to read on from.

`positions` maps each agent or conversation ID to the last sequence read from its log.

    Wardrik.cursor(%{"acme/triage" => 41})

# `delete_secret`

```elixir
@spec delete_secret(atom() | t(), String.t(), String.t()) :: :ok | {:error, term()}
```

Removes `owner`'s secret `name`, and records it in the audit. Returns `:ok` if there's none.

# `fetch_secret`

```elixir
@spec fetch_secret(atom() | t(), String.t(), String.t()) ::
  {:ok, binary()} | {:error, term()}
```

Returns `owner`'s secret `name`, unsealed.

# `finish_sign_in`

```elixir
@spec finish_sign_in(atom() | t(), map()) :: {:ok, String.t()} | {:error, term()}
```

Finishes a browser sign-in from its callback `params`. Returns the provider signed in to.

Use it if your app serves the callback page itself. `params` holds `"state"`, and `"code"` or
`"error"`.

# `grant`

```elixir
@spec grant(Wardrik.Scope.t(), keyword()) :: Wardrik.Scope.t()
```

Adds a statement to `scope`. `opts` are as `Wardrik.Statement.new/1` takes them.

    MyApp.AgentRuntime.as("acme/@maria") |> Wardrik.grant(roles: [:approver], resources: ["acme/agent/*"])

# `key_name`

```elixir
@spec key_name(String.t()) :: String.t() | nil
```

Returns the name of the API key `model`'s provider needs, or `nil`.

Calls to `model` use the secret of that name, the owner's or the deployment's.

    Wardrik.key_name("zai_coding_plan:glm-5.1")
    #=> "ZAI_API_KEY"

# `list_secrets`

```elixir
@spec list_secrets(atom() | t()) :: [{String.t(), String.t(), WardrikVault.kind()}]
```

Lists every secret as `{owner, name, kind}`, or with `owner`, the names of theirs.

Values aren't unsealed. Returns `[]` if Wardrik was started without `:keys`.

# `list_secrets`

```elixir
@spec list_secrets(atom() | t(), String.t()) :: [String.t()]
```

# `lookup_secret`

```elixir
@spec lookup_secret(atom() | t(), String.t(), String.t()) ::
  {:ok, WardrikVault.kind(), binary(), term()}
  | {:error, term()}
  | {:error, WardrikVault.kind(), term()}
```

Returns `owner`'s secret `name`, unsealed, with its kind and version.

The kind is `:secret`, or `:grant` for an account someone connected. Pass the version to
`put_secret/5` as `:if_vsn`.

# `positions`

```elixir
@spec positions(String.t()) ::
  {:ok, %{required(String.t()) =&gt; non_neg_integer()}} | :error
```

Returns the positions in `cursor`, or `:error` if it isn't one. The reverse of `cursor/1`.

# `principal`

```elixir
@spec principal(String.t(), map()) :: principal()
```

Returns the principal with `id`, such as `"acme/@maria"`, and optional `attributes`.

    Wardrik.principal("acme/@maria", %{name: "Maria"})

# `put_secret`

```elixir
@spec put_secret(atom() | t(), String.t(), String.t(), binary(), keyword()) ::
  :ok | {:error, term()}
```

Seals `value` as `owner`'s secret `name`, and records it in the audit.

`owner` is an org, as `"acme"`, or a person, as `"acme/@maria"`. Their agents' tools and servers
can use the secret. Returns `{:error, :no_keys}` if Wardrik was started without `:keys`.

    :ok = Wardrik.put_secret(MyApp.AgentRuntime, "acme", "crm-key", System.fetch_env!("CRM_KEY"))

## Options

  * `:if_vsn` - only write if the secret is still at this version. See `lookup_secret/3`.

# `refresh`

```elixir
@spec refresh(atom() | t(), keyword()) :: [String.t()]
```

Applies the org's current settings to its awake agents.

Sleeping agents pick them up when they wake. Returns a line for each agent it couldn't update.

    Wardrik.refresh(MyApp.AgentRuntime, org: "acme")
    #=> []

# `run`

```elixir
@spec run(Wardrik.Scope.t(), String.t(), map()) ::
  {:ok, term()} | {:error, Wardrik.Error.t()}
```

Runs the operation `id`, such as `"agent.ask"`, in `scope`.

`input` follows the operation's input schema.

    Wardrik.run(scope, "agent.ask", %{"agent" => "acme/triage", "content" => "What's on fire?"})
    #=> {:ok, %{"message" => %{"id" => "msg_Xq3f...", "cursor" => "eyJhY21lL3RyaWFnZSI6NDF9"}}}

# `sign_in_pages`

```elixir
@spec sign_in_pages(atom() | t()) :: {module(), term()} | nil
```

Returns the plug for sign-in callback pages, as `{plug, opts}`, or `nil` without `:oauth`.

Mount it at the `:oauth` option's `page`.

    Wardrik.sign_in_pages(MyApp.AgentRuntime)
    #=> {WardrikOAuth.Plug, MyApp.AgentRuntime.OAuth}

# `start_agent`

```elixir
@spec start_agent(atom() | t(), keyword()) :: {:ok, pid()} | {:error, term()}
```

Starts an agent on `instance`.

`opts` are as `WardrikCore.start_agent/2` takes them. `:policy` is the agent's own
`WardrikPolicy`, or its rules. The agent is held to its own policy, its org's and Wardrik's.
The strictest decides.

    Wardrik.start_agent(MyApp.AgentRuntime,
      agent_id: "acme/books",
      model: "anthropic:claude-haiku-4-5",
      policy: [ask: ["books/pay_bill"]]
    )

# `start_link`

```elixir
@spec start_link(keyword() | t()) :: Supervisor.on_start()
```

Starts Wardrik.

    Wardrik.start_link(
      name: MyApp.AgentRuntime,
      store: {WardrikStore.EKV, data_dir: "/var/lib/my_app"},
      keys: [System.fetch_env!("WARDRIK_KEY")],
      policy: [deny: ["shell/*"]]
    )

## Store

Where agents and their events are kept. In memory unless set, and gone when it stops.

    # On disk
    store: {WardrikStore.EKV, data_dir: "/var/lib/my_app"}

    # In your app's Postgres; create the table with WardrikStore.Ecto.Migration
    store: {WardrikStore.Ecto, repo: MyApp.Repo}

    # In a Postgres of its own, which migrates itself
    store: {WardrikStore.Ecto, url: "postgres://user:pass@db.example.com/my_app"}

    # A store you've built; one already running is left as it is
    store: WardrikStore.EKV.new(name: MyApp.Store, data_dir: "/var/lib/my_app")

`WardrikStore.Ecto` needs `:ecto_sql` and `:postgrex` in your deps.

## Options

  * `:name` - required. What Wardrik is reached by.
  * `:store` - see above.
  * `:deployment` - the deployment's name in each event's `source`. `"local"` unless set.
  * `:policy` - the `WardrikPolicy` every agent is held to, or its rules, as `[deny: ["shell/*"]]`.
  * `:keys` - keys to seal credentials with. Without them, no secrets are kept.
  * `:oauth` - services people sign in to, as `[page: ..., providers: ..., client_name: ...]`.
    Needs `:keys`. See `WardrikOAuth`.
  * `:mcp` - MCP servers agents may call, as `[github: [transport: ..., orgs: ["acme"]]]`.
    See `WardrikMCP`.
  * `:capabilities` - tools definitions name, as `[files: {MyApp.Files, []}]`.
  * `:policies` - policies definitions name, as `[careful: [ask: ["github/*"]]]`.
  * `:defaults` - what every definition gets where it says nothing, as
    `%{"model" => "anthropic:claude-haiku-4-5"}`.
  * `:middleware` - wraps every agent's calls.
  * `:idle_timeout` - seconds a defined agent idles before it sleeps, or `:infinity`. `900`.
  * `:channels` - chat apps, as `[slack: {WardrikSlack, opts}]`. See `Wardrik.Channel`.
  * `:notify` - where notices go. See `Wardrik.Notify`.
  * `:declarations` - agents defined outside the API. See `Wardrik.Declarations`.
  * `:orgs` - per-org settings. See `Wardrik.Orgs`.
  * `:workspace` - file trees shared beyond their owners. See `Wardrik.Workspaces`.
  * `:triggers` - `[req_options: ..., reconcile: ...]`. See `WardrikTriggers`.
  * `:listeners` - listeners to start. See `Wardrik.Listener`.
  * `:authorizer` - `{module, config}`. `{Wardrik.Statements, []}` unless set.
  * `:retention` - days events are kept, or `:infinity`. `30`.
  * `:backend` - where agents are kept. The store unless set.
  * `:discovery_interval_ms` - how often a node looks for agents no node runs. `30_000`.
  * `:trusted_workers` - `true` to treat tool workers' answers as the deployment's own. `false`.
  * `:egress` - which URLs users may point it at. `&Wardrik.Egress.public/1` unless set.
  * `:prices` - model prices the catalog lacks, per million tokens. See `Wardrik.Setup.price/2`.

        prices: %{"zai_coding_plan:glm-5.1" => [priced_as: "zai:glm-5.1", markup: 0.1]}

# `stream`

```elixir
@spec stream(Wardrik.Scope.t(), String.t(), map()) ::
  {:ok, Enumerable.t()} | {:error, Wardrik.Error.t()}
```

Streams the operation `id`, such as `"events.list"`, in `scope`.

The stream yields `{event, cursor}`. It reads what's there, then follows new events.

    {:ok, stream} = Wardrik.stream(scope, "events.list", %{"resources" => ["acme/*"]})

---

*Consult [api-reference.md](api-reference.md) for complete listing*
