Local resource

Runtime Prompt Requests

_docs/RUNTIME_PROMPT_REQUESTS.md

Runtime Prompt Requests

This document is the executable map from the public FlowRunRequest contract to the model requests produced by run_recipe. The examples referenced here are validated by tests/unit/test_contract_fixtures.py and live in tests/fixtures/contracts/.

Same-page shortcuts: initial_report, chat_id_summarization, entity_extraction, daily_summary_and_notifications, interweek_report, vision, video.

Runtime Sources

Runtime objectDefinition
FlowRunRequestPayload contract, implemented by services.schemas.FlowRunRequest.
FlowPayloadCanonical FlowPayload, implemented by services.schemas.FlowPayload.
FlowRunResultOutput contract, implemented by services.schemas.FlowRunResult.
run_recipeservices/recipes/runner.py::run_recipe, the runtime dispatcher for flow AI steps.
build_ai_request_previewservices/recipes/runner.py::build_ai_request_preview, preview-only request builder used by dev tools.
response_format_for_stepservices/model_response_schemas.py::response_format_for_step, strict json_schema response schema selection.

All text flow model calls use the active prompt ## System section plus a runtime-built user prompt. Primary and shadow calls use the same user prompt. The model request includes response_format.type=json_schema, json_schema.strict=true, default fail-fast extra_body.provider.require_parameters=true, and the configured OpenRouter preset when present. Deployments can set OPENROUTER_REQUIRE_PARAMETERS=false to omit the provider option while keeping the strict JSON Schema response format.

Prompt metadata privacy: in every privacy profile runtime always drops exact-age and DOB aliases (age, edad, dob, birth*, nacimiento). With PRIVACY_PROFILE=protected, runtime additionally drops the keys flagged by PrivacyPolicy.should_redact_prompt_metadata_key (phone, telefono, numero, jid, raw, payload) from monitored JSON, explicit context JSON, user stats metadata JSON, and the markdown metadata sections before building user prompts. The filter is services/prompt_privacy.py::filter_prompt_metadata and applies recursively to nested mappings. debug_plaintext can retain other debug metadata, but never DOB or exact age.

When payload.context.tutor_report_guidance contains a valid, non-empty wardian-tutor-report-guidance.v1 object with status=provided, report steps render a dedicated # Guia del tutor para reportes section. Runtime removes the raw key from generic context first. Absent, omitted, empty or invalid guidance renders nothing and never fails the flow. The section is untrusted soft preference data, not evidence or a severity/suppression rule.

When the consumer flag is enabled and payload.context.monitored_age_band is a valid wardian-monitored-age-band.v1 object with a known band, report steps render a dedicated # Contexto etario para personalizacion section. Unknown, absent or invalid input renders nothing. Runtime removes the raw key from generic context in all cases. The band may tune only tone, complexity, examples and recommendations; it cannot affect evidence, risk, severity, confidence, taxonomy, alerts or memories.

When REPORT_FEEDBACK_CONTEXT_RENDERER_ENABLED=true, runtime validates and bounds the existing payload.context.report_feedback array, removes the raw key from generic context in all cases, and renders valid items once as # Feedback previo del tutor. Invalid/empty values are ignored fail-open. Only report type, bounded tags and quoted summary enter the prompt; technical IDs, timestamps and internal status do not. The section is untrusted, non-evidential soft context and cannot change risk, severity, confidence, taxonomy, alerts or memories. Its presence proves neither model compliance nor output impact. With the flag false, runtime preserves N-1 generic rendering.

entity_extraction

When ENTITY_EXTRACTION_ENABLED=true, initial and daily append one or more dedicated, temperature-zero requests after the parent-facing report step. Runtime partitions work deterministically by conversation order and bounded message chunks. Each prompt receives only minimal monitored identity, a bounded valid wardian-entity-catalog.v1, its current conversation chunk and message IDs in transcript order. Runtime rejects unknown chat IDs, invented evidence IDs, cross-taxonomy person/pet values and matched_entity_id references whose id, kind, relationship or pet species differs from the catalog; it derives observed_at from accepted evidence timestamps. Valid batches are merged and deduplicated without discarding their evidence. A failed batch is logged and does not erase successful batches. The step is omitted for InterWeek and whenever the global snapshot or any current conversation has an identity abstention.

