# Run your agent on plori — integration brief

> **You are a coding agent.** A developer just handed you this file and pointed you at
> their agent's repository. Your job: make their agent run on **plori** by (1) writing an
> `agent-brain.yaml` manifest and (2) making the agent's container satisfy the small HTTP
> contract below. Read this whole file first, then inspect the repo and produce the manifest
> + any code changes. The canonical, always-current copy of this brief lives at
> <https://plori.ai/build/agent-spec.md>.

---

## 1. What plori is (context you need)

plori is a **hosting runtime for AI agents** — think "its own computer for your agent."
plori runs **no agent loop of its own and injects no tools**. Your agent keeps its loop, its
prompts, its model choice, and its tools. plori owns everything *around* the loop:

- **Hosting** — packages your container, schedules it, scales it to zero when idle (idle is free).
- **A persistent disk** — one durable working directory per agent, mounted across runs, so a
  one-shot process behaves as if it were long-lived.
- **Multi-tenancy & isolation** — every end user / agent is isolated; you don't manage this.
- **Tools** — plori provisions allow-listed CLI "skills" onto the sandbox (a binary on `PATH`
  plus a `SKILL.md`); your agent runs them with its own shell. No tool gateway, no SDK.
- **Observability & billing** — tracing, usage, and metered credits, handled for you.

You integrate **declaratively**: you describe your agent in one manifest. You do **not** send a
pull request to plori, learn an SDK, or adopt a framework. If your agent already speaks the
OpenAI API, you are almost done.

---

## 2. Your task (deliverables)

1. **Write `agent-brain.yaml`** at the repo root, conforming to §3–§9.
2. **Confirm the container exposes the endpoint** named in `invocation.endpoint` (§4–§5).
   Most OpenAI-compatible servers already do. If not, add a thin handler.
3. **Confirm a health endpoint** exists (§8). Add a trivial `200 OK` route if missing.
4. **Make state durable on disk** (§6): the agent must persist its session/memory under the
   mounted `workingDir`, not in container-local ephemeral storage, or memory is lost between runs.
5. **(Optional) Declare CLI tools** the agent expects (§7) and **richer streaming** (§9).
6. Print a short summary of what you changed and why, and the final manifest.

Do **not** invent fields. Use only the schema in §3. If a real agent capability has no field
here, leave it out and note it for the developer.

---

## 3. The manifest schema (complete field reference)

Format: YAML or JSON. It reads like a Kubernetes object on purpose.

```yaml
apiVersion: agents.plori.dev/v1   # REQUIRED, exact string
kind: AgentBrain                  # REQUIRED, exact string

metadata:
  name: my-agent                  # REQUIRED. DNS-label: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
  displayName: My Agent           # optional, human label
  vendor: acme                    # optional
  license: MIT                    # optional
  source: https://github.com/...  # optional, repo URL

spec:
  image: ghcr.io/acme/my-agent:1.0   # OCI image — the unit of pluggability.
                                     # Omit ONLY if `endpoint` points at an already-running remote brain.
  ports: [8642]                      # ports your container listens on
  endpoint: https://...              # optional: URL of a remote, already-running brain (instead of image)
  provider: openrouter               # optional: which BYOK model provider your model calls bill to

  invocation:                        # REQUIRED — how plori drives your agent. See §4.
    profile: openai-chat             # openai-chat | openai-responses | ari-native | adapter | a2a
    endpoint: /v1/chat/completions   # the HTTP path plori POSTs a turn to
    streaming: sse                   # sse (recommended) — stream the response

  health:                            # health probes. See §8.
    liveness: /v1/health             # cheap "process alive" check
    readiness: /v1/health/extended   # optional: "ready to serve" (model loaded, etc.)
    capabilities: /v1/capabilities   # optional: self-describe features

  lifecycle:                         # how plori schedules it. See §6.
    model: one-shot                  # one-shot | persistent | session-affinity
    scaleToZero: true                # idle costs nothing
    bootTimeoutSeconds: 300          # max seconds to wait for the pod to become ready

  state:                             # what to externalize so a one-shot process feels durable. See §6.
    workingDir: /workspace           # plori mounts the persistent disk here
    paths: [".my-agent/"]            # relative paths under workingDir that hold durable state
    sessionIdentity:
      kind: header                   # header | bearer | path
      name: X-My-Session-Id          # the header/param name (omit when kind: bearer)

  tools:                             # how tools reach your agent. See §7.
    protocol: skills                 # "skills" is the only supported protocol
    skillsDirEnv: PLORI_SKILLS_DIR   # env var whose value is the skills directory path

  commands:                          # optional: slash commands your agent understands (build the UI palette)
    - { name: compact, description: "Compact context", handler: agent, port: true }
      # handler: client | agent | unsupported   port: surface it in the hosted UI?

  streams: [text, reasoning, steps, tools]  # optional: which event classes you emit. See §9.

  exposes:                           # optional: peer capabilities advertised outward
    a2a: true                        # publish an A2A Agent Card (agent-to-agent)
    agui: false                      # emit AG-UI events directly
```

