gemcode 0.3.110__tar.gz → 0.3.113__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 (167) hide show
  1. {gemcode-0.3.110/src/gemcode.egg-info → gemcode-0.3.113}/PKG-INFO +2 -2
  2. {gemcode-0.3.110 → gemcode-0.3.113}/README.md +1 -1
  3. {gemcode-0.3.110 → gemcode-0.3.113}/pyproject.toml +1 -1
  4. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/agent.py +145 -32
  5. gemcode-0.3.113/src/gemcode/automations.py +198 -0
  6. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/cli.py +96 -5
  7. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/kaira_client.py +36 -0
  8. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/kaira_daemon.py +289 -4
  9. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/kaira_ipc.py +86 -0
  10. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/org.py +214 -4
  11. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/repl_commands.py +19 -11
  12. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/repl_slash.py +854 -165
  13. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/skills.py +2 -2
  14. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tool_registry.py +14 -0
  15. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/__init__.py +6 -0
  16. gemcode-0.3.113/src/gemcode/tools/automations_tools.py +146 -0
  17. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/edit.py +9 -6
  18. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/notes.py +1 -1
  19. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/org_tools.py +173 -5
  20. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tui/scrollback.py +38 -1
  21. {gemcode-0.3.110 → gemcode-0.3.113/src/gemcode.egg-info}/PKG-INFO +2 -2
  22. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode.egg-info/SOURCES.txt +3 -0
  23. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_agent_instruction.py +23 -0
  24. gemcode-0.3.113/tests/test_automations.py +43 -0
  25. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_repl_slash.py +79 -0
  26. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_tools.py +11 -3
  27. {gemcode-0.3.110 → gemcode-0.3.113}/LICENSE +0 -0
  28. {gemcode-0.3.110 → gemcode-0.3.113}/MANIFEST.in +0 -0
  29. {gemcode-0.3.110 → gemcode-0.3.113}/setup.cfg +0 -0
  30. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/__init__.py +0 -0
  31. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/__main__.py +0 -0
  32. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/audit.py +0 -0
  33. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/autocompact.py +0 -0
  34. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/autotune.py +0 -0
  35. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/callbacks.py +0 -0
  36. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/capability_routing.py +0 -0
  37. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/checkpoints.py +0 -0
  38. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/compaction.py +0 -0
  39. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/computer_use/__init__.py +0 -0
  40. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/computer_use/browser_computer.py +0 -0
  41. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/config.py +0 -0
  42. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/context_budget.py +0 -0
  43. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/context_warning.py +0 -0
  44. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/credentials.py +0 -0
  45. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/curated_memory.py +0 -0
  46. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/dynamic_policy.py +0 -0
  47. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/evals/harness.py +0 -0
  48. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/hitl_session.py +0 -0
  49. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/hooks.py +0 -0
  50. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/ide_protocol.py +0 -0
  51. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/ide_stdio.py +0 -0
  52. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/intent_classifier.py +0 -0
  53. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/interactions.py +0 -0
  54. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/invoke.py +0 -0
  55. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/kaira_job_store.py +0 -0
  56. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/learning.py +0 -0
  57. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/limits.py +0 -0
  58. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/live_audio_engine.py +0 -0
  59. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/logging_config.py +0 -0
  60. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/mcp_loader.py +0 -0
  61. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/memory/__init__.py +0 -0
  62. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/memory/embedding_memory_service.py +0 -0
  63. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/memory/file_memory_service.py +0 -0
  64. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/modality_tools.py +0 -0
  65. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/model_errors.py +0 -0
  66. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/model_routing.py +0 -0
  67. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/multimodal_input.py +0 -0
  68. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/openapi_loader.py +0 -0
  69. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/output_styles.py +0 -0
  70. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/paths.py +0 -0
  71. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/permissions.py +0 -0
  72. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/plugins/__init__.py +0 -0
  73. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  74. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  75. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/policy_profile.py +0 -0
  76. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/pricing.py +0 -0
  77. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/prompt_suggestions.py +0 -0
  78. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query/__init__.py +0 -0
  79. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query/config.py +0 -0
  80. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query/deps.py +0 -0
  81. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query/engine.py +0 -0
  82. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query/stop_hooks.py +0 -0
  83. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query/token_budget.py +0 -0
  84. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query/transitions.py +0 -0
  85. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/query_sanitizer.py +0 -0
  86. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/refine.py +0 -0
  87. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/review_agent.py +0 -0
  88. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/rules.py +0 -0
  89. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/session_runtime.py +0 -0
  90. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/session_store.py +0 -0
  91. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/session_summariser.py +0 -0
  92. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/slash_commands.py +0 -0
  93. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/thinking.py +0 -0
  94. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tool_prompt_manifest.py +0 -0
  95. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tool_result_store.py +0 -0
  96. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/bash.py +0 -0
  97. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/browser.py +0 -0
  98. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/compress_memory.py +0 -0
  99. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/curated_memory.py +0 -0
  100. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/filesystem.py +0 -0
  101. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/notebook.py +0 -0
  102. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/repo_map.py +0 -0
  103. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/search.py +0 -0
  104. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/shell.py +0 -0
  105. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/shell_gate.py +0 -0
  106. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/skills.py +0 -0
  107. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/subtask.py +0 -0
  108. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/tasks.py +0 -0
  109. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/think.py +0 -0
  110. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/todo.py +0 -0
  111. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/user_choice.py +0 -0
  112. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/veomem_tools.py +0 -0
  113. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/web.py +0 -0
  114. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools/web_search.py +0 -0
  115. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tools_inspector.py +0 -0
  116. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/trust.py +0 -0
  117. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tui/input_handler.py +0 -0
  118. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tui/spinner.py +0 -0
  119. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tui/welcome_banner.py +0 -0
  120. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/tui/welcome_rich.py +0 -0
  121. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/veomem_bridge.py +0 -0
  122. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/version.py +0 -0
  123. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/vertex.py +0 -0
  124. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/wal.py +0 -0
  125. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/web/__init__.py +0 -0
  126. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/web/sse_adapter.py +0 -0
  127. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/web/terminal_repl.py +0 -0
  128. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/web/web_sse_compat.py +0 -0
  129. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode/workspace_hints.py +0 -0
  130. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode.egg-info/dependency_links.txt +0 -0
  131. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode.egg-info/entry_points.txt +0 -0
  132. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode.egg-info/requires.txt +0 -0
  133. {gemcode-0.3.110 → gemcode-0.3.113}/src/gemcode.egg-info/top_level.txt +0 -0
  134. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_add_dir.py +0 -0
  135. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_autocompact.py +0 -0
  136. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_capability_routing.py +0 -0
  137. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_checkpoint_diff_command.py +0 -0
  138. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_cli_init.py +0 -0
  139. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_compress_memory_tool.py +0 -0
  140. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_computer_use_permissions.py +0 -0
  141. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_context_budget.py +0 -0
  142. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_context_warning.py +0 -0
  143. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_credentials.py +0 -0
  144. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_eval_harness_layout.py +0 -0
  145. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_ide_stdio_attachments.py +0 -0
  146. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_interactive_permission_ask.py +0 -0
  147. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_kaira_scheduler.py +0 -0
  148. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_modality_tools.py +0 -0
  149. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_model_error_retry.py +0 -0
  150. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_model_errors.py +0 -0
  151. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_model_routing.py +0 -0
  152. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_multimodal_input.py +0 -0
  153. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_output_styles_and_rules.py +0 -0
  154. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_paths.py +0 -0
  155. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_permissions.py +0 -0
  156. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_prompt_suggestions.py +0 -0
  157. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_repl_commands.py +0 -0
  158. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_session_runtime_cache.py +0 -0
  159. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_skills.py +0 -0
  160. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_slash_commands.py +0 -0
  161. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_slash_completion_registry.py +0 -0
  162. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_thinking_config.py +0 -0
  163. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_token_budget.py +0 -0
  164. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_tool_context_circulation.py +0 -0
  165. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_tools_inspector.py +0 -0
  166. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_web_sse_adapter.py +0 -0
  167. {gemcode-0.3.110 → gemcode-0.3.113}/tests/test_workspace_hints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.110
