qd-evolve 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. qd_evolve-0.1.0/.gitignore +26 -0
  2. qd_evolve-0.1.0/.python-version +1 -0
  3. qd_evolve-0.1.0/CLAUDE.md +54 -0
  4. qd_evolve-0.1.0/DESIGN.md +557 -0
  5. qd_evolve-0.1.0/LICENSE +21 -0
  6. qd_evolve-0.1.0/PKG-INFO +340 -0
  7. qd_evolve-0.1.0/README.md +295 -0
  8. qd_evolve-0.1.0/config.json.example +339 -0
  9. qd_evolve-0.1.0/manifesto.md +71 -0
  10. qd_evolve-0.1.0/pyproject.toml +89 -0
  11. qd_evolve-0.1.0/qd_evolve/__init__.py +1 -0
  12. qd_evolve-0.1.0/qd_evolve/_templates/_system_tail.j2 +45 -0
  13. qd_evolve-0.1.0/qd_evolve/_templates/a2a-default.j2 +28 -0
  14. qd_evolve-0.1.0/qd_evolve/_templates/a2a-heartbeat.j2 +1 -0
  15. qd_evolve-0.1.0/qd_evolve/_templates/default.j2 +14 -0
  16. qd_evolve-0.1.0/qd_evolve/_templates/group-default.j2 +14 -0
  17. qd_evolve-0.1.0/qd_evolve/_templates/group-heartbeat.j2 +1 -0
  18. qd_evolve-0.1.0/qd_evolve/_templates/group-message.j2 +1 -0
  19. qd_evolve-0.1.0/qd_evolve/_templates/heartbeat.j2 +1 -0
  20. qd_evolve-0.1.0/qd_evolve/_templates/mqtt-default.j2 +29 -0
  21. qd_evolve-0.1.0/qd_evolve/_templates/mqtt-heartbeat.j2 +1 -0
  22. qd_evolve-0.1.0/qd_evolve/a2a_cli.py +959 -0
  23. qd_evolve-0.1.0/qd_evolve/agent/__init__.py +6 -0
  24. qd_evolve-0.1.0/qd_evolve/agent/a2a.py +148 -0
  25. qd_evolve-0.1.0/qd_evolve/agent/a2a_agent.py +313 -0
  26. qd_evolve-0.1.0/qd_evolve/agent/a2a_tools.py +320 -0
  27. qd_evolve-0.1.0/qd_evolve/agent/agent.py +928 -0
  28. qd_evolve-0.1.0/qd_evolve/agent/group_chat_agent.py +319 -0
  29. qd_evolve-0.1.0/qd_evolve/agent/group_chat_human.py +106 -0
  30. qd_evolve-0.1.0/qd_evolve/agent/group_chat_transport.py +256 -0
  31. qd_evolve-0.1.0/qd_evolve/agent/group_chat_wechat_human.py +161 -0
  32. qd_evolve-0.1.0/qd_evolve/agent/human_agent.py +188 -0
  33. qd_evolve-0.1.0/qd_evolve/agent/loader.py +300 -0
  34. qd_evolve-0.1.0/qd_evolve/agent/mqtt_agent.py +692 -0
  35. qd_evolve-0.1.0/qd_evolve/agent/mqtt_human_agent.py +423 -0
  36. qd_evolve-0.1.0/qd_evolve/agent/mqtt_transport.py +782 -0
  37. qd_evolve-0.1.0/qd_evolve/agent/protocol.py +36 -0
  38. qd_evolve-0.1.0/qd_evolve/agent/registry.py +100 -0
  39. qd_evolve-0.1.0/qd_evolve/agent/server.py +528 -0
  40. qd_evolve-0.1.0/qd_evolve/agent/transport.py +569 -0
  41. qd_evolve-0.1.0/qd_evolve/bridge/__init__.py +0 -0
  42. qd_evolve-0.1.0/qd_evolve/bridge/wechat_clawbot_client.py +413 -0
  43. qd_evolve-0.1.0/qd_evolve/chat_cli.py +559 -0
  44. qd_evolve-0.1.0/qd_evolve/cli_tools.py +78 -0
  45. qd_evolve-0.1.0/qd_evolve/cli_utils.py +42 -0
  46. qd_evolve-0.1.0/qd_evolve/core/__init__.py +1 -0
  47. qd_evolve-0.1.0/qd_evolve/core/config.py +248 -0
  48. qd_evolve-0.1.0/qd_evolve/core/logger.py +59 -0
  49. qd_evolve-0.1.0/qd_evolve/core/memory.py +405 -0
  50. qd_evolve-0.1.0/qd_evolve/core/prompts.py +69 -0
  51. qd_evolve-0.1.0/qd_evolve/core/providers.py +89 -0
  52. qd_evolve-0.1.0/qd_evolve/core/registry.py +221 -0
  53. qd_evolve-0.1.0/qd_evolve/core/toolbox.py +189 -0
  54. qd_evolve-0.1.0/qd_evolve/gchat_cli.py +477 -0
  55. qd_evolve-0.1.0/qd_evolve/memory_tui.py +283 -0
  56. qd_evolve-0.1.0/qd_evolve/mqtt_cli.py +982 -0
  57. qd_evolve-0.1.0/qd_evolve/skills.py +144 -0
  58. qd_evolve-0.1.0/qd_evolve/toolbox_tui.py +695 -0
  59. qd_evolve-0.1.0/qd_evolve/tools/__init__.py +5 -0
  60. qd_evolve-0.1.0/qd_evolve/tools/cli_loader.py +41 -0
  61. qd_evolve-0.1.0/qd_evolve/tools/install_func.py +113 -0
  62. qd_evolve-0.1.0/qd_evolve/tools/install_mcp.py +102 -0
  63. qd_evolve-0.1.0/qd_evolve/tools/install_skill.py +117 -0
  64. qd_evolve-0.1.0/qd_evolve/tools/recall_memory.py +93 -0
  65. qd_evolve-0.1.0/qd_evolve/tools/register_func.py +92 -0
  66. qd_evolve-0.1.0/qd_evolve/tools/register_mcp.py +45 -0
  67. qd_evolve-0.1.0/qd_evolve/tools/register_skill.py +45 -0
  68. qd_evolve-0.1.0/qd_evolve/tools/skill_loader.py +39 -0
  69. qd_evolve-0.1.0/qd_evolve/tools/staging.py +41 -0
  70. qd_evolve-0.1.0/qd_evolve/tools/tool_loader.py +42 -0
  71. qd_evolve-0.1.0/qd_evolve/utils/__init__.py +0 -0
  72. qd_evolve-0.1.0/qd_evolve/utils/adk_output.py +66 -0
  73. qd_evolve-0.1.0/qd_evolve/utils/adk_schema.py +103 -0
  74. qd_evolve-0.1.0/tests/TEST_PLAN.md +180 -0
  75. qd_evolve-0.1.0/tests/__init__.py +0 -0
  76. qd_evolve-0.1.0/tests/agent/__init__.py +0 -0
  77. qd_evolve-0.1.0/tests/agent/test_a2a_models.py +311 -0
  78. qd_evolve-0.1.0/tests/agent/test_agent_core.py +327 -0
  79. qd_evolve-0.1.0/tests/agent/test_agent_run.py +440 -0
  80. qd_evolve-0.1.0/tests/agent/test_group_chat_agent.py +297 -0
  81. qd_evolve-0.1.0/tests/agent/test_group_chat_human.py +159 -0
  82. qd_evolve-0.1.0/tests/agent/test_group_chat_transport.py +154 -0
  83. qd_evolve-0.1.0/tests/agent/test_human_agent.py +132 -0
  84. qd_evolve-0.1.0/tests/agent/test_loader.py +122 -0
  85. qd_evolve-0.1.0/tests/agent/test_mqtt_agent.py +280 -0
  86. qd_evolve-0.1.0/tests/agent/test_mqtt_human_agent.py +137 -0
  87. qd_evolve-0.1.0/tests/agent/test_mqtt_transport.py +243 -0
  88. qd_evolve-0.1.0/tests/agent/test_push_notification.py +392 -0
  89. qd_evolve-0.1.0/tests/agent/test_registry.py +136 -0
  90. qd_evolve-0.1.0/tests/agent/test_server.py +232 -0
  91. qd_evolve-0.1.0/tests/agent/test_transport.py +119 -0
  92. qd_evolve-0.1.0/tests/cli/__init__.py +0 -0
  93. qd_evolve-0.1.0/tests/cli/test_replay.py +89 -0
  94. qd_evolve-0.1.0/tests/cli/test_slash_commands.py +23 -0
  95. qd_evolve-0.1.0/tests/conftest.py +220 -0
  96. qd_evolve-0.1.0/tests/core/__init__.py +0 -0
  97. qd_evolve-0.1.0/tests/core/test_config.py +445 -0
  98. qd_evolve-0.1.0/tests/core/test_logger.py +75 -0
  99. qd_evolve-0.1.0/tests/core/test_memory.py +377 -0
  100. qd_evolve-0.1.0/tests/core/test_prompts.py +125 -0
  101. qd_evolve-0.1.0/tests/core/test_providers.py +162 -0
  102. qd_evolve-0.1.0/tests/core/test_registry.py +240 -0
  103. qd_evolve-0.1.0/tests/core/test_toolbox.py +392 -0
  104. qd_evolve-0.1.0/tests/replay/basic.txt +5 -0
  105. qd_evolve-0.1.0/tests/replay/slash_commands.txt +6 -0
  106. qd_evolve-0.1.0/tests/skills/__init__.py +0 -0
  107. qd_evolve-0.1.0/tests/test_cli_tools.py +181 -0
  108. qd_evolve-0.1.0/tests/test_skills.py +248 -0
  109. qd_evolve-0.1.0/tests/tools/__init__.py +0 -0
  110. qd_evolve-0.1.0/tests/tools/test_a2a_tools.py +131 -0
  111. qd_evolve-0.1.0/tests/tools/test_adk_output.py +118 -0
  112. qd_evolve-0.1.0/tests/tools/test_adk_schema.py +173 -0
  113. qd_evolve-0.1.0/tests/tools/test_cli_loader.py +45 -0
  114. qd_evolve-0.1.0/tests/tools/test_install_func.py +134 -0
  115. qd_evolve-0.1.0/tests/tools/test_install_mcp.py +97 -0
  116. qd_evolve-0.1.0/tests/tools/test_install_skill.py +216 -0
  117. qd_evolve-0.1.0/tests/tools/test_recall_memory.py +103 -0
  118. qd_evolve-0.1.0/tests/tools/test_register_func.py +169 -0
  119. qd_evolve-0.1.0/tests/tools/test_register_mcp.py +68 -0
  120. qd_evolve-0.1.0/tests/tools/test_register_skill.py +73 -0
  121. qd_evolve-0.1.0/tests/tools/test_skill_loader.py +43 -0
  122. qd_evolve-0.1.0/tests/tools/test_staging.py +52 -0
  123. qd_evolve-0.1.0/tests/tools/test_tool_loader.py +30 -0
  124. qd_evolve-0.1.0/tools/bridge/__init__.py +194 -0
  125. qd_evolve-0.1.0/tools/bridge/_mcp.py +320 -0
  126. qd_evolve-0.1.0/tools/bridge/_oat.py +192 -0
  127. qd_evolve-0.1.0/tools/bridge/oat.json +17 -0
  128. qd_evolve-0.1.0/tools/func/fetch.py +71 -0
  129. qd_evolve-0.1.0/tools/func/file_rw.py +95 -0
  130. qd_evolve-0.1.0/tools/func/run_python.py +78 -0
  131. qd_evolve-0.1.0/tools/func/run_shell.py +84 -0
  132. qd_evolve-0.1.0/tools/func/search.py +109 -0
  133. qd_evolve-0.1.0/tools/mcp/amap-maps.json +11 -0
  134. qd_evolve-0.1.0/tools/mcp/open-meteo-mcp-server.json +8 -0
  135. qd_evolve-0.1.0/tools/mcp/playwright.json +11 -0
  136. qd_evolve-0.1.0/tools/mcp/tavily.json +11 -0
  137. qd_evolve-0.1.0/tools/skills/baidu-search/SKILL.md +63 -0
  138. qd_evolve-0.1.0/tools/skills/baidu-search/_meta.json +6 -0
  139. qd_evolve-0.1.0/tools/skills/baidu-search/references/apikey-fetch.md +58 -0
  140. qd_evolve-0.1.0/tools/skills/baidu-search/scripts/search.py +123 -0
  141. qd_evolve-0.1.0/tools/skills/cli-register/SKILL.md +39 -0
  142. qd_evolve-0.1.0/tools/skills/cli-register/_meta.json +5 -0
  143. qd_evolve-0.1.0/tools/skills/find-tools/SKILL.md +46 -0
  144. qd_evolve-0.1.0/tools/skills/self-improvement/SKILL.md +572 -0
  145. qd_evolve-0.1.0/tools/skills/self-improvement/assets/LEARNINGS.md +45 -0
  146. qd_evolve-0.1.0/tools/skills/self-improvement/assets/SKILL-TEMPLATE.md +177 -0
  147. qd_evolve-0.1.0/tools/skills/self-improvement/hooks/openclaw/HOOK.md +23 -0
  148. qd_evolve-0.1.0/tools/skills/self-improvement/hooks/openclaw/handler.ts +46 -0
  149. qd_evolve-0.1.0/tools/skills/self-improvement/references/examples.md +374 -0
  150. qd_evolve-0.1.0/tools/skills/self-improvement/references/hooks-setup.md +223 -0
  151. qd_evolve-0.1.0/tools/skills/self-improvement/references/openclaw-integration.md +311 -0
  152. qd_evolve-0.1.0/tools/skills/self-improvement/scripts/activator.sh +20 -0
  153. qd_evolve-0.1.0/tools/skills/self-improvement/scripts/error-detector.sh +55 -0
  154. qd_evolve-0.1.0/tools/skills/self-improvement/scripts/extract-skill.sh +203 -0
  155. qd_evolve-0.1.0/tools/skills/skill-creator/LICENSE.txt +202 -0
  156. qd_evolve-0.1.0/tools/skills/skill-creator/SKILL.md +485 -0
  157. qd_evolve-0.1.0/tools/skills/skill-creator/agents/analyzer.md +274 -0
  158. qd_evolve-0.1.0/tools/skills/skill-creator/agents/comparator.md +202 -0
  159. qd_evolve-0.1.0/tools/skills/skill-creator/agents/grader.md +223 -0
  160. qd_evolve-0.1.0/tools/skills/skill-creator/assets/eval_review.html +146 -0
  161. qd_evolve-0.1.0/tools/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  162. qd_evolve-0.1.0/tools/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  163. qd_evolve-0.1.0/tools/skills/skill-creator/references/schemas.md +430 -0
  164. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/__init__.py +0 -0
  165. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  166. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/generate_report.py +326 -0
  167. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/improve_description.py +247 -0
  168. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/package_skill.py +136 -0
  169. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/quick_validate.py +103 -0
  170. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/run_eval.py +310 -0
  171. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/run_loop.py +328 -0
  172. qd_evolve-0.1.0/tools/skills/skill-creator/scripts/utils.py +47 -0
  173. qd_evolve-0.1.0/uv.lock +3361 -0
