gemcode 0.4.15__tar.gz → 0.4.17__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. {gemcode-0.4.15/src/gemcode.egg-info → gemcode-0.4.17}/PKG-INFO +7 -1
  2. {gemcode-0.4.15 → gemcode-0.4.17}/README.md +6 -0
  3. {gemcode-0.4.15 → gemcode-0.4.17}/pyproject.toml +1 -1
  4. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/agent_habits.py +26 -6
  5. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/agent_mesh.py +106 -1
  6. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/repl_commands.py +1 -0
  7. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/repl_slash.py +60 -0
  8. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/__init__.py +28 -0
  9. {gemcode-0.4.15 → gemcode-0.4.17/src/gemcode.egg-info}/PKG-INFO +7 -1
  10. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_agent_mesh.py +26 -0
  11. {gemcode-0.4.15 → gemcode-0.4.17}/LICENSE +0 -0
  12. {gemcode-0.4.15 → gemcode-0.4.17}/MANIFEST.in +0 -0
  13. {gemcode-0.4.15 → gemcode-0.4.17}/setup.cfg +0 -0
  14. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/__init__.py +0 -0
  15. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/__main__.py +0 -0
  16. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/a2a_bridge.py +0 -0
  17. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/agent.py +0 -0
  18. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/agent_intelligence.py +0 -0
  19. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/agent_triggers.py +0 -0
  20. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/audit.py +0 -0
  21. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/autocompact.py +0 -0
  22. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/automations.py +0 -0
  23. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/autotune.py +0 -0
  24. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/callbacks.py +0 -0
  25. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/capability_routing.py +0 -0
  26. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/checkpoints.py +0 -0
  27. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/cli.py +0 -0
  28. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/codebase_awareness.py +0 -0
  29. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/compaction.py +0 -0
  30. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/computer_use/__init__.py +0 -0
  31. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/computer_use/browser_computer.py +0 -0
  32. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/config.py +0 -0
  33. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/context_budget.py +0 -0
  34. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/context_warning.py +0 -0
  35. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/credentials.py +0 -0
  36. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/curated_memory.py +0 -0
  37. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/delegation_learning.py +0 -0
  38. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/dynamic_policy.py +0 -0
  39. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/evals/harness.py +0 -0
  40. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/event_bus.py +0 -0
  41. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/fleet_reports.py +0 -0
  42. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/hitl_session.py +0 -0
  43. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/hooks.py +0 -0
  44. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/ide_protocol.py +0 -0
  45. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/ide_stdio.py +0 -0
  46. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/intent_classifier.py +0 -0
  47. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/interactions.py +0 -0
  48. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/invoke.py +0 -0
  49. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/kaira_client.py +0 -0
  50. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/kaira_daemon.py +0 -0
  51. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/kaira_ipc.py +0 -0
  52. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/kaira_job_store.py +0 -0
  53. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/learning.py +0 -0
  54. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/limits.py +0 -0
  55. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/live_audio_engine.py +0 -0
  56. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/logging_config.py +0 -0
  57. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/mcp_loader.py +0 -0
  58. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/memory/__init__.py +0 -0
  59. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/memory/embedding_memory_service.py +0 -0
  60. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/memory/file_memory_service.py +0 -0
  61. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/modality_tools.py +0 -0
  62. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/model_errors.py +0 -0
  63. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/model_routing.py +0 -0
  64. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/multimodal_input.py +0 -0
  65. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/openapi_loader.py +0 -0
  66. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/org.py +0 -0
  67. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/output_styles.py +0 -0
  68. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/paths.py +0 -0
  69. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/permissions.py +0 -0
  70. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/plugins/__init__.py +0 -0
  71. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  72. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  73. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/policy_profile.py +0 -0
  74. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/pricing.py +0 -0
  75. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/prompt_suggestions.py +0 -0
  76. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query/__init__.py +0 -0
  77. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query/config.py +0 -0
  78. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query/deps.py +0 -0
  79. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query/engine.py +0 -0
  80. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query/stop_hooks.py +0 -0
  81. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query/token_budget.py +0 -0
  82. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query/transitions.py +0 -0
  83. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/query_sanitizer.py +0 -0
  84. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/refine.py +0 -0
  85. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/review_agent.py +0 -0
  86. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/rules.py +0 -0
  87. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/self_healing.py +0 -0
  88. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/session_runtime.py +0 -0
  89. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/session_store.py +0 -0
  90. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/session_summariser.py +0 -0
  91. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/skills.py +0 -0
  92. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/slash_commands.py +0 -0
  93. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/thinking.py +0 -0
  94. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tool_prompt_manifest.py +0 -0
  95. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tool_registry.py +0 -0
  96. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tool_result_store.py +0 -0
  97. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tool_synthesis.py +0 -0
  98. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/automations_tools.py +0 -0
  99. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/bash.py +0 -0
  100. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/browser.py +0 -0
  101. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/compress_memory.py +0 -0
  102. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/curated_memory.py +0 -0
  103. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/edit.py +0 -0
  104. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/filesystem.py +0 -0
  105. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/notebook.py +0 -0
  106. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/notes.py +0 -0
  107. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/org_tools.py +0 -0
  108. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/repo_map.py +0 -0
  109. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/search.py +0 -0
  110. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/shell.py +0 -0
  111. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/shell_gate.py +0 -0
  112. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/skills.py +0 -0
  113. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/subtask.py +0 -0
  114. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/tasks.py +0 -0
  115. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/think.py +0 -0
  116. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/todo.py +0 -0
  117. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/user_choice.py +0 -0
  118. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/veomem_tools.py +0 -0
  119. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/web.py +0 -0
  120. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools/web_search.py +0 -0
  121. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tools_inspector.py +0 -0
  122. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/trust.py +0 -0
  123. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tui/input_handler.py +0 -0
  124. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tui/scrollback.py +0 -0
  125. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tui/spinner.py +0 -0
  126. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tui/welcome_banner.py +0 -0
  127. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/tui/welcome_rich.py +0 -0
  128. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/veomem_bridge.py +0 -0
  129. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/version.py +0 -0
  130. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/vertex.py +0 -0
  131. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/wal.py +0 -0
  132. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/web/__init__.py +0 -0
  133. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/web/sse_adapter.py +0 -0
  134. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/web/terminal_repl.py +0 -0
  135. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/web/web_sse_compat.py +0 -0
  136. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode/workspace_hints.py +0 -0
  137. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode.egg-info/SOURCES.txt +0 -0
  138. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode.egg-info/dependency_links.txt +0 -0
  139. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode.egg-info/entry_points.txt +0 -0
  140. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode.egg-info/requires.txt +0 -0
  141. {gemcode-0.4.15 → gemcode-0.4.17}/src/gemcode.egg-info/top_level.txt +0 -0
  142. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_add_dir.py +0 -0
  143. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_agent_habits.py +0 -0
  144. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_agent_instruction.py +0 -0
  145. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_autocompact.py +0 -0
  146. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_automations.py +0 -0
  147. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_capability_routing.py +0 -0
  148. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_checkpoint_diff_command.py +0 -0
  149. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_cli_init.py +0 -0
  150. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_compress_memory_tool.py +0 -0
  151. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_computer_use_permissions.py +0 -0
  152. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_context_budget.py +0 -0
  153. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_context_warning.py +0 -0
  154. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_credentials.py +0 -0
  155. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_eval_harness_layout.py +0 -0
  156. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_event_bus.py +0 -0
  157. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_fleet_reports.py +0 -0
  158. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_ide_stdio_attachments.py +0 -0
  159. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_interactive_permission_ask.py +0 -0
  160. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_kaira_ipc_paths.py +0 -0
  161. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_kaira_scheduler.py +0 -0
  162. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_modality_tools.py +0 -0
  163. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_model_error_retry.py +0 -0
  164. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_model_errors.py +0 -0
  165. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_model_routing.py +0 -0
  166. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_multimodal_input.py +0 -0
  167. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_output_styles_and_rules.py +0 -0
  168. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_paths.py +0 -0
  169. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_permissions.py +0 -0
  170. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_prompt_suggestions.py +0 -0
  171. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_repl_commands.py +0 -0
  172. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_repl_slash.py +0 -0
  173. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_session_runtime_cache.py +0 -0
  174. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_skills.py +0 -0
  175. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_slash_commands.py +0 -0
  176. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_slash_completion_registry.py +0 -0
  177. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_thinking_config.py +0 -0
  178. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_token_budget.py +0 -0
  179. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_tool_context_circulation.py +0 -0
  180. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_tools.py +0 -0
  181. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_tools_inspector.py +0 -0
  182. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_web_sse_adapter.py +0 -0
  183. {gemcode-0.4.15 → gemcode-0.4.17}/tests/test_workspace_hints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.4.15