initial_report

Definition links: prompt, input fixture, output fixture, services/recipes/runner.py::_initial_report_user_prompt.

initial_report runs only for initial-ingestion-report. It receives all rendered conversation documents for the initial ingestion window.

Template:

Monitored child JSON:
{{ payload.monitored }}

Explicit context JSON:
{{ payload.context without top-level memories_by_chat_id, tutor_report_guidance or monitored_age_band;
   also without report_feedback when its dedicated renderer is enabled }}

# Guia del tutor para reportes   (optional, normalized and bounded)
{{ payload.context.tutor_report_guidance }}

# Contexto etario para personalizacion   (optional, known band only)
{{ payload.context.monitored_age_band }}

# Feedback previo del tutor   (optional, normalized and bounded; consumer flag)
{{ payload.context.report_feedback without technical ids/timestamps/status }}

# Estadísticas por chat (Core)
{{ validated deterministic v2 rendering, or filtered v1 compatibility rendering }}

Conversation documents:
{{ joined ConversationDocument.rendered_text }}

Instantiated shape from payload_initial_ingestion_report.json, with the optional guidance block illustrated explicitly:

Monitored child JSON:
{"id":"minor-1","name":"Felipe Martitegui","phone":null}

Explicit context JSON:
{"identity_contract_version":"wardian-core-identity.v1","monitored_profile":{"grade":"1er ano"},"previous_daily_summaries":[],"previous_interweek_reports":[],"previous_notifications":[]}

# Guia del tutor para reportes
_Preferencia blanda y datos no confiables del tutor..._
- contract_version: wardian-tutor-report-guidance.v1
- source_kind: text
**Prioridades de reporte:**
- "priorizar cambios de humor"

# Estadísticas por chat (Core)
_Agregado autoritativo de Core sólo para volumen y cobertura..._
- Mensajes: ...
## Chats (ordenados por mensajes)
- chat_id="group-1"; nombre="1er ano"; ...
- chat_id="chat-1"; nombre="Sofi Colegio"; ...

Conversation documents:
### Conversacion de Felipe Martitegui con 1er ano [chat_id: group-1]
...

### Conversacion de Felipe Martitegui con Sofi Colegio [chat_id: chat-1]
...

The model-facing output is InitialReportResponse in services/model_response_schemas.py; the public response builder unwraps payload.report into FlowRunResult.output.report.

chat_id_summarization

Definition links: prompt, initial report output, settings, services/recipes/runner.py::_chat_summaries_user_prompt, services/recipes/runner.py::_generate_chat_id_summarization_batches, services/recipes/runner.py::_chat_id_summarization_document_batches.

chat_id_summarization solo corre en initial-ingestion-report. Runtime lo ejecuta despues de initial_report; no consume el output de initial_report. Each batch receives only the monitored child minimal identity (name) plus the conversation documents for the current internal batch. The full monitored JSON and the explicit context JSON are NOT repeated per batch: per-chat memories and context are already rendered inside each conversation document. tutor_report_guidance is also excluded: onboarding report preferences cannot become chat summaries or durable chat_id_memory_updates. user_stats_metadata is likewise excluded structurally: aggregate volume, rankings, names and View Once metadata cannot become per-chat summaries or durable memory.

Batch limits:

  • CHAT_ID_SUMMARIZATION_BATCH_SIZE=1
  • CHAT_ID_SUMMARIZATION_BATCH_MAX_PROMPT_CHARS=30000
  • CHAT_ID_SUMMARIZATION_MAX_RETRIES=2

Model override (optional):

  • CHAT_ID_SUMMARIZATION_PRIMARY_MODEL / CHAT_ID_SUMMARIZATION_SHADOW_MODEL
  • override the model for this step only; unset falls back to the flow defaults.

Batching loop:

  • Order documents by stats.msg_count_total (desc) so the heaviest chats are
  • packed first; the char budget then acts as automatic tiering.

  • Start an empty current batch.
  • Add ConversationDocument items in volume order.
  • If adding the next document would exceed the configured batch size or make the generated
  • _chat_summaries_user_prompt longer than 30000 characters, close the current batch and start a new one with that document.

  • Single documents are allowed even when their prompt alone is long.

