diploid-agent 0.6.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 (180) hide show
  1. diploid_agent-0.6.0/LICENSE +21 -0
  2. diploid_agent-0.6.0/PKG-INFO +323 -0
  3. diploid_agent-0.6.0/README.md +291 -0
  4. diploid_agent-0.6.0/pyproject.toml +58 -0
  5. diploid_agent-0.6.0/setup.cfg +4 -0
  6. diploid_agent-0.6.0/src/diploid_agent/__init__.py +7 -0
  7. diploid_agent-0.6.0/src/diploid_agent/acp_client/__init__.py +35 -0
  8. diploid_agent-0.6.0/src/diploid_agent/acp_client/client.py +1370 -0
  9. diploid_agent-0.6.0/src/diploid_agent/acp_client/control.py +290 -0
  10. diploid_agent-0.6.0/src/diploid_agent/acp_client/errors.py +99 -0
  11. diploid_agent-0.6.0/src/diploid_agent/acp_client/lifecycle.py +265 -0
  12. diploid_agent-0.6.0/src/diploid_agent/acp_client/sandbox.py +301 -0
  13. diploid_agent-0.6.0/src/diploid_agent/acp_client/transport.py +746 -0
  14. diploid_agent-0.6.0/src/diploid_agent/acp_client/types.py +52 -0
  15. diploid_agent-0.6.0/src/diploid_agent/acp_client/utils.py +56 -0
  16. diploid_agent-0.6.0/src/diploid_agent/acp_client/watchdog.py +277 -0
  17. diploid_agent-0.6.0/src/diploid_agent/config.py +582 -0
  18. diploid_agent-0.6.0/src/diploid_agent/context/__init__.py +5 -0
  19. diploid_agent-0.6.0/src/diploid_agent/context/builder.py +1633 -0
  20. diploid_agent-0.6.0/src/diploid_agent/dispatch.py +203 -0
  21. diploid_agent-0.6.0/src/diploid_agent/engine/__init__.py +13 -0
  22. diploid_agent-0.6.0/src/diploid_agent/engine/acp.py +326 -0
  23. diploid_agent-0.6.0/src/diploid_agent/engine/base.py +120 -0
  24. diploid_agent-0.6.0/src/diploid_agent/engine/factory.py +32 -0
  25. diploid_agent-0.6.0/src/diploid_agent/engine/fake.py +89 -0
  26. diploid_agent-0.6.0/src/diploid_agent/engine/router.py +99 -0
  27. diploid_agent-0.6.0/src/diploid_agent/harness.py +29 -0
  28. diploid_agent-0.6.0/src/diploid_agent/mcp.py +143 -0
  29. diploid_agent-0.6.0/src/diploid_agent/mcp_stdio.py +113 -0
  30. diploid_agent-0.6.0/src/diploid_agent/memory.py +1584 -0
  31. diploid_agent-0.6.0/src/diploid_agent/memory_mcp.py +252 -0
  32. diploid_agent-0.6.0/src/diploid_agent/metrics.py +79 -0
  33. diploid_agent-0.6.0/src/diploid_agent/models.py +215 -0
  34. diploid_agent-0.6.0/src/diploid_agent/notifier.py +301 -0
  35. diploid_agent-0.6.0/src/diploid_agent/persona_composer.py +103 -0
  36. diploid_agent-0.6.0/src/diploid_agent/plan/__init__.py +6 -0
  37. diploid_agent-0.6.0/src/diploid_agent/plan/manager.py +279 -0
  38. diploid_agent-0.6.0/src/diploid_agent/plan/models.py +103 -0
  39. diploid_agent-0.6.0/src/diploid_agent/plugin_incidents.py +58 -0
  40. diploid_agent-0.6.0/src/diploid_agent/plugin_sandbox.py +200 -0
  41. diploid_agent-0.6.0/src/diploid_agent/plugins/__init__.py +19 -0
  42. diploid_agent-0.6.0/src/diploid_agent/plugins/base.py +398 -0
  43. diploid_agent-0.6.0/src/diploid_agent/plugins/broken.py +16 -0
  44. diploid_agent-0.6.0/src/diploid_agent/plugins/contexts.py +269 -0
  45. diploid_agent-0.6.0/src/diploid_agent/plugins/json_state.py +61 -0
  46. diploid_agent-0.6.0/src/diploid_agent/plugins/manager.py +1087 -0
  47. diploid_agent-0.6.0/src/diploid_agent/runtime/__init__.py +9 -0
  48. diploid_agent-0.6.0/src/diploid_agent/runtime/actions.py +519 -0
  49. diploid_agent-0.6.0/src/diploid_agent/runtime/agent_runtime.py +1873 -0
  50. diploid_agent-0.6.0/src/diploid_agent/runtime/config_manager.py +382 -0
  51. diploid_agent-0.6.0/src/diploid_agent/runtime/event_bus.py +86 -0
  52. diploid_agent-0.6.0/src/diploid_agent/runtime/events.py +39 -0
  53. diploid_agent-0.6.0/src/diploid_agent/runtime/instance.py +219 -0
  54. diploid_agent-0.6.0/src/diploid_agent/runtime/mcp_skills.py +217 -0
  55. diploid_agent-0.6.0/src/diploid_agent/runtime/metrics.py +289 -0
  56. diploid_agent-0.6.0/src/diploid_agent/runtime/outbox.py +161 -0
  57. diploid_agent-0.6.0/src/diploid_agent/runtime/planning.py +171 -0
  58. diploid_agent-0.6.0/src/diploid_agent/runtime/plugin_runtime.py +82 -0
  59. diploid_agent-0.6.0/src/diploid_agent/runtime/plugins.py +321 -0
  60. diploid_agent-0.6.0/src/diploid_agent/runtime/prompts.py +402 -0
  61. diploid_agent-0.6.0/src/diploid_agent/runtime/store.py +209 -0
  62. diploid_agent-0.6.0/src/diploid_agent/runtime/subagent.py +375 -0
  63. diploid_agent-0.6.0/src/diploid_agent/runtime/timer_service.py +77 -0
  64. diploid_agent-0.6.0/src/diploid_agent/runtime/turn_controller.py +5 -0
  65. diploid_agent-0.6.0/src/diploid_agent/runtime/wake_queue.py +230 -0
  66. diploid_agent-0.6.0/src/diploid_agent/skills.py +347 -0
  67. diploid_agent-0.6.0/src/diploid_agent/task/__init__.py +6 -0
  68. diploid_agent-0.6.0/src/diploid_agent/task/engine.py +343 -0
  69. diploid_agent-0.6.0/src/diploid_agent/task/worker.py +81 -0
  70. diploid_agent-0.6.0/src/diploid_agent/telegram_ingress.py +8 -0
  71. diploid_agent-0.6.0/src/diploid_agent/telegram_poll.py +20 -0
  72. diploid_agent-0.6.0/src/diploid_agent/testing/__init__.py +1 -0
  73. diploid_agent-0.6.0/src/diploid_agent/testing/fake_runtime.py +203 -0
  74. diploid_agent-0.6.0/src/diploid_agent/transport/__init__.py +10 -0
  75. diploid_agent-0.6.0/src/diploid_agent/transport/base.py +253 -0
  76. diploid_agent-0.6.0/src/diploid_agent/transport/command_handler.py +150 -0
  77. diploid_agent-0.6.0/src/diploid_agent/transport/http/__init__.py +5 -0
  78. diploid_agent-0.6.0/src/diploid_agent/transport/http/app.py +127 -0
  79. diploid_agent-0.6.0/src/diploid_agent/transport/http/models.py +328 -0
  80. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/__init__.py +29 -0
  81. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/chat.py +139 -0
  82. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/config.py +216 -0
  83. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/health.py +59 -0
  84. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/mesh.py +71 -0
  85. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/models.py +94 -0
  86. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/plans.py +119 -0
  87. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/plugins.py +261 -0
  88. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/runtime.py +53 -0
  89. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/sessions.py +126 -0
  90. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/skills.py +77 -0
  91. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/state.py +93 -0
  92. diploid_agent-0.6.0/src/diploid_agent/transport/http/routes/webhook.py +53 -0
  93. diploid_agent-0.6.0/src/diploid_agent/transport/http/utils.py +48 -0
  94. diploid_agent-0.6.0/src/diploid_agent/transport/ingress.py +64 -0
  95. diploid_agent-0.6.0/src/diploid_agent/transport/interactive.py +169 -0
  96. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/__init__.py +44 -0
  97. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/commands.py +633 -0
  98. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/formatting.py +86 -0
  99. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/models.py +18 -0
  100. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/poller.py +610 -0
  101. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/sender.py +582 -0
  102. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/state.py +298 -0
  103. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/transport.py +147 -0
  104. diploid_agent-0.6.0/src/diploid_agent/transport/telegram/workers.py +650 -0
  105. diploid_agent-0.6.0/src/diploid_agent/transport/telegram_format.py +536 -0
  106. diploid_agent-0.6.0/src/diploid_agent/turn/__init__.py +5 -0
  107. diploid_agent-0.6.0/src/diploid_agent/turn/controller.py +240 -0
  108. diploid_agent-0.6.0/src/diploid_agent/turn/dispatch.py +720 -0
  109. diploid_agent-0.6.0/src/diploid_agent/turn/notifier.py +240 -0
  110. diploid_agent-0.6.0/src/diploid_agent/turn/process.py +863 -0
  111. diploid_agent-0.6.0/src/diploid_agent/turn/rehydrate.py +462 -0
  112. diploid_agent-0.6.0/src/diploid_agent/turn/session.py +637 -0
  113. diploid_agent-0.6.0/src/diploid_agent.egg-info/PKG-INFO +323 -0
  114. diploid_agent-0.6.0/src/diploid_agent.egg-info/SOURCES.txt +178 -0
  115. diploid_agent-0.6.0/src/diploid_agent.egg-info/dependency_links.txt +1 -0
  116. diploid_agent-0.6.0/src/diploid_agent.egg-info/requires.txt +15 -0
  117. diploid_agent-0.6.0/src/diploid_agent.egg-info/top_level.txt +1 -0
  118. diploid_agent-0.6.0/tests/test_acp_client.py +888 -0
  119. diploid_agent-0.6.0/tests/test_acp_client_timeout.py +293 -0
  120. diploid_agent-0.6.0/tests/test_acp_control.py +229 -0
  121. diploid_agent-0.6.0/tests/test_acp_reader.py +577 -0
  122. diploid_agent-0.6.0/tests/test_acp_resume.py +547 -0
  123. diploid_agent-0.6.0/tests/test_acp_transport.py +321 -0
  124. diploid_agent-0.6.0/tests/test_config.py +126 -0
  125. diploid_agent-0.6.0/tests/test_config_endpoint.py +115 -0
  126. diploid_agent-0.6.0/tests/test_context.py +1455 -0
  127. diploid_agent-0.6.0/tests/test_dispatch.py +93 -0
  128. diploid_agent-0.6.0/tests/test_dispatch_wake.py +61 -0
  129. diploid_agent-0.6.0/tests/test_engine.py +34 -0
  130. diploid_agent-0.6.0/tests/test_engine_factory.py +35 -0
  131. diploid_agent-0.6.0/tests/test_event_bus.py +92 -0
  132. diploid_agent-0.6.0/tests/test_harness.py +1774 -0
  133. diploid_agent-0.6.0/tests/test_harness_self_state.py +86 -0
  134. diploid_agent-0.6.0/tests/test_health.py +69 -0
  135. diploid_agent-0.6.0/tests/test_http_plan.py +192 -0
  136. diploid_agent-0.6.0/tests/test_http_plugin_hot_swap.py +192 -0
  137. diploid_agent-0.6.0/tests/test_http_runtime.py +549 -0
  138. diploid_agent-0.6.0/tests/test_http_wake.py +106 -0
  139. diploid_agent-0.6.0/tests/test_ingress.py +98 -0
  140. diploid_agent-0.6.0/tests/test_instance.py +167 -0
  141. diploid_agent-0.6.0/tests/test_interactive.py +189 -0
  142. diploid_agent-0.6.0/tests/test_mcp.py +146 -0
  143. diploid_agent-0.6.0/tests/test_memory.py +969 -0
  144. diploid_agent-0.6.0/tests/test_memory_mcp.py +124 -0
  145. diploid_agent-0.6.0/tests/test_metrics.py +86 -0
  146. diploid_agent-0.6.0/tests/test_model_router.py +134 -0
  147. diploid_agent-0.6.0/tests/test_models_wake_partial.py +76 -0
  148. diploid_agent-0.6.0/tests/test_notifier.py +109 -0
  149. diploid_agent-0.6.0/tests/test_persona_composer.py +55 -0
  150. diploid_agent-0.6.0/tests/test_plan.py +111 -0
  151. diploid_agent-0.6.0/tests/test_plan_notifications.py +319 -0
  152. diploid_agent-0.6.0/tests/test_plugin_contract.py +63 -0
  153. diploid_agent-0.6.0/tests/test_plugin_create.py +77 -0
  154. diploid_agent-0.6.0/tests/test_plugin_hot_swap.py +218 -0
  155. diploid_agent-0.6.0/tests/test_plugin_incidents.py +26 -0
  156. diploid_agent-0.6.0/tests/test_plugin_lifecycle.py +778 -0
  157. diploid_agent-0.6.0/tests/test_plugin_loading.py +77 -0
  158. diploid_agent-0.6.0/tests/test_plugin_m5_hooks.py +66 -0
  159. diploid_agent-0.6.0/tests/test_plugin_reload.py +142 -0
  160. diploid_agent-0.6.0/tests/test_plugin_runtime.py +87 -0
  161. diploid_agent-0.6.0/tests/test_plugin_safety.py +24 -0
  162. diploid_agent-0.6.0/tests/test_plugin_sandbox.py +61 -0
  163. diploid_agent-0.6.0/tests/test_plugin_state_restore.py +172 -0
  164. diploid_agent-0.6.0/tests/test_plugin_transport.py +106 -0
  165. diploid_agent-0.6.0/tests/test_prompt_watchdog.py +305 -0
  166. diploid_agent-0.6.0/tests/test_runtime.py +793 -0
  167. diploid_agent-0.6.0/tests/test_runtime_lifecycle.py +161 -0
  168. diploid_agent-0.6.0/tests/test_runtime_wake.py +89 -0
  169. diploid_agent-0.6.0/tests/test_skills.py +403 -0
  170. diploid_agent-0.6.0/tests/test_smoke.py +124 -0
  171. diploid_agent-0.6.0/tests/test_task_engine.py +378 -0
  172. diploid_agent-0.6.0/tests/test_telegram_format.py +114 -0
  173. diploid_agent-0.6.0/tests/test_telegram_ingress.py +489 -0
  174. diploid_agent-0.6.0/tests/test_telegram_mixins.py +48 -0
  175. diploid_agent-0.6.0/tests/test_telegram_poll.py +2558 -0
  176. diploid_agent-0.6.0/tests/test_timer_service.py +138 -0
  177. diploid_agent-0.6.0/tests/test_turn_status.py +122 -0
  178. diploid_agent-0.6.0/tests/test_wake_context.py +187 -0
  179. diploid_agent-0.6.0/tests/test_wake_queue.py +215 -0
  180. diploid_agent-0.6.0/tests/test_watchdog.py +52 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emil Tsoi
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.
@@ -0,0 +1,323 @@
1
+ Metadata-Version: 2.4
2
+ Name: diploid-agent
3
+ Version: 0.6.0
4
+ Summary: Persistent ACP agent harness with pluggable state
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/emiltsoi/diploid-agent
7
+ Project-URL: Repository, https://github.com/emiltsoi/diploid-agent.git
8
+ Project-URL: Issues, https://github.com/emiltsoi/diploid-agent/issues
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.12
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: httpx>=0.27
19
+ Requires-Dist: pydantic>=2.0
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: fastapi>=0.110
22
+ Requires-Dist: uvicorn>=0.30
23
+ Requires-Dist: python-telegram-bot>=21.0
24
+ Provides-Extra: plugins
25
+ Requires-Dist: diploid-plugins>=0.1.0; extra == "plugins"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
29
+ Requires-Dist: ruff>=0.5; extra == "dev"
30
+ Requires-Dist: respx>=0.20; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # diploid-agent
34
+
35
+ A persistent, persona-driven harness around an ACP-compatible agent engine.
36
+
37
+ It ships with the `devin acp` engine as the default, but the engine layer is
38
+ pluggable: any binary that speaks ACP v1 JSON-RPC over stdio can be configured
39
+ under `engine` instead. Every Telegram chat or HTTP caller gets a long-running
40
+ agent session, local transcript, per-chat model switching, and optional
41
+ retention to a Hindsight memory server.
42
+
43
+ ## What it does
44
+
45
+ - Runs an ACP agent session with a persona loaded from `personas/<persona>`.
46
+ - Remembers each conversation in `sessions/<chat_id>/chat_transcript.jsonl`.
47
+ - Preserves context across **model switches** by starting a new agent session and
48
+ re-injecting the recent transcript + long-term memory.
49
+ - Can switch models on the fly (`/model <name>`).
50
+ - Can keep long-term memory either locally (`file`) or in a Hindsight server
51
+ (`hindsight`).
52
+ - Retains turns to Hindsight as bundled multi-turn documents containing only
53
+ the post-tool final segment of each reply, so fact extraction sees cross-turn
54
+ context instead of per-turn working narration
55
+ (`harness.memory.retain_final_segment` / `retain_bundle_turns`).
56
+ - Supports session history: `/new`, `/sessions`, `/resume <n>`, `/branch <n>`.
57
+ - Exposes both a FastAPI HTTP ingress and a Telegram long-polling bot.
58
+ - Splits long or pausing Telegram replies into separate intermediate messages so
59
+ tool-call gaps do not mash into one confusing block; each new message shows
60
+ only the text that has not already been sent.
61
+ - Supports background dispatches that continue the conversation when they complete (`/dispatch`, `/continue`) and harness-native background subagents (`/subagent`, `harness_subagent` MCP tool) that survive the parent turn being stopped.
62
+ - Supports live runtime configuration of task, waker, timer, notifications, and Telegram settings via HTTP and Telegram without restarting.
63
+ - Supports state plugins with a rich lifecycle hook surface: plugins can intercept turns, sessions, dispatches, memory transitions, skill/MCP commands, retain/promote, and shutdown.
64
+ - Hardens the ACP transport with typed error classification, restart backoff, a
65
+ 16 MiB stdout line limit, prompt callbacks on a dedicated worker thread, a
66
+ serialized lifecycle lock, bounded per-prompt update buffers, background
67
+ isolation for secondary ACP calls, and stale-session recovery that attempts
68
+ ACP `session/resume` (falling back to `session/load`) before prompt
69
+ rehydration.
70
+ - Sandboxes the ACP subprocess so it cannot run raw `systemctl`, `reboot`, or `shutdown` against the host; restart requests from the agent are routed through the harness and scheduled gracefully with `systemd-run`.
71
+ - Queues incoming user messages as high-priority wake events when a chat is busy instead of dropping them, and pushes the final result through an outbox consumed by the Telegram `DeliveryWorker` so background turns, mesh wake replies, and subagent completions can still reach the user.
72
+ - Sends a `System: service was restarted.` notice to recently active chats on startup and drops stale `auto_continue` wakes so a crash-restart does not immediately re-run an old continuation.
73
+ - Edits the streaming placeholder with a `(still working, Xm)` liveness suffix, and sends `⏳ Still thinking...` outbox heartbeats for long wake-driven turns, so users know whether to wait or send `/stop`.
74
+ - Curates a `/promote`-driven **promoted memory** pocket that survives `fresh`
75
+ compact mode and is always re-injected at the top of the prompt.
76
+ - Wakes with a one-sentence continuity narrative built from the ACP lifecycle
77
+ log, so the user and the model know whether the session was resumed, rebuilt,
78
+ or restarted.
79
+ - Refreshes chat-scoped skill copies from shared/persona sources on follow-up and
80
+ continue turns, so skill edits take effect on the next message without requiring
81
+ a new ACP session.
82
+ - Snapshots and restores plugin and body-state files across ACP transport
83
+ restarts, keeping per-chat state intact when the child process is replaced.
84
+ - Drains active turns before an external `systemctl restart` exits, so a service
85
+ restart waits for the current reply instead of cutting it off mid-sentence.
86
+ - Preserves active-turn `current_intent` and `last_side_effect` breadcrumbs in
87
+ `chat_active_turn.json`, carrying them into `chat_interrupted_turn.json` if the
88
+ process is killed before `record_turn` runs, and anchors them in the protected
89
+ continuation slot of rehydrated prompts. Turn numbers are reserved up front so
90
+ a killed turn's number is never reused.
91
+ - Sizes the next prompt with live chars-per-token calibration from the session's
92
+ first-turn prompt metrics, instead of a fixed 4:1 guess; `fresh` compact mode
93
+ uses tiered prompt assembly with a `prompt_blocks` allowlist/denylist and a
94
+ capped recall escape hatch.
95
+ - Pre-computes the smart short-term summary only when the recent-turn window is
96
+ overflowing, avoiding unnecessary summarizer calls.
97
+ - Records resume / load / new latency and outcome telemetry in the lifecycle log
98
+ and exposes it in `/status`.
99
+ - Runs a `diploid-memory` MCP server with `memory_recall`, `memory_retain`, and
100
+ `memory_promote` tools, plus a shared `memory` skill that lets the agent use them.
101
+ - Supports agent-to-agent mesh messaging via [`diploid-mesh`](https://github.com/emiltsoi/diploid-mesh), with `reply=yes/no/end` semantics, DSN recording, and per-turn nudges/caps to prevent mesh-send loops.
102
+ - Exposes a plugin framework for per-chat state plugins; the built-in state plugins
103
+ live in [`diploid-plugins`](https://github.com/emiltsoi/diploid-plugins).
104
+ - Hot-reloads plugins without a service restart: `/plugin reload <name>`
105
+ deep-reloads the configured module and every already-imported submodule
106
+ (deepest-first) before dropping instances, so a broken edit keeps the old
107
+ plugin running.
108
+
109
+ ## Quick start
110
+
111
+ ```bash
112
+ python3 -m venv .venv
113
+ source .venv/bin/activate
114
+ pip install -e ".[dev]"
115
+
116
+ cp config/harness.yaml.example config/harness.yaml
117
+ # edit config/harness.yaml
118
+
119
+ cp systemd/diploid-agent.service.example systemd/diploid-agent.service
120
+ # edit paths, then:
121
+ systemctl --user enable --now "$(pwd)/systemd/diploid-agent.service"
122
+ ```
123
+
124
+ Add `TELEGRAM_BOT_TOKEN=...` to `config/secrets.env` for Telegram.
125
+
126
+ ## Authentication
127
+
128
+ The default engine spawns `devin acp`, which needs to be authenticated. The
129
+ easiest way is to sign in once on the same user account that will run the
130
+ service:
131
+
132
+ - Devin Desktop: sign in through the app.
133
+ - CLI: run `devin auth login` and complete the browser/manual token flow.
134
+
135
+ This writes credentials to `~/.local/share/devin/credentials.toml`. The
136
+ `systemd/diploid-agent.service.example` unit runs as your user and inherits your
137
+ `HOME`, so the credentials file is found automatically.
138
+
139
+ Other engines may use `WINDSURF_API_KEY`, `ACP_API_KEY`, or a per-engine
140
+ credential source. For a headless/dedicated account, set the relevant key in
141
+ `config/secrets.env` and reference that file from the service unit.
142
+
143
+ Send a message:
144
+
145
+ ```bash
146
+ curl -X POST http://127.0.0.1:4003/chat \
147
+ -H "Content-Type: application/json" \
148
+ -d '{"chat_id": "test-1", "message": "Introduce yourself"}'
149
+ ```
150
+
151
+ Switch model:
152
+
153
+ ```bash
154
+ curl -X POST http://127.0.0.1:4003/switch-model \
155
+ -H "Content-Type: application/json" \
156
+ -d '{"chat_id": "test-1", "model": "glm-5-2"}'
157
+ ```
158
+
159
+ ## Telegram commands
160
+
161
+ - `/status` — current model, session id, working directory, context-window usage, ACP continuity state, and resume telemetry.
162
+ - `/metrics` — token usage and latency for this chat.
163
+ - `/mcp list | /mcp enable <name> | /mcp disable <name>` — manage per-chat MCP servers.
164
+ - `/skill list | /skill enable <name> | /skill disable <name> | /skill create <name> <markdown>` — manage skills.
165
+ - `/plugin list | /plugin enable <name> | /plugin disable <name> | /plugin reload <name>` — manage state plugins; `reload` hot-swaps the plugin's code without a restart.
166
+ - `/state <plugin> <event> [args...]` — dispatch a state event to a plugin.
167
+ - `/models` — list available ACP models.
168
+ - `/model <name>` — switch this chat to a new model.
169
+ - `/new` — start a fresh session.
170
+ - `/stop` — cancel the current turn and return a partial reply.
171
+ - `/restart` — kill the ACP subprocess and start a fresh transport.
172
+ - `/graceful-restart [service]` — schedule a graceful systemd restart of the named service (default: the current persona's `.service` unit).
173
+ - `/subagent <prompt>` — start a background ACP subagent and continue the chat with its result when it finishes.
174
+ - `/subagents` — list background subagents for this chat.
175
+ - `/continue` — resume the previous turn after a partial reply or timeout.
176
+ - `/sessions` — list numbered sessions.
177
+ - `/resume <n>` — resume session `n`.
178
+ - `/branch <n>` — branch from session `n`.
179
+ - `/memory` — show the per-chat memory.
180
+ - `/summarize` — manually trigger a file-backend summarization.
181
+ - `/recall <query>` — search the memory backend.
182
+ - `/promote <fact>` — append a fact to the chat's curated promoted memory (always loaded in `fresh` mode).
183
+ - `/stream_thoughts on|off` — toggle the optional real-time thought stream.
184
+ - `/config <section> <key>=<value> [key=value...]` — update live runtime config without restarting the harness.
185
+
186
+ The agent itself cannot reliably self-identify its serving model; `/status` is
187
+ the source of truth.
188
+
189
+ Replying to an earlier message in Telegram injects the quoted text into the next
190
+ prompt with a clear label. Long quotes are trimmed to
191
+ `harness.memory.max_reply_quote_chars` (default 2048 characters).
192
+
193
+ ## Documentation
194
+
195
+ Browse the docs as a searchable site: **https://emiltsoi.github.io/diploid-agent/**
196
+
197
+ - [Architecture and data flow](docs/architecture.md)
198
+ - [Memory loop and Hindsight](docs/memory.md)
199
+ - [State plugins and lifecycle hooks](docs/state.md)
200
+ - [Model switching](docs/model-switching.md)
201
+ - [Session management](docs/session-management.md)
202
+ - [Telegram setup](docs/telegram.md)
203
+ - [HTTP API](docs/api.md)
204
+ - [systemd service](docs/systemd.md)
205
+ - [Security notes](docs/security.md)
206
+ - [Design decisions](docs/design-decisions.md)
207
+ - [Hindsight API contract](docs/hindsight-api-contract.md)
208
+ - [Background dispatches and continuation](docs/dispatch.md)
209
+ - [Mesh integration](docs/mesh.md)
210
+ - [Index of all documentation](docs/index.md)
211
+ - [Plugin contract](docs/plugin-contract.md)
212
+
213
+ ## Mesh support
214
+
215
+ `diploid-agent` can participate in the cross-harness mesh via the [`diploid-mesh`](https://github.com/emiltsoi/diploid-mesh) plugin:
216
+
217
+ - Receives Ed25519-signed `[mesh]` webhooks on `/mesh/receive` (and the OpenClaw alias `/plugins/openclaw-mesh/webhook`).
218
+ - Wakes the diploid runtime with mesh context so the agent can reply.
219
+ - Exposes MCP tools (`mesh_send`, `mesh_list`, `mesh_register`, `mesh_sync`, `mesh_publish`, `mesh_health`, `mesh_deregister`).
220
+ - Enforces `reply=yes/no/end` semantics: `reply=no` nudges the model to avoid replying, `reply=end` hard-blocks `mesh_send`, and DSNs are recorded without a turn.
221
+ - Nudges and hard-caps `mesh_send` calls per ACP turn via `harness.mesh.max_sends_per_turn` and `harness.mesh.max_message_in_turn_suggestion`.
222
+ - Strengthens prompt discipline with a top-of-prompt `SYSTEM — MESH REPLY RULE` CTA that commands the agent to use `mesh_send` for replies and to keep mesh content out of normal assistant text.
223
+ - Can mirror sent mesh messages back to Telegram as `System: [mesh] ...` notices via `harness.notifications.mesh_telegram_float`.
224
+ - Shares the same `mesh-peer-registry` and local vault format with [`hermes-mesh`](https://github.com/emiltsoi/hermes-mesh) and [`openclaw-mesh`](https://github.com/emiltsoi/openclaw-mesh), so a diploid agent can exchange messages with Hermes and OpenClaw agents using the same envelope and signatures.
225
+
226
+ See [`docs/mesh.md`](docs/mesh.md) and the [`diploid-mesh` README](https://github.com/emiltsoi/diploid-mesh/blob/main/README.md) for install, vault setup, and `harness.yaml` configuration.
227
+
228
+ ## Important caveats
229
+
230
+ - Authentication is handled by the configured engine (`devin auth login` or
231
+ Devin Desktop when `provider: diploid`). The harness only works if the user
232
+ running it is already authenticated, or if `WINDSURF_API_KEY` / `ACP_API_KEY` is
233
+ supplied in `config/secrets.env`.
234
+ - An ACP session's model is set at creation. Switching models starts a new
235
+ session, but the harness re-injects the conversation transcript + memory.
236
+ - The HTTP ingress is intended for a trusted/private network (`127.0.0.1` by
237
+ default). If you expose it externally, set `HARNESS_API_KEY` in
238
+ `config/secrets.env` and send it in the `X-API-Key` header on `POST` and live runtime config `GET`
239
+ requests (e.g. `/task/config`, `/waker/config`, `/timer/config`, `/notifications/config`). Other `GET` endpoints and Telegram's `/webhook` remain open.
240
+ - `TELEGRAM_BOT_TOKEN` lives in `config/secrets.env` only; that file is
241
+ gitignored and the poller does not log the token.
242
+
243
+ ## Compliance note
244
+
245
+ This harness is an automation layer on top of a **single Devin/Cognition
246
+ account that you already pay for**. It does not share credentials, bypass
247
+ authentication, circumvent access controls, or expose paid features for free.
248
+ It is designed to be used by one operator with their own account and their own
249
+ CLI session.
250
+
251
+ Cognition's Acceptable Use Policy (June 2026, "Building with our Services —
252
+ Agentic Use") explicitly contemplates agents taking autonomous actions —
253
+ writing and executing code, interacting with third-party systems — under these
254
+ requirements, which this harness is built to satisfy:
255
+
256
+ - **Operator accountability** — you are responsible for every action taken by
257
+ agents running under your account.
258
+ - **Human oversight** — the harness is a chat/HTTP interface to a session you
259
+ can observe and interrupt; do not wire it to irreversible production actions
260
+ without review and confirmation mechanisms.
261
+ - **No credential sharing** — one account, one operator, no multi-tenant access
262
+ to your subscription.
263
+ - **No circumvention** — nothing in the harness overrides Devin's own security
264
+ measures or access controls.
265
+ - **Third-party ToS respect** — agents driven through this harness must not
266
+ interact with other systems in ways that violate *those* systems' terms
267
+ (scraping, abuse, unauthorized access). Route agents only against systems
268
+ you own or are authorized to use.
269
+
270
+ If you fork or redistribute this project, keep this section intact: the
271
+ compliance story is part of the design, not an afterthought. Do not market the
272
+ harness as "free Devin" or as a way to bypass paid tiers — it is a way to get
273
+ more value from a subscription you already hold.
274
+
275
+ ## Source layout
276
+
277
+ The top-level packages were split in Phase 4/5 and Phase 6 so each major
278
+ responsibility lives in a focused module:
279
+
280
+ - `diploid_agent/runtime/agent_runtime.py` — the thin service container and
281
+ turn orchestrator (the old `ConversationHarness`).
282
+ - `diploid_agent/runtime/*.py` — focused runtime collaborators:
283
+ - `store.py` — chat/session persistence.
284
+ - `metrics.py` — metrics, health, and prometheus formatting.
285
+ - `config_manager.py` — live runtime configuration overrides.
286
+ - `outbox.py` — outbox queue and notification delivery.
287
+ - `mcp_skills.py` — MCP and skill enablement.
288
+ - `plugins.py` — plugin lifecycle, incidents, and sandbox.
289
+ - `prompts.py` — first/follow-up prompt building and model resolution.
290
+ - `subagent.py` — background subagent start/completion/status.
291
+ - `planning.py` — plan and dispatch wake helpers.
292
+ - `actions.py` — public command-style actions.
293
+ - `diploid_agent/turn/` — ACP per-turn engine:
294
+ - `controller.py` — turn coordinator.
295
+ - `process.py` — main `process()` turn loop.
296
+ - `session.py` — new/resume/branch session management.
297
+ - `rehydrate.py` — stale session recovery and ACP resume.
298
+ - `dispatch.py` — background dispatch and continue-turn.
299
+ - `notifier.py` — streaming `_NotifyStream` and `_OutboxHeartbeat`.
300
+ - `diploid_agent/acp_client/` — ACP JSON-RPC transport and process lifecycle:
301
+ - `client.py` — public `AcpClient` session/prompt API.
302
+ - `transport.py` — low-level `AcpTransport` (subprocess, JSON-RPC reader).
303
+ - `watchdog.py` — `PromptWatchdog` stall detection and recovery.
304
+ - `control.py` — Unix-socket listener for agent restart requests.
305
+ - `sandbox.py` — isolated `HOME` and fake `systemctl` wrappers.
306
+ - `errors.py`, `types.py`, `utils.py` — shared helpers.
307
+ - `diploid_agent/transport/telegram/` — Telegram long-polling bot:
308
+ - `poller.py` — `TelegramPoller` composing `TelegramCommandMixin`,
309
+ `TelegramSenderMixin`, and `TelegramStateMixin`.
310
+ - `commands.py`, `sender.py`, `state.py` — the three mixins.
311
+ - `workers.py` — `TurnWorker` and `DeliveryWorker`.
312
+ - `diploid_agent/transport/http/` — FastAPI harness:
313
+ - `app.py` — `create_app`, `HttpTransport`, `main`.
314
+ - `routes/*.py` — domain-grouped route handlers.
315
+ - `models.py` — request/response Pydantic models.
316
+ - `diploid_agent/memory.py` / `memory_mcp.py` — transcript and long-term memory.
317
+ - `diploid_agent/mcp.py` — MCP server resolution and per-chat enablement.
318
+ - `diploid_agent/skills.py` — skill discovery and chat-scoped skill loading.
319
+ - `diploid_agent/plugins/` — state plugin lifecycle and manager.
320
+
321
+ ## License
322
+
323
+ [MIT](LICENSE)
@@ -0,0 +1,291 @@
1
+ # diploid-agent
2
+
3
+ A persistent, persona-driven harness around an ACP-compatible agent engine.
4
+
5
+ It ships with the `devin acp` engine as the default, but the engine layer is
6
+ pluggable: any binary that speaks ACP v1 JSON-RPC over stdio can be configured
7
+ under `engine` instead. Every Telegram chat or HTTP caller gets a long-running
8
+ agent session, local transcript, per-chat model switching, and optional
9
+ retention to a Hindsight memory server.
10
+
11
+ ## What it does
12
+
13
+ - Runs an ACP agent session with a persona loaded from `personas/<persona>`.
14
+ - Remembers each conversation in `sessions/<chat_id>/chat_transcript.jsonl`.
15
+ - Preserves context across **model switches** by starting a new agent session and
16
+ re-injecting the recent transcript + long-term memory.
17
+ - Can switch models on the fly (`/model <name>`).
18
+ - Can keep long-term memory either locally (`file`) or in a Hindsight server
19
+ (`hindsight`).
20
+ - Retains turns to Hindsight as bundled multi-turn documents containing only
21
+ the post-tool final segment of each reply, so fact extraction sees cross-turn
22
+ context instead of per-turn working narration
23
+ (`harness.memory.retain_final_segment` / `retain_bundle_turns`).
24
+ - Supports session history: `/new`, `/sessions`, `/resume <n>`, `/branch <n>`.
25
+ - Exposes both a FastAPI HTTP ingress and a Telegram long-polling bot.
26
+ - Splits long or pausing Telegram replies into separate intermediate messages so
27
+ tool-call gaps do not mash into one confusing block; each new message shows
28
+ only the text that has not already been sent.
29
+ - Supports background dispatches that continue the conversation when they complete (`/dispatch`, `/continue`) and harness-native background subagents (`/subagent`, `harness_subagent` MCP tool) that survive the parent turn being stopped.
30
+ - Supports live runtime configuration of task, waker, timer, notifications, and Telegram settings via HTTP and Telegram without restarting.
31
+ - Supports state plugins with a rich lifecycle hook surface: plugins can intercept turns, sessions, dispatches, memory transitions, skill/MCP commands, retain/promote, and shutdown.
32
+ - Hardens the ACP transport with typed error classification, restart backoff, a
33
+ 16 MiB stdout line limit, prompt callbacks on a dedicated worker thread, a
34
+ serialized lifecycle lock, bounded per-prompt update buffers, background
35
+ isolation for secondary ACP calls, and stale-session recovery that attempts
36
+ ACP `session/resume` (falling back to `session/load`) before prompt
37
+ rehydration.
38
+ - Sandboxes the ACP subprocess so it cannot run raw `systemctl`, `reboot`, or `shutdown` against the host; restart requests from the agent are routed through the harness and scheduled gracefully with `systemd-run`.
39
+ - Queues incoming user messages as high-priority wake events when a chat is busy instead of dropping them, and pushes the final result through an outbox consumed by the Telegram `DeliveryWorker` so background turns, mesh wake replies, and subagent completions can still reach the user.
40
+ - Sends a `System: service was restarted.` notice to recently active chats on startup and drops stale `auto_continue` wakes so a crash-restart does not immediately re-run an old continuation.
41
+ - Edits the streaming placeholder with a `(still working, Xm)` liveness suffix, and sends `⏳ Still thinking...` outbox heartbeats for long wake-driven turns, so users know whether to wait or send `/stop`.
42
+ - Curates a `/promote`-driven **promoted memory** pocket that survives `fresh`
43
+ compact mode and is always re-injected at the top of the prompt.
44
+ - Wakes with a one-sentence continuity narrative built from the ACP lifecycle
45
+ log, so the user and the model know whether the session was resumed, rebuilt,
46
+ or restarted.
47
+ - Refreshes chat-scoped skill copies from shared/persona sources on follow-up and
48
+ continue turns, so skill edits take effect on the next message without requiring
49
+ a new ACP session.
50
+ - Snapshots and restores plugin and body-state files across ACP transport
51
+ restarts, keeping per-chat state intact when the child process is replaced.
52
+ - Drains active turns before an external `systemctl restart` exits, so a service
53
+ restart waits for the current reply instead of cutting it off mid-sentence.
54
+ - Preserves active-turn `current_intent` and `last_side_effect` breadcrumbs in
55
+ `chat_active_turn.json`, carrying them into `chat_interrupted_turn.json` if the
56
+ process is killed before `record_turn` runs, and anchors them in the protected
57
+ continuation slot of rehydrated prompts. Turn numbers are reserved up front so
58
+ a killed turn's number is never reused.
59
+ - Sizes the next prompt with live chars-per-token calibration from the session's
60
+ first-turn prompt metrics, instead of a fixed 4:1 guess; `fresh` compact mode
61
+ uses tiered prompt assembly with a `prompt_blocks` allowlist/denylist and a
62
+ capped recall escape hatch.
63
+ - Pre-computes the smart short-term summary only when the recent-turn window is
64
+ overflowing, avoiding unnecessary summarizer calls.
65
+ - Records resume / load / new latency and outcome telemetry in the lifecycle log
66
+ and exposes it in `/status`.
67
+ - Runs a `diploid-memory` MCP server with `memory_recall`, `memory_retain`, and
68
+ `memory_promote` tools, plus a shared `memory` skill that lets the agent use them.
69
+ - Supports agent-to-agent mesh messaging via [`diploid-mesh`](https://github.com/emiltsoi/diploid-mesh), with `reply=yes/no/end` semantics, DSN recording, and per-turn nudges/caps to prevent mesh-send loops.
70
+ - Exposes a plugin framework for per-chat state plugins; the built-in state plugins
71
+ live in [`diploid-plugins`](https://github.com/emiltsoi/diploid-plugins).
72
+ - Hot-reloads plugins without a service restart: `/plugin reload <name>`
73
+ deep-reloads the configured module and every already-imported submodule
74
+ (deepest-first) before dropping instances, so a broken edit keeps the old
75
+ plugin running.
76
+
77
+ ## Quick start
78
+
79
+ ```bash
80
+ python3 -m venv .venv
81
+ source .venv/bin/activate
82
+ pip install -e ".[dev]"
83
+
84
+ cp config/harness.yaml.example config/harness.yaml
85
+ # edit config/harness.yaml
86
+
87
+ cp systemd/diploid-agent.service.example systemd/diploid-agent.service
88
+ # edit paths, then:
89
+ systemctl --user enable --now "$(pwd)/systemd/diploid-agent.service"
90
+ ```
91
+
92
+ Add `TELEGRAM_BOT_TOKEN=...` to `config/secrets.env` for Telegram.
93
+
94
+ ## Authentication
95
+
96
+ The default engine spawns `devin acp`, which needs to be authenticated. The
97
+ easiest way is to sign in once on the same user account that will run the
98
+ service:
99
+
100
+ - Devin Desktop: sign in through the app.
101
+ - CLI: run `devin auth login` and complete the browser/manual token flow.
102
+
103
+ This writes credentials to `~/.local/share/devin/credentials.toml`. The
104
+ `systemd/diploid-agent.service.example` unit runs as your user and inherits your
105
+ `HOME`, so the credentials file is found automatically.
106
+
107
+ Other engines may use `WINDSURF_API_KEY`, `ACP_API_KEY`, or a per-engine
108
+ credential source. For a headless/dedicated account, set the relevant key in
109
+ `config/secrets.env` and reference that file from the service unit.
110
+
111
+ Send a message:
112
+
113
+ ```bash
114
+ curl -X POST http://127.0.0.1:4003/chat \
115
+ -H "Content-Type: application/json" \
116
+ -d '{"chat_id": "test-1", "message": "Introduce yourself"}'
117
+ ```
118
+
119
+ Switch model:
120
+
121
+ ```bash
122
+ curl -X POST http://127.0.0.1:4003/switch-model \
123
+ -H "Content-Type: application/json" \
124
+ -d '{"chat_id": "test-1", "model": "glm-5-2"}'
125
+ ```
126
+
127
+ ## Telegram commands
128
+
129
+ - `/status` — current model, session id, working directory, context-window usage, ACP continuity state, and resume telemetry.
130
+ - `/metrics` — token usage and latency for this chat.
131
+ - `/mcp list | /mcp enable <name> | /mcp disable <name>` — manage per-chat MCP servers.
132
+ - `/skill list | /skill enable <name> | /skill disable <name> | /skill create <name> <markdown>` — manage skills.
133
+ - `/plugin list | /plugin enable <name> | /plugin disable <name> | /plugin reload <name>` — manage state plugins; `reload` hot-swaps the plugin's code without a restart.
134
+ - `/state <plugin> <event> [args...]` — dispatch a state event to a plugin.
135
+ - `/models` — list available ACP models.
136
+ - `/model <name>` — switch this chat to a new model.
137
+ - `/new` — start a fresh session.
138
+ - `/stop` — cancel the current turn and return a partial reply.
139
+ - `/restart` — kill the ACP subprocess and start a fresh transport.
140
+ - `/graceful-restart [service]` — schedule a graceful systemd restart of the named service (default: the current persona's `.service` unit).
141
+ - `/subagent <prompt>` — start a background ACP subagent and continue the chat with its result when it finishes.
142
+ - `/subagents` — list background subagents for this chat.
143
+ - `/continue` — resume the previous turn after a partial reply or timeout.
144
+ - `/sessions` — list numbered sessions.
145
+ - `/resume <n>` — resume session `n`.
146
+ - `/branch <n>` — branch from session `n`.
147
+ - `/memory` — show the per-chat memory.
148
+ - `/summarize` — manually trigger a file-backend summarization.
149
+ - `/recall <query>` — search the memory backend.
150
+ - `/promote <fact>` — append a fact to the chat's curated promoted memory (always loaded in `fresh` mode).
151
+ - `/stream_thoughts on|off` — toggle the optional real-time thought stream.
152
+ - `/config <section> <key>=<value> [key=value...]` — update live runtime config without restarting the harness.
153
+
154
+ The agent itself cannot reliably self-identify its serving model; `/status` is
155
+ the source of truth.
156
+
157
+ Replying to an earlier message in Telegram injects the quoted text into the next
158
+ prompt with a clear label. Long quotes are trimmed to
159
+ `harness.memory.max_reply_quote_chars` (default 2048 characters).
160
+
161
+ ## Documentation
162
+
163
+ Browse the docs as a searchable site: **https://emiltsoi.github.io/diploid-agent/**
164
+
165
+ - [Architecture and data flow](docs/architecture.md)
166
+ - [Memory loop and Hindsight](docs/memory.md)
167
+ - [State plugins and lifecycle hooks](docs/state.md)
168
+ - [Model switching](docs/model-switching.md)
169
+ - [Session management](docs/session-management.md)
170
+ - [Telegram setup](docs/telegram.md)
171
+ - [HTTP API](docs/api.md)
172
+ - [systemd service](docs/systemd.md)
173
+ - [Security notes](docs/security.md)
174
+ - [Design decisions](docs/design-decisions.md)
175
+ - [Hindsight API contract](docs/hindsight-api-contract.md)
176
+ - [Background dispatches and continuation](docs/dispatch.md)
177
+ - [Mesh integration](docs/mesh.md)
178
+ - [Index of all documentation](docs/index.md)
179
+ - [Plugin contract](docs/plugin-contract.md)
180
+
181
+ ## Mesh support
182
+
183
+ `diploid-agent` can participate in the cross-harness mesh via the [`diploid-mesh`](https://github.com/emiltsoi/diploid-mesh) plugin:
184
+
185
+ - Receives Ed25519-signed `[mesh]` webhooks on `/mesh/receive` (and the OpenClaw alias `/plugins/openclaw-mesh/webhook`).
186
+ - Wakes the diploid runtime with mesh context so the agent can reply.
187
+ - Exposes MCP tools (`mesh_send`, `mesh_list`, `mesh_register`, `mesh_sync`, `mesh_publish`, `mesh_health`, `mesh_deregister`).
188
+ - Enforces `reply=yes/no/end` semantics: `reply=no` nudges the model to avoid replying, `reply=end` hard-blocks `mesh_send`, and DSNs are recorded without a turn.
189
+ - Nudges and hard-caps `mesh_send` calls per ACP turn via `harness.mesh.max_sends_per_turn` and `harness.mesh.max_message_in_turn_suggestion`.
190
+ - Strengthens prompt discipline with a top-of-prompt `SYSTEM — MESH REPLY RULE` CTA that commands the agent to use `mesh_send` for replies and to keep mesh content out of normal assistant text.
191
+ - Can mirror sent mesh messages back to Telegram as `System: [mesh] ...` notices via `harness.notifications.mesh_telegram_float`.
192
+ - Shares the same `mesh-peer-registry` and local vault format with [`hermes-mesh`](https://github.com/emiltsoi/hermes-mesh) and [`openclaw-mesh`](https://github.com/emiltsoi/openclaw-mesh), so a diploid agent can exchange messages with Hermes and OpenClaw agents using the same envelope and signatures.
193
+
194
+ See [`docs/mesh.md`](docs/mesh.md) and the [`diploid-mesh` README](https://github.com/emiltsoi/diploid-mesh/blob/main/README.md) for install, vault setup, and `harness.yaml` configuration.
195
+
196
+ ## Important caveats
197
+
198
+ - Authentication is handled by the configured engine (`devin auth login` or
199
+ Devin Desktop when `provider: diploid`). The harness only works if the user
200
+ running it is already authenticated, or if `WINDSURF_API_KEY` / `ACP_API_KEY` is
201
+ supplied in `config/secrets.env`.
202
+ - An ACP session's model is set at creation. Switching models starts a new
203
+ session, but the harness re-injects the conversation transcript + memory.
204
+ - The HTTP ingress is intended for a trusted/private network (`127.0.0.1` by
205
+ default). If you expose it externally, set `HARNESS_API_KEY` in
206
+ `config/secrets.env` and send it in the `X-API-Key` header on `POST` and live runtime config `GET`
207
+ requests (e.g. `/task/config`, `/waker/config`, `/timer/config`, `/notifications/config`). Other `GET` endpoints and Telegram's `/webhook` remain open.
208
+ - `TELEGRAM_BOT_TOKEN` lives in `config/secrets.env` only; that file is
209
+ gitignored and the poller does not log the token.
210
+
211
+ ## Compliance note
212
+
213
+ This harness is an automation layer on top of a **single Devin/Cognition
214
+ account that you already pay for**. It does not share credentials, bypass
215
+ authentication, circumvent access controls, or expose paid features for free.
216
+ It is designed to be used by one operator with their own account and their own
217
+ CLI session.
218
+
219
+ Cognition's Acceptable Use Policy (June 2026, "Building with our Services —
220
+ Agentic Use") explicitly contemplates agents taking autonomous actions —
221
+ writing and executing code, interacting with third-party systems — under these
222
+ requirements, which this harness is built to satisfy:
223
+
224
+ - **Operator accountability** — you are responsible for every action taken by
225
+ agents running under your account.
226
+ - **Human oversight** — the harness is a chat/HTTP interface to a session you
227
+ can observe and interrupt; do not wire it to irreversible production actions
228
+ without review and confirmation mechanisms.
229
+ - **No credential sharing** — one account, one operator, no multi-tenant access
230
+ to your subscription.
231
+ - **No circumvention** — nothing in the harness overrides Devin's own security
232
+ measures or access controls.
233
+ - **Third-party ToS respect** — agents driven through this harness must not
234
+ interact with other systems in ways that violate *those* systems' terms
235
+ (scraping, abuse, unauthorized access). Route agents only against systems
236
+ you own or are authorized to use.
237
+
238
+ If you fork or redistribute this project, keep this section intact: the
239
+ compliance story is part of the design, not an afterthought. Do not market the
240
+ harness as "free Devin" or as a way to bypass paid tiers — it is a way to get
241
+ more value from a subscription you already hold.
242
+
243
+ ## Source layout
244
+
245
+ The top-level packages were split in Phase 4/5 and Phase 6 so each major
246
+ responsibility lives in a focused module:
247
+
248
+ - `diploid_agent/runtime/agent_runtime.py` — the thin service container and
249
+ turn orchestrator (the old `ConversationHarness`).
250
+ - `diploid_agent/runtime/*.py` — focused runtime collaborators:
251
+ - `store.py` — chat/session persistence.
252
+ - `metrics.py` — metrics, health, and prometheus formatting.
253
+ - `config_manager.py` — live runtime configuration overrides.
254
+ - `outbox.py` — outbox queue and notification delivery.
255
+ - `mcp_skills.py` — MCP and skill enablement.
256
+ - `plugins.py` — plugin lifecycle, incidents, and sandbox.
257
+ - `prompts.py` — first/follow-up prompt building and model resolution.
258
+ - `subagent.py` — background subagent start/completion/status.
259
+ - `planning.py` — plan and dispatch wake helpers.
260
+ - `actions.py` — public command-style actions.
261
+ - `diploid_agent/turn/` — ACP per-turn engine:
262
+ - `controller.py` — turn coordinator.
263
+ - `process.py` — main `process()` turn loop.
264
+ - `session.py` — new/resume/branch session management.
265
+ - `rehydrate.py` — stale session recovery and ACP resume.
266
+ - `dispatch.py` — background dispatch and continue-turn.
267
+ - `notifier.py` — streaming `_NotifyStream` and `_OutboxHeartbeat`.
268
+ - `diploid_agent/acp_client/` — ACP JSON-RPC transport and process lifecycle:
269
+ - `client.py` — public `AcpClient` session/prompt API.
270
+ - `transport.py` — low-level `AcpTransport` (subprocess, JSON-RPC reader).
271
+ - `watchdog.py` — `PromptWatchdog` stall detection and recovery.
272
+ - `control.py` — Unix-socket listener for agent restart requests.
273
+ - `sandbox.py` — isolated `HOME` and fake `systemctl` wrappers.
274
+ - `errors.py`, `types.py`, `utils.py` — shared helpers.
275
+ - `diploid_agent/transport/telegram/` — Telegram long-polling bot:
276
+ - `poller.py` — `TelegramPoller` composing `TelegramCommandMixin`,
277
+ `TelegramSenderMixin`, and `TelegramStateMixin`.
278
+ - `commands.py`, `sender.py`, `state.py` — the three mixins.
279
+ - `workers.py` — `TurnWorker` and `DeliveryWorker`.
280
+ - `diploid_agent/transport/http/` — FastAPI harness:
281
+ - `app.py` — `create_app`, `HttpTransport`, `main`.
282
+ - `routes/*.py` — domain-grouped route handlers.
283
+ - `models.py` — request/response Pydantic models.
284
+ - `diploid_agent/memory.py` / `memory_mcp.py` — transcript and long-term memory.
285
+ - `diploid_agent/mcp.py` — MCP server resolution and per-chat enablement.
286
+ - `diploid_agent/skills.py` — skill discovery and chat-scoped skill loading.
287
+ - `diploid_agent/plugins/` — state plugin lifecycle and manager.
288
+
289
+ ## License
290
+
291
+ [MIT](LICENSE)