usage-rules.md

# ADK Ex Usage Rules

`adk_ex` is an Elixir port of Google's Agent Development Kit (ADK). It provides agent orchestration, session management, tool use, LLM abstraction, plugins, and telemetry. It is transport-agnostic — no HTTP/Plug dependencies.

## Architecture

```
User Message -> Runner -> Agent -> Flow -> LLM
                  |          |        |       |
              [plugins]  [plugins] [processors]|
                  |          |     [tool call loop]
                  |          |     [agent transfer]
                  |          |        |
               [commits events + state to Session]
                  |
               [yields Events as a stream]
```

## Core Patterns

### Creating an Agent

```elixir
agent = %ADK.Agent.LlmAgent{
  name: "my-agent",
  model: ADK.Model.Registry.resolve("gemini-2.0-flash"),
  instruction: "You are a helpful assistant.",
  tools: [my_tool],
  sub_agents: []
}
```

### Defining a Tool

Always use `handler:`, never `function:`.

```elixir
tool = ADK.Tool.FunctionTool.new(
  name: "get_weather",
  description: "Get weather for a city",
  handler: fn _ctx, %{"city" => city} ->
    {:ok, %{"weather" => "Sunny in #{city}"}}
  end
)
```

The handler signature is `(ADK.Tool.Context.t(), map()) -> {:ok, map()} | {:error, term()}`.

### Running an Agent

`ADK.Runner.run/4` returns a stream of `ADK.Event` structs.

```elixir
{:ok, runner} = ADK.Runner.new(
  app_name: "my-app",
  root_agent: agent,
  session_service: session_service
)

events =
  runner
  |> ADK.Runner.run("user-1", "session-1", content)
  |> Enum.to_list()
```

### Session Service

Start the built-in ETS-backed service:

```elixir
{:ok, service} = ADK.Session.InMemory.start_link(name: :my_sessions)

{:ok, session} = ADK.Session.InMemory.create(service,
  app_name: "my-app",
  user_id: "user-1",
  session_id: "session-1",
  state: %{}
)
```

For database-backed sessions, use the separate `adk_ex_ecto` package.

### State Prefixes

Session state keys use prefixes to control scope:

| Prefix | Scope | Persisted? |
|--------|-------|-----------|
| (none) | Session-local | Yes |
| `app:` | Cross-session for the app | Yes |
| `user:` | Cross-session for the user | Yes |
| `temp:` | Current invocation only | No |

### Creating Content

```elixir
content = ADK.Types.Content.new_from_text("user", "Hello, agent!")
```

### Configuring a Model

Four providers ship. `ADK.Model.LiteLlm` is the one to reach for when using OpenAI or anything OpenAI-compatible — it mirrors Google Python ADK's `LiteLlm(model="openai/gpt-4o")` pattern.

```elixir
# Gemini (Google)
{:ok, gemini} = ADK.Model.Registry.resolve("gemini-2.0-flash", api_key: key)

# Claude (direct Anthropic API)
{:ok, claude} = ADK.Model.Registry.resolve("claude-sonnet-4-5", api_key: key)

# OpenAI (GPT-4o, GPT-4, o1, o3 — direct)
{:ok, openai} = ADK.Model.Registry.resolve("gpt-4o", api_key: key)

# LiteLLM proxy (any of 100+ providers — `base_url` required)
{:ok, any} = ADK.Model.Registry.resolve(
  "anthropic/claude-3-5-sonnet-20241022",
  api_key: "sk-proxy",
  base_url: "http://localhost:4000"
)

# Any OpenAI-compatible endpoint (Ollama, Groq, Together, OpenRouter, ...)
ollama = %ADK.Model.LiteLlm{
  model_name: "llama3",
  api_key: "none",
  base_url: "http://localhost:11434/v1"
}
```

See `adk_ex:models` for deeper guidance.

## Critical Rules

1. **FunctionTool uses `handler:` not `function:`** — `FunctionTool.new(handler: fn/2)`.
2. **Mock model needs `Mock.new/1`** — use `ADK.Model.Mock.new(responses: [...])`, never bare `%ADK.Model.Mock{}`. It starts an Agent process for response sequencing.
3. **Agent behaviour has no module functions** — call `agent.__struct__.run(agent, ctx)` or the implementing module directly. `ADK.Agent` is a behaviour, not a dispatcher.
4. **Telemetry prefix is `[:adk_ex, ...]`** — not `[:adk, ...]`. Events: `[:adk_ex, :llm, :start|:stop]`, `[:adk_ex, :tool, :start|:stop]`.
5. **Model.Registry.resolve/2** — pattern-matches on name: `"gemini-*"` -> Gemini, `"claude-*"` -> Claude, `"gpt-*"`/`"o1*"`/`"o3*"` -> LiteLlm@OpenAI, `"provider/model"` (e.g. `"openai/gpt-4o"`, `"anthropic/claude-3-5-sonnet-20241022"`) -> LiteLlm (requires `base_url:` for a LiteLLM proxy).
6. **Use `ADK.Model.LiteLlm` for OpenAI and any OpenAI-compatible endpoint** — mirrors Python ADK's `LiteLlm(model="openai/gpt-4o")`. Set `base_url` to OpenAI, a LiteLLM proxy, or any OpenAI-compatible API (Groq, Together, OpenRouter, Ollama, vLLM, Azure OpenAI, LM Studio, etc.). Do not build a bespoke OpenAI client.
7. **Plugin callbacks return `{value | nil, updated_context}`** — nil means continue to next plugin; non-nil short-circuits.
8. **All Plugin.Manager.run_* functions accept `nil`** as first arg (no-op) — no nil checks needed at call sites.
9. **Nested module compile order** — define referenced modules before parent modules in the same file (e.g. `Event.Actions` before `Event`).
10. **Avoid MapSet with dialyzer** — use `%{key => true}` maps + `Map.has_key?/2` instead.

## Sub-rules

For detailed guidance on specific topics, see the `usage-rules/` directory:

- `adk_ex:agents` — Agent types, when to use each, sub-agents, agent transfer
- `adk_ex:tools` — FunctionTool, argument schemas, Tool.Context, Toolset behaviour
- `adk_ex:sessions` — Session struct, state prefixes, Service behaviour, switching backends
- `adk_ex:plugins` — Plugin struct, 12 callback hooks, execution order, short-circuit semantics
- `adk_ex:telemetry` — Event naming, OTel span conventions, testing with otel_simple_processor
- `adk_ex:models` — Provider selection (Gemini, Claude, OpenAI, LiteLLM proxy), registry, multi-provider patterns