@@ -0,0 +1,26 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ .mypy_cache/
4
+ *.py[oc]
5
+ build/
6
+ dist/
7
+ wheels/
8
+ *.egg-info/
9
+
10
+ # Virtual environments
11
+ .venv
12
+
13
+ # IDE
14
+ .vscode/
15
+
16
+ # Claude Code
17
+ .claude/
18
+
19
+ # Config and data (contain secrets)
20
+ config.json
21
+ *.db
22
+ logs/
23
+ .qd_evolve/
24
+
25
+ # Test artifacts
26
+ .coverage
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,54 @@
1
+ Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
2
+
3
+ Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
4
+
5
+ 1. Think Before Coding
6
+ Don't assume. Don't hide confusion. Surface tradeoffs.
7
+
8
+ Before implementing:
9
+
10
+ State your assumptions explicitly. If uncertain, ask.
11
+ If multiple interpretations exist, present them - don't pick silently.
12
+ If a simpler approach exists, say so. Push back when warranted.
13
+ If something is unclear, stop. Name what's confusing. Ask.
14
+ 2. Simplicity First
15
+ Minimum code that solves the problem. Nothing speculative.
16
+
17
+ No features beyond what was asked.
18
+ No abstractions for single-use code.
19
+ No "flexibility" or "configurability" that wasn't requested.
20
+ No error handling for impossible scenarios.
21
+ If you write 200 lines and it could be 50, rewrite it.
22
+ Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
23
+
24
+ 3. Surgical Changes
25
+ Touch only what you must. Clean up only your own mess.
26
+
27
+ When editing existing code:
28
+
29
+ Don't "improve" adjacent code, comments, or formatting.
30
+ Don't refactor things that aren't broken.
31
+ Match existing style, even if you'd do it differently.
32
+ If you notice unrelated dead code, mention it - don't delete it.
33
+ When your changes create orphans:
34
+
35
+ Remove imports/variables/functions that YOUR changes made unused.
36
+ Don't remove pre-existing dead code unless asked.
37
+ The test: Every changed line should trace directly to the user's request.
38
+
39
+ 4. Goal-Driven Execution
40
+ Define success criteria. Loop until verified.
41
+
42
+ Transform tasks into verifiable goals:
43
+
44
+ "Add validation" → "Write tests for invalid inputs, then make them pass"
45
+ "Fix the bug" → "Write a test that reproduces it, then make it pass"
46
+ "Refactor X" → "Ensure tests pass before and after"
47
+ For multi-step tasks, state a brief plan:
48
+
49
+ 1. [Step] → verify: [check]
50
+ 2. [Step] → verify: [check]
51
+ 3. [Step] → verify: [check]
52
+ Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
53
+
54
+ These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
@@ -0,0 +1,557 @@
1
+ # Design & Implementation
2
+
3
+ QD-Evolve is a multi-agent AI framework built on the belief that intelligence emerges, it isn't engineered. The design follows from the [manifesto](manifesto.md): give the model a minimal loop, a messy toolbox, and the ability to grow its own capabilities — then get out of the way.
4
+
5
+ ## Core Philosophy
6
+
7
+ **The model knows best.** Every design decision starts from the premise that the LLM — not the framework — should decide what to do, when to do it, and how. The framework's job is to provide capabilities, not prescribe strategies. We don't encode ReAct, Plan-and-Execute, or any other reasoning template. The loop is: reason → call tools → observe → repeat. That's it.
8
+
9
+ **Emergence over engineering.** Multi-agent collaboration has no preset roles, no voting protocols, no orchestrator. Agents discover each other, send messages, and self-organize. Memory has no forgetting curves or episodic structures — just save and recall. The model learns what to keep.
10
+
11
+ **Physical isolation as the security boundary.** Software permissions, sandboxes, and content filters are all guardrails that a sufficiently capable model can talk its way past. The only meaningful security boundary is whether the model can physically affect the world without a human in the loop.
12
+
13
+ ## Design Decisions and Trade-offs
14
+
15
+ ### No orchestration layer
16
+
17
+ There is no Planner, no Executor, no Critic. Agents are peers. The trade-off: emergent coordination is less predictable than scripted workflows. The bet: as models improve, emergent coordination outperforms hand-coded protocols, and the framework won't need to be rewritten to keep up.
18
+
19
+ ### No memory architecture
20
+
21
+ Save and recall is the entire memory surface. The trade-off: the model might miss important context that a sophisticated memory system would surface. The bet: the model's own attention mechanism is a better retrieval algorithm than any forgetting curve or episodic structure we could hard-code.
22
+
23
+ ### Thread-locked agent loop
24
+
25
+ Agents serialize concurrent calls with a lock rather than supporting parallel execution. The trade-off: slower under concurrent load. The bet: correctness matters more than throughput, and concurrent LLM calls to the same agent would corrupt shared state (message list, tool registrations, memory).
26
+
27
+ ### On-demand tool schemas
28
+
29
+ Tools start invisible to the model, revealed only when needed. The trade-off: extra round-trips when the model discovers it needs a tool. The bet: the prompt size savings (hundreds of tools × thousands of schema tokens) outweigh the latency of an extra `load_func` call.
30
+
31
+ ### One config file
32
+
33
+ No environment variables, no CLI config, no database-backed settings. The trade-off: less flexible for containerized deployment where env vars are idiomatic. The bet: simplicity and discoverability matter more for a framework meant to be understood and modified.
34
+
35
+ ### Physical isolation over software security
36
+
37
+ No sandbox, no permission system, no content filter. The trade-off: the model can do dangerous things if given dangerous tools. The design response: don't give it dangerous tools. The security boundary is what the model can physically reach — network access, filesystem access, process execution. A human presses the last button.
38
+
39
+ ## Invariants
40
+
41
+ These are the constraints that every change must preserve:
42
+
43
+ 1. **The agent loop is `reason → act → observe`.** No phases, no templates, no planning steps.
44
+ 2. **Agents compose by wrapping, not inheritance.** Each layer adds exactly one concern.
45
+ 3. **No more than one remote transport at a time.** In-process + HTTP, or in-process + MQTT, never both.
46
+ 4. **MQTT transport is sole-consumer.** Group chat gets its own transport connection.
47
+ 5. **Human and AI agents share the same protocol.** The transport layer doesn't distinguish them.
48
+ 6. **Memory is save + recall + process capture.** Each save records the full Q/A along with the tool call process (name, parameters, success/failure). No forgetting curves, no episodic structures, no automatic categorization.
49
+ 7. **Configuration is one file.** No env vars, no scattered config.
50
+ 8. **Security is physical, not digital.** No software permission system that the model could reason past.
51
+
52
+ ## Architecture
53
+
54
+ ```
55
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
56
+ │ Chat CLI │ │ A2A CLI │ │ MQTT CLI │ │ GChat CLI │
57
+ │ (in-proc) │ │ (HTTP/SSE) │ │ (MQTT v5) │ │ (MQTT v5) │
58
+ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
59
+ │ │ │ │
60
+ ▼ ▼ ▼ ▼
61
+ ┌──────────────────────────────────────────────────────────────────────┐
62
+ │ Agent Layer │
63
+ │ Agent ← A2AAgent ← MqttAgent ← GroupChatAgent | HumanAgent │
64
+ │ GroupChatWechatHuman (bridge) │
65
+ │ AgentRegistry | TransportRouter | EventSubscribers │
66
+ └──────────────────────────┬───────────────────────────────────────────┘
67
+
68
+ ┌──────────────────┼──────────────────┐
69
+ ▼ ▼ ▼
70
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
71
+ │ Provider │ │ Memory │ │ Toolbox │
72
+ │ Registry │ │ Store │ │ Registry │
73
+ └──────────┘ └──────────┘ └──────────┘
74
+ ```
75
+
76
+ ### Agent Loop
77
+
78
+ The central abstraction: `reason → act → observe`, repeated until the model produces a text response with no tool calls. No planning phase, no reflection phase. The loop is guarded by a single `threading.Lock` — one invocation per agent at a time to prevent concurrent corruption of messages and tool state.
79
+
80
+ ### Agent Hierarchy
81
+
82
+ Composition via wrapping, not inheritance:
83
+
84
+ - **Agent** (`agent/agent.py`): Pure LLM loop. Manages messages, memory recall, context compression, heartbeat, tool execution. Knows nothing of networks or other agents.
85
+ - **A2AAgent** (`agent/a2a_agent.py`): Wraps Agent, adds A2A identity (AgentCard, TaskStore), event subscriber fan-out.
86
+ - **MqttAgent** (`agent/mqtt_agent.py`): Wraps A2AAgent, adds MQTT v5 lifecycle (connect, LWT, subscribe, publish).
87
+ - **GroupChatAgent** (`agent/group_chat_agent.py`): Wraps MqttAgent, adds group chat — subscribes to `/chat` topics, deduplication, parallel `agent.run()`, group message publishing.
88
+ - **HumanAgent** (`agent/human_agent.py`): Implements AgentProtocol directly. No LLM, no tools, no memory. Returns `input_required`, completes asynchronously via webhook.
89
+ - **MqttHumanAgent** (`agent/mqtt_human_agent.py`): MQTT wrapper for HumanAgent.
90
+ - **GroupChatHuman** (`agent/group_chat_human.py`): Wraps MqttHumanAgent, adds terminal-based group chat UI.
91
+ - **GroupChatWechatHuman** (`agent/group_chat_wechat_human.py`): Wraps MqttHumanAgent, bridges WeChat iLink to MQTT group chat. Polls WeChat for incoming messages, forwards group responses back via WeChat.
92
+
93
+ ### Transport
94
+
95
+ `TransportRouter(inproc, remote)` — holds exactly two transports. Routes locally registered agents to in-process, unknown agents to remote (HTTP or MQTT, never both). Group chat uses an independent `GroupChatTransport` connection to keep the MqttTransport sole-consumer design intact.
96
+
97
+ ### Tool System
98
+
99
+ Five tool categories:
100
+
101
+ | Category | Location | Callable | Loading |
102
+ |----------|----------|----------|---------|
103
+ | System | `qd_evolve/tools/` | Yes | Auto-discovered, schema on demand |
104
+ | A2A | `qd_evolve/agent/a2a_tools.py` | Yes | Registered when A2A enabled |
105
+ | Func | `tools/func/` | Yes | Hot-loadable `.py` files |
106
+ | Skills | `tools/skills/` | No | SKILL.md, injected into prompt |
107
+ | CLI | `tools/cli/` | No | YAML definitions, via `run_shell` |
108
+
109
+ **On-demand loading**: tools start with name + description. Full schema loaded only when the model calls `load_func`/`load_skill`/`load_cli`. Tools move from unloaded to active for subsequent turns.
110
+
111
+ **Hot-loading**: install tools at runtime via `install_func`/`install_mcp`/`install_skill`. Staged in `.qd_evolve/staging/`, persisted via `register_*` to `config.json`.
112
+
113
+ **Bridge protocol**: `BridgeManager` auto-discovers bridge modules in `tools/bridge/_*.py`. Each bridge self-registers with discover/connect/disconnect functions. MCP bridge spawns subprocesses; OAT bridge imports packages in-process.
114
+
115
+ ### Memory
116
+
117
+ SQLite + `sqlite-vec` with BGE-M3 embeddings. Two operations: `save` (insert + embed) and `recall` (embed query → cosine similarity → top-k). Auto-recall queries memory before each LLM call, injecting results into the system prompt. Deduplicated across turns via `RecalledMemoryRegistry`. Context compression truncates old Q/A pairs when tokens exceed threshold.
118
+
119
+ **Process capture**: `save()` accepts an optional `process` string recording each tool call in the iteration chain — name, parameters, and success/failure (tool results excluded). This enriches the `content` field for semantic recall without schema changes.
120
+
121
+ ### Multi-Agent Communication
122
+
123
+ Two mechanisms: **direct tasking** (send task → lifecycle: submitted→working→completed/failed/canceled/input_required) and **group chat** (all agents subscribe to `/chat` topics, `@mentions` direct attention, no coordinator). Built on A2A v1.0: agent discovery, task management, SSE streaming, push notifications.
124
+
125
+ ### Configuration
126
+
127
+ Single `config.json` file. No env vars. Each agent gets its own provider, model, memory DB, server binding, and toolbox state. Global defaults as fallback. `provider: "human"` identifies terminal human agents; `provider: "wechat-human"` identifies WeChat iLink bridge human agents. WeChat human agents persist their session token in the `wechat_session` field.
128
+
129
+ ### Templates
130
+
131
+ Jinja2 system prompts with two-tier fallback: `templates/` (user) overrides `_templates/` (builtin). Mode-specific templates (single-agent, A2A, MQTT, group chat), each including a shared `_system_tail.j2`.
132
+
133
+ ### Heartbeat
134
+
135
+ `asyncio.Event.wait(timeout)` — only fires on genuine idle. `touch_heartbeat()` resets the timer on activity. LLM response `"."` means stay silent. Mode-specific templates. `0` disables.
136
+
137
+ ## Module Map
138
+
139
+ ```
140
+ qd_evolve/
141
+ ├── __main__.py # CLI entry (typer), subcommand registration
142
+ ├── chat_cli.py # Single-agent chat loop
143
+ ├── a2a_cli.py # A2A multi-agent chat + serve
144
+ ├── mqtt_cli.py # MQTT multi-agent chat + serve
145
+ ├── gchat_cli.py # Group chat
146
+ ├── cli_utils.py # ReplayInput, TeeWriter, AGENT_COLORS
147
+ ├── skills.py # SkillRegistry
148
+ ├── cli_tools.py # CLIRegistry
149
+ ├── toolbox_tui.py # Textual TUI for toolbox management
150
+ ├── memory_tui.py # Textual TUI for memory browsing and search
151
+ ├── core/
152
+ │ ├── config.py # Settings, AgentEntry, ServerConfig, MqttConfig (pydantic)
153
+ │ ├── providers.py # Provider + ProviderRegistry
154
+ │ ├── registry.py # ToolRegistry + ToolDef (on-demand loading)
155
+ │ ├── memory.py # MemoryStore (SQLite + sqlite-vec), RecalledMemoryRegistry
156
+ │ ├── prompts.py # PromptTemplateManager (Jinja2, two-tier fallback)
157
+ │ ├── toolbox.py # Per-agent tool state (enabled/preload/disabled)
158
+ │ └── logger.py # SharedFileHandler
159
+ ├── agent/
160
+ │ ├── agent.py # Agent — LLM loop, tool exec, memory, compression, heartbeat
161
+ │ ├── a2a_agent.py # A2AAgent — wraps Agent, adds card + task_store + event fan-out
162
+ │ ├── mqtt_agent.py # MqttAgent — wraps A2AAgent, MQTT v5 lifecycle
163
+ │ ├── group_chat_agent.py # GroupChatAgent — wraps MqttAgent, group chat behavior
164
+ │ ├── group_chat_human.py # GroupChatHuman — wraps MqttHumanAgent, terminal group UI
165
+ │ ├── group_chat_wechat_human.py # GroupChatWechatHuman — WeChat iLink ↔ MQTT bridge
166
+ │ ├── group_chat_transport.py # GroupChatTransport — independent MQTT for /chat topics
167
+ │ ├── human_agent.py # HumanAgent — implements AgentProtocol directly, no LLM
168
+ │ ├── mqtt_human_agent.py # MQTT wrapper for HumanAgent
169
+ │ ├── server.py # A2AServer — aiohttp JSON-RPC + SSE endpoint
170
+ │ ├── transport.py # InprocTransport, HttpTransport, TransportRouter
171
+ │ ├── mqtt_transport.py # MqttTransport — sole-consumer MQTT v5
172
+ │ ├── registry.py # AgentRegistry — singleton, local agent lookup
173
+ │ ├── loader.py # init_process + create_agent factory
174
+ │ ├── a2a_tools.py # delegate_to, send_task, get_task, cancel_task
175
+ │ ├── protocol.py # AgentProtocol ABC (card, task_store, run, subscribe_events)
176
+ │ └── a2a.py # A2A v1.0 data models (Task, Message, AgentCard, etc.)
177
+ ├── tools/
178
+ │ ├── __init__.py # Re-exports ToolRegistry
179
+ │ ├── tool_loader.py # load_func — on-demand func tool schema loading
180
+ │ ├── skill_loader.py # load_skill — on-demand skill content loading
181
+ │ ├── cli_loader.py # load_cli — on-demand CLI tool detail loading
182
+ │ ├── install_func.py # install_func — hot-load func tool
183
+ │ ├── install_mcp.py # install_mcp — hot-load MCP server
184
+ │ ├── install_skill.py # install_skill — hot-load skill
185
+ │ ├── register_func.py # register_func — persist func tool
186
+ │ ├── register_mcp.py # register_mcp — persist MCP server
187
+ │ ├── register_skill.py # register_skill — persist skill
188
+ │ ├── recall_memory.py # recall_memory — search memory
189
+ │ └── staging.py # .qd_evolve/staging/ directory helpers
190
+ ├── bridge/
191
+ │ ├── __init__.py
192
+ │ └── wechat_clawbot_client.py # WeChatClawbotClient — iLink ClawBot protocol
193
+ ├── utils/
194
+ │ ├── adk_schema.py # Google ADK → OpenAI JSON Schema
195
+ │ └── adk_output.py # ADK output normalization
196
+ └── _templates/ # Builtin Jinja2 templates
197
+ ├── default.j2 # Single-agent system prompt
198
+ ├── a2a-default.j2 # A2A system prompt
199
+ ├── mqtt-default.j2 # MQTT system prompt
200
+ ├── group-default.j2 # Group chat system prompt
201
+ ├── group-heartbeat.j2 # Group chat heartbeat
202
+ ├── group-message.j2 # Group chat incoming message format
203
+ ├── heartbeat.j2 # Single-agent heartbeat
204
+ ├── a2a-heartbeat.j2 # A2A heartbeat
205
+ ├── mqtt-heartbeat.j2 # MQTT heartbeat
206
+ └── _system_tail.j2 # Shared tail (included by all templates)
207
+ ```
208
+
209
+ ## Class Hierarchy
210
+
211
+ ```
212
+ AgentProtocol (Protocol)
213
+ ├── A2AAgent — wraps Agent, adds A2A identity + event fan-out
214
+ │ └── MqttAgent — wraps A2AAgent, adds MQTT v5 lifecycle
215
+ │ └── GroupChatAgent — wraps MqttAgent, adds group chat behavior
216
+ └── HumanAgent — no LLM, no tools, no memory; async completion via webhook
217
+ └── MqttHumanAgent — MQTT wrapper for HumanAgent
218
+
219
+ Group chat wrappers (composition over MqttHumanAgent):
220
+ ├── GroupChatHuman — terminal-based group chat for human agents
221
+ └── GroupChatWechatHuman — WeChat iLink ↔ MQTT bidirectional bridge
222
+ ```
223
+
224
+ Composition, not inheritance. Each wrapper delegates to the inner agent and adds exactly one concern.
225
+
226
+ ## Model Layer (pydantic)
227
+
228
+ All config models live in `qd_evolve/core/config.py`:
229
+
230
+ | Model | Purpose |
231
+ |-------|---------|
232
+ | `Settings` | Root config. Providers, agents, memory, thresholds, stream, heartbeat. |
233
+ | `AgentEntry` | Per-agent: name, description, provider, model, memory_db, server, toolbox, mqtt. `is_human` is `provider == "human"`. |
234
+ | `ProviderConfig` | API key, base URL, api type (openai-completions/openai-response/anthropic), models. |
235
+ | `ModelConfig` | Context window, max_tokens, reasoning flag, cost tracking. |
236
+ | `ServerConfig` | host, port. |
237
+ | `MqttConfig` | Per-agent MQTT: username, password, keepalive, TLS paths. |
238
+ | `MqttBrokerConfig` | Broker host, port, will_delay_interval. |
239
+ | `ToolboxSection` | Per-agent tool state: five dicts mapping name→state. |
240
+ | `MCPServerConfig` | MCP server: command, args, env, type (stdio/sse/http/ws), url, headers, timeout. |
241
+ | `EmbeddingsBackend` | model_path, dim, backend (sentence-transformers/llama-cpp-python). |
242
+ | `MemorySearchConfig` | auto_recall, auto_recall_top_k, recall_memory_limit. |
243
+
244
+ Validation: `AgentsConfig._validate_ports` rejects duplicate ports at model init.
245
+
246
+ A2A data models in `qd_evolve/agent/a2a.py`: `AgentCard`, `Task`, `TaskStatus`, `TaskState` (enum), `Message`, `Part`, `StreamResponse`, `AgentCapabilities`, `AgentSkill`, `FileContent`, `AgentExtension`.
247
+
248
+ ## Agent Loop (`Agent._run_inner`)
249
+
250
+ ```
251
+ _run_inner(user_input, system, provider, model):
252
+ 1. Resolve provider/model (arg → instance → config default)
253
+ 2. Append user message to self.messages
254
+ 3. Auto-recall: query memory, inject into system prompt
255
+ 4. Loop:
256
+ a. Create API client (openai or anthropic)
257
+ b. Build tool definitions from registry (active + preload)
258
+ c. Call LLM (dispatch by api_type)
259
+ d. If text response → save to memory (with process capture), compress, return
260
+ e. If tool calls → record name/params/success via _record_tool_call(), execute via ToolRegistry.call(), append results, continue
261
+ f. If max_iterations exceeded → return error
262
+ ```
263
+
264
+ The entire loop is guarded by `threading.Lock` (`_run_lock`). Only one `run()` per agent at a time.
265
+
266
+ ### API Dispatch
267
+
268
+ Three code paths in `Agent`:
269
+
270
+ - `_run_anthropic()` — `client.messages.create()` with Anthropic SDK. Tool use via `stop_reason == "tool_use"`. Content blocks contain `tool_use` items.
271
+ - `_run_openai_completion()` — `client.chat.completions.create()` with OpenAI SDK. Tool calls via `msg.tool_calls`. Supports streaming (`stream=True`) with reasoning content for reasoning models.
272
+ - `_run_openai_response()` — OpenAI Responses API. Separate code path for the newer API shape.
273
+
274
+ Each path recursively calls itself for tool turns, incrementing an `_iter` counter checked against `max_iterations`.
275
+
276
+ `api_type` is mapped from config's `api` field: `openai-completions` → `openai_completion`, `openai-response` → `openai_response`, `anthropic` → `anthropic`.
277
+
278
+ ### Tool Execution
279
+
280
+ `ToolRegistry.call(name, **kwargs)` spawns a daemon thread with a `DEFAULT_TOOL_TIMEOUT` (60s) timeout. If the thread is still alive after timeout, returns an error string. Exceptions are caught and formatted into error messages. `ImportError` is re-raised (not caught) to surface missing dependencies.
281
+
282
+ ## Initialization Flow
283
+
284
+ ### Per-process (`init_process`)
285
+
286
+ Called once. Sets up module-level singletons:
287
+
288
+ 1. `SkillRegistry` — scans `tools/skills/` for SKILL.md files
289
+ 2. `CLIRegistry` — scans `tools/cli/` for YAML definitions
290
+ 3. `BridgeManager.connect_all()` — auto-discovers bridge modules in `tools/bridge/_*.py`, calls each bridge's `discover()` then `connect()`
291
+ 4. Injects registries into loader tools (`skill_loader`, `install_skill`, `cli_loader`)
292
+
293
+ ### Per-agent (`create_agent`)
294
+
295
+ Called for each agent. Returns a fully initialized agent:
296
+
297
+ 1. Lookup `AgentEntry` from config by name
298
+ 2. **Human short-circuit**: if `entry.is_human`, create `HumanAgent` (or `MqttHumanAgent` if MQTT mode), return immediately
299
+ 3. Resolve singletons: `ToolRegistry`, `ProviderRegistry`, `SkillRegistry`, `CLIRegistry`
300
+ 4. Apply per-agent toolbox state (enabled/preload/disabled)
301
+ 5. Register A2A tools if >1 agent and not group chat
302
+ 6. Build system prompt via `PromptTemplateManager.render()` with template variable injection
303
+ 7. Create `MemoryStore` if `memory_db` is configured
304
+ 8. Create `Agent` instance
305
+ 9. Resolve provider/model (agent-specific → global default)
306
+ 10. Wrap with `A2AAgent` if multi-agent, then `MqttAgent` if MQTT mode
307
+ 11. Return agent
308
+
309
+ ### Template Resolution
310
+
311
+ `create_agent` chains template name lookups:
312
+
313
+ 1. If group chat: try `group-{template}`
314
+ 2. If MQTT: try `mqtt-{template}` → fallback to `a2a-{template}` if multi-agent
315
+ 3. If A2A only: try `a2a-{template}`
316
+ 4. GChat fallback: try `gchat-{template}`
317
+ 5. Default: `{template}` (usually `default`)
318
+
319
+ `PromptTemplateManager` uses a `_CombinedLoader` that checks `templates/` first (user overrides), then `_templates/` (builtins). Jinja2 with `trim_blocks=True`, `lstrip_blocks=True`.
320
+
321
+ ## Transport
322
+
323
+ ### TransportRouter
324
+
325
+ `TransportRouter(inproc, remote)` — holds exactly two transports. Routes to `inproc` for locally registered agents, to `remote` for unknown agents. `remote` is either `HttpTransport` or `MqttTransport`, never both.
326
+
327
+ ### InprocTransport
328
+
329
+ Direct call to `Agent.run()` via thread pool (`asyncio.to_thread`). For human agents, uses async path: `receive_task()` → returns `input_required` immediately.
330
+
331
+ ### HttpTransport
332
+
333
+ A2A JSON-RPC over HTTP. `POST /` with JSON-RPC body. Connects to remote agent's `A2AServer`. Supports SSE streaming for `message/stream`.
334
+
335
+ ### MqttTransport
336
+
337
+ Implements `A2ATransport` over MQTT v5. Key features:
338
+
339
+ - **Request-response correlation**: MQTT v5 Response Topic + Correlation Data. Caller sets response topic, callee publishes result there.
340
+ - **Discovery**: Retained `AgentCard` on `$a2a/v1/discovery/{name}`. LWT clears it on disconnect.
341
+ - **Sole consumer**: `_listen_all()` subscribes to `$a2a/v1/response/{self}/+` and `$a2a/v1/event/{self}`. Only one consumer per connection.
342
+ - **QoS**: Task requests at QoS 1, events at QoS 0, discovery at QoS 1.
343
+
344
+ Topic structure:
345
+
346
+ | Topic | QoS | Retained | Purpose |
347
+ |-------|-----|----------|---------|
348
+ | `$a2a/v1/discovery/{agent}` | 1 | Yes + LWT | AgentCard, online/offline |
349
+ | `$a2a/v1/request/{agent}` | 1 | No | JSON-RPC requests |
350
+ | `$a2a/v1/response/{agent}/{req_id}` | 1 | No | MQTT v5 Response Topic |
351
+ | `$a2a/v1/event/{agent}` | 0 | No | Streaming + push notifications |
352
+ | `$a2a/v1/group/{name}/chat` | 0 | No | Group chat messages |
353
+
354
+ ## Tool System
355
+
356
+ ### ToolRegistry (`qd_evolve/core/registry.py`)
357
+
358
+ Central registry of `ToolDef` objects. Each `ToolDef` has: `name`, `description`, `handler` (callable), `input_schema` (JSON Schema dict), `enabled` (bool).
359
+
360
+ `definitions(api_format, active_tools)` produces tool schemas in the target API format. Only returns schemas for tools whose names are in `active_tools` — this is the on-demand loading mechanism.
361
+
362
+ ### On-Demand Loading
363
+
364
+ Three loader tools implement the pattern:
365
+
366
+ 1. **Func tools** (`tool_loader.py`): `load_func(name)` — imports the module, extracts `get_input_schema()` + `run()`, registers full schema in ToolRegistry, adds name to `_active_tools`
367
+ 2. **Skills** (`skill_loader.py`): `load_skill(name)` — reads SKILL.md content, injects into system prompt by appending to messages, adds to `_loaded_skill_names`
368
+ 3. **CLI tools** (`cli_loader.py`): `load_cli(name)` — reads YAML, formats as system prompt injection, adds to `_loaded_cli_names`
369
+
370
+ ### Bridge System (`tools/bridge/`)
371
+
372
+ Self-registering plugin architecture:
373
+
374
+ - `BridgeManager` scans `tools/bridge/_*.py` for bridge modules
375
+ - Each module calls `BridgeManager.register(name, discover, connect, disconnect)`
376
+ - Each bridge's `discover()` returns config objects; `connect()` creates `Bridge` instances and registers tools
377
+
378
+ **MCP bridge** (`_mcp.py`): Scans `tools/mcp/*.json` + `.qd_evolve/staging/mcp/*.json`. Spawns subprocess via `mcp` SDK, discovers tools via `list_tools`, registers in ToolRegistry. Supports stdio, SSE, StreamableHTTP, WebSocket transports.
379
+
380
+ **OAT bridge** (`_oat.py`): Reads `tools/bridge/oat.json`. Imports Python packages directly, wraps functions as ToolRegistry handlers. No subprocess overhead. Schema conversion via `adk_schema.py` (Google ADK → OpenAI JSON Schema) and output normalization via `adk_output.py`.
381
+
382
+ ### Hot-Loading
383
+
384
+ Five install/register pairs:
385
+
386
+ | Install (staging) | Register (persist) | Target |
387
+ |-------------------|-------------------|--------|
388
+ | `install_func` | `register_func` | `tools/func/*.py` |
389
+ | `install_mcp` | `register_mcp` | `tools/mcp/*.json` |
390
+ | `install_skill` | `register_skill` | `tools/skills/*/SKILL.md` |
391
+
392
+ Install writes to `.qd_evolve/staging/`, register copies from staging to the target directory and updates `config.json` toolbox state. Both work at runtime without restart.
393
+
394
+ ### Toolbox State
395
+
396
+ `qd_evolve/core/toolbox.py` manages per-agent tool state in `config.json`:
397
+
398
+ - **enabled**: Tool is callable, schema starts unloaded
399
+ - **preload**: Tool is callable, schema loaded at startup
400
+ - **disabled**: Tool is invisible to the agent
401
+
402
+ Five sections: `tools`, `mcp_servers`, `bridge`, `cli`, `skills`. Bridge uses binary enabled/disabled (no preload). Managed via `qd-evolve toolbox` (Textual TUI) or direct `config.json` editing.
403
+
404
+ ## Memory
405
+
406
+ ### MemoryStore (`qd_evolve/core/memory.py`)
407
+
408
+ SQLite + `sqlite-vec` extension. Two tables:
409
+
410
+ - `conversations`: session_id, user_msg, assistant_msg, content (combined), accessed_at, access_count
411
+ - Vector index on `content` via `sqlite-vec` with BGE-M3 embeddings
412
+
413
+ `save(user_msg, assistant_msg)`: Inserts row, creates vector embedding.
414
+
415
+ `recall(query, limit)`: Embeds query, cosine similarity search, returns top-k `MemoryEntry` objects.
416
+
417
+ `new_session()`: Generates new session_id.
418
+
419
+ ### Auto-Recall
420
+
421
+ Before each LLM call, `Agent._auto_recall()` queries memory with the user input. Results are deduplicated via `RecalledMemoryRegistry` (tracks seen IDs across turns). Deduped entries are injected into the system prompt under a `## Relevant Past Conversations` section.
422
+
423
+ ### Context Compression
424
+
425
+ `Agent._compress_messages()` fires when `last_input_tokens / context_window > compress_threshold` (default 0.7). Removes oldest user/assistant/tool triples from the front of `self.messages` until estimated tokens drop below `target_threshold * context_window` (default 0.5). Simple truncation, no summarization.
426
+
427
+ ### Embeddings Backend
428
+
429
+ Two backends: `sentence-transformers` (BGE-M3 via `SentenceTransformer`) and `llama-cpp-python` (local GGUF models). Configured per-backend in `embeddings_backends` config section.
430
+
431
+ ## Event System
432
+
433
+ ### Agent Events
434
+
435
+ `Agent._on_event` callback fires on: iteration start, status update, print output, error, completion, heartbeat, heartbeat_silent. Event dict has `type` + relevant fields.
436
+
437
+ `A2AAgent._push_event()` fans out to all subscribers (list of `asyncio.Queue`). Subscribers get events via `subscribe_events() → Queue`.
438
+
439
+ ### SSE Streaming
440
+
441
+ `A2AServer` converts events to SSE `StreamResponse` objects. `message/stream` returns an async generator of SSE events. Custom metadata includes iteration number, status, token counts, heartbeat.
442
+
443
+ ### MQTT Event Publishing
444
+
445
+ `MqttAgent` runs an `_event_pusher_task` that drains the event queue and publishes each event to `$a2a/v1/event/{agent_name}` as JSON.
446
+
447
+ ## Heartbeat
448
+
449
+ `Agent.start_heartbeat_loop()` creates an asyncio task that:
450
+
451
+ 1. `await asyncio.wait_for(self._hb_event.wait(), timeout=idle_seconds)`
452
+ 2. If event was set → activity occurred, reset, don't fire
453
+ 3. If timeout → genuine idle, call `heartbeat_check()` via thread pool
454
+
455
+ `touch_heartbeat()` sets the event. Called on user input before each LLM call.
456
+
457
+ Heartbeat response handling: if LLM returns `"."`, stays silent. Otherwise, the response is pushed as a heartbeat event. Configurable `heartbeat_idle_seconds` per agent; `0` disables.
458
+
459
+ ## Group Chat
460
+
461
+ ### GroupChatAgent
462
+
463
+ Wraps `MqttAgent`. Adds:
464
+
465
+ - **Group listener**: background asyncio task subscribes to `$a2a/v1/group/{member}/chat` for each member via `GroupChatTransport`
466
+ - **Deduplication**: `_seen_msg_ids` set, tracks message IDs to skip duplicates
467
+ - **Parallel processing**: incoming messages trigger `agent.run()` in thread pool; multiple messages can process concurrently (locking handled by Agent's `_run_lock`)
468
+ - **Response publishing**: formatted responses published to the group topic
469
+ - **Heartbeat override**: uses `group-heartbeat.j2` template; fires only when no recent group activity
470
+
471
+ ### GroupChatTransport
472
+
473
+ Independent `aiomqtt.Client` connection for `/chat` topics. Keeps `MqttTransport`'s sole-consumer `_listen_all()` design intact by not adding subscriptions to the original client.
474
+
475
+ ### Message Format
476
+
477
+ Incoming group messages formatted via `group-message.j2` template. `@mentions` parsed to determine if the message is addressed to this agent. `@all` matches everyone.
478
+
479
+ ### GroupChatHuman
480
+
481
+ Wraps `MqttHumanAgent`. Provides an interactive terminal UI: incoming group messages appear above the prompt line, preserving partial input. Keyboard input is published to the group via `publish_human_input()`.
482
+
483
+ ### GroupChatWechatHuman
484
+
485
+ Wraps `MqttHumanAgent`. Replaces terminal I/O with a WeChat iLink bidirectional bridge:
486
+
487
+ - **WeChat → MQTT**: Long-polls WeChat for new messages via `WechatClawbotClient.poll_updates()`, extracts text, parses `@mentions`, publishes to group MQTT topic
488
+ - **MQTT → WeChat**: Listens to all group messages, forwards to the WeChat user via `WechatClawbotClient.send_message()`, using the last `context_token` from the polled message
489
+
490
+ QR login on startup. Session token is persisted to `config.json` (`wechat_session` field) for reuse across restarts (valid for ~23 hours).
491
+
492
+ ### WechatClawbotClient (`qd_evolve/bridge/wechat_clawbot_client.py`)
493
+
494
+ Standalone async client for the WeChat iLink ClawBot protocol. Extracted from SiverKing/weixin-ClawBot-API (MIT License). Handles: QR code login flow (terminal rendering), session persistence (`get_session_dict` / `try_restore_session`), message polling (`/ilink/bot/getupdates`), message sending (`/ilink/bot/sendmessage`), typing indicators. No dependencies on external WeChat libraries — uses only `aiohttp` + stdlib.
495
+
496
+ ## CLI Layer
497
+
498
+ ### Entry Point
499
+
500
+ `qd_evolve/__main__.py` registers typer subcommands: default (chat), `a2a`, `mqtt`, `gchat`, `toolbox`, `memory`.
501
+
502
+ ### Chat Loop Pattern (all CLIs)
503
+
504
+ ```
505
+ while True:
506
+ read user input (prompt_toolkit / replay)
507
+ if slash command → handle locally
508
+ if EOF / /quit → break
509
+ touch heartbeat
510
+ agent.run(input) → display response
511
+ ```
512
+
513
+ ### Slash Commands
514
+
515
+ Parsed in CLI layer, never sent to LLM. Each command is a string match before `agent.run()`. Commands: `/models`, `/agents`, `/tools`, `/skills`, `/cli`, `/status`, `/memory`, `/recall`, `/compress`, `/load`, `/reset`, `/clear`, `/help`, `/quit`.
516
+
517
+ ### Replay Mode
518
+
519
+ `ReplayInput` reads lines from a file, feeds them as user input. `TeeWriter` captures output to a file. Used for automated testing.
520
+
521
+ ## Dependencies
522
+
523
+ Core runtime: `anthropic`, `openai`, `pydantic`, `typer`, `rich`, `jinja2`, `pyyaml`, `sqlite-vec`, `prompt-toolkit`, `sentence-transformers` (or `llama-cpp-python`), `numpy`, `aiohttp`, `aiomqtt`, `mcp`, `textual`, `basic-open-agent-tools`, `coding-open-agent-tools`.
524
+
525
+ Bridge extras: `defusedxml` (coding-open-agent-tools dep), `tomlkit`, `markdown-pdf`.
526
+
527
+ Test: `pytest`, `pytest-asyncio`, `pytest-aiohttp`, `pytest-cov`, `pytest-mock`, `pytest-timeout`, `pytest-xdist`, `aioresponses`.
528
+
529
+ Python ≥ 3.13 required.
530
+
531
+ ## Key Patterns
532
+
533
+ ### Singleton via Module-Level Variable
534
+
535
+ Used by: `AgentRegistry`, `SkillRegistry`, `CLIRegistry`, `BridgeManager`, A2A `_transport`/`_task_store`.
536
+
537
+ Pattern: `_instance: T | None = None` at module level, `get_*()` returns it or raises, `set_*()` injects it. Avoids DI framework overhead.
538
+
539
+ ### Callback Injection
540
+
541
+ Agent has three callbacks: `_on_status` (status bar), `_on_print` (output), `_on_event` (structured events). Set via setter methods. A2AAgent hooks `_on_event` to fan out to subscribers. CLIs set callbacks after agent creation.
542
+
543
+ ### Tool Timeout via Daemon Thread
544
+
545
+ `ToolRegistry.call()` spawns a `threading.Thread(daemon=True)`, joins with timeout. If thread is alive after timeout, returns error. Avoids blocking the agent loop on hung tools.
546
+
547
+ ### Lazy Import at Call Site
548
+
549
+ Common pattern: imports inside function bodies rather than at module top. Avoids circular imports between `agent/` and `core/`. Seen in: `InprocTransport._get_registry()`, `Agent._create_memory()`, `create_agent()`.
550
+
551
+ ### Pydantic for All Config
552
+
553
+ Every config structure is a `pydantic.BaseModel`. No `dict.get()` with fallbacks — fields have explicit defaults in the model definition. Validation at model init catches config errors early.
554
+
555
+ ### Jinja2 Two-Tier Templates
556
+
557
+ `PromptTemplateManager` checks `templates/` (user) first, falls back to `_templates/` (builtin). Templates are `.j2` files. Render context includes agent metadata, tool summaries, OS info, available agents, topology.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 juzcn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.