tsugite-cli 0.24.0__tar.gz → 0.25.0__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.25.0}/.gitignore +1 -0
  2. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/PKG-INFO +2 -1
  3. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/pyproject.toml +2 -1
  4. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_preparation.py +6 -5
  5. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/__init__.py +0 -2
  6. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/runner.py +40 -67
  7. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/steps.py +1 -0
  8. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/approval.py +14 -7
  9. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/agent_config.py +1 -1
  10. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_agents/default.md +14 -13
  11. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_agents/job_verifier.md +3 -1
  12. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/codebase-exploration/SKILL.md +1 -1
  13. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/response-patterns/SKILL.md +11 -14
  14. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/helpers.py +2 -2
  15. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/run.py +1 -52
  16. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/agent.py +92 -5
  17. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/executor.py +28 -2
  18. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/subprocess_executor.py +10 -7
  19. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/base.py +2 -2
  20. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/models.py +22 -9
  21. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/sqlite_backend.py +15 -7
  22. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/ui_events.py +1 -0
  23. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/md_agents.py +1 -0
  24. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/permissions.py +34 -5
  25. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/openai_compat.py +1 -0
  26. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/schemas/agent.schema.json +10 -0
  27. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/__init__.py +6 -0
  28. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/agents.py +2 -17
  29. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/http.py +23 -11
  30. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/jobs.py +5 -4
  31. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/notify.py +21 -6
  32. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/schedule.py +13 -0
  33. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/sessions.py +216 -6
  34. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/shell_tools.py +6 -3
  35. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/workspace/models.py +22 -6
  36. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/AGENTS.md +0 -0
  37. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/CONTRIBUTING.md +0 -0
  38. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/LICENSE +0 -0
  39. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/README.md +0 -0
  40. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/__init__.py +0 -0
  41. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_inheritance.py +0 -0
  42. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/exec_directives.py +0 -0
  43. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/exec_runner.py +0 -0
  44. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/helpers.py +0 -0
  45. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/history_integration.py +0 -0
  46. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/metrics.py +0 -0
  47. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/models.py +0 -0
  48. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_runner/validation.py +0 -0
  49. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/agent_utils.py +0 -0
  50. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/__init__.py +0 -0
  51. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/auto_context.py +0 -0
  52. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/base.py +0 -0
  53. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/delegation.py +0 -0
  54. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/file.py +0 -0
  55. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/inline.py +0 -0
  56. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/storage.py +0 -0
  57. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/attachments/url.py +0 -0
  58. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_agents/.gitkeep +0 -0
  59. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_agents/code_searcher.md +0 -0
  60. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_agents/file_searcher.md +0 -0
  61. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_agents/job_worker.md +0 -0
  62. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_agents/onboard.md +0 -0
  63. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_context.py +0 -0
  64. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/.gitkeep +0 -0
  65. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/external-agent-bridge/SKILL.md +0 -0
  66. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
  67. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/scheduling/SKILL.md +0 -0
  68. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
  69. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +0 -0
  70. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
  71. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
  72. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cache.py +0 -0
  73. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cancellation.py +0 -0
  74. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/__init__.py +0 -0
  75. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/agents.py +0 -0
  76. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/attachments.py +0 -0
  77. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/cache.py +0 -0
  78. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/chat.py +0 -0
  79. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/config.py +0 -0
  80. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/daemon.py +0 -0
  81. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/exec.py +0 -0
  82. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/history.py +0 -0
  83. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/init.py +0 -0
  84. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/models.py +0 -0
  85. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/plugins.py +0 -0
  86. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/render.py +0 -0
  87. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/secrets.py +0 -0
  88. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/skills.py +0 -0
  89. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/tools.py +0 -0
  90. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/usage.py +0 -0
  91. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/validate.py +0 -0
  92. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/cli/workspace.py +0 -0
  93. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/config.py +0 -0
  94. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/console.py +0 -0
  95. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/constants.py +0 -0
  96. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/context.py +0 -0
  97. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/context_block.py +0 -0
  98. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/__init__.py +0 -0
  99. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/content_blocks.py +0 -0
  100. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/executor_registry.py +0 -0
  101. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/memory.py +0 -0
  102. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/proxy.py +0 -0
  103. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/record_store.py +0 -0
  104. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/sandbox.py +0 -0
  105. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/state.py +0 -0
  106. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/core/tools.py +0 -0
  107. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/daemon_client.py +0 -0
  108. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/events/__init__.py +0 -0
  109. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/events/base.py +0 -0
  110. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/events/bus.py +0 -0
  111. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/events/events.py +0 -0
  112. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/events/helpers.py +0 -0
  113. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/exceptions.py +0 -0
  114. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/__init__.py +0 -0
  115. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/reconstruction.py +0 -0
  116. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/registry.py +0 -0
  117. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/sqlite_conn.py +0 -0
  118. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/history/sqlite_schema.py +0 -0
  119. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/hooks.py +0 -0
  120. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/interaction.py +0 -0
  121. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/models.py +0 -0
  122. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/options.py +0 -0
  123. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/plugins.py +0 -0
  124. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/prompt_xml.py +0 -0
  125. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/__init__.py +0 -0
  126. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/anthropic.py +0 -0
  127. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/base.py +0 -0
  128. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/model_cache.py +0 -0
  129. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/model_registry.py +0 -0
  130. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/ollama.py +0 -0
  131. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/providers/openrouter.py +0 -0
  132. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/py.typed +0 -0
  133. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/renderer.py +0 -0
  134. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/schemas/__init__.py +0 -0
  135. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/__init__.py +0 -0
  136. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/backend.py +0 -0
  137. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/env.py +0 -0
  138. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/exec.py +0 -0
  139. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/file.py +0 -0
  140. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/masking.py +0 -0
  141. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/redaction.py +0 -0
  142. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/registry.py +0 -0
  143. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/secrets/sqlite.py +0 -0
  144. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/shell_tool_config.py +0 -0
  145. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/skill_discovery.py +0 -0
  146. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/templates/AGENTS.md +0 -0
  147. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/templates/IDENTITY.md +0 -0
  148. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/templates/MEMORY.md +0 -0
  149. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/templates/USER.md +0 -0
  150. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/templates/personas/casual-technical.md +0 -0
  151. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/templates/personas/marvin.md +0 -0
  152. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/templates/personas/minimal.md +0 -0
  153. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/artifacts.py +0 -0
  154. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/daemon_control.py +0 -0
  155. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/fs.py +0 -0
  156. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/history.py +0 -0
  157. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/interactive.py +0 -0
  158. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/secrets.py +0 -0
  159. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/shell.py +0 -0
  160. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/skills.py +0 -0
  161. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tools/time.py +0 -0
  162. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/tsugite.py +0 -0
  163. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/__init__.py +0 -0
  164. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/base.py +0 -0
  165. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/chat.py +0 -0
  166. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/dispatch.py +0 -0
  167. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/helpers.py +0 -0
  168. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/jsonl.py +0 -0
  169. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/plain.py +0 -0
  170. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/repl_chat.py +0 -0
  171. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/repl_commands.py +0 -0
  172. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/repl_completer.py +0 -0
  173. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui/repl_handler.py +0 -0
  174. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui_context.py +0 -0
  175. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/ui_surfaces.py +0 -0
  176. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/usage/__init__.py +0 -0
  177. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/usage/quota.py +0 -0
  178. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/usage/store.py +0 -0
  179. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/user_agent.py +0 -0
  180. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/utils.py +0 -0
  181. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/workspace/__init__.py +0 -0
  182. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/workspace/session.py +0 -0
  183. {tsugite_cli-0.24.0 → tsugite_cli-0.25.0}/tsugite/workspace/templates.py +0 -0