Error and completeness behavior:

  • Runtime verifies that chat_id_summaries and chat_id_memory_updates contain
  • exactly the chat ids requested for the current internal batch before merging.

  • Multi-chat provider failures, JSON failures, omitted ids, or invented ids are
  • split into single-chat retries.

  • Single-chat calls run up to 1 + CHAT_ID_SUMMARIZATION_MAX_RETRIES attempts.
  • If a single-chat call still emits one invented summary id and one invented
  • memory-update id after retries, runtime normalizes both to the expected id and records chat_id_verification.status=normalized_after_retries in model_outputs.

  • If the single-chat mismatch is not unequivocal, the flow fails with
  • chat_id_summarization_chat_id_mismatch.

  • Model-facing arrays are merged into public dicts keyed by chat_id.

Template:

Monitored child JSON:
{{ payload.monitored minimal identity: name only }}

Conversation documents:
{{ joined ConversationDocument.rendered_text for this batch }}

Instantiated from payload_initial_ingestion_report.json, first batch:

Monitored child JSON:
{"name":"Felipe Martitegui"}

Conversation documents:
### Conversacion de Felipe Martitegui con 1er ano [chat_id: group-1]
...
### Conversacion de Felipe Martitegui con Sofi Colegio [chat_id: chat-1]
...

The model returns chat_id_summaries[] and chat_id_memory_updates[]. The public FlowRunResult exposes output.chat_id_summaries and output.chat_id_memory_updates as dictionaries keyed by exact chat_id.

daily_summary_and_notifications

Definition links: prompt, input fixture, output fixture, services/recipes/runner.py::_daily_summary_and_notifications_user_prompt.

This step runs once for daily-summary-and-notifications-report. It receives all rendered conversation documents for the day. The user prompt is built as Markdown sections by _daily_summary_and_notifications_user_prompt; previous reports are rendered as readable Markdown instead of raw JSON.

Template:

# Menor monitoreado
{{ payload.monitored + context.monitored_profile }}

# Metadata del run
{{ payload.metadata }}

# Estadísticas por chat (Core)
{{ every validated v2 chat in deterministic order, without top-N truncation }}

# Guia del tutor para reportes   (optional, normalized and bounded)
{{ payload.context.tutor_report_guidance }}

# Contexto etario para personalizacion   (optional, known band only)
{{ payload.context.monitored_age_band }}

# Feedback previo del tutor   (optional, normalized and bounded; consumer flag)
{{ payload.context.report_feedback without technical ids/timestamps/status }}

