tsugite-cli 0.24.0__tar.gz → 0.24.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/PKG-INFO +1 -1
  2. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/pyproject.toml +1 -1
  3. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/runner.py +18 -6
  4. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/steps.py +1 -0
  5. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/agent_config.py +1 -1
  6. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_agents/default.md +11 -3
  7. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/agent.py +6 -3
  8. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/executor.py +28 -2
  9. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/subprocess_executor.py +10 -7
  10. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/md_agents.py +1 -0
  11. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/openai_compat.py +1 -0
  12. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/schemas/agent.schema.json +10 -0
  13. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/schedule.py +13 -0
  14. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/.gitignore +0 -0
  15. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/AGENTS.md +0 -0
  16. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/CONTRIBUTING.md +0 -0
  17. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/LICENSE +0 -0
  18. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/README.md +0 -0
  19. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/__init__.py +0 -0
  20. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_inheritance.py +0 -0
  21. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_preparation.py +0 -0
  22. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/__init__.py +0 -0
  23. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/exec_directives.py +0 -0
  24. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/exec_runner.py +0 -0
  25. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/helpers.py +0 -0
  26. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/history_integration.py +0 -0
  27. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/metrics.py +0 -0
  28. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/models.py +0 -0
  29. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_runner/validation.py +0 -0
  30. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/agent_utils.py +0 -0
  31. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/approval.py +0 -0
  32. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/__init__.py +0 -0
  33. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/auto_context.py +0 -0
  34. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/base.py +0 -0
  35. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/delegation.py +0 -0
  36. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/file.py +0 -0
  37. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/inline.py +0 -0
  38. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/storage.py +0 -0
  39. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/attachments/url.py +0 -0
  40. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_agents/.gitkeep +0 -0
  41. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_agents/code_searcher.md +0 -0
  42. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_agents/file_searcher.md +0 -0
  43. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_agents/job_verifier.md +0 -0
  44. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_agents/job_worker.md +0 -0
  45. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_agents/onboard.md +0 -0
  46. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_context.py +0 -0
  47. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/.gitkeep +0 -0
  48. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/codebase-exploration/SKILL.md +0 -0
  49. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/external-agent-bridge/SKILL.md +0 -0
  50. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
  51. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
  52. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/scheduling/SKILL.md +0 -0
  53. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
  54. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +0 -0
  55. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
  56. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
  57. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cache.py +0 -0
  58. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cancellation.py +0 -0
  59. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/__init__.py +0 -0
  60. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/agents.py +0 -0
  61. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/attachments.py +0 -0
  62. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/cache.py +0 -0
  63. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/chat.py +0 -0
  64. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/config.py +0 -0
  65. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/daemon.py +0 -0
  66. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/exec.py +0 -0
  67. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/helpers.py +0 -0
  68. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/history.py +0 -0
  69. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/init.py +0 -0
  70. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/models.py +0 -0
  71. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/plugins.py +0 -0
  72. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/render.py +0 -0
  73. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/run.py +0 -0
  74. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/secrets.py +0 -0
  75. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/skills.py +0 -0
  76. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/tools.py +0 -0
  77. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/usage.py +0 -0
  78. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/validate.py +0 -0
  79. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/cli/workspace.py +0 -0
  80. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/config.py +0 -0
  81. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/console.py +0 -0
  82. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/constants.py +0 -0
  83. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/context.py +0 -0
  84. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/context_block.py +0 -0
  85. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/__init__.py +0 -0
  86. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/content_blocks.py +0 -0
  87. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/executor_registry.py +0 -0
  88. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/memory.py +0 -0
  89. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/proxy.py +0 -0
  90. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/record_store.py +0 -0
  91. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/sandbox.py +0 -0
  92. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/state.py +0 -0
  93. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/core/tools.py +0 -0
  94. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/daemon_client.py +0 -0
  95. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/events/__init__.py +0 -0
  96. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/events/base.py +0 -0
  97. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/events/bus.py +0 -0
  98. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/events/events.py +0 -0
  99. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/events/helpers.py +0 -0
  100. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/exceptions.py +0 -0
  101. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/__init__.py +0 -0
  102. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/base.py +0 -0
  103. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/models.py +0 -0
  104. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/reconstruction.py +0 -0
  105. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/registry.py +0 -0
  106. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/sqlite_backend.py +0 -0
  107. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/sqlite_conn.py +0 -0
  108. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/sqlite_schema.py +0 -0
  109. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/history/ui_events.py +0 -0
  110. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/hooks.py +0 -0
  111. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/interaction.py +0 -0
  112. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/models.py +0 -0
  113. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/options.py +0 -0
  114. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/permissions.py +0 -0
  115. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/plugins.py +0 -0
  116. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/prompt_xml.py +0 -0
  117. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/__init__.py +0 -0
  118. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/anthropic.py +0 -0
  119. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/base.py +0 -0
  120. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/model_cache.py +0 -0
  121. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/model_registry.py +0 -0
  122. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/ollama.py +0 -0
  123. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/providers/openrouter.py +0 -0
  124. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/py.typed +0 -0
  125. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/renderer.py +0 -0
  126. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/schemas/__init__.py +0 -0
  127. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/__init__.py +0 -0
  128. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/backend.py +0 -0
  129. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/env.py +0 -0
  130. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/exec.py +0 -0
  131. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/file.py +0 -0
  132. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/masking.py +0 -0
  133. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/redaction.py +0 -0
  134. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/registry.py +0 -0
  135. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/secrets/sqlite.py +0 -0
  136. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/shell_tool_config.py +0 -0
  137. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/skill_discovery.py +0 -0
  138. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/templates/AGENTS.md +0 -0
  139. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/templates/IDENTITY.md +0 -0
  140. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/templates/MEMORY.md +0 -0
  141. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/templates/USER.md +0 -0
  142. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/templates/personas/casual-technical.md +0 -0
  143. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/templates/personas/marvin.md +0 -0
  144. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/templates/personas/minimal.md +0 -0
  145. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/__init__.py +0 -0
  146. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/agents.py +0 -0
  147. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/artifacts.py +0 -0
  148. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/daemon_control.py +0 -0
  149. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/fs.py +0 -0
  150. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/history.py +0 -0
  151. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/http.py +0 -0
  152. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/interactive.py +0 -0
  153. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/jobs.py +0 -0
  154. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/notify.py +0 -0
  155. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/secrets.py +0 -0
  156. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/sessions.py +0 -0
  157. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/shell.py +0 -0
  158. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/shell_tools.py +0 -0
  159. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/skills.py +0 -0
  160. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tools/time.py +0 -0
  161. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/tsugite.py +0 -0
  162. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/__init__.py +0 -0
  163. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/base.py +0 -0
  164. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/chat.py +0 -0
  165. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/dispatch.py +0 -0
  166. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/helpers.py +0 -0
  167. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/jsonl.py +0 -0
  168. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/plain.py +0 -0
  169. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/repl_chat.py +0 -0
  170. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/repl_commands.py +0 -0
  171. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/repl_completer.py +0 -0
  172. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui/repl_handler.py +0 -0
  173. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui_context.py +0 -0
  174. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/ui_surfaces.py +0 -0
  175. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/usage/__init__.py +0 -0
  176. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/usage/quota.py +0 -0
  177. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/usage/store.py +0 -0
  178. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/user_agent.py +0 -0
  179. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/utils.py +0 -0
  180. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/workspace/__init__.py +0 -0
  181. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/workspace/models.py +0 -0
  182. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/workspace/session.py +0 -0
  183. {tsugite_cli-0.24.0 → tsugite_cli-0.24.1}/tsugite/workspace/templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tsugite-cli
