Loading documentation

Access required

Your account doesn't have access to the documentation. Contact your administrator.

Sign out
Skip to main content

Nudge Helper

Status: stable Category: trinityassist Added: 2026-10-11 Last reviewed: 2026-10-11

Summary​

Read the patient's intake, the recent conversation and your index of care directives, and return a brief: what the visit is about right now, how the conversation compares with what the patient reported, whether the focus has shifted, and up to four of your directive documents that apply.

The brief is the input to suggest. The helper never produces a nudge card itself. It is one member of a three-call family:

RouteJobTierTypical cadence
POST /api/nudge/helperWork out the visit's focus and pick the directives that apply.fastevery 90 seconds
POST /api/nudge/suggestWrite suggestions from the latest brief and the documents it named.balancedevery 60 seconds
POST /api/nudge/check_resolutionDecide which open cards the conversation has dealt with.fastevery 30 seconds

The helper runs less often than suggest on purpose: what a visit is about changes far more slowly than what is worth suggesting next, and suggest checks the brief against the transcript on every call, so a brief up to 90 seconds old cannot mislead it.

:::info Everything about your world travels in the request

The engine knows nothing about your clinicians, visit types or programme rules. Your intake, your directive index and your instructions are the whole picture the model gets. A rule such as "visits are by video" belongs in assist_context.instructions.

:::

Endpoint​

FieldValue
MethodPOST
Path/api/nudge/helper
Full URLhttps://engine.tripod-health.com/api/nudge/helper
AuthenticationAPI key (required)
Request bodyapplication/json
Response bodyapplication/json
IdempotentNo (each call asks the model afresh)
Rate-limitedYes

Purpose​

A live visit drifts. The patient wrote "hip" on the intake and is now describing a thigh injury; a second complaint comes up halfway through. Your care directives may run to over a hundred documents, and sending them all to every suggestion is slow, expensive and over the payload limit. The helper reads the conversation against the intake and your index, says what the visit is about, and names the few documents that matter, so suggest can carry just those.

A working loop:

  1. Once real conversation has started, call the helper. Its brief becomes the current brief.
  2. Call suggest with that brief, and with the bodies of the documents in brief.directive_refs as assist_context references loaded always.
  3. Call the helper again every ~90 seconds, sending the previous brief as previous_brief. Each new brief replaces the last.

If a helper call fails or times out, keep suggesting without a brief. Suggest works from the transcript alone and reports brief_check: "no_brief". A failed helper never needs to block the rail.

Authentication​

Standard API-key. Set apikey: $ENGINE_API_KEY on the request. See authentication.md.

Request​

Headers​

HeaderRequiredNotes
apikeyYesYour engine API key.
Content-TypeYesapplication/json.

Body​

{
"encounter_id": "visit-ankle-01",
"intake": {
"description": "I stepped off the edge of the loading dock wrong and my right ankle rolled underneath me. It swelled up fast.",
"body_region": "Lower leg / ankle",
"answers": [
{ "question": "What were you doing when it happened?", "answer": "Unloading boxes from a truck" },
{ "question": "When did it start?", "answer": "Two days ago, right away" }
]
},
"care_directives": [
{ "ref": "care:general-care-philosophy", "path": ["Practice-wide"], "title": "General care philosophy" },
{ "ref": "care:musculoskeletal/overview", "path": ["Musculoskeletal"], "title": "Musculoskeletal overview",
"summary": "How the practice approaches acute musculoskeletal injuries" },
{ "ref": "care:musculoskeletal/ankle", "path": ["Musculoskeletal", "Ankle"], "title": "Ankle",
"summary": "Sprain pathway, decision rules and escalation" },
{ "ref": "care:musculoskeletal/knee", "path": ["Musculoskeletal", "Knee"], "title": "Knee" },
{ "ref": "governing:escalation-protocol", "path": ["Governing documents"], "title": "Escalation protocol" }
],
"assist_context": {
"instructions": [
{ "layer": "global.boundaries", "text": "Visits are by video. Clinicians never examine hands-on." }
]
},
"transcript_window": [
{ "segment_id": "s1", "speaker": "provider", "text": "Tell me what happened.",
"started_at_ms": 0, "ended_at_ms": 2400 },
{ "segment_id": "s2", "speaker": "patient",
"text": "I stepped off the edge of the loading dock wrong and my right ankle rolled underneath me.",
"started_at_ms": 2400, "ended_at_ms": 9100 },
{ "segment_id": "s3", "speaker": "patient",
"text": "It swelled up pretty fast. I can hobble on it but it hurts a lot to put weight on that side.",
"started_at_ms": 9100, "ended_at_ms": 15600 }
]
}