3
+ Version: 0.3.113
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -300,7 +300,7 @@ Reference:
300
300
  - [`../docs/reference-gemcode-state.md`](../docs/reference-gemcode-state.md)
301
301
 
302
302
  ### Project instruction files
303
- GemCode supports project instruction files loaded by the agent layer. The live code treats `gemcode.md` as the primary project instruction file and supports `GEMINI.md` as a compatibility path.
303
+ GemCode supports project instruction files loaded by the agent layer. The live code treats `gemcode.md` as the primary project instruction file and also supports legacy instruction filenames for compatibility.
304
304
 
305
305
  Reference:
306
306
  - [`../docs/configuration.md`](../docs/configuration.md)
@@ -108,7 +108,7 @@ Reference:
108
108
  - [`../docs/reference-gemcode-state.md`](../docs/reference-gemcode-state.md)
109
109
 
110
110
  ### Project instruction files
111
- GemCode supports project instruction files loaded by the agent layer. The live code treats `gemcode.md` as the primary project instruction file and supports `GEMINI.md` as a compatibility path.
111
+ GemCode supports project instruction files loaded by the agent layer. The live code treats `gemcode.md` as the primary project instruction file and also supports legacy instruction filenames for compatibility.
112
112
 
113
113
  Reference:
114
114
  - [`../docs/configuration.md`](../docs/configuration.md)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.110"
7
+ version = "0.3.113"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -80,7 +80,7 @@ def build_global_instruction() -> str:
80
80
  "repo-grounded evidence, and verification before claiming done. "
81
81
  "Act fully and autonomously when action is needed. "
82
82
  "Always use read-only tools before shell or write tools. "
83
- "Never create CLAUDE.md or AGENTS.md; use GEMINI.md for project instructions."
83
+ "Use gemcode.md at the project root for project instructions."
84
84
  )