3
- Version: 0.24.0
3
+ Version: 0.24.1
4
4
  Summary: Micro-agent runner for task automation using markdown definitions
5
5
  Author: Justyn Shull
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tsugite-cli"
3
- version = "0.24.0"
3
+ version = "0.24.1"
4
4
  description = "Micro-agent runner for task automation using markdown definitions"
5
5
  authors = [{ name = "Justyn Shull" }]
6
6
  requires-python = ">=3.11"
@@ -10,7 +10,7 @@ from pathlib import Path # noqa: E402
10
10
  from types import SimpleNamespace # noqa: E402
11
11
  from typing import TYPE_CHECKING, Any, Dict, List, Optional # noqa: E402
12
12
 
13
- from tsugite.core.agent import TsugiteAgent # noqa: E402
13
+ from tsugite.core.agent import NO_ACTION_MESSAGE, AgentResult, TsugiteAgent # noqa: E402
14
14
  from tsugite.core.executor_registry import get_executor_class # noqa: E402
15
15
  from tsugite.core.proxy import _parse_pattern # noqa: E402
16
16
  from tsugite.core.state import session_state_path # noqa: E402
@@ -401,6 +401,7 @@ async def _execute_agent_with_prompt(
401
401
  user_input_for_history: Optional[str] = None,
402
402
  channel_metadata: Optional[Dict[str, Any]] = None,
403
403
  usage_sink: Optional[Dict[str, Any]] = None,
404
+ requires_action: bool = False,
404
405
  ) -> str | AgentExecutionResult:
405
406
  """Execute agent with a prepared agent.
406
407
 
@@ -409,6 +410,9 @@ async def _execute_agent_with_prompt(
409
410
  `usage_sink`, when given, receives this call's token and cost counters. It is
410
411
  how a multi-step run collects accounting from steps that return plain
411
412
  strings.
413
+
414
+ `requires_action` makes a run that ends without executing any code raise, so a
415
+ step whose result feeds later steps cannot pass on a prose answer.
412
416
  """
413
417
  if exec_options is None:
414
418
  exec_options = ExecutionOptions()
@@ -615,7 +619,7 @@ async def _execute_agent_with_prompt(
615
619
  hook_vars=hook_vars,
616
620
  storage=session_storage,
617
621
  pre_llm_call=pre_llm_call_cb,
618
- expects_action=(channel_metadata or {}).get("source") == "scheduler",
622
+ expects_action=requires_action or (channel_metadata or {}).get("source") == "scheduler",
619
623
  )
620
624
  # Set event_bus in context so tools can access it during execution
621
625
  _setup_event_context(event_bus)
@@ -666,10 +670,18 @@ async def _execute_agent_with_prompt(
666
670
  except Exception as e:
667
671
  logger.debug("Failed to record usage: %s", e)
668
672
 
673
+ if requires_action and agent.ended_no_action:
674
+ steps = result.steps if isinstance(result, AgentResult) and result.steps else []
675
+ raise AgentExecutionError(
676
+ NO_ACTION_MESSAGE,
677
+ execution_steps=steps,
678
+ token_usage=agent.total_tokens,
679
+ cost=agent.reported_cost,
680
+ step_count=len(steps),
681
+ )
682
+
669
683
  # Return appropriate format
670
684
  if exec_options.return_token_usage:
671
- from tsugite.core.agent import AgentResult
672
-
673
685
  if isinstance(result, AgentResult):
674
686
  step_count = len(result.steps) if result.steps else 0
675
687
  steps_list = result.steps if result.steps else []
@@ -712,8 +724,6 @@ async def _execute_agent_with_prompt(
712
724
  session_id=session_storage.session_id if session_storage else None,
713
725
  )
714
726
  else:
715
- from tsugite.core.agent import AgentResult
716
-
717
727
  if isinstance(result, AgentResult):
718
728
  return str(result.output)
719
729
  else:
@@ -872,6 +882,7 @@ class _RunSetup:
872
882
  resume_after_compaction: bool = False
873
883
  resume_cost_baseline: float = 0.0
874
884
  conversation_id: Optional[str] = None
885
+ requires_action: bool = False
875
886
 
876
887
 
877
888
  def _prepare_step(agent: Any, prompt: str, context: Dict[str, Any], setup: "_RunSetup") -> "PreparedAgent":
@@ -955,6 +966,7 @@ async def _run_unit(
955
966
  model_kwargs=model_kwargs,
956
967
  injectable_vars=injectable_vars,
957
968
  usage_sink=usage_sink,
969
+ requires_action=setup.requires_action,
958
970
  )
959
971
 
960
972
  try:
@@ -252,6 +252,7 @@ async def _execute_step_with_retries(
252
252
  resume_session=None,
253
253
  resume_after_compaction=False,
254
254
  resume_cost_baseline=0.0,
255
+ requires_action=bool(step.assign_var),
255
256
  )
256
257
  errors = []
257
258
  step_start_time = time.time()
@@ -181,7 +181,7 @@ def _resolve_spec(
181
181
  if not resolved.exists():
182
182
  if spec.assign:
183
183
  bindings[spec.assign] = None
184
- else:
184
+ elif not spec.optional:
185
185
  _warn_skipped(spec.path, f"not found: {resolved}")
186
186
  return
187
187
 
@@ -6,9 +6,17 @@ extends: none
6
6
  # volatile memory last, so a change to today's memory / now.md doesn't invalidate
7
7
  # the identity files' cache. A flat list would be one block; these are three.
8
8
  attachments:
9
- - [PERSONA.md, SOUL.md, USER.md, IDENTITY.md, AGENTS.md]
10
- - [MEMORY.md, "memory/{{ yesterday() }}.md", "memory/{{ date_format(now(), '%Y-%m') }}.md"]
11
- - ["memory/{{ today() }}.md", notes/context/now.md, NOW.md]
9
+ - - {path: PERSONA.md, optional: true}
10
+ - {path: SOUL.md, optional: true}
11
+ - {path: USER.md, optional: true}
12
+ - {path: IDENTITY.md, optional: true}
13
+ - {path: AGENTS.md, optional: true}
14
+ - - {path: MEMORY.md, optional: true}
15
+ - {path: "memory/{{ yesterday() }}.md", optional: true}
16
+ - {path: "memory/{{ date_format(datetime.now().replace(day=1) - timedelta(days=1), '%Y-%m') }}.md", optional: true}
17
+ - - {path: "memory/{{ today() }}.md", optional: true}
18
+ - {path: notes/context/now.md, optional: true}
19
+ - {path: NOW.md, optional: true}
12
20
  max_turns: 10
13
21
  tools:
14
22
  - spawn_agent
@@ -72,6 +72,7 @@ def _usage_dump(usage) -> Optional[Dict[str, Any]]:
72
72
 
73
73
 
74
74
  CONTEXT_ACK = "Context loaded."
75
+ NO_ACTION_MESSAGE = "run ended without executing any code or calling return_value()"
75
76
 
76
77
 
77
78
  def estimate_content_tokens(content) -> int:
@@ -369,8 +370,8 @@ class TsugiteAgent:
369
370
  skills: List of Skill objects for loaded skills
370
371
  previous_messages: List of previous conversation messages (user/assistant pairs)
371
372
  hook_vars: Dict of pre_message hook captured outputs (e.g. rag_context)
372
- expects_action: The run exists to do work (a scheduled run). One that ends
373
- without executing any code is recorded as ``no_action``, not ``success``.
373
+ expects_action: Set for a scheduled run or a step with ``assign=``. Ending
374
+ without executing any code records ``no_action`` instead of ``success``.
374
375
  """
375
376
  from tsugite.models import get_model_kwargs, get_provider_and_model
376
377
 
@@ -392,6 +393,7 @@ class TsugiteAgent:
392
393
  self.hook_vars = hook_vars or {}
393
394
  self._pre_llm_call = pre_llm_call
394
395
  self.expects_action = expects_action
396
+ self.ended_no_action = False
395
397
  self._resume_session = resume_session
396
398
  self._resume_after_compaction = resume_after_compaction
397
399
 
@@ -662,7 +664,8 @@ class TsugiteAgent:
662
664
  self.event_bus.emit(WarningEvent(message=error_message, step=turn_num + 1))
663
665
  elif self.expects_action and not executed_code:
664
666
  status = "no_action"
665
- error_message = "run ended without executing any code or calling return_value()"
667
+ self.ended_no_action = True
668
+ error_message = NO_ACTION_MESSAGE
666
669
  if self.event_bus:
667
670
  self.event_bus.emit(WarningEvent(message=error_message, step=turn_num + 1))
668
671
  else:
@@ -11,8 +11,10 @@ import ast
11
11
  import asyncio
12
12
  import contextlib
13
13
  import io
14
+ import json
14
15
  import os
15
16
  import pprint
17
+ import re
16
18
  import sys
17
19
  import threading
18
20
  import time
@@ -105,6 +107,27 @@ NAME_ERROR_NOTE = (
105
107
  )
106
108
 
107
109
 
110
+ _UNDEFINED_NAME = re.compile(r"^NameError: name '(\w+)' is not defined")
111
+
112
+
113
+ class BlockNameOrigins:
114
+ """Remembers which code block last assigned each top-level name, for the NameError hint."""
115
+
116
+ def __init__(self) -> None:
117
+ self._block = 0
118
+ self._defined_in: Dict[str, int] = {}
119
+
120
+ def record(self, error: Optional[str], variables_set: Dict[str, str]) -> Optional[str]:
121
+ """Count this block, then return `error` with the defining block appended when it names one."""
122
+ self._block += 1
123
+ match = _UNDEFINED_NAME.match(error or "")
124
+ if match and match[1] in self._defined_in:
125
+ name = match[1]
126
+ error += f" ('{name}' was defined in block {self._defined_in[name]} earlier in this run, not in this block)"
127
+ self._defined_in.update(dict.fromkeys(variables_set, self._block))
128
+ return error
129
+
130
+
108
131
  def truncate_observation(text: str, max_output_kb: int = MAX_EXECUTION_OUTPUT_KB) -> tuple[str, bool]:
109
132
  """Clip text to the observation cap, returning (text, was_truncated).
110
133
 
@@ -354,7 +377,8 @@ class LocalExecutor:
354
377
 
355
378
  WARNING: This is NOT secure! Only use for development.
356
379
 
357
- Each call to ``execute()`` runs in a fresh namespace. Use the injected
380
+ Each call to ``execute()`` runs in a fresh namespace holding only ``re``,
381
+ ``json`` and ``os`` pre-imported. Use the injected
358
382
  ``state`` dict to persist values across turns; all other bindings are
359
383
  discarded when the turn ends.
360
384
 
@@ -403,6 +427,7 @@ class LocalExecutor:
403
427
  self._tool_functions: Dict[str, Callable[..., Any]] = {}
404
428
  self._sticky_injections: Dict[str, Any] = {}
405
429
  self._content_blocks: Dict[str, str] = {}
430
+ self._name_origins = BlockNameOrigins()
406
431
 
407
432
  target = workspace_dir
408
433
  if target is None and path_context is not None:
@@ -413,7 +438,7 @@ class LocalExecutor:
413
438
 
414
439
  def _build_turn_namespace(self) -> Dict[str, Any]:
415
440
  """Construct a fresh namespace populated with built-ins, tools, state, and sticky injections."""
416
- ns: Dict[str, Any] = {}
441
+ ns: Dict[str, Any] = {"re": re, "json": json, "os": os}
417
442
 
418
443
  def return_value(*args, **kwargs):
419
444
  if args:
@@ -610,6 +635,7 @@ class LocalExecutor:
610
635
  self._state = rebound
611
636
 
612
637
  variables_set = self._get_new_variables(namespace_before)
638
+ exec_error = self._name_origins.record(exec_error, variables_set)
613
639
  state_keys = self._summarize_state_keys()
614
640
  save_error = self._save_state()
615
641
 
@@ -24,7 +24,7 @@ from typing import Any, Dict, List, Optional
24
24
 
25
25
  from tsugite.permissions import get_permissions, web_fetch_blocked_hosts
26
26
 
27
- from .executor import EXECUTOR_BUILTIN_TOOLS, ExecutionResult, LocalExecutor
27
+ from .executor import EXECUTOR_BUILTIN_TOOLS, BlockNameOrigins, ExecutionResult, LocalExecutor
28
28
  from .state import load_state, save_state
29
29
 
30
30
  logger = logging.getLogger(__name__)
@@ -359,6 +359,7 @@ class SubprocessExecutor:
359
359
  self._tools_called: List[str] = []
360
360
  self._loaded_skills_for_turn: Dict[str, str] = {}
361
361
  self._unloaded_skills_for_turn: List[str] = []
362
+ self._name_origins = BlockNameOrigins()
362
363
 
363
364
  self._tmpdir = tempfile.mkdtemp(prefix="tsugite_sub_")
364
365
  self._proxy = None
@@ -471,7 +472,7 @@ RESULT_PATH = {json.dumps(result_path)}
471
472
  {"".join(tool_stubs)}
472
473
  {seed_denied_hosts}
473
474
 
474
- namespace = {{}}
475
+ namespace = {{"re": re, "json": json, "os": os}}
475
476
  namespace["return_value"] = return_value
476
477
  namespace["final_answer"] = return_value # backward-compat alias
477
478
  namespace["send_message"] = send_message
@@ -762,10 +763,11 @@ with open(RESULT_PATH, "w") as f:
762
763
  pass
763
764
  return ExecutionResult(
764
765
  output="",
765
- error=(
766
+ error=self._name_origins.record(
766
767
  f"tsu:exec exceeded its {self._exec_timeout}s wall-clock timeout and was terminated - "
767
768
  "a tool call or loop hung. Break long work into smaller blocks, add your own timeouts "
768
- "around blocking calls, or raise TSUGITE_EXEC_TIMEOUT."
769
+ "around blocking calls, or raise TSUGITE_EXEC_TIMEOUT.",
770
+ {},
769
771
  ),
770
772
  stdout="",
771
773
  stderr="",
@@ -793,16 +795,17 @@ with open(RESULT_PATH, "w") as f:
793
795
  except Exception as e:
794
796
  logger.warning("Failed to reload state from subprocess: %s", e)
795
797
 
798
+ variables_set = result_data.get("variables_set", {})
796
799
  return ExecutionResult(
797
800
  output=result_data.get("output", ""),
798
- error=result_data.get("error"),
801
+ error=self._name_origins.record(result_data.get("error"), variables_set),
799
802
  stdout=result_data.get("stdout", ""),
800
803
  stderr=result_data.get("stderr", ""),
801
804
  return_value=result_data.get("return_value"),
802
805
  tools_called=result_data.get("tools_called", []),
803
806
  tool_calls=result_data.get("tool_calls", []),
804
807
  groups=result_data.get("groups", []),
805
- variables_set=result_data.get("variables_set", {}),
808
+ variables_set=variables_set,
806
809
  state_keys=result_data.get("state_keys", {}),
807
810
  loaded_skills=self._loaded_skills_for_turn.copy(),
808
811
  unloaded_skills=list(self._unloaded_skills_for_turn),
@@ -817,7 +820,7 @@ with open(RESULT_PATH, "w") as f:
817
820
  pass
818
821
  return ExecutionResult(
819
822
  output="",
820
- error=f"Subprocess exited with code {proc.returncode}",
823
+ error=self._name_origins.record(f"Subprocess exited with code {proc.returncode}", {}),
821
824
  stdout="",
822
825
  stderr=stderr_output,
823
826
  return_value=None,
@@ -57,6 +57,7 @@ class AttachmentSpec(BaseModel):
57
57
  name: Optional[str] = None
58
58
  assign: Optional[str] = None
59
59
  attach: bool = True
60
+ optional: bool = False # a missing file is skipped without a warning
60
61
  index_format: Literal["path_only", "first_line", "first_heading", "frontmatter"] = "first_heading"
61
62
  max_entries: int = 50
62
63
  # Cache tier this attachment belongs to (0 = first/most-stable block). Set by
@@ -57,6 +57,7 @@ _OPENAI_MODELS: dict[str, ModelInfo] = {
57
57
  "openai/gpt-6-astra": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=10.0, output_cost_per_million=50.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "xhigh", "max"]),
58
58
  "openai/gpt-6-luna": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=0.1, output_cost_per_million=0.5, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
59
59
  "openai/gpt-6-sol": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=2.0, output_cost_per_million=10.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
60
+ "openai/gpt-6.1-sol": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=2.0, output_cost_per_million=10.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "xhigh", "max"]),
60
61
  "openai/gpt-daybreak-blue-latest": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=4.0, output_cost_per_million=20.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
61
62
  "openai/gpt-daybreak-red-latest": ModelInfo(max_input_tokens=400_000, max_output_tokens=128_000, input_cost_per_million=12.5, output_cost_per_million=75.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
62
63
  "openai/o1": ModelInfo(max_input_tokens=200_000, max_output_tokens=100_000, input_cost_per_million=15.0, output_cost_per_million=60.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high"]),
@@ -46,6 +46,11 @@
46
46
  "title": "Attach",
47
47
  "type": "boolean"
48
48
  },
49
+ "optional": {
50
+ "default": false,
51
+ "title": "Optional",
52
+ "type": "boolean"
53
+ },
49
54
  "index_format": {
50
55
  "default": "first_heading",
51
56
  "enum": [
@@ -61,6 +66,11 @@
61
66
  "default": 50,
62
67
  "title": "Max Entries",
63
68
  "type": "integer"
69
+ },
70
+ "tier": {
71
+ "default": 0,
72
+ "title": "Tier",
73
+ "type": "integer"
64
74
  }
65
75
  },
66
76
  "required": [
@@ -95,6 +95,8 @@ def schedule_create(
95
95
  target_session: Optional[str] = None,
96
96
  delivery_mode: str = "existing_session",
97
97
  delivery_kind: str = "fyi",
98
+ target_session_on_error: Optional[str] = None,
99
+ delivery_mode_on_error: Optional[str] = None,
98
100
  incident_key: Optional[str] = None,
99
101
  incident_title: Optional[str] = None,
100
102
  ) -> dict:
@@ -136,6 +138,9 @@ def schedule_create(
136
138
  session, "auto" picks existing_session for fyi and new_session for needs_ack.
137
139
  delivery_kind: "fyi" (default) or "needs_ack". A needs_ack delivery flags the session as needing a reply
138
140
  and pings the configured notification channels.
141
+ target_session_on_error: target_session for the repeated-failure card (default: use target_session).
142
+ When no session resolves, the failure card goes to the notify channels.
143
+ delivery_mode_on_error: delivery_mode for the repeated-failure card (default: use delivery_mode).
139
144
  incident_key: Dedupe key so repeat firings of this schedule share one incident session instead of opening
140
145
  a new one each run.
141
146
  incident_title: Title for the incident session (default: "Incident: <id>").
@@ -188,6 +193,8 @@ def schedule_create(
188
193
  chain_depth=chain_depth,
189
194
  delivery_mode=delivery_mode,
190
195
  delivery_kind=delivery_kind,
196
+ target_session_on_error=target_session_on_error,
197
+ delivery_mode_on_error=delivery_mode_on_error,
191
198
  incident_key=incident_key,
192
199
  incident_title=incident_title,
193
200
  )
@@ -283,6 +290,8 @@ def schedule_update(
283
290
  target_session: Optional[str] = None,
284
291
  delivery_mode: Optional[str] = None,
285
292
  delivery_kind: Optional[str] = None,
293
+ target_session_on_error: Optional[str] = None,
294
+ delivery_mode_on_error: Optional[str] = None,
286
295
  incident_key: Optional[str] = None,
287
296
  incident_title: Optional[str] = None,
288
297
  ) -> dict:
@@ -308,6 +317,8 @@ def schedule_update(
308
317
  target_session: Routing target for the delivered result (optional). See schedule_create for legal forms. Set to empty string to clear (reverts to fallback chain).
309
318
  delivery_mode: New delivery mode (optional). See schedule_create for legal values.
310
319
  delivery_kind: New delivery kind (optional). See schedule_create for legal values.
320
+ target_session_on_error: New failure-card target (optional). Set to empty string to clear.
321
+ delivery_mode_on_error: New failure-card delivery mode (optional). Set to empty string to clear.
311
322
  incident_key: New incident dedupe key (optional). Set to empty string to clear.
312
323
  incident_title: New incident session title (optional). Set to empty string to clear.
313
324
 
@@ -347,6 +358,8 @@ def schedule_update(
347
358
  ("expires_at", expires_at),
348
359
  ("session_id", session_id),
349
360
  ("target_session", target_session),
361
+ ("target_session_on_error", target_session_on_error),
362
+ ("delivery_mode_on_error", delivery_mode_on_error),
350
363
  ("incident_key", incident_key),
351
364
  ("incident_title", incident_title),
352
365
  ]:
File without changes
File without changes
File without changes
File without changes