3
+ Version: 0.4.17
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -292,6 +292,12 @@ Every GemCode run is anchored to a project root. This determines:
292
292
  - what instruction files are loaded
293
293
  - which repo-local assets are active
294
294
 
295
+ ### Multi-agent habits, skills, and mesh runtime
296
+
297
+ - **Habits** for the whole fleet live in **one** file: `.gemcode/habits.json` at the **fleet root**. Each row names which **org member** runs (`agent` field), on what schedule—different members can have different prompts and intervals at once.
298
+ - **Skills** and **per-turn runtime** are **per member**: org `skill_name`, member skill under `.gemcode/skills/`, and optional **agent workspace** `.gemcode/agents/<id>-<slug>/` (own session DB, memory, local skills). Mesh jobs use that context automatically.
299
+ - **Stopping automation:** removing a habit only stops *new* work. To cancel **queued** or **running** mesh jobs, use **`/mesh halt`** or the **`mesh_halt`** tool (see [`orchestration.md`](../docs/orchestration.md#stopping-background-work-habits-removed-but-jobs-still-finishing)).
300
+
295
301
  ### `.gemcode/`
296
302
  GemCode stores project-local state under `.gemcode/`, including:
297
303
  - sessions
@@ -99,6 +99,12 @@ Every GemCode run is anchored to a project root. This determines:
99
99
  - what instruction files are loaded
100
100
  - which repo-local assets are active
101
101
 
102
+ ### Multi-agent habits, skills, and mesh runtime
103
+
104
+ - **Habits** for the whole fleet live in **one** file: `.gemcode/habits.json` at the **fleet root**. Each row names which **org member** runs (`agent` field), on what schedule—different members can have different prompts and intervals at once.
105
+ - **Skills** and **per-turn runtime** are **per member**: org `skill_name`, member skill under `.gemcode/skills/`, and optional **agent workspace** `.gemcode/agents/<id>-<slug>/` (own session DB, memory, local skills). Mesh jobs use that context automatically.
106
+ - **Stopping automation:** removing a habit only stops *new* work. To cancel **queued** or **running** mesh jobs, use **`/mesh halt`** or the **`mesh_halt`** tool (see [`orchestration.md`](../docs/orchestration.md#stopping-background-work-habits-removed-but-jobs-still-finishing)).
107
+
102
108
  ### `.gemcode/`
103
109
  GemCode stores project-local state under `.gemcode/`, including:
104
110
  - sessions
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.4.15"
7
+ version = "0.4.17"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -10,9 +10,18 @@ Examples:
10
10
  - "Nightly at 2am, run a full security audit"
11
11
  - "Every 5 minutes, check for new issues in the tracker"
12
12
 
13
- Habits are stored in `.gemcode/habits.json` and can be managed via tools
14
- or the REPL. Each habit specifies:
15
- - Which agent runs it (org member)
13
+ Habits are stored in **one** fleet file, `.gemcode/habits.json` (next to `org.json`),
14
+ and can be managed via tools or the REPL. That file holds **many** rows; each row is
15
+ one schedule and names **which org member** runs it via the ``agent`` field—so
16
+ different agents can have different prompts, intervals, and enablement at the same time.
17
+
18
+ **Not** stored in habits: each member’s **skills** (org `skill_name`, workspace-local
19
+ skills under `.gemcode/agents/<id>-<slug>/.gemcode/skills/`) and **runtime/session**
20
+ (SQLite session, memory, routing). Those come from org membership and the mesh worker
21
+ context when the habit fires.
22
+
23
+ Each habit specifies:
24
+ - Which agent runs it (org member name)
16
25
  - What they do (prompt)
17
26
  - When they do it (interval, cron, or daily)
18
27
  - Whether they're enabled
@@ -296,7 +305,7 @@ def make_habits_tools(cfg: GemCodeConfig) -> list:
296
305
  """Build tools for managing agent habits."""
297
306
 
298
307
  def habits_list() -> dict:
299
- """List all configured agent habits (scheduled recurring tasks)."""
308
+ """List all fleet habits. Each entry targets one org member (`agent`); members differ by skills/workspace when the job runs."""
300
309
  habits = load_habits(cfg.project_root)
301
310
  return {
302
311
  "ok": True,
@@ -330,9 +339,14 @@ def make_habits_tools(cfg: GemCodeConfig) -> list:
330
339
  or send any message; the TUI also prints a throttled hint when mesh jobs finish
331
340
  (GEMCODE_FLEET_TUI_NOTIFY).
332
341
 
342
+ Removing a habit only stops **future** enqueues. Jobs already queued or running (including
343
+ verifier/trigger follow-ups) keep going until they finish unless you call **`mesh_halt`**
344
+ or **`/mesh halt`**.
345
+
333
346
  Args:
334
347
  name: Unique name for this habit (e.g., "test-watch", "nightly-audit").
335
- agent: Org member name to run this (e.g., "kaira", "verifier").
348
+ agent: Org **member** name to run this (e.g., "kaira", "verifier", "tcs_analyst").
349
+ That member’s own skills, workspace, and ADK session apply when the habit runs—not the manager’s.
336
350
  Use "self" or "main" to run as the main GemCode agent.
337
351
  prompt: What the agent should do each time it wakes up.
338
352
  every_minutes: Run every N minutes (e.g., 30 = every half hour).
@@ -391,6 +405,11 @@ def make_habits_tools(cfg: GemCodeConfig) -> list:
391
405
  save_habits(cfg.project_root, habits)
392
406
  return {"ok": True, "removed": before - len(habits)}
393
407
 
408
+ def habits_clear_all() -> dict:
409
+ """Remove every habit from `.gemcode/habits.json` (nothing will re-enqueue until you add new habits)."""
410
+ save_habits(cfg.project_root, [])
411
+ return {"ok": True, "cleared": True}
412
+
394
413
  def habits_pause(name: str) -> dict:
395
414
  """Pause a habit (stop it from firing until resumed)."""
396
415
  habits = load_habits(cfg.project_root)
@@ -414,7 +433,8 @@ def make_habits_tools(cfg: GemCodeConfig) -> list:
414
433
  habits_list.__name__ = "habits_list"
415
434
  habits_add.__name__ = "habits_add"
416
435
  habits_remove.__name__ = "habits_remove"
436
+ habits_clear_all.__name__ = "habits_clear_all"
417
437
  habits_pause.__name__ = "habits_pause"
418
438
  habits_resume.__name__ = "habits_resume"
419
439
 
420
- return [habits_list, habits_add, habits_remove, habits_pause, habits_resume]
440
+ return [habits_list, habits_add, habits_remove, habits_clear_all, habits_pause, habits_resume]
@@ -9,6 +9,12 @@ that does not require **`gemcode runtime`** for **`org_delegate`**. It manages:
9
9
  3. Event routing (via the in-memory EventBus)
10
10
  4. Automatic result reporting (fleet reports + bus messages)
11
11
 
12
+ Each queued job is bound to an **org member**. When it runs, that member gets their own
13
+ effective ``project_root`` (agent workspace under ``.gemcode/agents/…`` when configured),
14
+ their **skills** (member skill + workspace-local skills), **memory**, **SQLite session**,
15
+ and capability/model routing—so different agents do not share one generic runtime even
16
+ when habits or triggers are defined in the same fleet ``habits.json``.
17
+
12
18
  Optional **`gemcode runtime`** is a separate fleet-manager process (IPC, automations, stdin queue).
13
19
  Slash **`/agent assign`** / **`trigger`** publish `org.assign` over IPC when the socket is up; otherwise the REPL falls back to **`org_delegate`** (this mesh). The mesh also subscribes to **`org.assign`** on the in-process bus for the same payload shape.
14
20
  """
@@ -24,6 +30,7 @@ import time
24
30
  import uuid
25
31
  from dataclasses import dataclass, field
26
32
  from pathlib import Path
33
+ from collections.abc import Callable
27
34
  from typing import Any
28
35
 
29
36
  from gemcode.config import GemCodeConfig, _truthy_env
@@ -263,6 +270,67 @@ class AgentMesh:
263
270
  self._bg_loop.call_soon_threadsafe(_poke)
264
271
  fut.result(timeout=timeout)
265
272
 
273
+ def _call_on_mesh_loop(self, fn: Callable[[], Any], *, timeout: float = 30.0) -> Any:
274
+ """Run a sync callable on the mesh asyncio loop (safe from any thread)."""
275
+ if self._bg_thread is None or not self._bg_thread.is_alive():
276
+ self.start()
277
+ if not self._wait_bg_loop_ready():
278
+ raise RuntimeError("mesh background loop not available")
279
+ assert self._bg_loop is not None
280
+ fut: concurrent.futures.Future[Any] = concurrent.futures.Future()
281
+
282
+ def _wrap() -> None:
283
+ try:
284
+ fut.set_result(fn())
285
+ except Exception as e:
286
+ fut.set_exception(e)
287
+
288
+ self._bg_loop.call_soon_threadsafe(_wrap)
289
+ return fut.result(timeout=timeout)
290
+
291
+ def clear_pending_jobs(self) -> int:
292
+ """Remove jobs not yet started from the mesh queue. Returns how many were dropped."""
293
+
294
+ def _drain() -> int:
295
+ n = 0
296
+ while True:
297
+ try:
298
+ self._queue.get_nowait()
299
+ n += 1
300
+ except asyncio.QueueEmpty:
301
+ break
302
+ return n
303
+
304
+ return int(self._call_on_mesh_loop(_drain))
305
+
306
+ def cancel_running_jobs(self) -> int:
307
+ """Cancel in-flight mesh job tasks (habits, delegates, triggers). Returns cancel count."""
308
+
309
+ def _cancel() -> int:
310
+ n = 0
311
+ for _jid, task in list(self._running.items()):
312
+ if not task.done():
313
+ task.cancel()
314
+ n += 1
315
+ return n
316
+
317
+ return int(self._call_on_mesh_loop(_cancel))
318
+
319
+ def halt_jobs(
320
+ self,
321
+ *,
322
+ clear_queue: bool = True,
323
+ cancel_running: bool = True,
324
+ ) -> dict[str, Any]:
325
+ """Stop queued and/or running mesh work (does not edit ``habits.json``)."""
326
+ cleared = self.clear_pending_jobs() if clear_queue else 0
327
+ cancelled = self.cancel_running_jobs() if cancel_running else 0
328
+ return {
329
+ "ok": True,
330
+ "cleared_queued": cleared,
331
+ "cancelled_running": cancelled,
332
+ }
333
+
266
334
  def enqueue(
267
335
  self,
268
336
  *,
@@ -395,7 +463,10 @@ class AgentMesh:
395
463
 
396
464
  # Nested wait=True from inside a running mesh job would deadlock the scheduler
397
465
  # (parent holds a concurrency slot while the child waits for another slot).
398
- if self._mesh_job_depth > 0:
466
+ # Only use the inline path on the mesh thread: the manager runs on a different
467
+ # event loop; _mesh_job_depth can be >0 while a habit runs, but inline
468
+ # _run_job_inner must not run there (asyncio locks / ADK are mesh-loop-local).
469
+ if self._is_on_mesh_thread() and self._mesh_job_depth > 0:
399
470
  job_id = f"mesh_{uuid.uuid4().hex[:10]}"
400
471
  with self._enqueue_lock:
401
472
  self._seq += 1
@@ -600,6 +671,9 @@ class AgentMesh:
600
671
  pass
601
672
 
602
673
  finally:
674
+ if job.status == "running":
675
+ job.status = "cancelled"
676
+ job.error = "cancelled"
603
677
  cf = job.completion_future
604
678
  if cf is not None and not cf.done():
605
679
  try:
@@ -868,12 +942,41 @@ class AgentMesh:
868
942
  _global_mesh: AgentMesh | None = None
869
943
 
870
944
 
945
+ def _sync_mesh_cfg_project_root(mesh: AgentMesh, cfg: GemCodeConfig) -> None:
946
+ """
947
+ Keep the singleton aligned with the active manager ``cfg.project_root``.
948
+
949
+ If the mesh was created from a different cwd than a later ``ensure_mesh`` /
950
+ ``get_mesh`` (e.g. ``-C``), habits/triggers/fleet would otherwise read the
951
+ wrong ``.gemcode/`` tree while the UI used another.
952
+ """
953
+ try:
954
+ cur = Path(mesh.cfg.project_root).resolve()
955
+ nxt = Path(cfg.project_root).resolve()
956
+ if cur == nxt:
957
+ return
958
+ mesh.cfg.project_root = cfg.project_root
959
+ if mesh._trigger_engine is not None:
960
+ mesh._trigger_engine.cfg = mesh.cfg
961
+ mesh._trigger_engine.reload()
962
+ if mesh._habit_scheduler is not None:
963
+ mesh._habit_scheduler.cfg = mesh.cfg
964
+ if mesh._learner is not None:
965
+ mesh._learner.cfg = mesh.cfg
966
+ if mesh._self_healing is not None:
967
+ mesh._self_healing.cfg = mesh.cfg
968
+ except Exception:
969
+ pass
970
+
971
+
871
972
  def get_mesh(cfg: GemCodeConfig | None = None) -> AgentMesh | None:
872
973
  """Get or create the global agent mesh."""
873
974
  global _global_mesh
874
975
  if _global_mesh is None and cfg is not None:
875
976
  concurrency = int(os.environ.get("GEMCODE_MESH_CONCURRENCY", "3"))
876
977
  _global_mesh = AgentMesh(cfg, max_concurrency=concurrency)
978
+ elif _global_mesh is not None and cfg is not None:
979
+ _sync_mesh_cfg_project_root(_global_mesh, cfg)
877
980
  return _global_mesh
878
981
 
879
982
 
@@ -883,6 +986,8 @@ def ensure_mesh(cfg: GemCodeConfig) -> AgentMesh:
883
986
  if _global_mesh is None:
884
987
  concurrency = int(os.environ.get("GEMCODE_MESH_CONCURRENCY", "3"))
885
988
  _global_mesh = AgentMesh(cfg, max_concurrency=concurrency)
989
+ else:
990
+ _sync_mesh_cfg_project_root(_global_mesh, cfg)
886
991
  return _global_mesh
887
992
 
888
993
 
@@ -237,6 +237,7 @@ SLASH_COMMANDS: list[tuple[str, str]] = [
237
237
  ("bus", "Runtime bus — send/publish lightweight messages over IPC"),
238
238
  ("inbox", "Bus inbox filters for this UI (to/topics)"),
239
239
  ("fleet", "Fleet inbox — /fleet show | digest (habits / mesh reports)"),
240
+ ("mesh", "In-process mesh — /mesh halt | status (stop queued/running jobs)"),
240
241
  ("agent", "Create/manage a child agent workspace (folder + registry)"),
241
242
  # NOTE: /org and /delegate are deprecated aliases; keep working but do not list.
242
243
  ("limits", "Execution limits (calls, context, …)"),
@@ -146,6 +146,66 @@ async def process_repl_slash(
146
146
  return ReplSlashResult(skip_model_turn=True)
147
147
  return ReplSlashResult(model_prompt=fleet_digest_prompt())
148
148
 
149
+ # ── /mesh (in-process agent mesh — cancel queued/running work) ─────────────
150
+ if name == "mesh":
151
+ from gemcode.agent_mesh import get_mesh
152
+
153
+ raw_m = (sc.args or "").strip()
154
+ parts_m = raw_m.lower().split()
155
+ first_m = parts_m[0] if parts_m else ""
156
+
157
+ m = get_mesh(cfg)
158
+ if m is None:
159
+ out("[mesh] not initialized.")
160
+ out()
161
+ return ReplSlashResult(skip_model_turn=True)
162
+
163
+ if first_m in ("help", "?"):
164
+ out("In-process agent mesh (habits, org_delegate, triggers):")
165
+ out(" /mesh Show counts + short tip.")
166
+ out(" /mesh status Queued / running job counts.")
167
+ out(" /mesh halt Drop queued jobs and cancel running mesh tasks.")
168
+ out(" /mesh halt --habits Same, and clear `.gemcode/habits.json` entirely.")
169
+ out("Note: `habits_remove` only stops *new* enqueues; queued or running jobs continue")
170
+ out("until they finish unless you `/mesh halt`. Orchestration is in this process")
171
+ out("(no separate daemon required for the mesh).")
172
+ out()
173
+ return ReplSlashResult(skip_model_turn=True)
174
+
175
+ if first_m in ("halt", "stop", "kill"):
176
+ and_habits = "--habits" in parts_m
177
+ try:
178
+ h = m.halt_jobs(clear_queue=True, cancel_running=True)
179
+ except Exception as e:
180
+ out(f"[mesh] halt failed: {type(e).__name__}: {e}")
181
+ out()
182
+ return ReplSlashResult(skip_model_turn=True)
183
+ out(
184
+ f"[mesh] halted: cleared {h.get('cleared_queued', 0)} queued job(s), "
185
+ f"cancelled {h.get('cancelled_running', 0)} running task(s).",
186
+ )
187
+ if and_habits:
188
+ from gemcode.agent_habits import save_habits
189
+ save_habits(cfg.project_root, [])
190
+ out("[mesh] also cleared `.gemcode/habits.json` (all habits removed).")
191
+ out()
192
+ return ReplSlashResult(skip_model_turn=True)
193
+
194
+ if first_m in ("status", "") or not parts_m:
195
+ st = m.status()
196
+ out(
197
+ f"[mesh] queued={st['queued_jobs']} running={st['running_jobs']} "
198
+ f"completed_total={st['completed_jobs']} max_concurrency={st['max_concurrency']}",
199
+ )
200
+ if not parts_m or first_m == "":
201
+ out("Tip: `/mesh halt` stops queued + running work · `/mesh help`")
202
+ out()
203
+ return ReplSlashResult(skip_model_turn=True)
204
+
205
+ out("[mesh] unknown subcommand. Try `/mesh help`.")
206
+ out()
207
+ return ReplSlashResult(skip_model_turn=True)
208
+
149
209
  # ── /attach (queue files for the next user message: PDF, images, audio, …) ─
150
210
  if name in ("attach", "file", "image", "img"):
151
211
  raw_i = (sc.args or "").strip()
@@ -206,8 +206,36 @@ def build_function_tools(cfg: GemCodeConfig, *, include_subtask: bool = True) ->
206
206
  return {"ok": False, "error": "mesh not initialized"}
207
207
  return {"ok": True, **m.status()}
208
208
 
209
+ def mesh_halt(
210
+ clear_queued_jobs: bool = True,
211
+ cancel_running_jobs: bool = True,
212
+ remove_all_habits: bool = False,
213
+ ) -> dict:
214
+ """
215
+ Stop background mesh work: drop jobs waiting in the queue, cancel jobs currently running,
216
+ and optionally wipe all habits. Use when habits were removed but work keeps finishing.
217
+
218
+ This is the in-process mesh (same GemCode session), not a separate ``gemcode runtime`` daemon.
219
+ """
220
+ m = get_mesh(cfg)
221
+ if m is None:
222
+ return {"ok": False, "error": "mesh not initialized"}
223
+ try:
224
+ out = m.halt_jobs(clear_queue=clear_queued_jobs, cancel_running=cancel_running_jobs)
225
+ except Exception as e:
226
+ return {"ok": False, "error": f"{type(e).__name__}: {e}"}
227
+ if remove_all_habits:
228
+ from gemcode.agent_habits import save_habits
229
+ save_habits(cfg.project_root, [])
230
+ out["habits_cleared"] = True
231
+ else:
232
+ out["habits_cleared"] = False
233
+ return out
234
+
209
235
  mesh_status.__name__ = "mesh_status"
236
+ mesh_halt.__name__ = "mesh_halt"
210
237
  tools.append(mesh_status)
238
+ tools.append(mesh_halt)
211
239
  except Exception:
212
240
  pass
213
241
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.4.15
3
+ Version: 0.4.17
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -292,6 +292,12 @@ Every GemCode run is anchored to a project root. This determines:
292
292
  - what instruction files are loaded
293
293
  - which repo-local assets are active
294
294
 
295
+ ### Multi-agent habits, skills, and mesh runtime
296
+
297
+ - **Habits** for the whole fleet live in **one** file: `.gemcode/habits.json` at the **fleet root**. Each row names which **org member** runs (`agent` field), on what schedule—different members can have different prompts and intervals at once.
298
+ - **Skills** and **per-turn runtime** are **per member**: org `skill_name`, member skill under `.gemcode/skills/`, and optional **agent workspace** `.gemcode/agents/<id>-<slug>/` (own session DB, memory, local skills). Mesh jobs use that context automatically.
299
+ - **Stopping automation:** removing a habit only stops *new* work. To cancel **queued** or **running** mesh jobs, use **`/mesh halt`** or the **`mesh_halt`** tool (see [`orchestration.md`](../docs/orchestration.md#stopping-background-work-habits-removed-but-jobs-still-finishing)).
300
+
295
301
  ### `.gemcode/`
296
302
  GemCode stores project-local state under `.gemcode/`, including:
297
303
  - sessions
@@ -173,6 +173,32 @@ def test_apply_mesh_worker_unattended_off_inherits_manager(tmp_path: Path) -> No
173
173
  assert cfg.interactive_permission_ask is True
174
174
 
175
175
 
176
+ def test_mesh_halt_clears_pending_queue(tmp_path: Path) -> None:
177
+ old_s = os.environ.get("PYTEST_GEMCODE_MESH_SCHEDULER")
178
+ old_h = os.environ.get("GEMCODE_AGENT_HABITS")
179
+ os.environ["PYTEST_GEMCODE_MESH_SCHEDULER"] = "0"
180
+ os.environ["GEMCODE_AGENT_HABITS"] = "0"
181
+ try:
182
+ cfg = GemCodeConfig(project_root=tmp_path)
183
+ mesh = AgentMesh(cfg, max_concurrency=2)
184
+ mesh.enqueue(prompt="a", priority=1, member_name="x")
185
+ mesh.enqueue(prompt="b", priority=1, member_name="y")
186
+ mesh.wait_for_pending_enqueues()
187
+ assert mesh._queue.qsize() == 2
188
+ h = mesh.halt_jobs(clear_queue=True, cancel_running=False)
189
+ assert h["cleared_queued"] == 2
190
+ assert mesh._queue.qsize() == 0
191
+ finally:
192
+ if old_s is None:
193
+ os.environ.pop("PYTEST_GEMCODE_MESH_SCHEDULER", None)
194
+ else:
195
+ os.environ["PYTEST_GEMCODE_MESH_SCHEDULER"] = old_s
196
+ if old_h is None:
197
+ os.environ.pop("GEMCODE_AGENT_HABITS", None)
198
+ else:
199
+ os.environ["GEMCODE_AGENT_HABITS"] = old_h
200
+
201
+
176
202
  def test_mesh_priority_ordering(tmp_path: Path) -> None:
177
203
  """Higher priority jobs should be dequeued first."""
178
204
  old_s = os.environ.get("PYTEST_GEMCODE_MESH_SCHEDULER")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes