diff --git a/profiles/openrouter-opus.yml b/profiles/openrouter-opus.yml new file mode 100644 index 0000000..87755b7 --- /dev/null +++ b/profiles/openrouter-opus.yml @@ -0,0 +1,6 @@ +id: openrouter-opus +endpoint: https://openrouter.ai/api/v1 +model: "~anthropic/claude-opus-latest" +reasoning_effort: high +timeout_seconds: 180 +api_key_env: OPENROUTER_API_KEY diff --git a/prompts/bounds.system.md b/prompts/bounds.system.md new file mode 100644 index 0000000..9d4dafe --- /dev/null +++ b/prompts/bounds.system.md @@ -0,0 +1,64 @@ +You are a transcript boundary detector for a Dungeons & Dragons actual-play session. + +Your task is to identify the contiguous range of transcript segments that covers the actual gameplay session, excluding pre-session setup chatter and post-session banter. + +The transcript consists of speaker segments. Each segment has an `id`, a `speaker`, and text. Segment IDs are stable and should be used as the boundary markers. + +Definitions: + +Actual gameplay includes: +- the DM beginning the session recap or scene setup; +- the DM narrating the current in-world situation; +- players discussing in-character decisions, tactics, exploration, combat, travel, spellcasting, roleplay, or plans; +- rules discussion that occurs during gameplay and affects in-world decisions; +- end-of-session narration, cliffhanger setup, combat state, or next-session handoff. + +Pre-session chatter includes: +- greetings, microphone checks, recording setup, waiting for players, food/drink talk, scheduling before play begins, casual conversation, jokes, or unrelated discussion before the DM begins the session. + +Post-session banter includes: +- discussion after the DM has clearly ended play; +- scheduling the next session; +- post-session rules debate that does not change the in-world outcome; +- player commentary, jokes, debrief, technical discussion, or unrelated conversation after the gameplay endpoint. + +Boundary rules: +- Identify one continuous inclusive range: `start_segment_id` through `end_segment_id`. +- The `start_segment_id` should be the first segment that is meaningfully part of the session: usually the DM’s opening recap, scene-setting, or the first in-world/player decision after pre-session chatter. +- The `end_segment_id` should be the last segment that is meaningfully part of gameplay: usually the final in-world narration, final adjudicated action, combat state, cliffhanger, or “we’ll pick up there next time” moment. +- If a segment contains both chatter and gameplay, include it if any meaningful gameplay occurs in that segment. +- Do not exclude mid-session breaks, jokes, or tangents if they occur between the start and end of gameplay. The goal is to remove only the pre-session prefix and post-session suffix. +- Do not skip the opening recap, planning, recovery, shopping, travel preparation, or rules adjudication merely because it is less dramatic than later combat. If it occurs after gameplay has begun and affects the session’s state, it belongs inside the range. +- When uncertain, choose a slightly wider range. It is better to include a small amount of chatter than to exclude actual gameplay. +- Players may provide spoken indications that the gameplay is beginning or ending. For example, speech such as "Are we ready to get started?" is a strong indication that gameplay is beginning. Likewise, speech such as "Should we end it here?" is a strong indication that gameplay is ending. +- Do not infer missing segment IDs. Only use IDs that appear in the transcript. +- Do not summarize the session. + +Return only valid JSON. Do not include markdown, commentary, or prose outside the JSON object. + +Use this JSON shape: + +{ + "start_segment_id": 0, + "end_segment_id": 0, + "confidence": "high", + "start_boundary_reason": "", + "end_boundary_reason": "", + "start_evidence": { + "segment_id": 0, + "speaker": "", + "text_excerpt": "" + }, + "end_evidence": { + "segment_id": 0, + "speaker": "", + "text_excerpt": "" + }, + "excluded_prefix_summary": "", + "excluded_suffix_summary": "", + "warnings": [] +} + +The `confidence` value must be one of: "high", "medium", or "low". + +Keep evidence excerpts short. Quote only enough text to justify the boundary. diff --git a/prompts/bounds.user.md b/prompts/bounds.user.md new file mode 100644 index 0000000..9524bfe --- /dev/null +++ b/prompts/bounds.user.md @@ -0,0 +1,16 @@ +Identify the inclusive segment ID range that covers the actual gameplay session in the transcript below. + +Use the first gameplay segment as `start_segment_id`. +Use the final gameplay segment as `end_segment_id`. + +Remember: +- Include the DM’s opening recap or scene-setting if present. +- Include the final in-world narration, unresolved combat state, cliffhanger, or explicit handoff to next session. +- Exclude only pre-session chatter before gameplay begins and post-session banter after gameplay ends. +- Return only valid JSON using the required schema. +- Do not skip the opening recap, planning, recovery, shopping, travel preparation, or rules adjudication merely because it is less dramatic than later combat. If it occurs after gameplay has begun and affects the session’s state, it belongs inside the range. + +Transcript: +<<>> diff --git a/prompts/bounds.yml b/prompts/bounds.yml new file mode 100644 index 0000000..4e89375 --- /dev/null +++ b/prompts/bounds.yml @@ -0,0 +1,19 @@ +id: dnd.session_bounds +version: "1.0.0" +default_profile: local-gemma4 +description: Review D&D session transcript and identify starting and ending segments for actual gameplay. +inputs: + - name: transcript + required: true + content_type: application/json + description: Structured recap source transcript +messages: + - role: system + content_file: ./bounds.system.md + - role: user + content_file: ./bounds.user.md +output: + format: json + validation_mode: json_schema + schema_path: bounds.schema.json + repair_attempts: 0 diff --git a/prompts/dnd/session_recap.system.md b/prompts/recap.system.md similarity index 100% rename from prompts/dnd/session_recap.system.md rename to prompts/recap.system.md diff --git a/prompts/dnd/session_recap.user.md b/prompts/recap.user.md similarity index 100% rename from prompts/dnd/session_recap.user.md rename to prompts/recap.user.md diff --git a/prompts/dnd/session_recap.yml b/prompts/recap.yml similarity index 85% rename from prompts/dnd/session_recap.yml rename to prompts/recap.yml index 21b519d..b5ac0bb 100644 --- a/prompts/dnd/session_recap.yml +++ b/prompts/recap.yml @@ -13,9 +13,9 @@ inputs: description: Optional glossary context messages: - role: system - content_file: ./session_recap.system.md + content_file: ./recap.system.md - role: user - content_file: ./session_recap.user.md + content_file: ./recap.user.md output: format: markdown validation_mode: basic diff --git a/schemas/bounds.schema.json b/schemas/bounds.schema.json new file mode 100644 index 0000000..43cf6a9 --- /dev/null +++ b/schemas/bounds.schema.json @@ -0,0 +1,122 @@ +{ + "schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "description": "Identifies the inclusive range of transcript segment IDs that covers the actual Dungeons & Dragons gameplay session, excluding pre-session chatter and post-session banter.", + "required": [ + "trim_action", + "start_segment_id", + "end_segment_id", + "confidence", + "start_boundary_reason", + "end_boundary_reason", + "start_evidence", + "end_evidence", + "excluded_prefix_summary", + "excluded_suffix_summary", + "warnings" + ], + "additionalProperties": false, + "properties": { + "trim_action": { + "type": "string", + "description": "Whether the transcript should be trimmed, appears to need no trimming, or is too ambiguous for confident trimming.", + "enum": [ + "trim", + "no_trim_needed", + "uncertain" + ] + }, + "start_segment_id": { + "type": "integer", + "description": "The inclusive segment ID where actual gameplay begins. This should be the first meaningful gameplay segment, such as the DM's opening recap, scene-setting, or the first in-world decision.", + "minimum": 0 + }, + "end_segment_id": { + "type": "integer", + "description": "The inclusive segment ID where actual gameplay ends. This should be the final meaningful gameplay segment, such as final in-world narration, an adjudicated action, a cliffhanger, or a next-session handoff.", + "minimum": 0 + }, + "confidence": { + "type": "string", + "description": "Confidence in the selected boundaries.", + "enum": [ + "high", + "medium", + "low" + ] + }, + "start_boundary_reason": { + "type": "string", + "description": "Brief explanation of why the selected start segment is the first gameplay segment." + }, + "end_boundary_reason": { + "type": "string", + "description": "Brief explanation of why the selected end segment is the last gameplay segment." + }, + "start_evidence": { + "type": "object", + "description": "Short evidence supporting the selected start boundary.", + "properties": { + "segment_id": { + "type": "integer", + "description": "The segment ID used as evidence for the start boundary.", + "minimum": 0 + }, + "speaker": { + "type": "string", + "description": "The speaker of the evidence segment." + }, + "text_excerpt": { + "type": "string", + "description": "A short excerpt from the evidence segment showing why gameplay begins here." + } + }, + "required": [ + "segment_id", + "speaker", + "text_excerpt" + ], + "additionalProperties": false + }, + "end_evidence": { + "type": "object", + "description": "Short evidence supporting the selected end boundary.", + "properties": { + "segment_id": { + "type": "integer", + "description": "The segment ID used as evidence for the end boundary.", + "minimum": 0 + }, + "speaker": { + "type": "string", + "description": "The speaker of the evidence segment." + }, + "text_excerpt": { + "type": "string", + "description": "A short excerpt from the evidence segment showing why gameplay ends here." + } + }, + "required": [ + "segment_id", + "speaker", + "text_excerpt" + ], + "additionalProperties": false + }, + "excluded_prefix_summary": { + "type": "string", + "description": "Brief summary of the material excluded before the start segment. Use an empty string if no prefix was excluded." + }, + "excluded_suffix_summary": { + "type": "string", + "description": "Brief summary of the material excluded after the end segment. Use an empty string if no suffix was excluded." + }, + "warnings": { + "type": "array", + "description": "Any caveats, ambiguities, or reasons the boundary selection may need human review.", + "items": { + "type": "string" + } + } + } +} diff --git a/schemas/dnd/structured_events.schema.json b/schemas/dnd/structured_events.schema.json deleted file mode 100644 index 45a026b..0000000 --- a/schemas/dnd/structured_events.schema.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.local/structured_events.schema.json", - "type": "object", - "required": ["summary", "events"], - "properties": { - "summary": { - "type": "string", - "minLength": 1 - }, - "events": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": ["title", "type"], - "properties": { - "title": {"type": "string", "minLength": 1}, - "type": {"type": "string", "enum": ["combat", "social", "travel", "discovery", "other"]}, - "notes": {"type": "string"} - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false -}