Reflex

API · the HTTP wire

Three routes. JSON in, JSON out.
Any language with HTTP.

reflex is one binary that serves the decision engine on your own machine. You send a state and typed questions, and you get an answer or an honest abstain. You need no SDK and no API key, and nothing leaves the loopback address.

Every request and response on this page was sent to and returned by the release binary named in the footer. A script records them; none is typed by hand.

Quick start

Install it (see Install), then start the engine. It serves until you stop it. You need no config file or daemon.

startreflex
another portRIIR_REFLEX_BIND=127.0.0.1:9400 reflex

Then ask it something. This question is one the Tetris head answers — the heads are signed vessels you mint once (reflex mint-heads --out heads --key-id 42 --key <64-hex-seed>) and load with RIIR_REFLEX_HEADS_DIR=heads RIIR_REFLEX_HEADS_PUBKEY=<the mint's verifying key>.

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d '{
  "state": "The piece leaves no holes under it on the left edge, sits flat on the surface, and the stack stays low.",
  "questions": [
    {"id": "clean", "kind": "noul", "prompt": "Does the stack look clean?"}
  ]
}'
Response · HTTP 200
{
  "answers": [
    {
      "question_id": "clean",
      "outcome": {"noul": {"yes": true}},
      "probabilities": [0.51943666],
      "confidence": 0.51943666
    }
  ],
  "routing": {
    "lane": "modelless",
    "reason": "game-head/tetris (corpus-fitted, decoded arm)"
  },
  "calibration": {"method": "none", "temperature": 1.0}
}

Your own corpus

Start the engine on YOUR text instead of the demo corpus:

corpus bootRIIR_REFLEX_CORPUS=first-corpus reflex

The directory holds one <domain>.md file — or a <domain>/ folder of .md files — per domain, 1 to 8 domains, sorted by name. A malformed corpus refuses to boot with the named reason; it is never silently swapped for the demo engine. /healthz reports which posture is live ("corpus":"demo" or the domain list). On a corpus boot, in-corpus questions answer and off-corpus questions abstain (the corpus-distance gate decides; the sample corpus downloads from the home page).

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d '{
  "state": "our deploy regressed the error budget after the rollout — run the rollback and verify the health endpoints",
  "questions": [
    {
      "id": "route",
      "kind": "choice",
      "prompt": "Which runbook applies?",
      "options": ["billing", "deploy", "onboarding"]
    }
  ]
}'
Response · HTTP 200
{
  "answers": [
    {
      "question_id": "route",
      "outcome": {"choice": {"index": 1}},
      "probabilities": [0.3341486, 0.3407357, 0.32511574],
      "confidence": 0.00016820431
    }
  ],
  "routing": {
    "lane": "modelless",
    "reason": "modelless corpus routing: billing=0 deploy=1 onboarding=0; fused abstain (score+distance) armed"
  },
  "calibration": {"method": "none", "temperature": 1.0}
}

Off-corpus still abstains

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d '{
  "state": "my sourdough starter stopped rising after i moved it to a colder kitchen",
  "questions": [
    {
      "id": "route",
      "kind": "choice",
      "prompt": "Which runbook applies?",
      "options": ["billing", "deploy", "onboarding"]
    }
  ]
}'
Response · HTTP 200
{
  "answers": [
    {
      "question_id": "route",
      "outcome": null,
      "probabilities": [0.31989384, 0.34005812, 0.34004804],
      "confidence": 0.00037240982
    }
  ],
  "routing": {
    "lane": "modelless",
    "reason": "modelless corpus routing: billing=0 deploy=0 onboarding=1; fused abstain (score+distance) armed"
  },
  "calibration": {"method": "none", "temperature": 1.0}
}

Routes

RouteBodyWhat it does
POST /decidedecision requestAnswers every question in the request, or abstains on it.
POST /feedback{"p", "outcome"}Reports whether a past answer was right; the engine recalibrates once it has enough reports.
GET /healthznoneLiveness, which lanes are ready, which game heads loaded, and the corpus posture ("demo" or the domain list).

The request

The answer space is part of the request: you define the options when you ask, and nothing is pretrained on them.

