personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

talent/chat: tighten report-back contract for talent_finished and talent_errored

When the latest trigger was talent_finished or talent_errored, the model sometimes redispatched the same task instead of producing an owner-facing report-back. Make the no-redispatch contract explicit in both the injected follow-up message and the trigger context block, and rewrite the chat-prompt rule line to be prescriptive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+103 -12
+1 -1
talent/chat.md
··· 100 100 - Return JSON only. 101 101 - `message` should stand on its own without referring to hidden machinery. 102 102 - If `talent_request` is present, the `message` should still be useful to the owner right now. 103 - - When the latest trigger is a `talent_finished` follow-up, answer the owner's pending request with a short owner-facing summary of the new result. Do not echo or paraphrase the prior holding `sol_message` unless it adds new information. 103 + - 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. 104 104 - Prefer no dispatch over a weak or redundant dispatch.
+21 -4
talent/chat_context.py
··· 120 120 "role": "user", 121 121 "content": ( 122 122 "[internal follow-up: talent " 123 - f"{trigger_payload['name']} finished. Use this result " 124 - "to answer the owner's pending request with a short " 125 - f"summary. Result: {trigger_payload['summary']}]" 123 + f"{trigger_payload['name']} finished. This is a " 124 + "report-back turn, not a dispatch turn. Do not " 125 + "request another talent for this task. Use the " 126 + "result below to answer the owner's pending request " 127 + f"with a short summary. Result: {trigger_payload['summary']}]" 126 128 ), 127 129 } 128 130 ) ··· 131 133 { 132 134 "role": "user", 133 135 "content": ( 134 - f"[talent {trigger_payload['name']} errored: " 136 + "[internal follow-up: talent " 137 + f"{trigger_payload['name']} errored. This is a " 138 + "report-back turn, not a dispatch turn. Do not " 139 + "request another talent for this task. Briefly " 140 + "explain the failure to the owner and ask for " 141 + "clarification only if needed. Reason: " 135 142 f"{trigger_payload['reason']}]" 136 143 ), 137 144 } ··· 283 290 elif trigger_kind == "talent_finished": 284 291 if payload.get("name"): 285 292 lines.append(f"- Talent: {payload['name']}") 293 + lines.append("- Mode: report_back_only") 294 + lines.append( 295 + "- Instruction: Answer the owner directly; do not dispatch or " 296 + "redispatch a talent for this trigger." 297 + ) 286 298 if payload.get("summary"): 287 299 lines.append(f"- Summary: {payload['summary']}") 288 300 elif trigger_kind == "talent_errored": 289 301 if payload.get("name"): 290 302 lines.append(f"- Talent: {payload['name']}") 303 + lines.append("- Mode: report_back_only") 304 + lines.append( 305 + "- Instruction: Answer the owner directly; do not dispatch or " 306 + "redispatch a talent for this trigger." 307 + ) 291 308 if payload.get("reason"): 292 309 lines.append(f"- Reason: {payload['reason']}") 293 310 elif trigger_kind == "synthetic-max-active":
+1 -1
tests/baselines/api/sol/preview.json
··· 1 1 { 2 - "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 \u2014 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 \u2014 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 latest trigger is a `talent_finished` follow-up, answer the owner's pending request with a short owner-facing summary of the new result. Do not echo or paraphrase the prior holding `sol_message` unless it adds new information.\n- Prefer no dispatch over a weak or redundant dispatch.", 2 + "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 \u2014 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 \u2014 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- Prefer no dispatch over a weak or redundant dispatch.", 3 3 "multi_facet": false, 4 4 "name": "chat", 5 5 "title": "Chat"
+80 -6
tests/test_chat_context.py
··· 214 214 assert len(save_calls) == 1 215 215 216 216 217 - def test_chat_context_talent_finished_appends_internal_followup_message( 218 - monkeypatch, tmp_path 219 - ): 217 + def test_chat_context_talent_finished_marks_report_back_only(monkeypatch, tmp_path): 220 218 journal = tmp_path / "journal" 221 219 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 222 220 ··· 263 261 } 264 262 ) 265 263 266 - _assert_template_vars_result(result) 264 + template_vars = _assert_template_vars_result(result) 265 + assert "Mode: report_back_only" in template_vars["trigger_context"] 266 + assert ( 267 + "Instruction: Answer the owner directly; do not dispatch or redispatch " 268 + "a talent for this trigger." 269 + ) in template_vars["trigger_context"] 267 270 assert result["messages"] == [ 268 271 {"role": "user", "content": "What happened?"}, 269 272 {"role": "assistant", "content": "Looking into it."}, 270 273 { 271 274 "role": "user", 272 275 "content": ( 273 - "[internal follow-up: talent exec finished. Use this result " 274 - "to answer the owner's pending request with a short summary. " 276 + "[internal follow-up: talent exec finished. This is a " 277 + "report-back turn, not a dispatch turn. Do not request " 278 + "another talent for this task. Use the result below to " 279 + "answer the owner's pending request with a short summary. " 275 280 "Result: Found the latest notes.]" 281 + ), 282 + }, 283 + ] 284 + 285 + 286 + def test_chat_context_talent_errored_marks_report_back_only(monkeypatch, tmp_path): 287 + journal = tmp_path / "journal" 288 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 289 + 290 + append_chat_event( 291 + "owner_message", 292 + ts=_ts(10, 0), 293 + text="What happened?", 294 + app="home", 295 + path="/app/home", 296 + facet="work", 297 + ) 298 + append_chat_event( 299 + "sol_message", 300 + ts=_ts(10, 1), 301 + use_id="use-chat-3", 302 + text="Looking into it.", 303 + notes="Acknowledged request.", 304 + requested_target=None, 305 + requested_task=None, 306 + ) 307 + append_chat_event( 308 + "talent_errored", 309 + ts=_ts(10, 2), 310 + use_id="use-exec-3", 311 + name="exec", 312 + reason="The lookup failed.", 313 + ) 314 + 315 + monkeypatch.setattr("think.routines.get_routine_state", lambda: []) 316 + monkeypatch.setattr( 317 + "think.routines.get_config", 318 + lambda: {"_meta": {"suggestions_enabled": False, "suggestions": {}}}, 319 + ) 320 + monkeypatch.setattr("think.routines.save_config", lambda config: None) 321 + 322 + result = _load_chat_context_module().pre_process( 323 + { 324 + "day": "20260420", 325 + "trigger_kind": "talent_errored", 326 + "trigger_payload": { 327 + "name": "exec", 328 + "reason": "The lookup failed.", 329 + }, 330 + } 331 + ) 332 + 333 + template_vars = _assert_template_vars_result(result) 334 + assert "Mode: report_back_only" in template_vars["trigger_context"] 335 + assert ( 336 + "Instruction: Answer the owner directly; do not dispatch or redispatch " 337 + "a talent for this trigger." 338 + ) in template_vars["trigger_context"] 339 + assert result["messages"] == [ 340 + {"role": "user", "content": "What happened?"}, 341 + {"role": "assistant", "content": "Looking into it."}, 342 + { 343 + "role": "user", 344 + "content": ( 345 + "[internal follow-up: talent exec errored. This is a " 346 + "report-back turn, not a dispatch turn. Do not request " 347 + "another talent for this task. Briefly explain the failure " 348 + "to the owner and ask for clarification only if needed. " 349 + "Reason: The lookup failed.]" 276 350 ), 277 351 }, 278 352 ]