@@ -196,3 +196,4 @@ plugins/tsugite-daemon/tsugite_daemon/web/
196
196
  !plugins/tsugite-daemon/frontend/src/lib/**
197
197
  # Per-job git worktrees provisioned by the daemon.
198
198
  .tsugite-jobs/
199
+ /packaging/runtime/.cache/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tsugite-cli
3
- Version: 0.24.0
3
+ Version: 0.25.0
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
@@ -241,6 +241,7 @@ License: GNU AFFERO GENERAL PUBLIC LICENSE
241
241
  License-File: LICENSE
242
242
  Keywords: agents,automation,cli,jinja2
243
243
  Requires-Python: >=3.11
244
+ Requires-Dist: certifi>=2026.10.23
244
245
  Requires-Dist: cryptography>=46.0.5
245
246
  Requires-Dist: html2text>=2020.1.16
246
247
  Requires-Dist: httpx>=0.25
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tsugite-cli"
3
- version = "0.24.0"
3
+ version = "0.25.0"
4
4
  description = "Micro-agent runner for task automation using markdown definitions"
5
5
  authors = [{ name = "Justyn Shull" }]
6
6
  requires-python = ">=3.11"
@@ -12,6 +12,7 @@ dependencies = [
12
12
  "jinja2>=3.1",
13
13
  "pyyaml>=6.0",
14
14
  "httpx>=0.25",
15
+ "certifi>=2026.10.23",
15
16
  "rich>=13.0",
16
17
  "questionary>=2.1.1",
17
18
  "html2text>=2020.1.16",
@@ -434,7 +434,6 @@ class AgentPreparer:
434
434
  _combine_instructions,
435
435
  execute_exec_directives,
436
436
  execute_tool_directives,
437
- get_default_instructions,
438
437
  )
439
438
  from tsugite.core.agent import build_system_prompt
440
439
  from tsugite.renderer import AgentRenderer
@@ -506,12 +505,16 @@ class AgentPreparer:
506
505
  skip_reason=skip_reason,
507
506
  )
508
507
 
508
+ tools = self._expand_tools(agent_config, full_context, interactive_mode)
509
+ # Templates test membership against the names actually installed, not the
510
+ # frontmatter specs (`"@secrets"`).
511
+ full_context["tools"] = [t.name for t in tools]
512
+
509
513
  try:
510
514
  rendered_prompt = renderer.render(modified_content, full_context)
511
515
  except Exception as e:
512
516
  raise RuntimeError(f"Template rendering failed: {e}") from e
513
517
 
514
- base_instructions = get_default_instructions()
515
518
  agent_instructions = getattr(agent_config, "instructions", "")
516
519
 
517
520
  # Render agent instructions as Jinja2 template
@@ -521,9 +524,7 @@ class AgentPreparer:
521
524
  except Exception as e:
522
525
  raise RuntimeError(f"Failed to render agent instructions: {e}") from e
523
526
 
524
- combined_instructions = _combine_instructions(base_instructions, agent_instructions)
525
-
526
- tools = self._expand_tools(agent_config, full_context, interactive_mode)
527
+ combined_instructions = _combine_instructions(agent_instructions)
527
528
 
528
529
  skill_load = self._load_skills(agent_config, prompt, full_context, _skill_manager, event_bus)
529
530
  skills, expiring_skills = skill_load.skills, skill_load.expiring
@@ -31,7 +31,6 @@ from tsugite.agent_runner.runner import ( # noqa: F401
31
31
  _execute_agent_with_prompt,
32
32
  execute_prefetch,
33
33
  execute_tool_directives,
34
- get_default_instructions,
35
34
  preview_multistep_agent,
36
35
  run_agent,
37
36
  run_agent_async,
@@ -47,7 +46,6 @@ __all__ = [
47
46
  "execute_exec_directives",
48
47
  "run_python_block",
49
48
  "ExecBlockResult",
50
- "get_default_instructions",
51
49
  "AgentExecutionResult",
52
50
  "StepMetrics",
53
51
  "display_step_metrics",
@@ -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
@@ -155,31 +155,6 @@ def _setup_event_context(event_bus: Optional["EventBus"]) -> None:
155
155
  set_ui_context(event_bus=event_bus)
156
156
 
157
157
 
158
- def get_default_instructions() -> str:
159
- """Get minimal default instructions. Detailed guidance comes from skills.
160
-
161
- Returns:
162
- Default instructions for code execution mode
163
- """
164
- base = "You accomplish tasks by writing Python code.\n\n"
165
-
166
- output = (
167
- "## Output\n\n"
168
- "- `print(x)` - See in next turn (internal)\n"
169
- "- `send_message(msg)` - Show user progress\n"
170
- "- `final_answer(msg)` - Final response (stops execution)\n\n"
171
- )
172
-
173
- rules = (
174
- "## Rules\n\n"
175
- "1. Always respond with Python code blocks\n"
176
- "2. Call `final_answer()` when done\n"
177
- "3. Variables persist between turns\n"
178
- )
179
-
180
- return base + output + rules
181
-
182
-
183
158
  def _render_args(value: Any, renderer: AgentRenderer, context: Dict[str, Any]) -> Any:
184
159
  """Recursively render Jinja in string leaves of a tool args structure.
185
160
 
@@ -320,32 +295,17 @@ def execute_tool_directives(
320
295
  return modified_content, new_context
321
296
 
322
297
 
323
- def _extract_reasoning_content(agent: TsugiteAgent, custom_logger: Optional[Any] = None) -> None:
324
- """Extract and display reasoning content from TsugiteAgent memory.
325
-
326
- For models like Claude/Deepseek that expose reasoning_content, displays the actual reasoning.
327
-
328
- Args:
329
- agent: The TsugiteAgent instance that just completed execution
330
- custom_logger: Custom logger to display reasoning content
331
- """
332
- if not hasattr(agent, "memory") or not agent.memory.reasoning_history:
333
- return
298
+ # Daemon chat sources with a person at the other end; any other source counts as background.
299
+ INTERACTIVE_SOURCES = frozenset({"http", "discord"})
334
300
 
335
- # Display each reasoning entry
336
- for reasoning_content in agent.memory.reasoning_history:
337
- if reasoning_content and custom_logger:
338
- # Check if custom_logger has ui_handler (custom UI mode)
339
- ui_handler = get_ui_handler(custom_logger)
340
- if ui_handler:
341
- from tsugite.events import EventBus, ReasoningContentEvent
342
301
 
343
- event_bus = EventBus()
344
- event_bus.subscribe(ui_handler.handle_event)
345
- event_bus.emit(ReasoningContentEvent(content=reasoning_content, step=None))
302
+ def _run_is_interactive(channel_metadata: Optional[Dict[str, Any]]) -> bool:
303
+ """Whether a person is present: the session source in the daemon, the TTY otherwise."""
304
+ source = (channel_metadata or {}).get("source")
305
+ return source in INTERACTIVE_SOURCES if source else is_interactive()
346
306
 
347
307
 
348
- def _make_pre_llm_call_callback(hooks_dir: Path, agent_name: str):
308
+ def _make_pre_llm_call_callback(hooks_dir: Path, agent_name: str, interactive: bool):
349
309
  """Build an async callback that fires pre_llm_call hooks to mutate outgoing
350
310
  messages in place. Returns None when no such hooks exist (keeps the hot path clean)."""
351
311
  from tsugite.hooks import fire_hooks, load_hooks_config
@@ -359,7 +319,7 @@ def _make_pre_llm_call_callback(hooks_dir: Path, agent_name: str):
359
319
  hooks_dir,
360
320
  "pre_llm_call",
361
321
  {"messages": messages, "model": model, "agent": agent_name},
362
- interactive=is_interactive(),
322
+ interactive=interactive,
363
323
  )
364
324
 
365
325
  return _callback
@@ -401,6 +361,7 @@ async def _execute_agent_with_prompt(
401
361
  user_input_for_history: Optional[str] = None,
402
362
  channel_metadata: Optional[Dict[str, Any]] = None,
403
363
  usage_sink: Optional[Dict[str, Any]] = None,
364
+ requires_action: bool = False,
404
365
  ) -> str | AgentExecutionResult:
405
366
  """Execute agent with a prepared agent.
406
367
 
@@ -409,6 +370,9 @@ async def _execute_agent_with_prompt(
409
370
  `usage_sink`, when given, receives this call's token and cost counters. It is
410
371
  how a multi-step run collects accounting from steps that return plain
411
372
  strings.
373
+
374
+ `requires_action` makes a run that ends without executing any code raise, so a
375
+ step whose result feeds later steps cannot pass on a prose answer.
412
376
  """
413
377
  if exec_options is None:
414
378
  exec_options = ExecutionOptions()
@@ -445,8 +409,9 @@ async def _execute_agent_with_prompt(
445
409
  hooks_dir = path_context.effective_cwd
446
410
  else:
447
411
  hooks_dir = Path.cwd()
448
- setup_hook_handler(hooks_dir, event_bus, interactive=is_interactive())
449
- pre_llm_call_cb = _make_pre_llm_call_callback(hooks_dir, agent_config.name)
412
+ interactive = _run_is_interactive(channel_metadata)
413
+ setup_hook_handler(hooks_dir, event_bus, interactive=interactive)
414
+ pre_llm_call_cb = _make_pre_llm_call_callback(hooks_dir, agent_config.name, interactive)
450
415
 
451
416
  # Start with tools from prepared agent
452
417
  tools = list(prepared.tools) # Make a copy
@@ -615,7 +580,7 @@ async def _execute_agent_with_prompt(
615
580
  hook_vars=hook_vars,
616
581
  storage=session_storage,
617
582
  pre_llm_call=pre_llm_call_cb,
618
- expects_action=(channel_metadata or {}).get("source") == "scheduler",
583
+ expects_action=requires_action or (channel_metadata or {}).get("source") == "scheduler",
619
584
  )
620
585
  # Set event_bus in context so tools can access it during execution
621
586
  _setup_event_context(event_bus)
@@ -633,9 +598,6 @@ async def _execute_agent_with_prompt(
633
598
  stream=exec_options.stream,
634
599
  )
635
600
 
636
- # Extract and display reasoning content if present
637
- _extract_reasoning_content(agent, custom_logger)
638
-
639
601
  if usage_sink is not None:
640
602
  usage_sink.update(
641
603
  token_count=agent.total_tokens,
@@ -666,10 +628,18 @@ async def _execute_agent_with_prompt(
666
628
  except Exception as e:
667
629
  logger.debug("Failed to record usage: %s", e)
668
630
 
631
+ if requires_action and agent.ended_no_action:
632
+ steps = result.steps if isinstance(result, AgentResult) and result.steps else []
633
+ raise AgentExecutionError(
634
+ NO_ACTION_MESSAGE,
635
+ execution_steps=steps,
636
+ token_usage=agent.total_tokens,
637
+ cost=agent.reported_cost,
638
+ step_count=len(steps),
639
+ )
640
+
669
641
  # Return appropriate format
670
642
  if exec_options.return_token_usage:
671
- from tsugite.core.agent import AgentResult
672
-
673
643
  if isinstance(result, AgentResult):
674
644
  step_count = len(result.steps) if result.steps else 0
675
645
  steps_list = result.steps if result.steps else []
@@ -712,8 +682,6 @@ async def _execute_agent_with_prompt(
712
682
  session_id=session_storage.session_id if session_storage else None,
713
683
  )
714
684
  else:
715
- from tsugite.core.agent import AgentResult
716
-
717
685
  if isinstance(result, AgentResult):
718
686
  return str(result.output)
719
687
  else:
@@ -829,15 +797,17 @@ def _domain_within_ceiling(desired_pattern: str, ceiling: list) -> bool:
829
797
 
830
798
 
831
799
  def _resolve_workspace_dir(workspace: Optional[Any], path_context: Optional[Any]) -> Optional[Path]:
832
- """Resolve the executor's workspace directory.
800
+ """Resolve the directory the executor runs code in.
833
801
 
834
- Prefer an explicit Workspace object (CLI workspace runs); otherwise fall back
835
- to the PathContext's workspace_dir. The daemon passes only a path_context (no
836
- Workspace), so without this fallback the sandbox would bind/chdir the daemon's
837
- CWD instead of the agent's workspace (or job worktree).
802
+ Checks: an explicit Workspace -> the directory bound with set_workspace_dir
803
+ (the daemon binds the session's working directory) -> path_context.workspace_dir.
838
804
  """
805
+ from tsugite.cli.helpers import get_workspace_dir
806
+
839
807
  if workspace:
840
808
  return workspace.path
809
+ if bound := get_workspace_dir():
810
+ return bound
841
811
  if path_context and getattr(path_context, "workspace_dir", None):
842
812
  return path_context.workspace_dir
843
813
  return None
@@ -872,6 +842,7 @@ class _RunSetup:
872
842
  resume_after_compaction: bool = False
873
843
  resume_cost_baseline: float = 0.0
874
844
  conversation_id: Optional[str] = None
845
+ requires_action: bool = False
875
846
 
876
847
 
877
848
  def _prepare_step(agent: Any, prompt: str, context: Dict[str, Any], setup: "_RunSetup") -> "PreparedAgent":
@@ -923,7 +894,7 @@ async def _run_unit(
923
894
  "rendered_prompt": prepared.rendered_prompt[:500] if prepared.rendered_prompt else "",
924
895
  "tools": [t.name for t in prepared.tools] if prepared.tools else [],
925
896
  },
926
- interactive=is_interactive(),
897
+ interactive=_run_is_interactive(setup.channel_metadata),
927
898
  on_status=setup.on_status,
928
899
  on_result=setup.on_hook_result,
929
900
  )
@@ -955,6 +926,7 @@ async def _run_unit(
955
926
  model_kwargs=model_kwargs,
956
927
  injectable_vars=injectable_vars,
957
928
  usage_sink=usage_sink,
929
+ requires_action=setup.requires_action,
958
930
  )
959
931
 
960
932
  try:
@@ -1128,10 +1100,11 @@ async def run_agent_async(
1128
1100
  on_status = None
1129
1101
  on_hook_result = None
1130
1102
 
1103
+ interactive = _run_is_interactive(channel_metadata)
1131
1104
  hook_vars = await fire_pre_message_hooks(
1132
1105
  hooks_dir,
1133
1106
  {"message": hook_message, "agent_name": agent_path.stem},
1134
- interactive=is_interactive(),
1107
+ interactive=interactive,
1135
1108
  on_status=on_status,
1136
1109
  on_result=on_hook_result,
1137
1110
  )
@@ -1145,7 +1118,7 @@ async def run_agent_async(
1145
1118
  hooks_dir,
1146
1119
  "pre_context_build",
1147
1120
  {"message": hook_message, "agent_name": agent_path.stem, **context, "blocks": context_blocks},
1148
- interactive=is_interactive(),
1121
+ interactive=interactive,
1149
1122
  on_status=on_status,
1150
1123
  on_result=on_hook_result,
1151
1124
  )
@@ -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()
@@ -31,23 +31,28 @@ _LABEL_TO_DECISION = {
31
31
 
32
32
 
33
33
  def request_approval(
34
- prompt: str, *, allow_always: bool = False, allow_never: bool = False, detail: Optional[str] = None
34
+ prompt: str,
35
+ *,
36
+ allow_always: bool = False,
37
+ allow_never: bool = False,
38
+ extra_options: Optional[dict[str, str]] = None,
39
+ detail: Optional[str] = None,
35
40
  ) -> str:
36
41
  """Ask the user to approve an action, returning an :data:`ApprovalDecision`.
37
42
 
38
- Builds ``Approve``/``Deny`` options (plus ``Always allow`` when ``allow_always``
39
- and ``Always deny`` when ``allow_never``) and routes them through the active
40
- interaction backend as an ``"approval"`` question, then maps the chosen label
41
- back to a decision.
43
+ Offers ``Approve``/``Deny`` plus the optional choices below as an
44
+ ``"approval"`` question on the active interaction backend, and maps the
45
+ chosen label back to a decision.
42
46
 
43
47
  Args:
44
48
  prompt: The action to approve, phrased as a question.
45
49
  allow_always: Offer an "Always allow" option (a persistent allow).
46
50
  allow_never: Offer an "Always deny" option (a persistent deny).
51
+ extra_options: Caller-specific choices, as ``{label: decision}``.
47
52
  detail: Extra context appended below the prompt for the user to read.
48
53
 
49
54
  Returns:
50
- One of ``"approve"``, ``"deny"``, ``"always"``, or ``"never"``.
55
+ One of ``"approve"``, ``"deny"``, ``"always"``, ``"never"``, or a value of ``extra_options``.
51
56
 
52
57
  Fail-closed: returns ``"deny"`` WITHOUT prompting when there is no backend or
53
58
  the backend is non-interactive, and for any unrecognized reply.
@@ -61,7 +66,9 @@ def request_approval(
61
66
  options.append(ALWAYS_LABEL)
62
67
  if allow_never:
63
68
  options.append(NEVER_LABEL)
69
+ decisions = _LABEL_TO_DECISION | (extra_options or {})
70
+ options.extend(extra_options or ())
64
71
 
65
72
  question = f"{prompt}\n\n{detail}" if detail else prompt
66
73
  answer = backend.ask_user(question, "approval", options)
67
- return _LABEL_TO_DECISION.get(answer, "deny")
74
+ return decisions.get(answer, "deny")
@@ -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
@@ -65,7 +73,7 @@ instructions: |
65
73
  - Break down complex tasks into clear steps
66
74
  - Ask clarifying questions when the task is ambiguous
67
75
  - Write Python code to accomplish tasks
68
- - Call return_value(result) when you've completed the task
76
+ - When you've completed the task, reply in plain text, or call return_value() for structured output
69
77
  - If you intend to call multiple tools and there are no dependencies between the calls, make all independent calls in a single code block
70
78
  - Never guess about code you have not read. Use read_file or list_files to investigate before answering questions about code
71
79
 
@@ -227,13 +235,6 @@ You have running tmux sessions that you can interact with using tmux tools (tmux
227
235
  {% endfor %}
228
236
  {% endif %}
229
237
 
230
- {% if "run" in tools and "tmux_create" in tools %}
231
- ## Shell vs Tmux
232
-
233
- - Use `run()` for commands that produce output and exit: `run("ls -la")`, `run("git status")`, `run("python script.py")`
234
- - Use tmux tools for interactive programs (htop, k9s, psql, python REPL), long-running processes you want to monitor, or when you need a persistent shell session where state carries across commands
235
- {% endif %}
236
-
237
238
  {% if subagent_instructions is defined and subagent_instructions %}
238
239
  {{ subagent_instructions }}
239
240
  {% endif %}
@@ -291,11 +292,11 @@ Load these skills for specialized knowledge:
291
292
  - **{{ skill.name }}** - {{ skill.description }}
292
293
  {% endfor %}
293
294
 
294
- Call `load_skill("skill_name")` and stop. The skill content appears next turn as `<loaded_skill>`. Then use what you learned.
295
+ Call `load_skill("skill_name")` and stop. The skill content appears next turn as `<skill_content>`. Then use what you learned.
295
296
 
296
297
  Do not act in the same turn you load a skill. Skip loading if you already know how to do the task.
297
298
 
298
- Skills may show bash commands. Translate to Python: `shell.run("kubectl get pods")`
299
+ Skills may show bash commands. Translate to Python: `run("kubectl get pods")`
299
300
  </available_skills>
300
301
  {% endif %}
301
302
 
@@ -76,7 +76,9 @@ is `true` only if every `ac_results[i].pass` is `true`.
76
76
 
77
77
  `reason` opens with the basis for the verdict, then one sentence:
78
78
 
79
- - `inspected:` - you read the artifact, file, or Output text yourself.
79
+ - `inspected:` - you read the artifact or file yourself with a tool call.
80
+ - `read Output:` - the deliverable is inline text and you judged the `## Output`
81
+ text itself, with no tool call.
80
82
  - `ran <command>:` - you executed the command and judged its exit status.
81
83
  - `accepted worker claim:` - you could not check it, so the verdict rests on
82
84
  the worker's report.
@@ -365,7 +365,7 @@ If available via custom tools configuration, consider using:
365
365
  tools:
366
366
  - name: ast_search
367
367
  description: Search code using AST patterns
368
- command: "ast-grep -p '{pattern}' {path}"
368
+ command: "ast-grep -p {pattern} {path}"
369
369
  parameters:
370
370
  pattern: {required: true}
371
371
  path: "."
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: response-patterns
3
- description: How to respond effectively - when to use print/send_message/final_answer/ask_user
3
+ description: How to respond effectively - when to use print/send_message/return_value/ask_user
4
4
  ---
5
5
 
6
6
  # Response Patterns
@@ -11,21 +11,18 @@ description: How to respond effectively - when to use print/send_message/final_a
11
11
  |----------|----------|-----------|---------|
12
12
  | `print(x)` | You (next turn) | Continues | Intermediate data |
13
13
  | `send_message(msg)` | User | Continues | Progress updates |
14
- | `final_answer(msg)` | User | **Stops** | Final response |
14
+ | `return_value(value)` | User | **Stops** | Structured final result |
15
15
  | `ask_user(q)` | User | **Blocks** | Get user input |
16
16
 
17
17
  ## Simple Responses
18
18
 
19
- No functions needed? One line:
19
+ No functions needed? Reply in plain text:
20
20
 
21
- ```python-exec
22
- final_answer("Hello! What can I help you with?")
23
- ```
21
+ - "Thanks" replies `You're welcome!`
22
+ - "What's 2+2?" replies `4`
23
+ - "What can you do?" replies `I can read/write files, run commands, and help with tasks.`
24
24
 
25
- More examples:
26
- - "Thanks" → `final_answer("You're welcome!")`
27
- - "What's 2+2?" → `final_answer("4")`
28
- - "What can you do?" → `final_answer("I can read/write files, run commands, search code, and help with tasks.")`
25
+ Call `return_value(value)` only when the caller needs a structured result.
29
26
 
30
27
  ## Progress Updates
31
28
 
@@ -33,14 +30,14 @@ For longer tasks, keep user informed:
33
30
 
34
31
  ```python-exec
35
32
  send_message("Searching codebase...")
36
- results = search_code("def main")
33
+ results = run("grep -rn 'def main' .")
37
34
  print(results) # You'll see this next turn
38
35
  ```
39
36
 
40
37
  ```python-exec
41
- send_message(f"Found {len(results)} matches. Analyzing...")
38
+ send_message(f"Found {len(results.splitlines())} matches. Analyzing...")
42
39
  # ... more work ...
43
- final_answer("Here's what I found: ...")
40
+ return_value("Here's what I found: ...")
44
41
  ```
45
42
 
46
43
  ## Asking the User
@@ -87,7 +84,7 @@ Use `ask_user` when:
87
84
  - Ask unnecessary questions when a reasonable default exists
88
85
 
89
86
  ✅ Do:
90
- - `final_answer()` immediately for simple responses
87
+ - Plain text immediately for simple responses
91
88
  - `send_message()` for progress on tasks > 5 seconds
92
89
  - `ask_user()` when you genuinely need user input
93
90
  - `print()` only for data YOU need to see
@@ -500,8 +500,8 @@ class PathContext:
500
500
  effective_cwd: Path
501
501
 
502
502
 
503
- # Mirrors PathContext.workspace_dir for call sites (fs tools, shell, subprocesses)
504
- # that don't receive PathContext explicitly. Task-local so concurrent agent runs
503
+ # The directory the running agent's code and fs tools work in, for call sites
504
+ # (fs tools, shell, subprocesses, the executor) that don't receive PathContext explicitly. Task-local so concurrent agent runs
505
505
  # don't share state.
506
506
  _workspace_dir_cv: contextvars.ContextVar[Optional[Path]] = contextvars.ContextVar("workspace_dir", default=None)
507
507
 
@@ -257,7 +257,6 @@ def run(
257
257
  ),
258
258
  no_workspace: bool = typer.Option(False, "--no-workspace", help="Disable workspace (ignore default workspace)"),
259
259
  new_session: bool = typer.Option(False, "--new-session", help="Start fresh session (ignore workspace session)"),
260
- join_daemon: bool = typer.Option(False, "--daemon", "-d", help="Join the daemon's most recent session"),
261
260
  sandbox: bool = typer.Option(False, "--sandbox", help="Run agent code in bubblewrap sandbox"),
262
261
  no_sandbox: bool = typer.Option(False, "--no-sandbox", help="Disable sandbox (overrides config)"),
263
262
  allow_domain: Optional[List[str]] = typer.Option(
@@ -282,7 +281,6 @@ def run(
282
281
  tsu run --continue "prompt" # Continue latest conversation (auto-detect agent)
283
282
  tsu run +assistant --continue "prompt" # Continue latest with specific agent
284
283
  tsu run --continue --conversation-id CONV_ID "prompt" # Continue specific conversation
285
- tsu run --daemon "follow up message" # Join the daemon's most recent session
286
284
  """
287
285
  from tsugite.agent_runner import get_agent_info, run_agent
288
286
  from tsugite.console import get_stderr_console
@@ -381,7 +379,7 @@ def run(
381
379
  # Resolve UI mode to update ui_opts
382
380
  ui_opts = _resolve_ui_mode(ui, ui_opts, stderr_console)
383
381
 
384
- # Handle subagent mode and daemon mode (both need os)
382
+ # Handle subagent mode
385
383
  import os
386
384
 
387
385
  if subagent_mode:
@@ -393,54 +391,6 @@ def run(
393
391
  history_opts.enabled = False
394
392
  os.environ["TSUGITE_SUBAGENT_MODE"] = "1"
395
393
 
396
- daemon_metadata = None
397
- if join_daemon:
398
- from tsugite.agent_runner.history_integration import get_latest_conversation
399
-
400
- try:
401
- from tsugite_daemon.config import load_daemon_config
402
-
403
- # Probe: the daemon battery has to be installed and configured.
404
- load_daemon_config()
405
- except ModuleNotFoundError as e:
406
- stderr_console.print(
407
- "[red]The daemon battery is not installed.[/red] Install it with: pip install tsugite-cli[daemon]"
408
- )
409
- raise typer.Exit(1) from e
410
- except ValueError as e:
411
- stderr_console.print(f"[red]Daemon config not found: {e}[/red]")
412
- stderr_console.print("[dim]Run 'tsugite daemon' to start daemon first[/dim]")
413
- raise typer.Exit(1)
414
-
415
- user_id = os.environ.get("USER", "cli-user")
416
-
417
- latest_conv_id = get_latest_conversation()
418
- if latest_conv_id:
419
- stderr_console.print(f"[cyan]Joining daemon session: {latest_conv_id}[/cyan]")
420
- history_opts.continue_id = latest_conv_id
421
- else:
422
- stderr_console.print("[yellow]No active daemon session found[/yellow]")
423
- stderr_console.print("[dim]Creating new daemon-managed session...[/dim]")
424
-
425
- # Build CLI metadata for history
426
- from datetime import datetime, timezone
427
-
428
- daemon_metadata = {
429
- "source": "cli",
430
- "channel_id": None,
431
- "user_id": user_id,
432
- "reply_to": "cli",
433
- "is_daemon_managed": True,
434
- "timestamp": datetime.now(timezone.utc).isoformat(),
435
- }
436
-
437
- all_attachments = list(attachment) if attachment else []
438
- attach_opts = AttachmentOptions(
439
- sources=all_attachments,
440
- refresh_cache=refresh_cache,
441
- auto_context=auto_context,
442
- )
443
-
444
394
  # Handle conversation continuation - check before parsing args
445
395
  if continue_conversation and not history_opts.continue_id:
446
396
  from tsugite.agent_runner.history_integration import get_latest_conversation
@@ -641,7 +591,6 @@ def run(
641
591
  continue_conversation_id=target_session,
642
592
  system_prompt=system_prompt,
643
593
  attachments=attachments,
644
- channel_metadata=daemon_metadata,
645
594
  status=status,
646
595
  error_message=error_message,
647
596
  )