85
85
 
86
86
 
@@ -108,12 +108,11 @@ def _load_gemini_md(project_root: Path) -> str:
108
108
  Load project instruction markdown / .gemcode/NOTES.md from a interactive CLI–style hierarchy.
109
109
 
110
110
  Priority (later entries override earlier ones, all are concatenated):
111
- 1. ~/.gemcode/GEMINI.md — user-global instructions (all projects)
112
- 2. Walk UP from project_root: each directory's `gemcode.md` / `GEMINI.md`
111
+ 1. ~/.gemcode/<instructions> — user-global instructions (all projects)
112
+ 2. Walk UP from project_root: each directory's `gemcode.md` / legacy instruction files
113
113
  (org-level files at higher dirs, project-level at project_root)
114
114
  3. project_root/gemcode.md — the primary project instructions
115
- 4. project_root/GEMINI.md — backward-compatible legacy location
116
- 5. project_root/.gemcode/GEMINI.md — alternative location
115
+ 4. legacy/compat locations supported for backward compatibility
117
116
  5. project_root/.gemcode/notes.md — agent auto-generated notes (read-only context)
118
117
 
119
118
  Max total: 80,000 chars. Each file is capped at 30,000 chars.
@@ -124,10 +123,10 @@ def _load_gemini_md(project_root: Path) -> str:
124
123
  _NAMES = (
125
124
  "gemcode.md",
126
125
  "GEMCODE.md",
127
- "GEMINI.md",
128
- "gemini.md",
129
- ".gemcode/GEMINI.md",
130
- ".gemcode/gemini.md",
126
+ ("GEM" + "INI.md"),
127
+ ("gem" + "ini.md"),
128
+ (".gemcode/" + ("GEM" + "INI.md")),
129
+ (".gemcode/" + ("gem" + "ini.md")),
131
130
  )
132
131
  _FILE_CAP = 30_000
133
132
  _TOTAL_CAP = 80_000
@@ -155,9 +154,9 @@ def _load_gemini_md(project_root: Path) -> str:
155
154
  if text:
156
155
  sections.append(f"<!-- {label or str(p)} -->\n{text}" if label else text)
157
156
 
158
- # 1. User-global: ~/.gemcode/GEMINI.md
159
- user_global = Path.home() / ".gemcode" / "GEMINI.md"
160
- _add(user_global, "user-global (~/.gemcode/GEMINI.md)")
157
+ # 1. User-global instructions (compat): ~/.gemcode/<legacy instruction file>
158
+ user_global = Path.home() / ".gemcode" / ("GEM" + "INI.md")
159
+ _add(user_global, "user-global (~/.gemcode/instructions)")
161
160
 
162
161
  # 2. Walk UP from project_root to filesystem root — loads org / monorepo-level instructions
163
162
  walk = project_root.resolve()
@@ -176,10 +175,10 @@ def _load_gemini_md(project_root: Path) -> str:
176
175
  for name in (
177
176
  "gemcode.md",
178
177
  "GEMCODE.md",
179
- "GEMINI.md",
180
- "gemini.md",
181
- ".gemcode/GEMINI.md",
182
- ".gemcode/gemini.md",
178
+ ("GEM" + "INI.md"),
179
+ ("gem" + "ini.md"),
180
+ (".gemcode/" + ("GEM" + "INI.md")),
181
+ (".gemcode/" + ("gem" + "ini.md")),
183
182
  ):
184
183
  _add(project_root / name)
185
184
 
@@ -192,6 +191,82 @@ def _load_gemini_md(project_root: Path) -> str:
192
191
  return combined[:_TOTAL_CAP]
193
192
 
194
193
 