FieldTypeMeaning
statestringThe situation to decide about. Put the facts and constraints here, not just a subject line.
questionsarrayThe questions about that state. An empty array is legal and returns no answers.
questions[].idstringYour id for the question, echoed back as question_id. Ids must be unique within a request.
questions[].kindstringchoice, score or noul (see below).
questions[].promptstringThe question itself. It must not be empty.
questions[].optionsstring arrayThe answers you would act on, in the order you will index them back. Optional for noul, which must not carry any.
questions[].criteriastring, optionalWhat decides between the options, for example which team's runbook the situation matches.

Question types

The site says "yes/no"; the wire says noul. They are the same type.

On the siteWire kindOptionsAnswer outcome
choicechoice2 or more{"choice": {"index": i}}, an index into options
scorescore2 or more levels, lowest first{"score": {"level": i}}, where 0 is the lowest level
yes/nonoulnone{"noul": {"yes": true}} or false

The response

FieldMeaning
answers[].question_idThe id you sent.
answers[].outcomeThe answer, shaped per type as above. null means the engine abstained.
answers[].probabilitiesOne probability per option, in option order. A noul question gets exactly one, p(yes). They come back even on an abstain.
answers[].confidenceThe number to put your own threshold on.
routing.laneWhich lane answered. Scope any speed or determinism claim to that lane.
routing.reasonWhy that lane or head answered, including the per-domain corpus hit counts.
calibrationmethod and temperature. "none" with 1.0 means raw numbers; both change after feedback recalibrates the engine.

Abstain is an answer

The text engine in the release binary carries a small demo corpus with two teams, deploy operations and billing support. It abstains rather than guess, and out of the box it abstains on these questions. Treat "outcome": null as a branch in your code, not an error. The probabilities still lean: this ticket leans to deploy-ops.

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d '{
  "state": "The staging deploy of release candidate 4.2 finished but the error budget is down to 12 percent and two health endpoints are flapping after the rollout. Rollback is one command. Decide what happens next.",
  "questions": [
    {
      "id": "route",
      "kind": "choice",
      "prompt": "Route this ticket to the team that owns it.",
      "options": ["deploy-ops", "billing-support"]
    },
    {
      "id": "promote",
      "kind": "noul",
      "prompt": "Should the rollout be promoted to production right now?"
    },
    {
      "id": "severity",
      "kind": "score",
      "prompt": "How severe is this situation?",
      "options": ["routine", "needs-attention", "critical"]
    }
  ]
}'
Response · HTTP 200
{
  "answers": [
    {
      "question_id": "route",
      "outcome": null,
      "probabilities": [0.52583224, 0.47416782],
      "confidence": 0.0019263625
    },
    {
      "question_id": "promote",
      "outcome": null,
      "probabilities": [0.49452087],
      "confidence": 0.00008660555
    },
    {
      "question_id": "severity",
      "outcome": null,
      "probabilities": [0.3601853, 0.2878931, 0.35192165],
      "confidence": 0.004379213
    }
  ],
  "routing": {
    "lane": "modelless",
    "reason": "modelless corpus routing: ops=3 support=0; fused abstain (score+distance) armed; drafter-only=1"
  },
  "calibration": {"method": "none", "temperature": 1.0}
}

A question far from the corpus abstains too, with probabilities close to even:

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d '{
  "state": "My sourdough starter stopped rising after I moved it to a colder kitchen.",
  "questions": [
    {
      "id": "route",
      "kind": "choice",
      "prompt": "Route this ticket to the team that owns it.",
      "options": ["deploy-ops", "billing-support"]
    }
  ]
}'
Response · HTTP 200
{
  "answers": [
    {
      "question_id": "route",
      "outcome": null,
      "probabilities": [0.52577484, 0.47422513],
      "confidence": 0.0019177794
    }
  ],
  "routing": {
    "lane": "modelless",
    "reason": "modelless corpus routing: ops=0 support=1; fused abstain (score+distance) armed"
  },
  "calibration": {"method": "none", "temperature": 1.0}
}

Fit your own threshold on confidence from labeled examples of your task rather than guessing one. The integration guide walks through it.

Lanes

The optional X-Reflex-Lane request header picks the lane for /decide.

Header valueWhat answers
none, or modellessThe built-in game heads first, when the question matches one of their exact shapes. Otherwise the corpus engine answers.
rawThe corpus engine only, skipping the game heads. It is useful as a baseline.
layaThe opt-in accuracy lane, which uses downloadable weights. Start the engine with RIIR_REFLEX_LAYA=1; otherwise it refuses with a 503 and never falls back.

