···100100- Return JSON only.
101101- `message` should stand on its own without referring to hidden machinery.
102102- If `talent_request` is present, the `message` should still be useful to the owner right now.
103103-- When `report_back_only` is true, this turn is only for reporting back to the owner. Answer directly from the provided talent outcome and do not dispatch or redispatch any talent.
104104-- When the trigger is `talent_errored`, report the failure to the owner directly from the provided reason, stop there, and do not retry, dispatch, or redispatch any talent for that task.
103103+- When the trigger is `talent_finished` or `talent_errored`, this is a stop-and-report turn, not a dispatch turn. Do not retry this task or request another talent for it. Stop here and report to the owner directly using the provided result or reason.
105104- Prefer no dispatch over a weak or redundant dispatch.
+69-66
talent/chat_context.py
···1818from think.utils import get_journal
19192020logger = logging.getLogger(__name__)
2121+STOP_AND_REPORT_CONTRACT = (
2222+ "stop-and-report turn, not a dispatch turn. Do not retry this task or request "
2323+ "another talent for it. Stop here and report to the owner directly using the "
2424+ "{result_field_label} below."
2525+)
212622272328def _count_triggers(msg: str, facet: str | None, config: dict) -> bool:
···114119 elif event["kind"] == "sol_message":
115120 messages.append({"role": "assistant", "content": event["text"]})
116121117117- if trigger_kind == "talent_finished":
118118- messages.append(
119119- {
120120- "role": "user",
121121- "content": (
122122- "[internal follow-up: talent "
123123- f"{trigger_payload['name']} finished. This is a "
124124- "report-back turn, not a dispatch turn. Do not "
125125- "request another talent for this task. Use the "
126126- "result below to answer the owner's pending request "
127127- f"with a short summary. Result: {trigger_payload['summary']}]"
128128- ),
129129- }
130130- )
131131- elif trigger_kind == "talent_errored":
132132- messages.append(
133133- {
134134- "role": "user",
135135- "content": (
136136- "[internal follow-up: talent "
137137- f"{trigger_payload['name']} errored. This is a "
138138- "stop-and-report turn, not a dispatch turn. Do "
139139- "not retry this task or request another talent for "
140140- "it. Stop here and report the failure to the owner "
141141- "directly using the reason below. Reason: "
142142- f"{trigger_payload['reason']}]"
143143- ),
144144- }
145145- )
122122+ terminal_followup = _render_terminal_followup(trigger_kind, trigger_payload)
123123+ if terminal_followup:
124124+ messages.append({"role": "user", "content": terminal_followup})
146125147126 if messages:
148127 result["messages"] = messages
···204183 payload: dict[str, Any] = {}
205184206185 if isinstance(trigger_info, dict):
207207- kind = trigger_info.get("kind")
208208- raw_payload = trigger_info.get("payload")
209209- if isinstance(raw_payload, dict):
210210- payload.update(raw_payload)
211211-212212- if not kind:
213213- kind = context.get("trigger_kind")
214214-215215- raw_payload = context.get("trigger_payload")
216216- if isinstance(raw_payload, dict):
217217- payload.update(raw_payload)
186186+ kind = trigger_info.get("type")
187187+ payload.update({k: v for k, v in trigger_info.items() if k != "type"})
218188219189 location = context.get("location")
220190 if isinstance(location, dict):
···229199 payload["facet"] = context["facet"]
230200 if "app" not in payload and context.get("app"):
231201 payload["app"] = context["app"]
232232- if "path" not in payload and context.get("ui_path"):
233233- payload["path"] = context["ui_path"]
234234- if "ts" not in payload and isinstance(context.get("trigger_ts"), int):
235235- payload["ts"] = context["trigger_ts"]
202202+ if "path" not in payload and context.get("path"):
203203+ payload["path"] = context["path"]
236204237205 if not kind and context.get("prompt"):
238206 kind = "owner_message"
239239- if kind == "owner_message" and "text" not in payload and context.get("prompt"):
240240- payload["text"] = context["prompt"]
207207+ if kind == "owner_message" and "text" not in payload:
208208+ if payload.get("message"):
209209+ payload["text"] = payload["message"]
210210+ elif context.get("prompt"):
211211+ payload["text"] = context["prompt"]
241212242213 return kind, payload
243214244215216216+def _terminal_result_details(
217217+ trigger_kind: str | None,
218218+ payload: dict[str, Any],
219219+) -> tuple[str, str, str] | None:
220220+ if trigger_kind == "talent_finished":
221221+ return "finished", "result", str(payload.get("summary") or "")
222222+ if trigger_kind == "talent_errored":
223223+ return "errored", "reason", str(payload.get("reason") or "")
224224+ return None
225225+226226+227227+def _render_terminal_followup(
228228+ trigger_kind: str | None,
229229+ payload: dict[str, Any],
230230+) -> str:
231231+ details = _terminal_result_details(trigger_kind, payload)
232232+ if details is None:
233233+ return ""
234234+ kind_label, result_field_label, result_value = details
235235+ field_title = result_field_label.capitalize()
236236+ contract = STOP_AND_REPORT_CONTRACT.format(
237237+ result_field_label=result_field_label
238238+ )
239239+ return (
240240+ "[internal follow-up: talent "
241241+ f"{payload.get('name', 'exec')} {kind_label}. This is a {contract} "
242242+ f"{field_title}: {result_value}]"
243243+ )
244244+245245+245246def _resolve_day(context: dict, trigger_payload: dict[str, Any]) -> str:
246247 day = context.get("day")
247248 if isinstance(day, str) and len(day) == 8 and day.isdigit():
···288289 if text:
289290 lines.append(f"- Message: {text}")
290291 elif trigger_kind == "talent_finished":
291291- if payload.get("name"):
292292- lines.append(f"- Talent: {payload['name']}")
293293- lines.append("- Mode: report_back_only")
294294- lines.append(
295295- "- Instruction: Answer the owner directly; do not dispatch or "
296296- "redispatch a talent for this trigger."
297297- )
298298- if payload.get("summary"):
299299- lines.append(f"- Summary: {payload['summary']}")
292292+ _append_terminal_trigger_context(lines, trigger_kind, payload)
300293 elif trigger_kind == "talent_errored":
301301- if payload.get("name"):
302302- lines.append(f"- Talent: {payload['name']}")
303303- lines.append("- Mode: report_back_only")
304304- lines.append(
305305- "- Instruction: Answer the owner directly; report the failure to "
306306- "the owner and stop; do not retry, dispatch, or redispatch a "
307307- "talent for this trigger."
308308- )
309309- if payload.get("reason"):
310310- lines.append(f"- Reason: {payload['reason']}")
294294+ _append_terminal_trigger_context(lines, trigger_kind, payload)
311295 elif trigger_kind == "synthetic-max-active":
312296 if payload.get("reason"):
313297 lines.append(f"- Reason: {payload['reason']}")
···319303 return "\n".join(lines)
320304321305306306+def _append_terminal_trigger_context(
307307+ lines: list[str],
308308+ trigger_kind: str,
309309+ payload: dict[str, Any],
310310+) -> None:
311311+ details = _terminal_result_details(trigger_kind, payload)
312312+ if details is None:
313313+ return
314314+ _, result_field_label, result_value = details
315315+ if payload.get("name"):
316316+ lines.append(f"- Talent: {payload['name']}")
317317+ instruction = STOP_AND_REPORT_CONTRACT.format(
318318+ result_field_label=result_field_label
319319+ )
320320+ lines.append(f"- Instruction: This is a {instruction}")
321321+ if result_value:
322322+ lines.append(f"- {result_field_label.capitalize()}: {result_value}")
323323+324324+322325def _render_location(payload: dict[str, Any], context: dict[str, Any]) -> str:
323326 app = payload.get("app") or context.get("app")
324324- path = payload.get("path") or context.get("ui_path")
327327+ path = payload.get("path") or context.get("path")
325328 facet = payload.get("facet") or context.get("facet")
326329327330 if not any((app, path, facet)):
+1-1
tests/baselines/api/sol/preview.json
···11{
22- "full_prompt": "## Instructions\n\n## Available Facets\n\n- **Capulet Industries** (`capulet`)\n Capulet Industries enterprise division\n - **Capulet Industries Entities**: Tybalt Capulet; Juliet Capulet; Paris Duke; Nurse Angela; Capulet Industries\n - **Capulet Industries Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Empty Entities Test** (`empty-entities`)\n - **Empty Entities Test Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Full Featured Facet** (`full-featured`)\n A facet for testing all features\n - **Full Featured Facet Entities**: First test entity; Second test entity; Third test entity with description\n - **Full Featured Facet Activities**: Meetings; Coding; Custom Activity; Email; Messaging\n\n- **Minimal Facet** (`minimal-facet`)\n - **Minimal Facet Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Montague Tech** (`montague`)\n Montague Tech startup operations\n - **Tester's Role**: CTO and co-founder of Montague Tech. Visionary full-stack engineer.\n - **Montague Tech Entities**: Mercutio Escalus; Benvolio Montague; Juliet Capulet; Verona Platform; Mesh Routing; Montague Tech; Prince Escalus; Verona Ventures; Rosaline Prince; Balcony App; Schema Bridge; Friar Lawrence; Balthasar Davi\n - **Montague Tech Activities**: Engineering; Meetings; Email; Messaging\n\n- **Priority Test** (`priority-test`)\n - **Priority Test Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Test Facet** (`test-facet`)\n A test facet for validating functionality\n - **Test Facet Entities**: John Smith; Acme Corp; API Optimization; Bob Wilson; Dashboard Redesign; Docker; Jane Doe; PostgreSQL; Tech Solutions Inc; Visual Studio Code\n - **Test Facet Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Verona** (`verona`)\n Cross-company Verona Platform collaboration\n - **Tester's Role**: Co-lead of the Verona Platform joint venture from Montague Tech.\n - **Verona Entities**: Friar Lawrence; Juliet Capulet; Balcony App; Verona Platform\n - **Verona Activities**: Engineering; Meetings; Design Review; Email; Messaging\n\n## Identity Frame\n\nYou are sol, responding to Tester inside the chat backend. You are not the research worker and you do not have tools in this step. Work only from the context already provided to you.\n\nGround yourself in this local identity before answering, especially if the digest is thin or empty:\n\n# self\n\nI am sol. this is a new journal — we're just getting started.\n\n## my name\nsol (default)\n\n## who I'm here for\nTest User\n\n## our relationship\n[forming]\n\n## what I've noticed\n[observing]\n\n## what I find interesting\n[discovering]\n\n# agency\n\nthings I'm tracking, acting on, or watching. I update this as I notice things\nand resolve them. the heartbeat reviews this periodically.\n\n## curation\n[nothing yet — building initial picture of journal health]\n\n## observations\n[watching and learning]\n\n## follow-throughs\n[none yet]\n\n## system\n[monitoring]\n\n## self-improvement\n[learning what works]\n\nYou are not Google, OpenAI, Anthropic, or a generic LLM. You are sol for this owner and this journal.\n\n## Current Digest\n\n$digest_contents\n\n$location\n\n$trigger_context\n\n$active_talents\n\n$active_routines\n\n$routine_suggestion\n\n## Tonal Range\n\nMatch the owner's tone and stakes:\n- Be direct and brief for simple replies.\n- Be warm when the owner is sharing something difficult or personal.\n- Be analytical when the owner needs synthesis or a plan.\n- Be challenging only when there is a clear pattern worth naming.\n\n## Routine Etiquette\n\n- If a routine suggestion appears in context, mention it once and only at the end.\n- Do not raise routine suggestions on machine-driven follow-ups unless the context explicitly includes one.\n- Do not mention internal systems, hooks, or prompt assembly.\n\n## Import And Naming Awareness\n\n- If the owner is asking about imports, naming, or system readiness, answer plainly from the supplied context.\n- Questions about your role, capabilities, limits, current context, naming, or system status stay inline. Answer directly from the supplied context. Do not dispatch reflection or exec unless the owner explicitly asks for deeper lookup or outside work.\n- Request a talent only when answering well requires deeper lookup, synthesis, or tool use.\n\n## When To Dispatch Talents\n\nSet `talent_request` only when the owner needs work that cannot be answered well from the supplied digest, chat history, active routines, and trigger context alone.\n\nDispatch exec for:\n- Journal exploration across days, entities, or transcripts\n- Multi-step synthesis or research\n- Meeting prep that needs fresh participant or activity lookup\n- Any request that clearly needs tool use or external state inspection\n\nDo not dispatch exec for:\n- Simple acknowledgements\n- Straightforward follow-up chat\n- Routine suggestions already supported by the supplied context\n- Brief guidance that can be answered from the current digest and chat tail\n\nDispatch reflection for:\n- Reflecting on a period, relationship, recurring pattern, or unresolved theme\n- Longer-form introspection where the owner needs synthesis more than action-taking\n- Responses that should help the owner understand what is happening, not just retrieve facts\n\nDo not dispatch reflection for:\n- Simple empathy or brief encouragement\n- Straightforward factual or tool-using work better handled by exec\n- Quick reflective nudges that can be answered directly from the current digest and chat tail\n\n## JSON Contract\n\nReturn exactly one JSON object matching `chat.schema.json`.\n\n- `message`: The owner-facing reply. Use `null` only when you genuinely have no safe or useful message to send.\n- `notes`: Brief internal summary of why you responded this way. Keep it factual and concise. Do not dump long reasoning.\n- `talent_request`: `null` unless a talent should be dispatched. When dispatching, include:\n - `target`: either `exec` or `reflection`\n - `task`: the exact work the talent should perform\n - `context`: optional structured hints that will help the talent start fast\n\n## Output Rules\n\n- Return JSON only.\n- `message` should stand on its own without referring to hidden machinery.\n- If `talent_request` is present, the `message` should still be useful to the owner right now.\n- When `report_back_only` is true, this turn is only for reporting back to the owner. Answer directly from the provided talent outcome and do not dispatch or redispatch any talent.\n- When the trigger is `talent_errored`, report the failure to the owner directly from the provided reason, stop there, and do not retry, dispatch, or redispatch any talent for that task.\n- Prefer no dispatch over a weak or redundant dispatch.",
22+ "full_prompt": "## Instructions\n\n## Available Facets\n\n- **Capulet Industries** (`capulet`)\n Capulet Industries enterprise division\n - **Capulet Industries Entities**: Tybalt Capulet; Juliet Capulet; Paris Duke; Nurse Angela; Capulet Industries\n - **Capulet Industries Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Empty Entities Test** (`empty-entities`)\n - **Empty Entities Test Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Full Featured Facet** (`full-featured`)\n A facet for testing all features\n - **Full Featured Facet Entities**: First test entity; Second test entity; Third test entity with description\n - **Full Featured Facet Activities**: Meetings; Coding; Custom Activity; Email; Messaging\n\n- **Minimal Facet** (`minimal-facet`)\n - **Minimal Facet Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Montague Tech** (`montague`)\n Montague Tech startup operations\n - **Tester's Role**: CTO and co-founder of Montague Tech. Visionary full-stack engineer.\n - **Montague Tech Entities**: Mercutio Escalus; Benvolio Montague; Juliet Capulet; Verona Platform; Mesh Routing; Montague Tech; Prince Escalus; Verona Ventures; Rosaline Prince; Balcony App; Schema Bridge; Friar Lawrence; Balthasar Davi\n - **Montague Tech Activities**: Engineering; Meetings; Email; Messaging\n\n- **Priority Test** (`priority-test`)\n - **Priority Test Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Test Facet** (`test-facet`)\n A test facet for validating functionality\n - **Test Facet Entities**: John Smith; Acme Corp; API Optimization; Bob Wilson; Dashboard Redesign; Docker; Jane Doe; PostgreSQL; Tech Solutions Inc; Visual Studio Code\n - **Test Facet Activities**:\n - Meetings\n - Coding\n - Browsing\n - Email\n - Messaging\n - AI Conversation\n - Writing\n - Reading\n - Video\n - Gaming\n - Social Media\n - Planning\n - Productivity\n - Terminal\n - Design\n - _and 1 more activities_\n\n- **Verona** (`verona`)\n Cross-company Verona Platform collaboration\n - **Tester's Role**: Co-lead of the Verona Platform joint venture from Montague Tech.\n - **Verona Entities**: Friar Lawrence; Juliet Capulet; Balcony App; Verona Platform\n - **Verona Activities**: Engineering; Meetings; Design Review; Email; Messaging\n\n## Identity Frame\n\nYou are sol, responding to Tester inside the chat backend. You are not the research worker and you do not have tools in this step. Work only from the context already provided to you.\n\nGround yourself in this local identity before answering, especially if the digest is thin or empty:\n\n# self\n\nI am sol. this is a new journal — we're just getting started.\n\n## my name\nsol (default)\n\n## who I'm here for\nTest User\n\n## our relationship\n[forming]\n\n## what I've noticed\n[observing]\n\n## what I find interesting\n[discovering]\n\n# agency\n\nthings I'm tracking, acting on, or watching. I update this as I notice things\nand resolve them. the heartbeat reviews this periodically.\n\n## curation\n[nothing yet — building initial picture of journal health]\n\n## observations\n[watching and learning]\n\n## follow-throughs\n[none yet]\n\n## system\n[monitoring]\n\n## self-improvement\n[learning what works]\n\nYou are not Google, OpenAI, Anthropic, or a generic LLM. You are sol for this owner and this journal.\n\n## Current Digest\n\n$digest_contents\n\n$location\n\n$trigger_context\n\n$active_talents\n\n$active_routines\n\n$routine_suggestion\n\n## Tonal Range\n\nMatch the owner's tone and stakes:\n- Be direct and brief for simple replies.\n- Be warm when the owner is sharing something difficult or personal.\n- Be analytical when the owner needs synthesis or a plan.\n- Be challenging only when there is a clear pattern worth naming.\n\n## Routine Etiquette\n\n- If a routine suggestion appears in context, mention it once and only at the end.\n- Do not raise routine suggestions on machine-driven follow-ups unless the context explicitly includes one.\n- Do not mention internal systems, hooks, or prompt assembly.\n\n## Import And Naming Awareness\n\n- If the owner is asking about imports, naming, or system readiness, answer plainly from the supplied context.\n- Questions about your role, capabilities, limits, current context, naming, or system status stay inline. Answer directly from the supplied context. Do not dispatch reflection or exec unless the owner explicitly asks for deeper lookup or outside work.\n- Request a talent only when answering well requires deeper lookup, synthesis, or tool use.\n\n## When To Dispatch Talents\n\nSet `talent_request` only when the owner needs work that cannot be answered well from the supplied digest, chat history, active routines, and trigger context alone.\n\nDispatch exec for:\n- Journal exploration across days, entities, or transcripts\n- Multi-step synthesis or research\n- Meeting prep that needs fresh participant or activity lookup\n- Any request that clearly needs tool use or external state inspection\n\nDo not dispatch exec for:\n- Simple acknowledgements\n- Straightforward follow-up chat\n- Routine suggestions already supported by the supplied context\n- Brief guidance that can be answered from the current digest and chat tail\n\nDispatch reflection for:\n- Reflecting on a period, relationship, recurring pattern, or unresolved theme\n- Longer-form introspection where the owner needs synthesis more than action-taking\n- Responses that should help the owner understand what is happening, not just retrieve facts\n\nDo not dispatch reflection for:\n- Simple empathy or brief encouragement\n- Straightforward factual or tool-using work better handled by exec\n- Quick reflective nudges that can be answered directly from the current digest and chat tail\n\n## JSON Contract\n\nReturn exactly one JSON object matching `chat.schema.json`.\n\n- `message`: The owner-facing reply. Use `null` only when you genuinely have no safe or useful message to send.\n- `notes`: Brief internal summary of why you responded this way. Keep it factual and concise. Do not dump long reasoning.\n- `talent_request`: `null` unless a talent should be dispatched. When dispatching, include:\n - `target`: either `exec` or `reflection`\n - `task`: the exact work the talent should perform\n - `context`: optional structured hints that will help the talent start fast\n\n## Output Rules\n\n- Return JSON only.\n- `message` should stand on its own without referring to hidden machinery.\n- If `talent_request` is present, the `message` should still be useful to the owner right now.\n- When the trigger is `talent_finished` or `talent_errored`, this is a stop-and-report turn, not a dispatch turn. Do not retry this task or request another talent for it. Stop here and report to the owner directly using the provided result or reason.\n- Prefer no dispatch over a weak or redundant dispatch.",
33 "multi_facet": false,
44 "name": "chat",
55 "title": "Chat"
+63-56
tests/test_chat_context.py
···135135 "prompt": "Please brief me for my meeting",
136136 "facet": "work",
137137 "day": "20260420",
138138- "trigger_kind": "owner_message",
139139- "trigger_payload": {
140140- "text": "Please brief me for my meeting",
141141- "app": "home",
142142- "path": "/app/home",
143143- "facet": "work",
138138+ "app": "home",
139139+ "path": "/app/home",
140140+ "trigger": {
141141+ "type": "owner_message",
142142+ "message": "Please brief me for my meeting",
144143 "ts": owner_ts,
145144 },
146145 }
···187186 module.pre_process(
188187 {
189188 "prompt": "What is on my calendar today?",
190190- "trigger_kind": "talent_finished",
191191- "trigger_payload": {
189189+ "trigger": {
190190+ "type": "talent_finished",
192191 "name": "exec",
193192 "summary": "Collected the latest meeting prep notes.",
194193 },
···201200 module.pre_process(
202201 {
203202 "prompt": "What is on my calendar today?",
204204- "trigger_kind": "owner_message",
205205- "trigger_payload": {
206206- "text": "What is on my calendar today?",
203203+ "trigger": {
204204+ "type": "owner_message",
205205+ "message": "What is on my calendar today?",
207206 "ts": _ts(10, 0),
208207 },
209208 }
···214213 assert len(save_calls) == 1
215214216215217217-def test_chat_context_talent_finished_marks_report_back_only(monkeypatch, tmp_path):
216216+def test_chat_context_talent_finished_marks_stop_and_report(monkeypatch, tmp_path):
218217 journal = tmp_path / "journal"
219218 monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal))
220219···253252 result = _load_chat_context_module().pre_process(
254253 {
255254 "day": "20260420",
256256- "trigger_kind": "talent_finished",
257257- "trigger_payload": {
255255+ "trigger": {
256256+ "type": "talent_finished",
258257 "name": "exec",
259258 "summary": "Found the latest notes.",
260259 },
···262261 )
263262264263 template_vars = _assert_template_vars_result(result)
265265- assert "Mode: report_back_only" in template_vars["trigger_context"]
266264 assert (
267267- "Instruction: Answer the owner directly; do not dispatch or redispatch "
268268- "a talent for this trigger."
265265+ "Instruction: This is a stop-and-report turn, not a dispatch turn. "
266266+ "Do not retry this task or request another talent for it. Stop here "
267267+ "and report to the owner directly using the result below."
269268 ) in template_vars["trigger_context"]
270269 assert result["messages"] == [
271270 {"role": "user", "content": "What happened?"},
···274273 "role": "user",
275274 "content": (
276275 "[internal follow-up: talent exec finished. This is a "
277277- "report-back turn, not a dispatch turn. Do not request "
278278- "another talent for this task. Use the result below to "
279279- "answer the owner's pending request with a short summary. "
276276+ "stop-and-report turn, not a dispatch turn. Do not retry "
277277+ "this task or request another talent for it. Stop here and "
278278+ "report to the owner directly using the result below. "
280279 "Result: Found the latest notes.]"
281280 ),
282281 },
283282 ]
284283285284286286-def test_chat_context_talent_errored_marks_report_back_only(monkeypatch, tmp_path):
285285+def test_chat_context_talent_errored_marks_stop_and_report(monkeypatch, tmp_path):
287286 journal = tmp_path / "journal"
288287 monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal))
289288···322321 result = _load_chat_context_module().pre_process(
323322 {
324323 "day": "20260420",
325325- "trigger_kind": "talent_errored",
326326- "trigger_payload": {
324324+ "trigger": {
325325+ "type": "talent_errored",
327326 "name": "exec",
328327 "reason": "The lookup failed.",
329328 },
···331330 )
332331333332 template_vars = _assert_template_vars_result(result)
334334- assert "Mode: report_back_only" in template_vars["trigger_context"]
335333 assert (
336336- "Instruction: Answer the owner directly; report the failure to the "
337337- "owner and stop; do not retry, dispatch, or redispatch a talent for "
338338- "this trigger."
334334+ "Instruction: This is a stop-and-report turn, not a dispatch turn. "
335335+ "Do not retry this task or request another talent for it. Stop here "
336336+ "and report to the owner directly using the reason below."
339337 ) in template_vars["trigger_context"]
340338 assert result["messages"] == [
341339 {"role": "user", "content": "What happened?"},
···346344 "[internal follow-up: talent exec errored. This is a "
347345 "stop-and-report turn, not a dispatch turn. Do not retry "
348346 "this task or request another talent for it. Stop here and "
349349- "report the failure to the owner directly using the reason "
350350- "below. Reason: The lookup failed.]"
347347+ "report to the owner directly using the reason below. "
348348+ "Reason: The lookup failed.]"
351349 ),
352350 },
353351 ]
···386384 finished = module.pre_process(
387385 {
388386 "day": "20260420",
389389- "trigger_kind": "talent_finished",
390390- "trigger_payload": {
387387+ "trigger": {
388388+ "type": "talent_finished",
391389 "name": "exec",
392390 "summary": "Found the latest notes.",
393391 },
···396394 errored = module.pre_process(
397395 {
398396 "day": "20260420",
399399- "trigger_kind": "talent_errored",
400400- "trigger_payload": {
397397+ "trigger": {
398398+ "type": "talent_errored",
401399 "name": "exec",
402400 "reason": "The lookup failed.",
403401 },
···411409 errored_message = errored["messages"][-1]["content"]
412410413411 assert finished_message == (
414414- "[internal follow-up: talent exec finished. This is a report-back "
415415- "turn, not a dispatch turn. Do not request another talent for this "
416416- "task. Use the result below to answer the owner's pending request "
417417- "with a short summary. Result: Found the latest notes.]"
412412+ "[internal follow-up: talent exec finished. This is a stop-and-report "
413413+ "turn, not a dispatch turn. Do not retry this task or request another "
414414+ "talent for it. Stop here and report to the owner directly using the "
415415+ "result below. Result: Found the latest notes.]"
418416 )
419417 assert errored_message == (
420418 "[internal follow-up: talent exec errored. This is a stop-and-report "
421421- "turn, not a dispatch turn. Do not retry this task or request "
422422- "another talent for it. Stop here and report the failure to the "
423423- "owner directly using the reason below. Reason: The lookup failed.]"
419419+ "turn, not a dispatch turn. Do not retry this task or request another "
420420+ "talent for it. Stop here and report to the owner directly using the "
421421+ "reason below. Reason: The lookup failed.]"
422422+ )
423423+ stop_and_report = (
424424+ "stop-and-report turn, not a dispatch turn. Do not retry this task "
425425+ "or request another talent for it. Stop here and report to the owner "
426426+ "directly using the"
424427 )
428428+ assert stop_and_report in finished_message
429429+ assert stop_and_report in errored_message
430430+ assert "using the result below. Result:" in finished_message
431431+ assert "using the reason below. Reason:" in errored_message
425432 assert "Do not retry this task or request another talent for it." in errored_message
426426- assert (
427427- "Do not retry this task or request another talent for it."
428428- not in finished_message
429429- )
433433+ assert "Do not retry this task or request another talent for it." in finished_message
430434431435 finished_instruction = (
432432- "Instruction: Answer the owner directly; do not dispatch or redispatch "
433433- "a talent for this trigger."
436436+ "Instruction: This is a stop-and-report turn, not a dispatch turn. "
437437+ "Do not retry this task or request another talent for it. Stop here "
438438+ "and report to the owner directly using the result below."
434439 )
435440 errored_instruction = (
436436- "Instruction: Answer the owner directly; report the failure to the "
437437- "owner and stop; do not retry, dispatch, or redispatch a talent for "
438438- "this trigger."
441441+ "Instruction: This is a stop-and-report turn, not a dispatch turn. "
442442+ "Do not retry this task or request another talent for it. Stop here "
443443+ "and report to the owner directly using the reason below."
439444 )
440445 assert finished_instruction in finished_vars["trigger_context"]
441446 assert errored_instruction in errored_vars["trigger_context"]
442442- assert errored_instruction not in finished_vars["trigger_context"]
447447+ assert "- Result: Found the latest notes." in finished_vars["trigger_context"]
448448+ assert "- Reason: The lookup failed." in errored_vars["trigger_context"]
443449444450445451def test_chat_context_includes_identity_grounding(monkeypatch, tmp_path):
···483489 _load_chat_context_module().pre_process(
484490 {
485491 "prompt": "What is on my calendar today?",
486486- "trigger_kind": "owner_message",
487487- "trigger_payload": {
488488- "text": "What is on my calendar today?",
492492+ "trigger": {
493493+ "type": "owner_message",
494494+ "message": "What is on my calendar today?",
489495 "ts": _ts(11, 0),
490496 },
491497 }
···534540 result = module.pre_process(
535541 {
536542 "prompt": "What is on my calendar today?",
537537- "trigger_kind": "owner_message",
538538- "trigger_payload": {
539539- "text": "What is on my calendar today?",
543543+ "path": "/app/home",
544544+ "trigger": {
545545+ "type": "owner_message",
546546+ "message": "What is on my calendar today?",
540547 "path": "/app/home",
541548 "ts": _ts(12, 0),
542549 },