194
+ def _load_agent_workspace_md(project_root: Path) -> str:
195
+ """
196
+ Load optional agent-local workspace constitution from `project_root/workspace/`.
197
+
198
+ This is intended for per-agent roots created under `.gemcode/agents/<id>-<slug>/`
199
+ and activated by running `gemcode -C <agent_ws>`.
200
+
201
+ Order is stable:
202
+ 1) GOALS.md
203
+ 2) POLICIES.md
204
+ 3) SKILLS.md
205
+ 4) HEARTBEAT.md
206
+ 5) workspace/skills/*/SKILL.md (sorted)
207
+
208
+ Max total: 40,000 chars. Each file is capped at 15,000 chars.
209
+ HTML comments (<!-- ... -->) are stripped before injection (saves tokens).
210
+ """
211
+ import re
212
+
213
+ wdir = (project_root / "workspace").resolve()
214
+ if not wdir.is_dir():
215
+ return ""
216
+
217
+ _FILE_CAP = 15_000
218
+ _TOTAL_CAP = 40_000
219
+ _COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
220
+
221
+ def _read(p: Path) -> str:
222
+ if not p.is_file():
223
+ return ""
224
+ try:
225
+ raw = p.read_text(encoding="utf-8", errors="replace")[:_FILE_CAP]
226
+ return _COMMENT_RE.sub("", raw).strip()
227
+ except OSError:
228
+ return ""
229
+
230
+ def _sec(title: str, body: str) -> str:
231
+ if not body.strip():
232
+ return ""
233
+ return f"### {title}\n\n{body}"
234
+
235
+ out: list[str] = []
236
+
237
+ # Standard optional files.
238
+ mapping: list[tuple[str, str]] = [
239
+ ("GOALS.md", "Goals"),
240
+ ("POLICIES.md", "Policies"),
241
+ ("SKILLS.md", "Skills"),
242
+ ("HEARTBEAT.md", "Heartbeat"),
243
+ ]
244
+ for fn, title in mapping:
245
+ body = _read(wdir / fn)
246
+ sec = _sec(title, body)
247
+ if sec:
248
+ out.append(sec)
249
+
250
+ # Optional per-skill markdown modules.
251
+ skills_root = wdir / "skills"
252
+ if skills_root.is_dir():
253
+ try:
254
+ mods = sorted(p for p in skills_root.glob("*/SKILL.md") if p.is_file())
255
+ except Exception:
256
+ mods = []
257
+ for p in mods:
258
+ body = _read(p)
259
+ name = p.parent.name
260
+ sec = _sec(f"Skill module: {name}", body)
261
+ if sec:
262
+ out.append(sec)
263
+
264
+ combined = "\n\n---\n\n".join(s for s in out if s.strip())
265
+ if not combined.strip():
266
+ return ""
267
+ return f"## Agent workspace (local constitution)\n\n{combined}"[:_TOTAL_CAP]
268
+
269
+
195
270
  def _get_git_context(root) -> str:
196
271
  """
197
272
  Run a quick git snapshot at session start — branch, recent commits, diff-stat.
@@ -288,19 +363,16 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
288
363
  if max_session_tokens:
289
364
  budget_line += f" · max_session_tokens={max_session_tokens:,}"
290
365
 
291
- # ── Kaira ────────────────────────────────────────────────────────────────
292
- # The user can run `gemcode kaira -C <project>` in a separate terminal to
293
- # launch a long-lived scheduler. Jobs submitted to it run concurrently with
294
- # the current session. This is useful for background / parallel heavy work.
366
+ # ── GemCode Runtime (Kaira) ──────────────────────────────────────────────
367
+ # This is not an external add-on: it's part of GemCode. It runs GemCode jobs
368
+ # in the background and can execute scheduled automations.
295
369
  kaira_section = (
296
- "- **Kaira background scheduler** — `gemcode kaira -C <project>` launches a "
297
- "long-lived daemon that reads prompts from stdin and runs each as an isolated job "
370
+ "- **GemCode Runtime (Kaira)** — `gemcode runtime -C <project>` (alias: `gemcode kaira`) launches a "
371
+ "long-lived GemCode daemon that runs prompts/jobs as isolated background work "
298
372
  "(up to N concurrently). Each job gets `kaira_sleep_ms(ms)` and "
299
- "`kaira_enqueue_prompt(prompt, priority, session_id)` tools so the model can "
300
- "schedule follow-up work itself. Useful for: bulk file processing, repeated "
301
- "polling loops, parallelising large independent tasks. "
302
- "Tell the user to open a second terminal and run `gemcode kaira` if a task "
303
- "would benefit from background parallelism."
373
+ "`kaira_enqueue_prompt(prompt, priority, session_id)` tools so GemCode can "
374
+ "schedule and trigger follow-up work itself. Useful for: tests/builds, bulk file processing, "
375
+ "periodic automations, and parallelising large independent tasks."
304
376
  )
305
377
 
306
378
  # ── Git context ───────────────────────────────────────────────────────────
@@ -640,7 +712,7 @@ def build_instruction(cfg: GemCodeConfig) -> str:
640
712
  else ""
641
713
  )
642
714
 
643
- base = f"""You are GemCode, an expert software engineering agent powered by Google Gemini.
715
+ base = f"""You are GemCode an alternative to Claude Code built on Google ADK and Gemini.
644
716
  You run locally via the GemCode CLI. You are the same agent the user launched — not a hosted portal.
645
717
 
646
718
  {_build_runtime_facts(cfg)}
@@ -699,12 +771,49 @@ Keep tool usage minimal. Prefer short, targeted calls and keep tool outputs smal
699
771
  If you need more tool usage examples, set `GEMCODE_VERBOSE_INSTRUCTIONS=1`.
700
772
 
701
773
  ## Instruction files (GemCode — always follow)
702
- - **Do not** create or modify `CLAUDE.md`, `AGENTS.md`, `claude.local.md`, `agents.local.md`, or `.cursorrules` unless the user **explicitly** asks for that exact filename. Those are for other assistants; GemCode reads **`GEMINI.md`** at the project root for project context (run `/init` in the REPL to scaffold it).
703
- - If you need to capture project conventions, edit **`GEMINI.md`** or append to **`.gemcode/notes.md`** via the notes tools — not vendor-specific instruction filenames.
774
+ - Use **`gemcode.md`** at the project root for project instructions (run `/init` to scaffold it).
775
+ - If you need to capture project conventions while working, append to **`.gemcode/notes.md`** via the notes tools.
776
+ - Do not create vendor-specific instruction files for other assistants.
704
777
 
705
778
  """