---

## 4. Pick your invocation profile (the only real decision)

Choose by **what HTTP surface your agent already has**. This drives `invocation.profile`:

| Your agent already… | Use `profile:` | `endpoint:` | plori normalizes the rest? |
|---|---|---|---|
| Speaks OpenAI **Chat Completions** | `openai-chat` | `/v1/chat/completions` | yes — easiest path |
| Speaks OpenAI **Responses** | `openai-responses` | `/v1/responses` | yes |
| Wants to stream its **whole loop** (reasoning, steps, tool calls) natively | `ari-native` | `/invocations` | n/a — you emit canonical events |
| Has a **bespoke** wire | `adapter` | (yours) | ship a thin sidecar that presents one of the above |

If you have an OpenAI-compatible endpoint, choose `openai-chat` (or `openai-responses`) and you
are essentially done — plori translates everything else. Pick `ari-native` only when you want
the UI to show intermediate reasoning/steps/tool events, and you're willing to emit them (§9).

---

## 5. The endpoint contract (what plori sends, per profile)

**`openai-chat`** — plori `POST`s an OpenAI Chat Completions request to `invocation.endpoint`:
- Body: standard `{ "model": ..., "messages": [...], "stream": true }`.
- Respond with a normal SSE stream of `chat.completion.chunk` deltas, terminated by `data: [DONE]`.
- The latest user turn is the last `messages` entry. Prior turns may be replayed, but **your
  durable memory (§6) is the source of truth** — don't rely on plori resending full history.

**`openai-responses`** — `POST` to `/v1/responses` in OpenAI Responses format; SSE stream of
response items.

**`ari-native`** — `POST` to `/invocations`; you stream plori's canonical **Event** objects as
SSE / NDJSON (one JSON event per line). This is how you surface the full turn (see §9). Use this
when a final-answer-only response isn't enough.

**`adapter`** — your container (or a sidecar plori runs alongside it) translates plori's request
into your bespoke wire and back. Use this as the escape hatch when nothing above fits.

In all cases plori passes the **session identity** you declared in `state.sessionIdentity`
(e.g. the `X-My-Session-Id` header) so you can load/save the right session's state.

---

## 6. State & session continuity (the part that matters most)

plori's superpower is **persistent experience on one-shot execution**: by default each turn can
run in a fresh, ephemeral pod (`lifecycle.model: one-shot`, `scaleToZero: true`) — cheap and
clean — yet the agent must *feel* durable. That only works if **all durable state lives on the
mounted disk**:

- plori mounts the per-agent persistent disk at `state.workingDir` (e.g. `/workspace`) and
  exposes the mount path via the `PLORI_MOUNT_PATH` env var.
- Your agent must read/write its session store and long-term memory under that directory. List
  the relative paths in `state.paths` (e.g. `[".my-agent/"]`).
- Each turn carries a stable **session id** via `state.sessionIdentity` (header/bearer/path).
  Key your session store by it: load on request, save before responding.

> **Concrete check:** if your agent writes its DB/memory to `$HOME`, `/tmp`, or a hardcoded path,
> redirect it under `workingDir` (or symlink). SQLite/WAL on a network FS can be slow on cold
> init — prefer writing to a local path during a turn and syncing to `workingDir` at the end if
> your agent is sensitive to fsync latency.

`lifecycle.model` options:
- `one-shot` — fresh pod per turn; state externalized to disk. **Default choice; best memory hygiene.**
- `session-affinity` — a warm pod serves consecutive turns over HTTP, recycled after idle. Lower
  first-turn latency; same disk contract.
- `persistent` — a long-lived singleton pod.

---

## 7. Tools (CLI skills)

plori does not inject tools into your loop. Instead it provisions allow-listed **CLI skills**
onto the sandbox: each is an executable on `PATH` plus a `SKILL.md` describing it, inside a
skills directory. Your agent discovers them and runs them with its **own shell**.