How the evidence is weighed​

  • The patient's description is weighted heavily. It is treated as the strongest evidence of why they came, above all early in the visit.
  • The picked body region is weighted moderately. Patients pick a near-enough region from a list (elbow for a hand injury), so a region that disagrees with the description or the conversation loses.
  • The transcript is what is being discussed now. When the conversation is clearly about something, that is the focus.

Body fields​

FieldTypeRequiredConstraintsDescription
encounter_idstringNo1-64 charsOptional opaque correlation key. Send the same value on suggest and check_resolution to group the calls in your own logs.
transcript_windowarrayYes1-256 segmentsFinalized transcript segments, the same shape as on suggest.
intakeobjectNo--What the patient reported before the visit. Every field is optional.
intake.descriptionstringNo1-4,000 charsThe patient's own words about what happened. Clinical free text; identifier spans are scrubbed, not rejected (see below).
intake.body_regionstringNo1-120 charsThe region the patient picked, as a free label (for example "hip / pelvis"). Not validated against any vocabulary.
intake.answersarrayNo0-40 entriesOther intake answers as question/answer pairs: mechanism, activity, equipment, onset.
intake.answers[].questionstringYes*1-200 charsThe question as asked. *Required when the enclosing object is present.
intake.answers[].answerstringYes*1-1,000 charsThe patient's answer. Scrubbed like description.
care_directivesarrayNo0-400 entries, refs uniqueYour directive index. Never bodies.
care_directives[].refstringYes*1-120 chars, ^[A-Za-z0-9_.:\-/]+$Your id for the document. A ref the brief returns is always one of these.
care_directives[].patharray of stringYes*1-4 entries, each 1-120 charsThe folder trail, for example ["Musculoskeletal", "Ankle"].
care_directives[].titlestringYes*1-200 charsThe document's title.
care_directives[].summarystringNoup to 300 charsOne line on what it covers. Helps the model choose.
previous_briefobjectNoa briefThe last brief this call returned for the visit, sent back unchanged, so shift can say whether the focus moved.
assist_contextobjectNosee assist_contextYour persona and instructions. References are not accepted -- the helper picks documents from the index and never reads one.

The rest of the body must not contain identifier-shaped values. See De-identification contract.

Response​

Success -- 200 OK​

{
"brief": {
"focus": {
"summary": "Acute right ankle inversion injury two days ago, with fast swelling and painful weight bearing.",
"body_region": "right ankle",
"complaint_kind": "acute sprain"
},
"intake_alignment": {
"status": "consistent",
"note": "The conversation matches the patient's description and the ankle region they picked."
},
"shift": { "shifted": false, "note": null },
"directive_refs": [
"care:musculoskeletal/ankle",
"care:musculoskeletal/overview",
"governing:escalation-protocol"
],
"notes": [
"Weight bearing immediately after the injury and now has not been established.",
"No pain score yet."
]
},
"request_id": "5b0f4c2e-7d1a-4f7e-9a51-2c6f0d8e3b94",
"timestamp": "2026-10-11T18:19:47.565951+00:00"
}

The brief​

FieldTypeAlways presentDescription
brief.focus.summarystringYesUp to 400 chars. What the visit is about right now.
brief.focus.body_regionstring | nullYesUp to 120 chars. The region actually involved, or null when none is (a mood or sleep complaint).
brief.focus.complaint_kindstring | nullYesUp to 120 chars. The kind of complaint, or null when not yet clear. Never a diagnosis.
brief.intake_alignment.statusenumYesconsistent | differs | unclear -- how the conversation compares with what the patient wrote and picked. unclear when there is no intake or too little conversation.
brief.intake_alignment.notestringYesUp to 300 chars. One line on the comparison.
brief.shift.shiftedbooleanYesWhether the focus moved since previous_brief (or since the window began, with none).
brief.shift.notestring | nullYesUp to 300 chars. What it moved from and to, or null.
brief.directive_refsarray of stringYes0-4 refs from your care_directives, most relevant first. Validated: a ref the model returns that is not in your index is dropped, never passed on. Empty when you sent no index.
brief.notesarray of stringYes0-3 pointers of up to 200 chars each for the suggestion pass: what to verify, what is missing.
request_idstringYesEngine-assigned request ID.
timestampstringYesISO 8601 UTC server timestamp at response emission.

Pass brief to suggest exactly as you received it.

Status codes​

CodeMeaningWhen
200OKBrief generated.
400ValidationErrorBody shape failure, a repeated care_directives[].ref, assist_context.references present, or an unknown field. field names the offender.
400DeIdentificationViolationAn identifier-shaped value outside the scrubbed fields.
401UnauthenticatedMissing or invalid API key.
402PaymentRequiredOut of credits, or a key or account budget is exhausted.
413PayloadTooLargeBody exceeds 512 KB.
429RateLimitedPer-consumer rate limit exceeded.
502UpstreamErrorThe model call failed after one retry, or the brief's focus.summary or intake_alignment.note failed the outbound identifier scan. Carry on without a brief.
503ServiceUnavailableService temporarily unhealthy.

See errors.md for the full error code list and envelope.

Timeouts and cost​

One fast-tier call, held to 10 seconds per attempt with one retry. A client timeout of 30 seconds is right. Priced as one call; see credits.md. Your index is cached with the engine's rules between calls in the same visit, so consecutive calls are cheaper to serve than the first.

Examples​

curl​

curl -sS -X POST "https://engine.tripod-health.com/api/nudge/helper" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"intake": {"description": "Rolled my right ankle stepping off the loading dock.", "body_region": "Lower leg / ankle"},
"care_directives": [
{"ref": "care:musculoskeletal/ankle", "path": ["Musculoskeletal", "Ankle"], "title": "Ankle"},
{"ref": "care:musculoskeletal/knee", "path": ["Musculoskeletal", "Knee"], "title": "Knee"}
],
"transcript_window": [
{"segment_id": "s1", "speaker": "provider", "text": "Tell me what happened.", "started_at_ms": 0, "ended_at_ms": 2400},
{"segment_id": "s2", "speaker": "patient", "text": "My right ankle rolled underneath me on the dock.", "started_at_ms": 2400, "ended_at_ms": 9100}
]
}'

Python (requests)​

import os, requests

resp = requests.post(
"https://engine.tripod-health.com/api/nudge/helper",
headers={"apikey": os.environ["ENGINE_API_KEY"]},
json={
"intake": {"description": "Rolled my right ankle stepping off the loading dock."},
"care_directives": [
{"ref": "care:musculoskeletal/ankle", "path": ["Musculoskeletal", "Ankle"], "title": "Ankle"},
],
"transcript_window": [
{"segment_id": "s1", "speaker": "patient",
"text": "My right ankle rolled underneath me on the dock.",
"started_at_ms": 0, "ended_at_ms": 4000},
],
},
timeout=30,
)
resp.raise_for_status()
brief = resp.json()["brief"]
print(brief["focus"]["summary"], brief["directive_refs"])

Intended use​

  • The slow half of a live-visit nudge loop: refresh the brief every ~90 seconds and hand it, with the named documents, to suggest.
  • Choosing which of a large directive library is relevant to a conversation, without sending the library.

Mistaken use​

  • Waiting on the helper before suggesting. A failed or slow helper should never stop the rail; suggest runs without a brief.
  • Sending document bodies in the index. The index carries refs, paths, titles and summaries only. Bodies go to suggest, and only for the refs the brief named.
  • Treating focus as a diagnosis. It describes what is being discussed; it concludes nothing clinically.
  • Editing the brief before passing it on. Suggest validates it again and checks it against the transcript; an edited brief only removes that signal.

De-identification contract​

  • Inbound, free text: identifier spans in transcript_window[].text, intake.description and intake.answers[].answer are SCRUBBED, not rejected -- patients describing an injury in their own words can quote a phone number. Everything downstream sees only the scrubbed text.
  • Inbound, everything else: the rest of the body, including the intake questions, the body region and your index, is walked for identifier-shaped strings and a match returns 400 DeIdentificationViolation.
  • Outbound: every prose field in the brief is scanned. A failing body_region, complaint_kind or shift.note comes back null, a failing note is dropped, and a failing focus.summary or intake_alignment.note fails the call with 502.

See conventions.md.

Change history​

DateChange
2026-10-11Initial publication.
2026-10-14The engine assigns every request ID; an X-Request-ID request header is not used. The X-Request-ID response header is the gateway's trace ID for the request and does not match request_id; quote request_id to support.