# HTTP Routes
Module-defined agents and typed operations can be exposed as JSON HTTP
endpoints with `Condukt.Plug`.
Use an agent route when you want to run a normal `use Condukt` module as a
one-shot request handler. Use an operation route when you want a statically
declared entrypoint with input and output schemas.
## Agent routes
Agent routes run `Condukt.run(MyApp.Agent, prompt, opts)` for each HTTP
request. The request body can be a raw prompt string, a JSON string, or a JSON
object with an optional `"prompt"` string. If it is missing, the route's
`:prompt` option is used. If neither is provided, Condukt runs the agent with
an empty prompt.
```elixir
defmodule MyApp.AssistantAgent do
use Condukt
@impl true
def system_prompt do
"You are a helpful assistant for support requests."
end
end
```
### Plug Router
```elixir
defmodule MyApp.Router do
use Plug.Router
plug Plug.Parsers, parsers: [:json], json_decoder: JSON
plug :match
plug :dispatch
post "/assistant",
to: Condukt.Plug,
init_opts: [
agent: MyApp.AssistantAgent,
prompt: "Help with this support request.",
run_opts: [timeout: 120_000]
]
end
```
Agent route request:
```text
Summarize the last customer message and suggest a reply.
```
Or as JSON:
```json
{
"prompt": "Summarize the last customer message and suggest a reply."
}
```
Agent route response:
```json
{
"ok": true,
"result": "The customer is asking for..."
}
```
Use `:prompt_param` if the request uses a different field name:
```elixir
post "/assistant",
to: Condukt.Plug,
init_opts: [
agent: MyApp.AssistantAgent,
prompt_param: "message"
]
```
## Operation routes
Operation routes run `Condukt.Operation.run/4` for each HTTP request. The
request body must match the operation input schema, and the response contains
the validated structured output.
```elixir
defmodule MyApp.ReviewAgent do
use Condukt
operation :review_pr,
input: %{
type: "object",
properties: %{
repo: %{type: "string"},
pr_number: %{type: "integer"}
},
required: ["repo", "pr_number"]
},
output: %{
type: "object",
properties: %{
verdict: %{type: "string", enum: ["approve", "request_changes", "comment"]},
summary: %{type: "string"}
},
required: ["verdict", "summary"]
},
instructions: "Review the pull request and return a verdict."
end
```
Each request runs the operation through Condukt's one-shot structured run path.
No conversation history is kept between HTTP requests.
### Plug Router
```elixir
defmodule MyApp.Router do
use Plug.Router
plug Plug.Parsers, parsers: [:json], json_decoder: JSON
plug :match
plug :dispatch
post "/review-pr",
to: Condukt.Plug,
init_opts: [
agent: MyApp.ReviewAgent,
operation: :review_pr,
run_opts: [timeout: 120_000]
]
end
```
If `conn.body_params` has not been fetched, `Condukt.Plug` reads and decodes
the request body itself.
### Request and response shape
Requests are JSON objects that match the operation input schema:
```json
{
"repo": "tuist/condukt",
"pr_number": 42
}
```
Successful responses use this envelope:
```json
{
"ok": true,
"result": {
"verdict": "approve",
"summary": "Looks good."
}
}
```
Errors use this envelope:
```json
{
"ok": false,
"error": {
"code": "invalid_input",
"message": "..."
}
}
```
Input validation failures return `422`, malformed JSON returns `400`, unknown
operations return `404`, and structured agent failures return `502` or `500`
depending on where the failure happened.
## Per-request run options
Use `:run_opts` to pass options to `Condukt.run/3` for agent routes or
`Condukt.Operation.run/4` for operation routes. The value can be a keyword list
or a one-arity function that receives the connection:
```elixir
post "/review-pr",
to: Condukt.Plug,
init_opts: [
agent: MyApp.ReviewAgent,
operation: :review_pr,
run_opts: &MyApp.RouteOptions.condukt_run_opts/1
]
```
If the HTTP input should come from somewhere other than the JSON body, pass an
`:input` function. In router declarations, use remote captures rather than
anonymous functions because route options are stored at compile time:
```elixir
post "/repos/:owner/:repo/pulls/:number/review",
to: Condukt.Plug,
init_opts: [
agent: MyApp.ReviewAgent,
operation: :review_pr,
input: &MyApp.RouteOptions.review_input/1
]
```
```elixir
defmodule MyApp.RouteOptions do
def condukt_run_opts(conn) do
[
api_key: fetch_session_api_key(conn),
timeout: 120_000
]
end
def review_input(conn) do
%{
"repo" => conn.path_params["owner"] <> "/" <> conn.path_params["repo"],
"pr_number" => String.to_integer(conn.path_params["number"])
}
end
end
```