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

# Jev API Engine

> @obversa/engine-jev-api: typed questions about state your run already recorded, answered by Jev over the TypeSafe API.

`@obversa/engine-jev-api` asks Jev structured questions about state your
run already recorded: should the writer run again, which stage should take it,
how ready is this change to land. Each attempt is one Jev decision call over
the TypeSafe API, and the answers come back as the structured result. Jev
runs with workspace mode `none`; it never reads or edits files.

It loads none of your own setup, so it always runs clean and has no `clean` option. Its
row in the [engines table](/docs/packages/engines#the-engines) says the same.

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @obversa/engine-jev-api
  ```

  ```bash pnpm theme={null}
  pnpm add @obversa/engine-jev-api
  ```
</CodeGroup>

Not in `@obversa/obversa`; install it on its own.

## Requirements

* A Jev endpoint and API key, passed as the `endpoint` and `apiKey` options.
  `jev()` also reads them from `TYPESAFE_ENDPOINT` and `TYPESAFE_API_KEY`.

## Identity

The plugin reports `provider: 'typesafe'`, with the model family read from
the model name. It sends the model the request names, or `model` from its
options, or `jev-latest`. If the API doesn't echo a model, or echoes one that
can't be read, the recorded effective model and family are both `null`, and
the answers are still returned.

## Quickstart

Construct the engine with the endpoint and key, and ask it what it will run
as, with no network call:

```ts examples/engine-jev-api-binding.ts {4-7,9} theme={null}
import { JevApiEngine } from '@obversa/engine-jev-api';

// The key is a placeholder. `admit` checks the request and sends nothing.
const engine = new JevApiEngine({
  endpoint: 'https://api.typesafe.ai/v1/systemone',
  apiKey: 'example-key',
});

const identity = await engine.admit({ workspaceMode: 'none' }, new AbortController().signal);
console.log(JSON.stringify(identity, null, 2));
```

`admit` returns the identity the engine reports before any request. Run it
with `npx tsx engine-jev-api-binding.ts`:

```json Output theme={null}
{
  "adapter": "jev-api",
  "adapterVersion": null,
  "provider": "typesafe",
  "modelFamily": "jev",
  "model": "jev-latest",
  "executable": null,
  "capabilities": []
}
```

In a run, read the key from your environment instead of a literal. The
result's structured value is the `answers` object itself, so a binding's
`parseResult` reads `part.value.send_back` directly.

## A seat for a team

In a workflow, `jev()` builds the seat in one line, as `claude(model)` does
for Claude. It reads the endpoint and key from `TYPESAFE_ENDPOINT` and
`TYPESAFE_API_KEY`, or from its `endpoint` and `apiKey` options. It throws
when either is missing, and the message names both variables. The model
defaults to `jev-latest`. The seat returns the answers object as assistant
text, the JSON of that object, so an `agentJob` and the runtime's `judge`
read it as they read any seat's reply. Offline, `recordedJudge` from
`@obversa/runtime/testing` stands in for it and replays recorded answers
from a JSON file:

```ts examples/judge-stops-the-loop.ts (excerpt) theme={null}
const judgeSeat = process.env.JUDGE === 'jev' ? jev() : recordedJudge('judge.json');
```

## Ask a question

The prompt is a JSON document of the shape `{ state, questions }`:

```json Request theme={null}
{
  "state": { "diff": "..." },
  "questions": {
    "send_back": {
      "type": "noul",
      "instructions": "Should this work be sent back for more iteration?",
      "criteria": {
        "true": "A finding describes incorrect behaviour a user would see",
        "false": "The findings are cosmetic or advisory only"
      }
    },
    "which_stage": {
      "type": "choice",
      "instructions": "Which stage should the work go back to?",
      "criteria": {
        "implement": "The defect is in the code itself",
        "test": "The code is right but the tests do not prove it",
        "none": "Nothing needs to go back"
      }
    },
    "readiness": {
      "type": "score",
      "instructions": "How ready is this change to land?",
      "criteria": ["Unsafe to land", "Needs another round", "Lands clean"]
    }
  }
}
```

`state` is whatever your run recorded. Each question names a type: `noul`
(true or false), whose criteria say what true and false mean; `choice`,
whose criteria map each option to its description; or `score`, whose
criteria are the array of labels the score indexes. Before any network
call, the adapter checks that the prompt parses to an object, that
`questions` is a non-empty object, and that every question names one of the
three types. A missing or `null` `state` is sent as an empty object; the
adapter forwards `criteria` unchanged.

The adapter returns the answer objects unchanged and doesn't check their
shape. What the provider has been seen to return: a `noul` answer carries a
probability and no separate `confidence`; `choice` and `score` answers carry
a `confidence`, and may carry `probabilities`. `parseJevDocument` reads a
prompt into the request shape before it's sent.

## Options

| Field | Type | Default | Description |
| - | - | - | - |
| `endpoint` | `string` | required | The POST target, such as `https://api.typesafe.ai/v1/systemone`. |
| `apiKey` | `string` | required | The bearer credential. |
| `model` | `string` | `'jev-latest'` | The wire model when the request names none. |
| `adapterVersion` | `string` | none | The package version recorded in the requested identity. |
| `fetch` | `typeof fetch` | global `fetch` | Injectable for tests. |
| `effort` | `string` | none | Not supported: the Jev service decides how hard it works, so setting it throws. A step that sets `effort` fails the same way. Also a `JevSeatOptions` field. |

## Errors

* **`invalid-config`** before any network call, for a malformed prompt or a
  request field Jev can't take: `system` or `systemMode` (Jev carries no
  system prompt), `env` (the key comes from the option), `maxTokens` (no
  server-side cap), `effort` (the service decides how hard it works), a
  `workspaceMode` other than `none`, or a non-empty `tools` list.
* **`timeout`** when `timeoutMs`, plus `timeoutGraceMs` when set, elapses
  across the whole call, from connecting to reading the body. **`aborted`**
  on a caller abort.
* **Provider response data**, error bodies included, is recorded in the
  result and in error details.

## Limits

* **One HTTP request per attempt.** The adapter never retries by itself;
  retry safety belongs to the node's binding.
* **`maxOutputBytes`** on the request caps the response body the adapter
  reads. It isn't a general memory limit.
* **A low-confidence answer completes like any other result.** Thresholds
  and routing are up to your workflow.

## API

* **`JevApiEngine`**: the engine class. **`JevApiEngineOptions`**: its
  options. [Quickstart](#quickstart).
* **`jev`**: the seat for a team workflow or a judge. **`JevSeat`** and
  **`JevSeatOptions`**: its types. [A seat for a team](#a-seat-for-a-team).
* **`parseJevDocument`**: read a prompt into the request shape.
  [Ask a question](#ask-a-question).

## Next steps

* [Evals in an agent workflow](/docs/reviewing/evals): scored conditions, and
  what a confidence does and doesn't mean.
* [Fast and deliberate, side by side](/docs/coming-soon/fast-and-deliberate):
  the routing a typed decision will feed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.