> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Capabilities & Gaps

> The capability matrix, the honest gaps, and why a false flag does not mean a missing feature.

A capability flag describes what an engine can **report**, not what it can do.

```ts theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { PRAISONAI_TS_CAPABILITIES } from "praisonai-mobile/engines/praisonai-ts";

if (PRAISONAI_TS_CAPABILITIES.tools) renderToolRows();
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Engine[🧠 Engine] --> Caps[📋 capabilities]
    Caps -->|true| Render[✅ UI renders rows]
    Caps -->|false| Skip[⚠️ UI renders nothing]

    classDef engine fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef caps fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef render fill:#10B981,stroke:#7C90A0,color:#fff
    classDef skip fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Engine engine
    class Caps caps
    class Render render
    class Skip skip
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Closed Gaps"
        G4[💾 Gap 4: turn write] --> C[✅ CLOSED]
        G5[⌨️ Gap 5: keyboard seed] --> C
        G6[⚠️ Gap 6: decoder refusals] --> C
    end

    classDef gap fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef done fill:#10B981,stroke:#7C90A0,color:#fff

    class G4,G5,G6 gap
    class C done
```

## Quick Start

<Steps>
  <Step title="Read a capability before rendering">
    ```ts theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    const caps = engine.capabilities;
    ```

    Capabilities are a property, so the UI decides what to render before the first token.
  </Step>

  <Step title="Print the unsupported scenarios">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    npm run test
    ```

    The conformance suite prints every scenario an engine cannot produce.
  </Step>
</Steps>

***

## The Capability Matrix

What each shipped engine can report.

| Capability     | `praisonai-ts` | `remote-http` |
| -------------- | :------------: | :-----------: |
| `streaming`    |        ✅       |       ✅       |
| `reasoning`    |        ❌       |       ✅       |
| `tools`        |        ❌       |       ✅       |
| `approvals`    |        ❌       |       ✅       |
| `cancellation` |        ✅       |       ✅       |
| `attachments`  |        ❌       |       ✅       |

***

## Closed Gaps

The rows kept from the old gap report so a reader arriving from an old bug can find where they went.

| Capability                                         | Status      | Notes                                                                                |
| -------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------ |
| In-process engine writes through the app's session | ✅ Supported | Closes Gap 4. Only `praisonai-ts` writes; `remote-http` does not. (PR #4552)         |
| Keyboard snapshot on first paint                   | ✅ Supported | Closes Gap 5. Seeded from `visualViewport`, guarded against pinch-zoom. (PR #4552)   |
| Decoder refusals reach the transcript              | ✅ Supported | Closes Gap 6. A refused frame becomes a dropped row instead of vanishing. (PR #4560) |

***

## Why `tools: false` For praisonai-ts

`praisonai-ts` executes tools normally; it just never announces them. Upstream `Agent.streamEvents()` emits a three-variant union — `text`, `finish`, `error` — and none of those carry a tool call, so the engine cannot report one.

```ts theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
type AgentEvent =
  | { type: "text";   delta: string }
  | { type: "finish"; text: string }
  | { type: "error";  error: Error };
```

The flag describes what the engine can report. A UI that renders tool rows off a `true` flag would render nothing and look broken — and a tool call that silently failed would be indistinguishable from a normal answer, which is exactly what `tool_result.ok` was introduced to prevent.

***

## How Each Gap Closed

Gap 4 was "mechanism landed, not wired": `createSession` was called and `RunPersistence.record` was called, and nothing connected them — the two signatures did not even line up (`record(prompt, answer)` against `record(request, answer)`).

```ts theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
// The named adapter where the two vocabularies meet.
export function persistenceFor(session: Session) {
  return { record: (request, answer) => session.record(request.prompt, answer) };
}
```

Gap 5 was "property added, snapshot not seeded": `keyboardHeightPx` was declared `= 0` and only updated by an event, so a component mounting while the keyboard was already up laid out at 0 for one frame and then jumped.

```ts theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
let keyboardHeightPx = readKeyboardHeight(view); // seeded at construction
```

Both are verified by a positive control: reverting either makes a named test fail.

Gap 6 was also "mechanism landed, not wired": the `Dropped` type, the view-model row and the seven user-facing strings all existed with no producer. `remote-http` was the only production caller of `decodeEvent` and discarded every rejection, so a malformed frame made its tool vanish and the turn rendered as a clean answer.

```ts theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
// The channel between the engine and the controller, neither of which can
// call the other. Refusals are drained onto the transcript per event and
// again in finally, so one beside the last frame is still shown.
export function createDropSink(): DropSink { /* note + drain */ }
```

A `carry` field fixed the cross-turn contamination: `apply(start)` no longer carries the previous turn's entire `dropped` list, so one refusal on turn 1 no longer paints every later turn as damaged. Two composition tests pin the two wiring hops — `createApp` and the real `enginesFor` — because removing the sink from `createRunController` or dropping the registry's forward each left the suite green in isolation. See [Dropped Events](/docs/features/mobile/dropped-events).

<Note>
  `showDiagnostics` (labelled **"Show dropped events"**) is now meaningful: dropped events exist in the transcript for it to hide or show. It is declared but not yet consumed — dropped rows currently render unconditionally until a settings screen reads the flag.
</Note>

***

## The 5 Unsupported Scenarios

From `src/praisonai-mobile/docs/gaps.md`, the conformance scenarios `praisonai-ts` declares unsupported.

| Scenario          | Reason                                                  |
| ----------------- | ------------------------------------------------------- |
| `tool_ok`         | No `tool_call`/`tool_result` variant upstream.          |
| `tool_failed`     | No `tool_result`, so `ok: false` cannot be reported.    |
| `tool_unresolved` | No `tool_call`, so there is no row to leave unresolved. |
| `approval`        | `ApprovalManager` cannot reach the event channel.       |
| `two_approvals`   | Same as `approval`.                                     |

Each is printed on every run, so a contract that quietly shrinks is visible rather than silently green.

<Info>
  Closing the gap needs an upstream change: `AgentEvent` gaining tool and approval variants, mirroring Python's `StreamEventType`. Until then, use `remote-http` for tool visibility.
</Info>

***

## Roadmap

Closing the `TurnState` interleave gap is the prerequisite to `remote-http` becoming the default. Two Node-only imports on the Agent graph (`crypto` in `agent/simple.ts`, `events` in `ai/tool-approval.ts`) still block the in-process device build — tracked as PraisonAI PR #4438.

***

## Best Practices

<AccordionGroup>
  <Accordion title="Render from the capability, not from hope">
    Check `capabilities.tools` before drawing tool rows; the flag is the contract the conformance suite enforces in both directions.
  </Accordion>

  <Accordion title="Declare gaps, never fake them">
    An honest `unsupported` entry keeps the suite meaningful; a faked scenario hides a defect it exists to catch.
  </Accordion>

  <Accordion title="Switch engines to gain capabilities">
    `remote-http` speaks the full vocabulary because the desktop server already emits it.
  </Accordion>

  <Accordion title="Close a gap only when it is wired end-to-end">
    A mechanism existing is not the same as a mechanism working. Gaps 4 and 5 each landed a type or a function before the wiring, and read as closed from either end until re-audited.
  </Accordion>

  <Accordion title="Keep retired gap rows visible">
    The Gap-4 and Gap-5 rows stay in the matrix, linked to PR #4552, so a reader coming from an old bug report can find where they went rather than assuming they were dropped.
  </Accordion>

  <Accordion title="State scope so it is not read later as an oversight">
    The remote-http exclusion is deliberate. Documenting it up front stops a future reader from filing the empty local session as a regression.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Agent Engine Port" icon="plug" href="/docs/features/mobile/engines">
    The port and its conformance harness.
  </Card>

  <Card title="The 11 Events" icon="network-wired" href="/docs/features/mobile/protocol">
    The full event vocabulary.
  </Card>

  <Card title="Mobile Engines" icon="microchip" href="/docs/features/mobile/engines">
    Which engine owns the write.
  </Card>

  <Card title="Shell & Adapters" icon="mobile-screen" href="/docs/features/mobile/shell-and-adapters">
    The keyboard snapshot and its guard.
  </Card>

  <Card title="Dropped Events" icon="triangle-exclamation" href="/docs/features/mobile/dropped-events">
    The closed decode-rejection gap, end to end.
  </Card>
</CardGroup>