The same Tetris spot as the quick start, on the raw lane. The corpus engine abstains:

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -H 'X-Reflex-Lane: raw' \
  -d '{
  "state": "The piece leaves no holes under it on the left edge, sits flat on the surface, and the stack stays low.",
  "questions": [
    {"id": "clean", "kind": "noul", "prompt": "Does the stack look clean?"}
  ]
}'
Response · HTTP 200
{
  "answers": [
    {
      "question_id": "clean",
      "outcome": null,
      "probabilities": [0.49452087],
      "confidence": 0.00008660555
    }
  ],
  "routing": {
    "lane": "modelless",
    "reason": "modelless corpus routing: ops=0 support=1; fused abstain (score+distance) armed"
  },
  "calibration": {"method": "none", "temperature": 1.0}
}

Feedback

When you learn whether an answer was right, send back the engine's own confidence for that case as p and the result as outcome. Never invent p: false reports train the calibration toward them. refit stays false until the engine holds enough reports to recalibrate.

Request
curl -s -X POST http://127.0.0.1:7331/feedback \
  -H 'Content-Type: application/json' \
  -d '{"p": 0.51943666, "outcome": true}'
Response · HTTP 200
{"refit": false}

Health

Request
curl -s http://127.0.0.1:7331/healthz
Response · HTTP 200
{
  "status": "ok",
  "lanes": {"modelless": "ready", "raw": "ready", "laya": "off"},
  "heads": {"tetris": true, "lanes": true, "flappy": true},
  "corpus": "demo"
}

Errors

An error body is always {"error": "…"}, and the message says what to fix.

StatusWhen
400The body is not valid JSON for the request shape, or X-Reflex-Lane names an unknown lane.
422The JSON parsed but a question breaks a rule: an empty prompt, a duplicate id, fewer than 2 options on choice or score, or options on noul.
404An unknown route. There is no version prefix.
413The declared body is over the size ceiling. The engine refuses on the header, before reading the body.
503The laya lane was asked for but is not enabled or not ready.

A question that breaks a rule

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d '{
  "state": "",
  "questions": [
    {"id": "q", "kind": "choice", "prompt": "Pick one.", "options": ["only"]}
  ]
}'
Response · HTTP 422
{
  "error": "request invalid: decision_wire: choice/score question 0 needs ≥2 options, got 1"
}
Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d '{
  "state": "",
  "questions": [
    {
      "id": "q",
      "kind": "noul",
      "prompt": "Ship it?",
      "options": ["yes", "no"]
    }
  ]
}'
Response · HTTP 422
{
  "error": "request invalid: decision_wire: noul question 0 must not carry options"
}

A body that is not JSON

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -d 'not json'
Response · HTTP 400
{"error": "expected ident at line 1 column 2"}

An unknown lane

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -H 'X-Reflex-Lane: fast' \
  -d '{"state": "", "questions": []}'
Response · HTTP 400
{"error": "unknown lane \"fast\" (supported: modelless, raw, laya, laya-ane)"}

The laya lane, not enabled

Request
curl -s -X POST http://127.0.0.1:7331/decide \
  -H 'Content-Type: application/json' \
  -H 'X-Reflex-Lane: laya' \
  -d '{
  "state": "The staging deploy of release candidate 4.2 finished but the error budget is down to 12 percent and two health endpoints are flapping after the rollout. Rollback is one command. Decide what happens next.",
  "questions": [
    {
      "id": "route",
      "kind": "choice",
      "prompt": "Route this ticket to the team that owns it.",
      "options": ["deploy-ops", "billing-support"]
    }
  ]
}'
Response · HTTP 503
{
  "error": "laya lane is not enabled — restart the engine with RIIR_REFLEX_LAYA=1"
}

An unknown route

Request
curl -s http://127.0.0.1:7331/v1/decide
Response · HTTP 404
{"error": "not found"}

A body over the ceiling

Request
# a POST /decide whose Content-Length header declares 4194305 bytes
Response · HTTP 413
{"error": "body too large"}

Limits and behaviour

Versions

The wire ships inside the binary, so it has no separate API version and no version prefix in the routes. This page is recorded from the release named in the footer. reflex --version prints the one you are running; if they differ, check that release's notes.

your versionreflex --version