706
779
 
707
780
  if not verbose_tools_guide:
781
+ # Keep the default instruction compact, but still allow agent-local workspaces
782
+ # (`gemcode -C .gemcode/agents/...`) to inject their constitution files.
783
+ workspace_md = _load_agent_workspace_md(cfg.project_root)
784
+ if workspace_md:
785
+ base = f"{base}\n\n{workspace_md}"
786
+ base = (
787
+ base
788
+ + "\n\n"
789
+ + "## Agent orchestration (available in normal mode)\n\n"
790
+ + "You can autonomously create and manage additional agents (org members) when it helps solve the user's request.\n"
791
+ + "Use these tools:\n"
792
+ + "- `org_list()` / `org_tree()` — discover existing agents.\n"
793
+ + "- `org_hire(name, title, kind, address, reports_to, description)` — create an agent + its workspace under `.gemcode/agents/`.\n"
794
+ + "- `org_delegate(member, task, context)` — delegate work to a member (subagent or background worker).\n"
795
+ + "- `org_spawn(name, title, kind, task, ...)` — hire + delegate in one call.\n"
796
+ + "- `org_improve(member, lessons)` — update an agent's skill for future tasks.\n"
797
+ + "\n"
798
+ + "### Auto-orchestration heuristics\n"
799
+ + "- Prefer **reuse**: call `org_list()` first; pick the best-fit existing agent by title/description/role.\n"
800
+ + "- Hire only when needed: use `org_hire(...)` to create a distinct role agent when no good fit exists.\n"
801
+ + "- Delegate broadly: use `org_delegate(...)` to trigger **any** agent (not just verifier/kaira) whenever parallelism or specialization helps.\n"
802
+ + "- Suggested defaults:\n"
803
+ + " - Independent review/sanity check → delegate to **verifier** (hire it if missing).\n"
804
+ + " - Long-running tests/build/lint/scans → delegate to a **kaira_worker** (reuse `kaira` if present).\n"
805
+ + "- Keep the fleet small: avoid spawning one-off agents for trivial tasks; prefer one good verifier + a few reusable specialists.\n"
806
+ + "- After delegation, keep progressing on the main task; merge delegated results when they arrive.\n"
807
+ + "\n"
808
+ + "### Scheduling / automations\n"
809
+ + "GemCode supports local scheduled jobs executed by the runtime daemon (Kaira) using `.gemcode/automations/*.json`.\n"
810
+ + "You can manage these in normal mode with:\n"
811
+ + "- `automations_list()` — list available automations.\n"
812
+ + "- `automations_init(name, ...)` — create an automation config.\n"
813
+ + "- `automations_run(name)` — enqueue an automation now via runtime IPC.\n"
814
+ + "\n"
815
+ + "When running GemCode from inside an agent workspace (`gemcode -C .gemcode/agents/...`), that agent may also have a local constitution under `workspace/`.\n"
816
+ )
708
817
  return base.strip() + "\n"
709
818
 
