tsugite-cli 0.23.2__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 (184) hide show
  1. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/PKG-INFO +1 -1
  2. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/pyproject.toml +1 -1
  3. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_preparation.py +2 -2
  4. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/history_integration.py +28 -20
  5. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/runner.py +25 -5
  6. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/steps.py +2 -0
  7. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/agent_config.py +14 -14
  8. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_agents/default.md +11 -3
  9. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/scheduling/SKILL.md +2 -0
  10. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/daemon.py +64 -83
  11. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/history.py +2 -2
  12. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/agent.py +23 -6
  13. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/executor.py +28 -2
  14. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/subprocess_executor.py +22 -7
  15. tsugite_cli-0.24.1/tsugite/daemon_client.py +201 -0
  16. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/events/helpers.py +14 -3
  17. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/models.py +1 -1
  18. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/sqlite_backend.py +17 -7
  19. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/md_agents.py +1 -0
  20. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/permissions.py +31 -3
  21. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/anthropic.py +5 -3
  22. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/openai_compat.py +4 -1
  23. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/schemas/agent.schema.json +10 -0
  24. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/skill_discovery.py +6 -0
  25. tsugite_cli-0.24.1/tsugite/tools/daemon_control.py +107 -0
  26. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/http.py +27 -2
  27. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/jobs.py +21 -2
  28. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/schedule.py +13 -0
  29. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/skills.py +19 -13
  30. tsugite_cli-0.23.2/tsugite/tools/daemon_control.py +0 -60
  31. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/.gitignore +0 -0
  32. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/AGENTS.md +0 -0
  33. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/CONTRIBUTING.md +0 -0
  34. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/LICENSE +0 -0
  35. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/README.md +0 -0
  36. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/__init__.py +0 -0
  37. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_inheritance.py +0 -0
  38. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/__init__.py +0 -0
  39. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/exec_directives.py +0 -0
  40. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/exec_runner.py +0 -0
  41. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/helpers.py +0 -0
  42. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/metrics.py +0 -0
  43. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/models.py +0 -0
  44. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_runner/validation.py +0 -0
  45. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/agent_utils.py +0 -0
  46. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/approval.py +0 -0
  47. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/__init__.py +0 -0
  48. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/auto_context.py +0 -0
  49. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/base.py +0 -0
  50. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/delegation.py +0 -0
  51. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/file.py +0 -0
  52. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/inline.py +0 -0
  53. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/storage.py +0 -0
  54. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/attachments/url.py +0 -0
  55. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_agents/.gitkeep +0 -0
  56. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_agents/code_searcher.md +0 -0
  57. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_agents/file_searcher.md +0 -0
  58. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_agents/job_verifier.md +0 -0
  59. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_agents/job_worker.md +0 -0
  60. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_agents/onboard.md +0 -0
  61. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_context.py +0 -0
  62. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/.gitkeep +0 -0
  63. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/codebase-exploration/SKILL.md +0 -0
  64. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/external-agent-bridge/SKILL.md +0 -0
  65. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
  66. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
  67. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
  68. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +0 -0
  69. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
  70. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
  71. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cache.py +0 -0
  72. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cancellation.py +0 -0
  73. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/__init__.py +0 -0
  74. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/agents.py +0 -0
  75. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/attachments.py +0 -0
  76. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/cache.py +0 -0
  77. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/chat.py +0 -0
  78. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/config.py +0 -0
  79. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/exec.py +0 -0
  80. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/helpers.py +0 -0
  81. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/init.py +0 -0
  82. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/models.py +0 -0
  83. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/plugins.py +0 -0
  84. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/render.py +0 -0
  85. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/run.py +0 -0
  86. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/secrets.py +0 -0
  87. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/skills.py +0 -0
  88. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/tools.py +0 -0
  89. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/usage.py +0 -0
  90. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/validate.py +0 -0
  91. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/cli/workspace.py +0 -0
  92. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/config.py +0 -0
  93. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/console.py +0 -0
  94. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/constants.py +0 -0
  95. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/context.py +0 -0
  96. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/context_block.py +0 -0
  97. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/__init__.py +0 -0
  98. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/content_blocks.py +0 -0
  99. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/executor_registry.py +0 -0
  100. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/memory.py +0 -0
  101. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/proxy.py +0 -0
  102. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/record_store.py +0 -0
  103. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/sandbox.py +0 -0
  104. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/state.py +0 -0
  105. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/core/tools.py +0 -0
  106. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/events/__init__.py +0 -0
  107. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/events/base.py +0 -0
  108. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/events/bus.py +0 -0
  109. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/events/events.py +0 -0
  110. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/exceptions.py +0 -0
  111. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/__init__.py +0 -0
  112. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/base.py +0 -0
  113. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/reconstruction.py +0 -0
  114. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/registry.py +0 -0
  115. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/sqlite_conn.py +0 -0
  116. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/sqlite_schema.py +0 -0
  117. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/history/ui_events.py +0 -0
  118. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/hooks.py +0 -0
  119. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/interaction.py +0 -0
  120. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/models.py +0 -0
  121. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/options.py +0 -0
  122. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/plugins.py +0 -0
  123. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/prompt_xml.py +0 -0
  124. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/__init__.py +0 -0
  125. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/base.py +0 -0
  126. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/model_cache.py +0 -0
  127. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/model_registry.py +0 -0
  128. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/ollama.py +0 -0
  129. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/providers/openrouter.py +0 -0
  130. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/py.typed +0 -0
  131. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/renderer.py +0 -0
  132. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/schemas/__init__.py +0 -0
  133. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/__init__.py +0 -0
  134. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/backend.py +0 -0
  135. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/env.py +0 -0
  136. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/exec.py +0 -0
  137. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/file.py +0 -0
  138. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/masking.py +0 -0
  139. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/redaction.py +0 -0
  140. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/registry.py +0 -0
  141. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/secrets/sqlite.py +0 -0
  142. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/shell_tool_config.py +0 -0
  143. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/templates/AGENTS.md +0 -0
  144. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/templates/IDENTITY.md +0 -0
  145. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/templates/MEMORY.md +0 -0
  146. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/templates/USER.md +0 -0
  147. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/templates/personas/casual-technical.md +0 -0
  148. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/templates/personas/marvin.md +0 -0
  149. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/templates/personas/minimal.md +0 -0
  150. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/__init__.py +0 -0
  151. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/agents.py +0 -0
  152. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/artifacts.py +0 -0
  153. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/fs.py +0 -0
  154. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/history.py +0 -0
  155. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/interactive.py +0 -0
  156. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/notify.py +0 -0
  157. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/secrets.py +0 -0
  158. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/sessions.py +0 -0
  159. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/shell.py +0 -0
  160. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/shell_tools.py +0 -0
  161. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tools/time.py +0 -0
  162. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/tsugite.py +0 -0
  163. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/__init__.py +0 -0
  164. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/base.py +0 -0
  165. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/chat.py +0 -0
  166. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/dispatch.py +0 -0
  167. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/helpers.py +0 -0
  168. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/jsonl.py +0 -0
  169. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/plain.py +0 -0
  170. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/repl_chat.py +0 -0
  171. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/repl_commands.py +0 -0
  172. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/repl_completer.py +0 -0
  173. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui/repl_handler.py +0 -0
  174. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui_context.py +0 -0
  175. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/ui_surfaces.py +0 -0
  176. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/usage/__init__.py +0 -0
  177. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/usage/quota.py +0 -0
  178. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/usage/store.py +0 -0
  179. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/user_agent.py +0 -0
  180. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/utils.py +0 -0
  181. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/workspace/__init__.py +0 -0
  182. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/workspace/models.py +0 -0
  183. {tsugite_cli-0.23.2 → tsugite_cli-0.24.1}/tsugite/workspace/session.py +0 -0
  184. {tsugite_cli-0.23.2 → 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.23.2
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.23.2"
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"
@@ -242,7 +242,7 @@ class AgentPreparer:
242
242
  auto_load_skills = [s for s in (agent_config.auto_load_skills or []) if s not in suppressed_skills]
243
243
 
244
244
  for skill_name in auto_load_skills:
245
- result = skill_manager.load_skill(skill_name)
245
+ result = skill_manager.load_skill(skill_name, explicit=True)
246
246
  if result.startswith("Failed") or result.startswith("Skill '"):
247
247
  if event_bus:
248
248
  event_bus.emit(SkillLoadFailedEvent(skill_name=skill_name, error_message=result))
@@ -268,7 +268,7 @@ class AgentPreparer:
268
268
  if name in auto_load_skills:
269
269
  # Already loaded above; no need to double-load but it's still sticky.
270
270
  continue
271
- result = skill_manager.load_skill(name)
271
+ result = skill_manager.load_skill(name, explicit=True)
272
272
  if result.startswith("Failed") or result.startswith("Skill '"):
273
273
  if event_bus:
274
274
  event_bus.emit(SkillLoadFailedEvent(skill_name=name, error_message=result))
@@ -73,7 +73,11 @@ def open_or_create_session(
73
73
  backend = get_history_backend()
74
74
  if continue_conversation_id:
75
75
  if backend.exists(continue_conversation_id):
76
- return backend.load(continue_conversation_id)
76
+ session = backend.load(continue_conversation_id)
77
+ # Daemon-spawned sessions are created without a model.
78
+ if session.summary().model is None:
79
+ session.record("session_start", agent=agent_name, model=model, workspace=workspace)
80
+ return session
77
81
  return backend.create(
78
82
  agent_name=agent_name,
79
83
  model=model,
@@ -92,8 +96,8 @@ def _current_turn_already_has_user_input(storage: Session) -> bool:
92
96
  session_end after it; the post-hoc save_run_to_history on the error path
93
97
  would otherwise record a second, identical one (a duplicate user bubble).
94
98
  A genuinely repeated message opens a new turn *after* the prior session_end,
95
- so this stays False for it. A final_result also ends a turn: compaction copies
96
- retained turns into the successor without their session_end.
99
+ so this stays False for it. A final_result also ends a turn copied without
100
+ its session_end (branches, older compacted sessions).
97
101
  """
98
102
  events = list(storage.iter_events(types=("user_input", "session_end", "final_result")))
99
103
  return bool(events) and events[-1].type == "user_input"
@@ -129,8 +133,9 @@ def record_user_input(
129
133
  data["attachments"] = [
130
134
  {"name": a.name, "type": a.content_type.value, "source_url": a.source_url} for a in uploads
131
135
  ]
132
- if channel_metadata:
133
- data["channel"] = channel_metadata
136
+ channel = {k: v for k, v in (channel_metadata or {}).items() if k != "tsugite_token"}
137
+ if channel:
138
+ data["channel"] = channel
134
139
  # Structured client context, recorded alongside the folded <client_context> XML
135
140
  # in `text` so the UI reads it back without re-parsing the prompt string.
136
141
  if client_context_items:
@@ -244,8 +249,8 @@ def save_run_to_history(
244
249
  agent_already_recorded = last_model_response > last_session_start
245
250
 
246
251
  if agent_already_recorded:
247
- # A continued conversation pins session_start at index 0, so the current
248
- # turn starts at its own user_input.
252
+ # A continued conversation's session_start can sit at index 0 or come from
253
+ # a backfill. The current turn starts at its own user_input.
249
254
  turn_start = max(last_session_start, last_user_input)
250
255
  if last_session_end < turn_start:
251
256
  record_session_end(storage, status=status, error_message=error_message)
@@ -264,19 +269,20 @@ def save_run_to_history(
264
269
  tools_called=list(getattr(step, "tools_called", []) or []),
265
270
  )
266
271
 
267
- state_delta = provider_state if provider_state else None
268
- # Split as the agent loop records it: bare id in `model`, prefix in `provider`.
269
- model_provider, _, model_id = model.partition(":")
270
- storage.record(
271
- "model_response",
272
- provider=model_provider if model_id else None,
273
- model=model_id or model,
274
- raw_content=result or "",
275
- usage={"total_tokens": token_count} if token_count else None,
276
- cost=cost,
277
- duration_ms=duration_ms,
278
- state_delta=state_delta,
279
- )
272
+ if status == "success":
273
+ state_delta = provider_state if provider_state else None
274
+ # Split as the agent loop records it: bare id in `model`, prefix in `provider`.
275
+ model_provider, _, model_id = model.partition(":")
276
+ storage.record(
277
+ "model_response",
278
+ provider=model_provider if model_id else None,
279
+ model=model_id or model,
280
+ raw_content=result or "",
281
+ usage={"total_tokens": token_count} if token_count else None,
282
+ cost=cost,
283
+ duration_ms=duration_ms,
284
+ state_delta=state_delta,
285
+ )
280
286
 
281
287
  record_session_end(storage, status=status, error_message=error_message)
282
288
  return storage.session_id
@@ -288,6 +294,7 @@ class ResumableSessionState:
288
294
 
289
295
  session_id: str
290
296
  compacted: bool = False
297
+ cumulative_cost: float = 0.0
291
298
 
292
299
 
293
300
  def get_resumable_session_state(conversation_id: str) -> Optional[ResumableSessionState]:
@@ -324,6 +331,7 @@ def get_resumable_session_state(conversation_id: str) -> Optional[ResumableSessi
324
331
  return ResumableSessionState(
325
332
  session_id=session_id,
326
333
  compacted=bool(state.get("compacted", False)),
334
+ cumulative_cost=state.get("cumulative_cost") or 0.0,
327
335
  )
328
336
  return None
329
337
  except Exception:
@@ -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
@@ -395,11 +395,13 @@ async def _execute_agent_with_prompt(
395
395
  path_context: Optional[Any] = None,
396
396
  resume_session: Optional[str] = None,
397
397
  resume_after_compaction: bool = False,
398
+ resume_cost_baseline: float = 0.0,
398
399
  hook_vars: Optional[Dict[str, str]] = None,
399
400
  continue_conversation_id: Optional[str] = None,
400
401
  user_input_for_history: Optional[str] = None,
401
402
  channel_metadata: Optional[Dict[str, Any]] = None,
402
403
  usage_sink: Optional[Dict[str, Any]] = None,
404
+ requires_action: bool = False,
403
405
  ) -> str | AgentExecutionResult:
404
406
  """Execute agent with a prepared agent.
405
407
 
@@ -408,6 +410,9 @@ async def _execute_agent_with_prompt(
408
410
  `usage_sink`, when given, receives this call's token and cost counters. It is
409
411
  how a multi-step run collects accounting from steps that return plain
410
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.
411
416
  """
412
417
  if exec_options is None:
413
418
  exec_options = ExecutionOptions()
@@ -610,9 +615,11 @@ async def _execute_agent_with_prompt(
610
615
  previous_messages=previous_messages,
611
616
  resume_session=resume_session,
612
617
  resume_after_compaction=resume_after_compaction,
618
+ resume_cost_baseline=resume_cost_baseline,
613
619
  hook_vars=hook_vars,
614
620
  storage=session_storage,
615
621
  pre_llm_call=pre_llm_call_cb,
622
+ expects_action=requires_action or (channel_metadata or {}).get("source") == "scheduler",
616
623
  )
617
624
  # Set event_bus in context so tools can access it during execution
618
625
  _setup_event_context(event_bus)
@@ -663,10 +670,18 @@ async def _execute_agent_with_prompt(
663
670
  except Exception as e:
664
671
  logger.debug("Failed to record usage: %s", e)
665
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
+
666
683
  # Return appropriate format
667
684
  if exec_options.return_token_usage:
668
- from tsugite.core.agent import AgentResult
669
-
670
685
  if isinstance(result, AgentResult):
671
686
  step_count = len(result.steps) if result.steps else 0
672
687
  steps_list = result.steps if result.steps else []
@@ -709,8 +724,6 @@ async def _execute_agent_with_prompt(
709
724
  session_id=session_storage.session_id if session_storage else None,
710
725
  )
711
726
  else:
712
- from tsugite.core.agent import AgentResult
713
-
714
727
  if isinstance(result, AgentResult):
715
728
  return str(result.output)
716
729
  else:
@@ -867,7 +880,9 @@ class _RunSetup:
867
880
  previous_messages: List[Dict] = field(default_factory=list)
868
881
  resume_session: Optional[str] = None
869
882
  resume_after_compaction: bool = False
883
+ resume_cost_baseline: float = 0.0
870
884
  conversation_id: Optional[str] = None
885
+ requires_action: bool = False
871
886
 
872
887
 
873
888
  def _prepare_step(agent: Any, prompt: str, context: Dict[str, Any], setup: "_RunSetup") -> "PreparedAgent":
@@ -951,6 +966,7 @@ async def _run_unit(
951
966
  model_kwargs=model_kwargs,
952
967
  injectable_vars=injectable_vars,
953
968
  usage_sink=usage_sink,
969
+ requires_action=setup.requires_action,
954
970
  )
955
971
 
956
972
  try:
@@ -958,6 +974,7 @@ async def _run_unit(
958
974
  previous_messages=setup.previous_messages,
959
975
  resume_session=setup.resume_session,
960
976
  resume_after_compaction=setup.resume_after_compaction,
977
+ resume_cost_baseline=setup.resume_cost_baseline,
961
978
  **execute_kwargs,
962
979
  )
963
980
  except (RuntimeError, AgentExecutionError) as e:
@@ -1151,6 +1168,7 @@ async def run_agent_async(
1151
1168
  previous_messages = []
1152
1169
  resume_session = None
1153
1170
  resume_after_compaction = False
1171
+ resume_cost_baseline = 0.0
1154
1172
  if continue_conversation_id:
1155
1173
  from tsugite.agent_runner.history_integration import (
1156
1174
  get_resumable_session_state,
@@ -1162,6 +1180,7 @@ async def run_agent_async(
1162
1180
  if session_info:
1163
1181
  resume_session = session_info.session_id
1164
1182
  resume_after_compaction = session_info.compacted
1183
+ resume_cost_baseline = session_info.cumulative_cost
1165
1184
  logger.info(
1166
1185
  "Resuming provider session %s (compacted=%s)",
1167
1186
  resume_session,
@@ -1210,6 +1229,7 @@ async def run_agent_async(
1210
1229
  previous_messages=previous_messages,
1211
1230
  resume_session=resume_session,
1212
1231
  resume_after_compaction=resume_after_compaction,
1232
+ resume_cost_baseline=resume_cost_baseline,
1213
1233
  conversation_id=continue_conversation_id,
1214
1234
  )
1215
1235
 
@@ -251,6 +251,8 @@ async def _execute_step_with_retries(
251
251
  user_input_for_history=f"[Step {i}/{total_steps}: {step.name}]",
252
252
  resume_session=None,
253
253
  resume_after_compaction=False,
254
+ resume_cost_baseline=0.0,
255
+ requires_action=bool(step.assign_var),
254
256
  )
255
257
  errors = []
256
258
  step_start_time = time.time()
@@ -17,6 +17,12 @@ from tsugite.utils import has_glob_chars
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
+ def _warn_skipped(subject: str, detail: str) -> None:
21
+ from tsugite.events.helpers import emit_warning_event
22
+
23
+ emit_warning_event(f"Agent attachment skipped: {subject} ({detail})")
24
+
25
+
20
26
  def _render_path(template: str, template_vars: Dict[str, Any]) -> Optional[str]:
21
27
  """Render a Jinja path template; return None on render failure."""
22
28
  from tsugite.renderer import AgentRenderer
@@ -24,7 +30,7 @@ def _render_path(template: str, template_vars: Dict[str, Any]) -> Optional[str]:
24
30
  try:
25
31
  return AgentRenderer().render_string(template, template_vars)
26
32
  except Exception as e:
27
- logger.debug("Failed to render attachment path %r: %s", template, e)
33
+ _warn_skipped(template, f"path template failed to render: {e}")
28
34
  return None
29
35
 
30
36
 
@@ -124,12 +130,10 @@ def _resolve_string_item(
124
130
  return
125
131
  resolved = _resolve_path(rendered, workspace_path)
126
132
  if not resolved.exists():
127
- logger.debug("Agent attachment not found (skipped): %s", resolved)
133
+ _warn_skipped(template, f"not found: {resolved}")
128
134
  return
129
- att = file_handler.fetch(str(resolved))
130
- if att:
131
- attachments.append(att)
132
- logger.debug("Loaded agent attachment: %s", resolved)
135
+ attachments.append(file_handler.fetch(str(resolved)))
136
+ logger.debug("Loaded agent attachment: %s", resolved)
133
137
 
134
138
 
135
139
  def _resolve_spec(
@@ -161,13 +165,12 @@ def _resolve_spec(
161
165
  if not spec.attach and spec.assign:
162
166
  try:
163
167
  content = path.read_text(encoding="utf-8")
164
- except (OSError, UnicodeDecodeError):
168
+ except (OSError, UnicodeDecodeError) as e:
169
+ _warn_skipped(str(path), f"unreadable: {e}")
165
170
  continue
166
171
  bindings[spec.assign].append({"path": str(path), "content": content})
167
172
  continue
168
173
  att = file_handler.fetch(str(path))
169
- if not att:
170
- continue
171
174
  if spec.attach:
172
175
  attachments.append(att)
173
176
  if spec.assign and att.content_type == AttachmentContentType.TEXT:
@@ -178,14 +181,11 @@ def _resolve_spec(
178
181
  if not resolved.exists():
179
182
  if spec.assign:
180
183
  bindings[spec.assign] = None
181
- logger.debug("Agent attachment not found (skipped): %s", resolved)
184
+ elif not spec.optional:
185
+ _warn_skipped(spec.path, f"not found: {resolved}")
182
186
  return
183
187
 
184
188
  att = file_handler.fetch(str(resolved))
185
- if not att:
186
- if spec.assign:
187
- bindings[spec.assign] = None
188
- return
189
189
  if spec.attach:
190
190
  attachments.append(att)
191
191
  if spec.assign:
@@ -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
@@ -178,6 +178,8 @@ print(schedules)
178
178
 
179
179
  Each schedule includes: `id`, `execution_type`, `schedule_type`, `enabled`, `next_run`, `last_run`, `last_status`, `last_error`.
180
180
 
181
+ `last_status` is `success`, `error`, `skipped`, or `no_action`.
182
+
181
183
  ## Managing Schedules
182
184
 
183
185
  ```python-exec