gemcode 0.3.105__tar.gz → 0.3.106__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 (163) hide show
  1. {gemcode-0.3.105/src/gemcode.egg-info → gemcode-0.3.106}/PKG-INFO +28 -1
  2. {gemcode-0.3.105 → gemcode-0.3.106}/README.md +27 -0
  3. {gemcode-0.3.105 → gemcode-0.3.106}/pyproject.toml +1 -1
  4. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/agent.py +5 -0
  5. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/invoke.py +174 -3
  6. gemcode-0.3.106/src/gemcode/kaira_client.py +51 -0
  7. gemcode-0.3.106/src/gemcode/kaira_daemon.py +653 -0
  8. gemcode-0.3.106/src/gemcode/kaira_ipc.py +289 -0
  9. gemcode-0.3.106/src/gemcode/kaira_job_store.py +157 -0
  10. gemcode-0.3.106/src/gemcode/org.py +296 -0
  11. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/repl_commands.py +3 -0
  12. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/repl_slash.py +294 -0
  13. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/__init__.py +8 -1
  14. gemcode-0.3.106/src/gemcode/tools/org_tools.py +158 -0
  15. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/subtask.py +45 -0
  16. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tui/scrollback.py +294 -0
  17. {gemcode-0.3.105 → gemcode-0.3.106/src/gemcode.egg-info}/PKG-INFO +28 -1
  18. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode.egg-info/SOURCES.txt +5 -0
  19. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_multimodal_input.py +3 -1
  20. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_tools.py +5 -1
  21. gemcode-0.3.105/src/gemcode/kaira_daemon.py +0 -221
  22. {gemcode-0.3.105 → gemcode-0.3.106}/LICENSE +0 -0
  23. {gemcode-0.3.105 → gemcode-0.3.106}/MANIFEST.in +0 -0
  24. {gemcode-0.3.105 → gemcode-0.3.106}/setup.cfg +0 -0
  25. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/__init__.py +0 -0
  26. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/__main__.py +0 -0
  27. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/audit.py +0 -0
  28. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/autocompact.py +0 -0
  29. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/autotune.py +0 -0
  30. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/callbacks.py +0 -0
  31. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/capability_routing.py +0 -0
  32. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/checkpoints.py +0 -0
  33. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/cli.py +0 -0
  34. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/compaction.py +0 -0
  35. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/computer_use/__init__.py +0 -0
  36. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/computer_use/browser_computer.py +0 -0
  37. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/config.py +0 -0
  38. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/context_budget.py +0 -0
  39. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/context_warning.py +0 -0
  40. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/credentials.py +0 -0
  41. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/curated_memory.py +0 -0
  42. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/dynamic_policy.py +0 -0
  43. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/evals/harness.py +0 -0
  44. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/hitl_session.py +0 -0
  45. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/hooks.py +0 -0
  46. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/ide_protocol.py +0 -0
  47. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/ide_stdio.py +0 -0
  48. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/intent_classifier.py +0 -0
  49. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/interactions.py +0 -0
  50. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/learning.py +0 -0
  51. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/limits.py +0 -0
  52. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/live_audio_engine.py +0 -0
  53. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/logging_config.py +0 -0
  54. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/mcp_loader.py +0 -0
  55. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/memory/__init__.py +0 -0
  56. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/memory/embedding_memory_service.py +0 -0
  57. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/memory/file_memory_service.py +0 -0
  58. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/modality_tools.py +0 -0
  59. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/model_errors.py +0 -0
  60. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/model_routing.py +0 -0
  61. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/multimodal_input.py +0 -0
  62. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/openapi_loader.py +0 -0
  63. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/output_styles.py +0 -0
  64. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/paths.py +0 -0
  65. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/permissions.py +0 -0
  66. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/plugins/__init__.py +0 -0
  67. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  68. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  69. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/policy_profile.py +0 -0
  70. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/pricing.py +0 -0
  71. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/prompt_suggestions.py +0 -0
  72. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query/__init__.py +0 -0
  73. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query/config.py +0 -0
  74. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query/deps.py +0 -0
  75. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query/engine.py +0 -0
  76. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query/stop_hooks.py +0 -0
  77. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query/token_budget.py +0 -0
  78. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query/transitions.py +0 -0
  79. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/query_sanitizer.py +0 -0
  80. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/refine.py +0 -0
  81. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/review_agent.py +0 -0
  82. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/rules.py +0 -0
  83. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/session_runtime.py +0 -0
  84. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/session_store.py +0 -0
  85. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/session_summariser.py +0 -0
  86. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/skills.py +0 -0
  87. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/slash_commands.py +0 -0
  88. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/thinking.py +0 -0
  89. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tool_prompt_manifest.py +0 -0
  90. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tool_registry.py +0 -0
  91. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tool_result_store.py +0 -0
  92. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/bash.py +0 -0
  93. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/browser.py +0 -0
  94. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/compress_memory.py +0 -0
  95. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/curated_memory.py +0 -0
  96. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/edit.py +0 -0
  97. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/filesystem.py +0 -0
  98. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/notebook.py +0 -0
  99. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/notes.py +0 -0
  100. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/repo_map.py +0 -0
  101. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/search.py +0 -0
  102. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/shell.py +0 -0
  103. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/shell_gate.py +0 -0
  104. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/skills.py +0 -0
  105. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/tasks.py +0 -0
  106. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/think.py +0 -0
  107. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/todo.py +0 -0
  108. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/veomem_tools.py +0 -0
  109. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/web.py +0 -0
  110. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools/web_search.py +0 -0
  111. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tools_inspector.py +0 -0
  112. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/trust.py +0 -0
  113. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tui/input_handler.py +0 -0
  114. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tui/spinner.py +0 -0
  115. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tui/welcome_banner.py +0 -0
  116. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/tui/welcome_rich.py +0 -0
  117. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/veomem_bridge.py +0 -0
  118. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/version.py +0 -0
  119. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/vertex.py +0 -0
  120. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/wal.py +0 -0
  121. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/web/__init__.py +0 -0
  122. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/web/sse_adapter.py +0 -0
  123. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/web/terminal_repl.py +0 -0
  124. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/web/web_sse_compat.py +0 -0
  125. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode/workspace_hints.py +0 -0
  126. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode.egg-info/dependency_links.txt +0 -0
  127. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode.egg-info/entry_points.txt +0 -0
  128. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode.egg-info/requires.txt +0 -0
  129. {gemcode-0.3.105 → gemcode-0.3.106}/src/gemcode.egg-info/top_level.txt +0 -0
  130. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_add_dir.py +0 -0
  131. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_agent_instruction.py +0 -0
  132. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_autocompact.py +0 -0
  133. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_capability_routing.py +0 -0
  134. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_checkpoint_diff_command.py +0 -0
  135. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_cli_init.py +0 -0
  136. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_compress_memory_tool.py +0 -0
  137. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_computer_use_permissions.py +0 -0
  138. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_context_budget.py +0 -0
  139. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_context_warning.py +0 -0
  140. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_credentials.py +0 -0
  141. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_eval_harness_layout.py +0 -0
  142. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_ide_stdio_attachments.py +0 -0
  143. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_interactive_permission_ask.py +0 -0
  144. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_kaira_scheduler.py +0 -0
  145. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_modality_tools.py +0 -0
  146. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_model_error_retry.py +0 -0
  147. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_model_errors.py +0 -0
  148. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_model_routing.py +0 -0
  149. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_output_styles_and_rules.py +0 -0
  150. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_paths.py +0 -0
  151. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_permissions.py +0 -0
  152. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_prompt_suggestions.py +0 -0
  153. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_repl_commands.py +0 -0
  154. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_repl_slash.py +0 -0
  155. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_skills.py +0 -0
  156. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_slash_commands.py +0 -0
  157. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_slash_completion_registry.py +0 -0
  158. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_thinking_config.py +0 -0
  159. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_token_budget.py +0 -0
  160. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_tool_context_circulation.py +0 -0
  161. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_tools_inspector.py +0 -0
  162. {gemcode-0.3.105 → gemcode-0.3.106}/tests/test_web_sse_adapter.py +0 -0
  163. {gemcode-0.3.105 → gemcode-0.3.106}/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.105
3
+ Version: 0.3.106
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -349,6 +349,33 @@ gemcode -C . --attach ./report.pdf "Summarize this"
349
349
  gemcode kaira -C .
350
350
  ```
351
351
 
352
+ ### Orchestration (Kaira + org delegation)
353
+
354
+ Docs:
355
+ - `../docs/orchestration.md`
356
+
357
+ In one terminal:
358
+
359
+ ```bash
360
+ gemcode kaira -C .
361
+ ```
362
+
363
+ In another terminal:
364
+
365
+ ```bash
366
+ gemcode -C .
367
+ ```
368
+
369
+ Then in the REPL/TUI:
370
+
371
+ ```text
372
+ /org tree
373
+ /org hire verifier "QA / test planner" subagent gemcode "Find risks, propose tests, review plans."
374
+ /org assign verifier "Review the plan and propose tests"
375
+ /kaira jobs
376
+ /kaira follow <job_id_prefix>
377
+ ```
378
+
352
379
  ### Start the IDE bridge
353
380
  ```bash
354
381
  gemcode ide --stdio
@@ -157,6 +157,33 @@ gemcode -C . --attach ./report.pdf "Summarize this"
157
157
  gemcode kaira -C .
158
158
  ```
159
159
 
160
+ ### Orchestration (Kaira + org delegation)
161
+
162
+ Docs:
163
+ - `../docs/orchestration.md`
164
+
165
+ In one terminal:
166
+
167
+ ```bash
168
+ gemcode kaira -C .
169
+ ```
170
+
171
+ In another terminal:
172
+
173
+ ```bash
174
+ gemcode -C .
175
+ ```
176
+
177
+ Then in the REPL/TUI:
178
+
179
+ ```text
180
+ /org tree
181
+ /org hire verifier "QA / test planner" subagent gemcode "Find risks, propose tests, review plans."
182
+ /org assign verifier "Review the plan and propose tests"
183
+ /kaira jobs
184
+ /kaira follow <job_id_prefix>
185
+ ```
186
+
160
187
  ### Start the IDE bridge
161
188
  ```bash
162
189
  gemcode ide --stdio
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.105"
7
+ version = "0.3.106"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -733,6 +733,11 @@ If you need more tool usage examples, set `GEMCODE_VERBOSE_INSTRUCTIONS=1`.
733
733
  - Always give the sub-agent enough context to operate independently.
734
734
  - End your task prompt with "Summarise your findings clearly." so the result is useful.
735
735
 
736
+ - **`spawn_subtasks`** — spawn multiple isolated sub-agents in parallel (preferred fan-out helper).
737
+ - Use this instead of manually issuing many `run_subtask` calls.
738
+ - Provide 3–6 focused tasks; keep each subtask self-contained.
739
+ - Then synthesise the combined findings into a single plan/answer.
740
+
736
741
  ## Multi-step task execution
737
742
  One user message = many model↔tool rounds (up to 256 LLM calls by default). This is intentional — you are expected to do complete tasks autonomously.
738
743
 
@@ -25,6 +25,82 @@ _TRANSIENT_RETRY_DELAYS = [2.0, 5.0, 12.0]
25
25
 
26
26
  _HITL_PROMPT_LOCK = Lock()
27
27
 
28
+ async def _maybe_enqueue_kaira_autopilot(*, cfg: "GemCodeConfig", session_id: str) -> None:
29
+ """If Kaira IPC is available, enqueue background quality checks after edits."""
30
+ try:
31
+ enabled = os.environ.get("GEMCODE_KAIRA_AUTOPILOT", "1").strip().lower() in (
32
+ "1",
33
+ "true",
34
+ "yes",
35
+ "on",
36
+ )
37
+ if not enabled:
38
+ return
39
+ # Only run when files were touched (edit tools track this).
40
+ touched = getattr(cfg, "_touched_paths", None)
41
+ if not touched:
42
+ return
43
+ # Avoid enqueuing repeatedly within the same session unless touched paths change.
44
+ last_fp = str(getattr(cfg, "_kaira_autopilot_fp", "") or "")
45
+ fp = ",".join(sorted(str(x) for x in touched))[:4000]
46
+ if fp and fp == last_fp:
47
+ return
48
+ object.__setattr__(cfg, "_kaira_autopilot_fp", fp)
49
+
50
+ sock = os.environ.get("GEMCODE_KAIRA_SOCKET") or str(cfg.project_root / ".gemcode" / "ipc.sock")
51
+ if not Path(sock).exists():
52
+ return
53
+ # Heuristic: suggest likely checks, but let the agent choose based on repo.
54
+ prompt = (
55
+ "You are Kaira (background worker). Run the most relevant automated quality checks for this repo "
56
+ "based on its files, and report succinctly.\n\n"
57
+ "Rules:\n"
58
+ "- Prefer fast checks first (lint/typecheck/unit tests).\n"
59
+ "- If Python: try `pytest -q` (or detect other common runners).\n"
60
+ "- If Node: try `npm test` / `npm run lint` when package.json exists.\n"
61
+ "- If there are failures, include the smallest actionable summary and exact command to reproduce.\n"
62
+ "- If everything passes, say PASS and list what you ran.\n"
63
+ "- Return a final STRICT JSON report with keys: status, summary, evidence, recommended_next_actions.\n\n"
64
+ f"Touched files (recent): {', '.join(sorted(list(touched))[:30])}"
65
+ )
66
+
67
+ # Org-aware routing: delegate to the kaira org member if present so the
68
+ # manager/worker hierarchy stays consistent.
69
+ try:
70
+ auto_org = os.environ.get("GEMCODE_AUTO_SLASH_ORG", "1").strip().lower() in (
71
+ "1",
72
+ "true",
73
+ "yes",
74
+ "on",
75
+ )
76
+ if auto_org:
77
+ from gemcode.org import find_member
78
+ m = find_member(cfg.project_root, "kaira")
79
+ if m is not None and m.kind == "kaira_worker":
80
+ from gemcode.tools.org_tools import make_org_tools
81
+ tools = make_org_tools(cfg)
82
+ org_delegate = None
83
+ for t in tools:
84
+ if getattr(t, "__name__", "") == "org_delegate":
85
+ org_delegate = t
86
+ break
87
+ if org_delegate is not None:
88
+ await org_delegate("kaira", prompt, "") # type: ignore[misc]
89
+ return
90
+ except Exception:
91
+ pass
92
+
93
+ from gemcode.kaira_client import KairaIpcClient
94
+
95
+ client = await KairaIpcClient.connect(socket_path=sock)
96
+ try:
97
+ # Low priority by default; user can override with env in future.
98
+ await client.request(action="enqueue", prompt=prompt, priority=-1, session_id=session_id)
99
+ finally:
100
+ await client.close()
101
+ except Exception:
102
+ return
103
+
28
104
 