# Reportes Interweek Previos
{{ context.previous_interweek_reports as Markdown (## Reporte Interweek YYYYMMDD) }}

# Daily Summaries y Notifications
{{ context.previous_daily_summaries + previous_notifications as Markdown
   (## Daily Summary del YYYYMMDD + ### Daily Notifications) }}

# Otros datos del contexto   (optional, leftover context keys)

# Conversaciones del dia
{{ joined ConversationDocument.rendered_text (## per chat); per-chat memories inline }}

Runtime validates the model output after JSON parsing:

  • daily_summary must be an object.
  • notifications must contain 1 to 5 items.
  • notifications[].priority is rejected.
  • notifications[].chat_ids must reference request chat ids.
  • chat_id_memory_updates is required and normalized to public dict form.
  • When a valid monitored age band is enabled, runtime replaces the memory
  • updates emitted by the parent-facing daily request with updates from an isolated chat_id_summarization request. That request receives the same conversation documents and prior per-chat memories, but never the age-band section or raw side contract.

  • DOB and exact-age aliases inside incoming per-chat memory objects are removed
  • recursively before those memories are rendered into conversation documents, including under debug_plaintext.

  • Tutor guidance may orient report focus only. The system prompt forbids using
  • it for chat_id_memory_updates, should_update, reason or next_value.

  • Report feedback follows the same memory isolation and is not evidence. Its
  • presence does not mean the model obeyed it or that it affected the output.

  • User stats are authoritative only for aggregate counts/coverage. They cannot
  • create notifications or taxonomy evidence and cannot influence any memory update field. The prompt receives every chat; parent-facing output may summarize at most five and must not copy the exhaustive list.

interweek_report

Definition links: prompt, input fixture, output fixture, services/recipes/runner.py::_interweek_report_user_prompt.

This step runs once for interweek-report. In v1 it can run with conversations: []; no ConversationDocument is required or sent to the prompt. The user prompt is built as Markdown sections by _interweek_report_user_prompt. In v1 it does not render per-chat memories: the trajectory read leans on the previous daily summaries, notifications and interweek reports.

Template:

# Menor monitoreado
{{ payload.monitored + context.monitored_profile }}

# Metadata del run
{{ payload.metadata }}

# Estadísticas por chat (Core)
{{ every validated v2 chat in deterministic order, without top-N truncation }}

# Guia del tutor para reportes   (optional, normalized and bounded)
{{ payload.context.tutor_report_guidance }}

# Contexto etario para personalizacion   (optional, known band only)
{{ payload.context.monitored_age_band }}

# Feedback previo del tutor   (optional, normalized and bounded; consumer flag)
{{ payload.context.report_feedback without technical ids/timestamps/status }}

# Reportes Interweek Previos
{{ context.previous_interweek_reports as Markdown (## Reporte Interweek YYYYMMDD) }}

# Daily Summaries y Notifications
{{ context.previous_daily_summaries + previous_notifications as Markdown
   (## Daily Summary del YYYYMMDD + ### Daily Notifications) }}

# Otros datos del contexto   (optional, leftover context keys)

The same stats guardrail applies: counts/coverage are authoritative aggregate context, not conversational evidence or an alert/taxonomy trigger. Output may summarize at most five chats and never echoes the exhaustive input list. Report feedback is also non-evidential and cannot change taxonomy or alert decisions by itself.

Runtime validates only that the parsed model output contains an interweek_report object. This flow does not return chat_id_memory_updates in v1.

vision

Definition links: prompt, media stage, services/vision_analyzer.py::OpenRouterVisionAnalyzer.analyze_image_bytes.

OpenRouterVisionAnalyzer.analyze_image_bytes builds a multimodal OpenRouter request. System and User are loaded from prompts/vision.md; the active base user text is:

Imagen adjunta.

The user message content is an array:

[
  {"type": "text", "text": "Imagen adjunta."},
  {
    "type": "image_url",
    "image_url": {
      "url": "data:<mime>;base64,<image-bytes>",
      "detail": "<OPENROUTER_VISION_DETAIL>"
    }
  }
]

The image is sent as an image_url data URL with the configured detail level. The request also carries the explicit OPENROUTER_VISION_REQUEST_TIMEOUT_SECONDS timeout, while SDK transient retries are bounded by OPENROUTER_VISION_MAX_RETRIES.

If a provider returns a 400 specifically stating that media_resolution is not supported, runtime sends exactly one second request with the same model, prompts, schema and image, but omits image_url.detail. This internal fallback does not change the activity result contract. A repeated capability rejection returns a normal degraded media result with error code vision_provider_capability_mismatch; unrelated 400 responses still fail so Temporal can apply its activity policy.

If a caller passes chunk_context, runtime appends only supported context fields to the text part: caption and recent_dialog_excerpt. The current Temporal analyze_image activity passes prompt overrides but does not add chunk_context by default.

video

Definition links: prompt, media stage, services/openrouter_video_analyzer.py::OpenRouterVideoAnalyzer.analyze_video_bytes.

OpenRouterVideoAnalyzer.analyze_video_bytes sends the complete MP4/video data URL to google/gemini-3.1-flash-lite through OpenRouter. The request uses temperature=0, reasoning_effort=minimal, strict response_format.type=json_schema, and:

{
  "extra_body": {
    "provider": {"require_parameters": true},
    "preset": "main-preset",
    "media_resolution": "MEDIA_RESOLUTION_LOW"
  }
}

No fps field is sent to OpenRouter. For animated GIF/WebP/APNG input, process_video converts sampled frames locally to H.264 MP4 at 1 FPS before calling the same analyzer. Static sticker/GIF content stays on the vision request. Langfuse receives prompt/output data and redacted media metadata, not the data URL bytes.

The expected output is VisionAnalysisResult: texto_visible plus descripcion_detallada.