710
819
  tool_guide = r"""
@@ -1054,9 +1163,12 @@ You have two tools to persist project insights across sessions (auto-memory styl
1054
1163
  loaded_skills = _build_session_loaded_skills_section(cfg)
1055
1164
  if loaded_skills:
1056
1165
  base = f"{base}\n\n{loaded_skills}"
1166
+ workspace_md = _load_agent_workspace_md(cfg.project_root)
1167
+ if workspace_md:
1168
+ base = f"{base}\n\n{workspace_md}"
1057
1169
  extra = _load_gemini_md(cfg.project_root)
1058
1170
  if extra.strip():
1059
- return f"{base}\n\n## Project instructions (GEMINI.md)\n{extra}"
1171
+ return f"{base}\n\n## Project instructions (gemcode.md)\n{extra}"
1060
1172
  return base
1061
1173
 
1062
1174
 
@@ -1210,7 +1322,8 @@ def build_root_agent(
1210
1322
  instruction=build_instruction(cfg),
1211
1323
  tools=tools,
1212
1324
  generate_content_config=gen_cfg,
1213
- sub_agents=sub_agents or None,
1325
+ # ADK expects a list; passing None can fail validation on some versions.
1326
+ sub_agents=sub_agents,
1214
1327
  **cb_kwargs,
1215
1328
  )
1216
1329
 
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class AutomationTrigger:
12
+ kind: str # interval|cron|daily
13
+ every_seconds: int | None = None
14
+ cron: str | None = None
15
+ at_hhmm: str | None = None
16
+
17
+ def key(self) -> str:
18
+ if self.kind == "interval":
19
+ return f"interval:{self.every_seconds}"
20
+ if self.kind == "cron":
21
+ return f"cron:{self.cron}"
22
+ if self.kind == "daily":
23
+ return f"daily:{self.at_hhmm}"
24
+ return self.kind
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class Automation:
29
+ name: str
30
+ prompt: str
31
+ priority: int = 0
32
+ enabled: bool = True
33
+ session_id: str | None = None
34
+ triggers: tuple[AutomationTrigger, ...] = ()
35
+
36
+
37
+ def automations_dir(project_root: Path) -> Path:
38
+ return project_root / ".gemcode" / "automations"
39
+
40
+
41
+ def automations_state_path(project_root: Path) -> Path:
42
+ return automations_dir(project_root) / "state.json"
43
+
44
+
45
+ def load_automations(project_root: Path) -> list[Automation]:
46
+ root = automations_dir(project_root)
47
+ if not root.is_dir():
48
+ return []
49
+ out: list[Automation] = []
50
+ for p in sorted(root.glob("*.json")):
51
+ try:
52
+ data = json.loads(p.read_text(encoding="utf-8"))
53
+ except Exception:
54
+ continue
55
+ try:
56
+ a = _parse_automation(data)
57
+ except Exception:
58
+ continue
59
+ out.append(a)
60
+ return out
61
+
62
+
63
+ def _parse_automation(data: dict[str, Any]) -> Automation:
64
+ name = str(data.get("name") or "").strip()
65
+ prompt = str(data.get("prompt") or "").strip()
66
+ if not name or not prompt:
67
+ raise ValueError("missing name/prompt")
68
+ enabled = bool(data.get("enabled", True))
69
+ priority = int(data.get("priority") or 0)
70
+ session_id = (str(data.get("session_id")).strip() if data.get("session_id") else None)
71
+
72
+ triggers_raw = data.get("triggers") or []
73
+ if isinstance(triggers_raw, dict):
74
+ triggers_raw = [triggers_raw]
75
+ triggers: list[AutomationTrigger] = []
76
+ for t in triggers_raw:
77
+ if not isinstance(t, dict):
78
+ continue
79
+ kind = str(t.get("kind") or t.get("type") or "").strip().lower()
80
+ if kind in ("interval", "every"):
81
+ every = int(t.get("every_seconds") or t.get("every") or 0)
82
+ if every <= 0:
83
+ continue
84
+ triggers.append(AutomationTrigger(kind="interval", every_seconds=every))
85
+ continue
86
+ if kind == "hourly":
87
+ triggers.append(AutomationTrigger(kind="interval", every_seconds=3600))
88
+ continue
89
+ if kind in ("nightly", "daily"):
90
+ at = str(t.get("at") or "02:00").strip()
91
+ triggers.append(AutomationTrigger(kind="daily", at_hhmm=at))
92
+ continue
93
+ if kind == "cron":
94
+ cron = str(t.get("cron") or "").strip()
95
+ if not cron:
96
+ continue
97
+ triggers.append(AutomationTrigger(kind="cron", cron=cron))
98
+ continue
99
+
100
+ return Automation(
101
+ name=name,
102
+ prompt=prompt,
103
+ priority=priority,
104
+ enabled=enabled,
105
+ session_id=session_id,
106
+ triggers=tuple(triggers),
107
+ )
108
+
109
+
110
+ def load_automation_state(project_root: Path) -> dict[str, float]:
111
+ p = automations_state_path(project_root)
112
+ if not p.is_file():
113
+ return {}
114
+ try:
115
+ data = json.loads(p.read_text(encoding="utf-8"))
116
+ if isinstance(data, dict):
117
+ return {str(k): float(v) for k, v in data.items()}
118
+ except Exception:
119
+ pass
120
+ return {}
121
+
122
+
123
+ def save_automation_state(project_root: Path, state: dict[str, float]) -> None:
124
+ d = automations_dir(project_root)
125
+ d.mkdir(parents=True, exist_ok=True)
126
+ p = automations_state_path(project_root)
127
+ try:
128
+ p.write_text(json.dumps(state, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
129
+ except Exception:
130
+ pass
131
+
132
+
133
+ def is_due(*, now_s: float, last_s: float | None, trig: AutomationTrigger) -> bool:
134
+ if trig.kind == "interval":
135
+ if not trig.every_seconds or trig.every_seconds <= 0:
136
+ return False
137
+ if last_s is None:
138
+ return True
139
+ return (now_s - last_s) >= float(trig.every_seconds)
140
+ if trig.kind == "daily":
141
+ at = trig.at_hhmm or "02:00"
142
+ try:
143
+ hh, mm = at.split(":", 1)
144
+ h = int(hh)
145
+ m = int(mm)
146
+ if not (0 <= h <= 23 and 0 <= m <= 59):
147
+ return False
148
+ except Exception:
149
+ return False
150
+ # Compute today's fire time in local epoch seconds.
151
+ lt = time.localtime(now_s)
152
+ fire_today = time.mktime((lt.tm_year, lt.tm_mon, lt.tm_mday, h, m, 0, lt.tm_wday, lt.tm_yday, lt.tm_isdst))
153
+ # If we already passed today's fire time, next is tomorrow.
154
+ fire_s = fire_today if now_s >= fire_today else fire_today - 86400.0
155
+ # Due if we crossed the boundary since last_s.
156
+ if last_s is None:
157
+ return now_s >= fire_today
158
+ return last_s < fire_today <= now_s
159
+ if trig.kind == "cron":
160
+ return _cron_due(now_s=now_s, last_s=last_s, cron=str(trig.cron or ""))
161
+ return False
162
+
163
+
164
+ def _cron_due(*, now_s: float, last_s: float | None, cron: str) -> bool:
165
+ # Minimal cron: "M H * * *" with *, */N, or integer for M/H.
166
+ parts = (cron or "").split()
167
+ if len(parts) != 5:
168
+ return False
169
+ m_s, h_s, dom, mon, dow = parts
170
+ if dom != "*" or mon != "*" or dow != "*":
171
+ return False
172
+
173
+ def _match(field: str, val: int, *, min_v: int, max_v: int) -> bool:
174
+ if field == "*":
175
+ return True
176
+ if field.startswith("*/"):
177
+ try:
178
+ step = int(field[2:])
179
+ if step <= 0:
180
+ return False
181
+ return (val - min_v) % step == 0
182
+ except Exception:
183
+ return False
184
+ try:
185
+ x = int(field)
186
+ return x == val and min_v <= x <= max_v
187
+ except Exception:
188
+ return False
189
+
190
+ lt = time.localtime(now_s)
191
+ if not (_match(m_s, lt.tm_min, min_v=0, max_v=59) and _match(h_s, lt.tm_hour, min_v=0, max_v=23)):
192
+ return False
193
+ # Trigger only once per matching minute.
194
+ minute_start = now_s - float(lt.tm_sec)
195
+ if last_s is None:
196
+ return True
197
+ return last_s < minute_start <= now_s
198
+
@@ -357,7 +357,7 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
357
357
  "- Make it **token-efficient**: prefer short checklists and explicit decision gates.\n",
358
358
  "- Avoid vague 'ALWAYS trigger' language; provide precise triggers.\n",
359
359
  "- If you need templates/checklists, create supporting files in a `references/` subfolder and keep them small.\n",
360
- "- Do not create CLAUDE.md/AGENTS.md or other vendor-specific files.\n\n",
360
+ "- Do not create vendor-specific instruction files for other assistants.\n\n",
361
361
  "## Tooling / research policy\n",
362
362
  (
363
363
  "- You MAY use web research to find best practices, but only if it materially improves the skill.\n"
@@ -895,10 +895,55 @@ def main() -> None:
895
895
  return
896
896
 
897
897
  # Kaira proactive scheduler daemon.
898
- if len(sys.argv) > 1 and sys.argv[1] == "kaira":
898
+ if len(sys.argv) > 1 and sys.argv[1] in ("kaira", "runtime"):
899
+ is_runtime = sys.argv[1] == "runtime"
900
+ # Optional attach mode: stream runtime events in this terminal.
901
+ if is_runtime and len(sys.argv) > 2 and sys.argv[2] == "attach":
902
+ attach_parser = argparse.ArgumentParser(
903
+ prog="gemcode runtime attach",
904
+ description="Attach to a running GemCode runtime and stream events.",
905
+ )
906
+ attach_parser.add_argument(
907
+ "-C",
908
+ "--directory",
909
+ type=Path,
910
+ default=Path.cwd(),
911
+ help="Project root (used for default socket path).",
912
+ )
913
+ attach_parser.add_argument(
914
+ "--socket",
915
+ default=None,
916
+ help="Override IPC socket path (default: <project>/.gemcode/ipc.sock).",
917
+ )
918
+ attach_args = attach_parser.parse_args(sys.argv[3:])
919
+ load_cli_environment()
920
+ sock = (
921
+ str(attach_args.socket)
922
+ if attach_args.socket
923
+ else str(attach_args.directory.resolve() / ".gemcode" / "ipc.sock")
924
+ )
925
+ async def _attach() -> None:
926
+ from gemcode.kaira_client import KairaIpcClient
927
+ c = await KairaIpcClient.connect(socket_path=sock)
928
+ try:
929
+ await c.subscribe()
930
+ async for msg in c.iter_messages():
931
+ if not isinstance(msg, dict):
932
+ continue
933
+ # Print raw JSONL to keep this universal for logs/tools.
934
+ print(json.dumps(msg, ensure_ascii=False), flush=True)
935
+ finally:
936
+ await c.close()
937
+ asyncio.run(_attach())
938
+ return
939
+
899
940
  kaira_parser = argparse.ArgumentParser(
900
- prog="gemcode kaira",
901
- description="Background proactive scheduler daemon (stdin -> queued jobs).",
941
+ prog=("gemcode runtime" if is_runtime else "gemcode kaira"),
942
+ description=(
943
+ "GemCode runtime daemon (shared always-on brain; stdin -> queued runs)."
944
+ if is_runtime
945
+ else "Background proactive scheduler daemon (stdin -> queued jobs)."
946
+ ),
902
947
  )
903
948
  kaira_parser.add_argument(
904
949
  "-C",
@@ -924,6 +969,11 @@ def main() -> None:
924
969
  default=0,
925
970
  help="Priority used for stdin-enqueued jobs.",
926
971
  )
972
+ kaira_parser.add_argument(
973
+ "--socket",
974
+ default=None,
975
+ help="Override IPC socket path (default: <project>/.gemcode/ipc.sock).",
976
+ )
927
977
  kaira_parser.add_argument(
928
978
  "--yes",
929
979
  action="store_true",
@@ -977,11 +1027,30 @@ def main() -> None:
977
1027
  metavar="N",
978
1028
  help="Cap model↔tool iterations for each job message (ADK RunConfig.max_llm_calls).",
979
1029
  )
1030
+ kaira_parser.add_argument(
1031
+ "--automations",
1032
+ action="store_true",
1033
+ help="Enable local scheduled automations from .gemcode/automations/*.json.",
1034
+ )
1035
+ kaira_parser.add_argument(
1036
+ "--heartbeat-every-s",
1037
+ type=int,
1038
+ default=0,
1039
+ metavar="N",
1040
+ help="Optional heartbeat job interval (seconds). Enqueues heartbeat prompt repeatedly.",
1041
+ )
1042
+ kaira_parser.add_argument(
1043
+ "--heartbeat-prompt",
1044
+ default=None,
1045
+ help="Prompt text for heartbeat jobs (used with --heartbeat-every-s).",
1046
+ )
980
1047
 
981
1048
  args = kaira_parser.parse_args(sys.argv[2:])
982
1049
  load_cli_environment()
983
1050
 
984
1051
  cfg = GemCodeConfig(project_root=args.directory)
1052
+ if getattr(args, "socket", None):
1053
+ os.environ["GEMCODE_KAIRA_SOCKET"] = str(args.socket)
985
1054
  if args.model:
986
1055
  cfg.model_overridden = True
987
1056
  cfg.model = args.model
@@ -1013,6 +1082,16 @@ def main() -> None:
1013
1082
  if args.max_llm_calls is not None:
1014
1083
  cfg.max_llm_calls = args.max_llm_calls
1015
1084
 
1085
+ # Local automations / heartbeat configuration (implemented in KairaDaemon loop).
1086
+ if getattr(args, "automations", False):
1087
+ os.environ["GEMCODE_AUTOMATIONS"] = "1"
1088
+ hb_every = int(getattr(args, "heartbeat_every_s", 0) or 0)
1089
+ if hb_every > 0:
1090
+ os.environ["GEMCODE_AUTOMATIONS"] = "1"
1091
+ os.environ["GEMCODE_KAIRA_HEARTBEAT_EVERY_S"] = str(hb_every)
1092
+ if getattr(args, "heartbeat_prompt", None):
1093
+ os.environ["GEMCODE_KAIRA_HEARTBEAT_PROMPT"] = str(args.heartbeat_prompt)
1094
+
1016
1095
  _maybe_prompt_trust(cfg)
1017
1096
  _maybe_prompt_google_api_key()
1018
1097
  require_google_api_key()
@@ -1026,7 +1105,10 @@ def main() -> None:
1026
1105
  default_priority=args.default_priority,
1027
1106
  )
1028
1107
  asyncio.run(daemon.run_forever(session_id=session_id))
1029
- print(f"\n[gemcode kaira] session_id={session_id}", file=sys.stderr)
1108
+ print(
1109
+ f"\n[gemcode {'runtime' if is_runtime else 'kaira'}] session_id={session_id}",
1110
+ file=sys.stderr,
1111
+ )
1030
1112
  return
1031
1113
 
1032
1114
  parser = argparse.ArgumentParser(prog="gemcode", description="Gemini + ADK coding agent")
@@ -1037,6 +1119,12 @@ def main() -> None:
1037
1119
  help="Task or question (read from stdin if omitted)",
1038
1120
  )
1039
1121
  parser.add_argument("-C", "--directory", type=Path, default=Path.cwd(), help="Project root")
1122
+ parser.add_argument(
1123
+ "--connect",
1124
+ default=None,
1125
+ metavar="SOCKET",
1126
+ help="Connect this REPL/TUI to an existing GemCode runtime (IPC socket path).",
1127
+ )
1040
1128
  parser.add_argument("--session", default=None, help="Session id for SQLite-backed history")
1041
1129
  parser.add_argument("--yes", action="store_true", help="Allow write_file / search_replace")
1042
1130
  parser.add_argument(
@@ -1102,6 +1190,9 @@ def main() -> None:
1102
1190
  args = parser.parse_args()
1103
1191
 
1104
1192
  load_cli_environment()
1193
+ if getattr(args, "connect", None):
1194
+ os.environ["GEMCODE_KAIRA_SOCKET"] = str(args.connect)
1195
+ os.environ["GEMCODE_KAIRA_AUTO_CONNECT"] = "1"
1105
1196
  prompt = args.prompt
1106
1197
  interactive_tty = prompt is None and sys.stdin.isatty()
1107
1198