29
105
  def _events_to_text(events: list[Any]) -> str:
30
106
  """Best-effort extraction of assistant text from ADK events."""
@@ -102,6 +178,56 @@ async def run_turn(
102
178
  object.__setattr__(cfg, "_risk_score", risk)
103
179
  except Exception:
104
180
  pass
181
+
182
+ # Deterministic manager dispatcher: pre-delegate certain work to org members
183
+ # and inject their results into the prompt before the main agent runs.
184
+ try:
185
+ auto_mgr = os.environ.get("GEMCODE_MANAGER_DISPATCH", "1").strip().lower() in (
186
+ "1","true","yes","on"
187
+ )
188
+ if auto_mgr:
189
+ import re
190
+ p0 = (prompt or "")[:12_000]
191
+ # Only do this for broad/complex prompts.
192
+ score = float(getattr(cfg, "_parallelism_score", 0.0) or 0.0)
193
+ if score >= float(os.environ.get("GEMCODE_MANAGER_DISPATCH_THRESHOLD", "0.7")):
194
+ from gemcode.tools.org_tools import make_org_tools
195
+ tools = make_org_tools(cfg)
196
+ org_delegate = None
197
+ for t in tools:
198
+ if getattr(t, "__name__", "") == "org_delegate":
199
+ org_delegate = t
200
+ break
201
+ # Ask verifier for a quick risk check / plan critique (fast, in-process).
202
+ if org_delegate is not None and re.search(r"\\b(fix|refactor|rewrite|change|implement)\\b", p0, re.I):
203
+ v = await org_delegate("verifier", "Review the requested task and list key risks + verification steps.", p0) # type: ignore[misc]
204
+ if isinstance(v, dict) and v.get("ok") and v.get("result"):
205
+ object.__setattr__(cfg, "_manager_verifier_context", str(v.get("result"))[:4000])
206
+ except Exception:
207
+ pass
208
+ # Parallelism score: heuristic signal for automatic subtask fan-out.
209
+ try:
210
+ import re
211
+ p2 = (prompt or "")[:20_000]
212
+ par = 0.0
213
+ if len(p2) > 800:
214
+ par += 0.15
215
+ if len(p2) > 2400:
216
+ par += 0.2
217
+ if re.search(r"\b(analy[sz]e|audit|map|inventory|scan|survey)\b", p2, re.I):
218
+ par += 0.2
219
+ if re.search(r"\b(refactor|migrate|rewrite|redesign|architecture)\b", p2, re.I):
220
+ par += 0.2
221
+ if re.search(r"\b(across|entire|whole|end-to-end|e2e|multiple modules|many files)\b", p2, re.I):
222
+ par += 0.2
223
+ if p2.count("/") >= 8 or p2.count(".py") + p2.count(".ts") + p2.count(".tsx") >= 5:
224
+ par += 0.15
225
+ if attachment_paths:
226
+ par += 0.08
227
+ par = max(0.0, min(1.0, float(par)))
228
+ object.__setattr__(cfg, "_parallelism_score", par)
229
+ except Exception:
230
+ pass
105
231
  run_config = (
106
232
  RunConfig(max_llm_calls=max_llm_calls) if max_llm_calls is not None else None
107
233
  )
@@ -222,9 +348,48 @@ async def run_turn(
222
348
  for w in attach_warn:
223
349
  print(f"[gemcode] {w}", file=sys.stderr)
224
350
  else:
225
- current_message = types.Content(
226
- role="user", parts=[types.Part(text=prompt)]
227
- )
351
+ effective_prompt = prompt
352
+ # Auto fan-out: when prompt looks broad, guide model to spawn parallel
353
+ # isolated subtasks first (bounded) and then synthesise.
354
+ try:
355
+ auto_on = os.environ.get("GEMCODE_AUTO_FANOUT", "1").strip().lower() in (
356
+ "1",
357
+ "true",
358
+ "yes",
359
+ "on",
360
+ )
361
+ threshold = float(os.environ.get("GEMCODE_AUTO_FANOUT_THRESHOLD", "0.6"))
362
+ if auto_on and cfg is not None:
363
+ score = float(getattr(cfg, "_parallelism_score", 0.0) or 0.0)
364
+ already = bool(getattr(cfg, "_auto_fanout_applied", False))
365
+ if (not already) and score >= threshold:
366
+ object.__setattr__(cfg, "_auto_fanout_applied", True)
367
+ effective_prompt = (
368
+ "Before you answer, do a quick parallel exploration pass:\n"
369
+ "- Decompose this into 3–6 independent investigation subtasks.\n"
370
+ "- Call `spawn_subtasks(tasks=[...], max_concurrency=4)` to run them in parallel.\n"
371
+ "- If an org member is appropriate, prefer delegating with `org_delegate` / `org_spawn`.\n"
372
+ "- Synthesize the results into a single plan/answer, then proceed.\n\n"
373
+ + (prompt or "")
374
+ )
375
+ except Exception:
376
+ pass
377
+
378
+ # Inject dispatcher context (e.g., verifier critique) if available.
379
+ try:
380
+ if cfg is not None:
381
+ ctx = str(getattr(cfg, "_manager_verifier_context", "") or "").strip()
382
+ if ctx:
383
+ effective_prompt = (
384
+ "Manager pre-delegation result (verifier):\n"
385
+ + ctx
386
+ + "\n\nUser request:\n"
387
+ + (effective_prompt or "")
388
+ )
389
+ except Exception:
390
+ pass
391
+
392
+ current_message = types.Content(role="user", parts=[types.Part(text=effective_prompt)])
228
393
 
229
394
  async def _await_runner_events(
230
395
  *, next_message: types.Content, do_reset: bool
@@ -347,6 +512,12 @@ async def run_turn(
347
512
  )
348
513
  continue
349
514
 
515
+ # Background autopilot: if we touched files, enqueue Kaira checks (best-effort).
516
+ if cfg is not None:
517
+ try:
518
+ await _maybe_enqueue_kaira_autopilot(cfg=cfg, session_id=session_id)
519
+ except Exception:
520
+ pass
350
521
  return collected
351
522
  finally:
352
523
  if cfg is not None and orig_ctx_chars is not None:
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import uuid
5
+ from dataclasses import dataclass
6
+ from typing import Any, AsyncIterator
7
+
8
+ from gemcode.ide_protocol import IdeEmitter, make_response, parse_json_line
9
+
10
+
11
+ @dataclass
12
+ class KairaIpcClient:
13
+ reader: asyncio.StreamReader
14
+ writer: asyncio.StreamWriter
15
+ emitter: IdeEmitter
16
+
17
+ @classmethod
18
+ async def connect(cls, *, socket_path: str) -> "KairaIpcClient":
19
+ reader, writer = await asyncio.open_unix_connection(socket_path)
20
+ return cls(reader=reader, writer=writer, emitter=IdeEmitter(stream=writer))
21
+
22
+ async def close(self) -> None:
23
+ try:
24
+ self.writer.close()
25
+ await self.writer.wait_closed()
26
+ except Exception:
27
+ pass
28
+
29
+ async def request(self, *, action: str, **payload: Any) -> dict[str, Any]:
30
+ req_id = f"req_{uuid.uuid4().hex[:12]}"
31
+ msg = {"type": "request", "id": req_id, "action": action}
32
+ msg.update(payload)
33
+ self.emitter.send(msg)
34
+ # Wait for matching response.
35
+ while True:
36
+ line = await self.reader.readline()
37
+ if not line:
38
+ return make_response(id=req_id, ok=False, error="ipc_eof")
39
+ obj = parse_json_line(line.decode("utf-8", errors="replace").strip())
40
+ if obj.get("type") == "response" and str(obj.get("id") or "") == req_id:
41
+ return obj
42
+ # Other messages (events) are ignored here; caller should use iter_messages.
43
+
44
+ async def iter_messages(self) -> AsyncIterator[dict[str, Any]]:
45
+ while True:
46
+ line = await self.reader.readline()
47
+ if not line:
48
+ return
49
+ obj = parse_json_line(line.decode("utf-8", errors="replace").strip())
50
+ yield obj
51
+