- Set `tools.protocol: skills` and `tools.skillsDirEnv: PLORI_SKILLS_DIR`.
- plori sets that env var to the skills directory path at runtime.
- Your agent should enumerate that directory, read each `SKILL.md`, and shell out to the binary.

There is no tool gateway and no schema you must adopt — if your agent can run shell commands,
it can use plori skills.

---

## 8. Health

plori probes the paths in `spec.health`:
- `liveness` (required in practice) — a cheap endpoint returning `200` when the process is up.
- `readiness` (optional) — returns `200` only when truly ready to serve (model loaded, etc.).
  plori gates the pod on this; use it if your boot is slow.
- `capabilities` (optional) — returns a JSON description of what your agent supports.

If your server has no health route, add a trivial one (e.g. `GET /v1/health → 200 {"ok":true}`).

---

## 9. Streaming richer events (optional)

By default plori assumes your agent emits only the final answer (`text`). To make the hosted UI
show the agent *working* — its reasoning, step/phase boundaries, and tool calls — emit plori's
canonical events on the `ari-native` profile and advertise them in `spec.streams`:

- `text` — the assistant's answer (implicit; every agent emits this).
- `reasoning` — thinking/reasoning deltas.
- `steps` — phase/step boundaries (e.g. "planning", "executing").
- `tools` — tool calls and their results.

Only declare what you actually emit — the UI degrades gracefully to "working…" for the rest.
`ari-native` defaults to the full set `[text, reasoning, steps, tools]`; override with
`spec.streams` if you emit a subset (e.g. `streams: [text, steps]`).

---

## 10. Two complete, real examples

### A. OpenAI-chat agent (the common case — start here)

```yaml
apiVersion: agents.plori.dev/v1
kind: AgentBrain
metadata:
  name: my-agent
  displayName: My Agent
  vendor: acme
  license: MIT
spec:
  image: ghcr.io/acme/my-agent:1.0
  ports: [8642]
  provider: openrouter
  invocation:
    profile: openai-chat
    endpoint: /v1/chat/completions
    streaming: sse
  health:
    liveness: /v1/health
    readiness: /v1/health/extended
    capabilities: /v1/capabilities
  lifecycle:
    model: one-shot
    scaleToZero: true
    bootTimeoutSeconds: 300
  state:
    workingDir: /workspace
    paths: [".my-agent/"]
    sessionIdentity:
      kind: header
      name: X-My-Session-Id
  tools:
    protocol: skills
    skillsDirEnv: PLORI_SKILLS_DIR
  exposes:
    a2a: true
    agui: false
```

### B. Native-events agent (stream the whole loop)

```yaml
apiVersion: agents.plori.dev/v1
kind: AgentBrain
metadata:
  name: my-agent
  displayName: My Agent
spec:
  image: ghcr.io/acme/my-agent:1.0
  ports: [8642]
  provider: openrouter
  invocation:
    profile: ari-native
    endpoint: /invocations
    streaming: sse
  streams: [text, steps]          # be honest: declare only what you emit
  health:
    liveness: /health
    readiness: /health
  lifecycle:
    model: one-shot
    scaleToZero: true
    bootTimeoutSeconds: 180
  state:
    workingDir: /workspace
    paths: [".my-agent/"]
    sessionIdentity:
      kind: header
      name: X-My-Session-Id
  tools:
    protocol: skills
    skillsDirEnv: PLORI_SKILLS_DIR
  exposes:
    a2a: true
    agui: false
```

---

## 11. Before you finish — checklist

- [ ] `apiVersion: agents.plori.dev/v1` and `kind: AgentBrain` exact.
- [ ] `metadata.name` is a valid DNS label.
- [ ] `spec.image` points at a real, pullable OCI image (or `spec.endpoint` is a live URL).
- [ ] `invocation.profile` matches the agent's actual HTTP surface, and `invocation.endpoint`
      is a path the container really serves.
- [ ] A `health.liveness` path returns `200`.
- [ ] All durable state is written under `state.workingDir`, and `state.paths` lists it.
- [ ] `state.sessionIdentity` is read on each turn to load/save the right session.
- [ ] You did **not** invent any field outside this schema.

**Submitting:** self-serve upload is in preview. For now, share the finished `agent-brain.yaml`
(and the image) via the waitlist at <https://plori.ai/build> or email **dev@plori.ai**, and
plori will bring the agent online. Early builders help shape the contract.
