diploid-agent
A persistent, persona-driven harness around an ACP-compatible agent engine.
It ships with the devin acp engine as the default, but the engine layer is
pluggable: any binary that speaks ACP v1 JSON-RPC over stdio can be configured
under engine instead. Every Telegram chat or HTTP caller gets a long-running
agent session, local transcript, per-chat model switching, and optional
retention to a Hindsight memory server.
What it does
- Runs an ACP agent session with a persona loaded from
personas/<persona>. - Remembers each conversation in
sessions/<chat_id>/chat_transcript.jsonl. - Preserves context across model switches by starting a new agent session and re-injecting the recent transcript + long-term memory.
- Can switch models on the fly (
/model <name>). - Can keep long-term memory either locally (
file) or in a Hindsight server (hindsight). - Retains turns to Hindsight as bundled multi-turn documents containing only
the post-tool final segment of each reply, so fact extraction sees cross-turn
context instead of per-turn working narration
(
harness.memory.retain_final_segment/retain_bundle_turns). - Supports session history:
/new,/sessions,/resume <n>,/branch <n>. - Exposes both a FastAPI HTTP ingress and a Telegram long-polling bot.
- Splits long or pausing Telegram replies into separate intermediate messages so tool-call gaps do not mash into one confusing block; each new message shows only the text that has not already been sent.
- Supports background dispatches that continue the conversation when they complete (
/dispatch,/continue) and harness-native background subagents (/subagent,harness_subagentMCP tool) that survive the parent turn being stopped. - Supports live runtime configuration of task, waker, timer, notifications, and Telegram settings via HTTP and Telegram without restarting.
- Supports state plugins with a rich lifecycle hook surface: plugins can intercept turns, sessions, dispatches, memory transitions, skill/MCP commands, retain/promote, and shutdown.
- Hardens the ACP transport with typed error classification, restart backoff, a
16 MiB stdout line limit, prompt callbacks on a dedicated worker thread, a
serialized lifecycle lock, bounded per-prompt update buffers, background
isolation for secondary ACP calls, and stale-session recovery that attempts
ACP
session/resume(falling back tosession/load) before prompt rehydration. - Sandboxes the ACP subprocess so it cannot run raw
systemctl,reboot, orshutdownagainst the host; restart requests from the agent are routed through the harness and scheduled gracefully withsystemd-run. - 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
DeliveryWorkerso background turns, mesh wake replies, and subagent completions can still reach the user. - Sends a
System: service was restarted.notice to recently active chats on startup and drops staleauto_continuewakes so a crash-restart does not immediately re-run an old continuation. - 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. - Curates a
/promote-driven promoted memory pocket that survivesfreshcompact mode and is always re-injected at the top of the prompt. - Wakes with a one-sentence continuity narrative built from the ACP lifecycle log, so the user and the model know whether the session was resumed, rebuilt, or restarted.
- Refreshes chat-scoped skill copies from shared/persona sources on follow-up and continue turns, so skill edits take effect on the next message without requiring a new ACP session.
- Snapshots and restores plugin and body-state files across ACP transport restarts, keeping per-chat state intact when the child process is replaced.
- Drains active turns before an external
systemctl restartexits, so a service restart waits for the current reply instead of cutting it off mid-sentence. - Preserves active-turn
current_intentandlast_side_effectbreadcrumbs inchat_active_turn.json, carrying them intochat_interrupted_turn.jsonif the process is killed beforerecord_turnruns, and anchors them in the protected continuation slot of rehydrated prompts. Turn numbers are reserved up front so a killed turn's number is never reused. - Sizes the next prompt with live chars-per-token calibration from the session's
first-turn prompt metrics, instead of a fixed 4:1 guess;
freshcompact mode uses tiered prompt assembly with aprompt_blocksallowlist/denylist and a capped recall escape hatch. - Pre-computes the smart short-term summary only when the recent-turn window is overflowing, avoiding unnecessary summarizer calls.
- Records resume / load / new latency and outcome telemetry in the lifecycle log
and exposes it in
/status. - Runs a
diploid-memoryMCP server withmemory_recall,memory_retain, andmemory_promotetools, plus a sharedmemoryskill that lets the agent use them. - Supports agent-to-agent mesh messaging via
diploid-mesh, withreply=yes/no/endsemantics, DSN recording, and per-turn nudges/caps to prevent mesh-send loops. - Exposes a plugin framework for per-chat state plugins; the built-in state plugins
live in
diploid-plugins. - Hot-reloads plugins without a service restart:
/plugin reload <name>deep-reloads the configured module and every already-imported submodule (deepest-first) before dropping instances, so a broken edit keeps the old plugin running.
Quick start
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp config/harness.yaml.example config/harness.yaml
# edit config/harness.yaml
cp systemd/diploid-agent.service.example systemd/diploid-agent.service
# edit paths, then:
systemctl --user enable --now "$(pwd)/systemd/diploid-agent.service"
Add TELEGRAM_BOT_TOKEN=... to config/secrets.env for Telegram.
Authentication
The default engine spawns devin acp, which needs to be authenticated. The
easiest way is to sign in once on the same user account that will run the
service:
- Devin Desktop: sign in through the app.
- CLI: run
devin auth loginand complete the browser/manual token flow.
This writes credentials to ~/.local/share/devin/credentials.toml. The
systemd/diploid-agent.service.example unit runs as your user and inherits your
HOME, so the credentials file is found automatically.
Other engines may use WINDSURF_API_KEY, ACP_API_KEY, or a per-engine
credential source. For a headless/dedicated account, set the relevant key in
config/secrets.env and reference that file from the service unit.
Send a message:
curl -X POST http://127.0.0.1:4003/chat \
-H "Content-Type: application/json" \
-d '{"chat_id": "test-1", "message": "Introduce yourself"}'
Switch model:
curl -X POST http://127.0.0.1:4003/switch-model \
-H "Content-Type: application/json" \
-d '{"chat_id": "test-1", "model": "glm-5-2"}'
Telegram commands
/status— current model, session id, working directory, context-window usage, ACP continuity state, and resume telemetry./metrics— token usage and latency for this chat./mcp list | /mcp enable <name> | /mcp disable <name>— manage per-chat MCP servers./skill list | /skill enable <name> | /skill disable <name> | /skill create <name> <markdown>— manage skills./plugin list | /plugin enable <name> | /plugin disable <name> | /plugin reload <name>— manage state plugins;reloadhot-swaps the plugin's code without a restart./state <plugin> <event> [args...]— dispatch a state event to a plugin./models— list available ACP models./model <name>— switch this chat to a new model./new— start a fresh session./stop— cancel the current turn and return a partial reply./restart— kill the ACP subprocess and start a fresh transport./graceful-restart [service]— schedule a graceful systemd restart of the named service (default: the current persona's.serviceunit)./subagent <prompt>— start a background ACP subagent and continue the chat with its result when it finishes./subagents— list background subagents for this chat./continue— resume the previous turn after a partial reply or timeout./sessions— list numbered sessions./resume <n>— resume sessionn./branch <n>— branch from sessionn./memory— show the per-chat memory./summarize— manually trigger a file-backend summarization./recall <query>— search the memory backend./promote <fact>— append a fact to the chat's curated promoted memory (always loaded infreshmode)./stream_thoughts on|off— toggle the optional real-time thought stream./config <section> <key>=<value> [key=value...]— update live runtime config without restarting the harness.
The agent itself cannot reliably self-identify its serving model; /status is
the source of truth.
Replying to an earlier message in Telegram injects the quoted text into the next
prompt with a clear label. Long quotes are trimmed to
harness.memory.max_reply_quote_chars (default 2048 characters).
Documentation
Browse the docs as a searchable site: https://emiltsoi.github.io/diploid-agent/
- Architecture and data flow
- Memory loop and Hindsight
- State plugins and lifecycle hooks
- Model switching
- Session management
- Telegram setup
- HTTP API
- systemd service
- Security notes
- Design decisions
- Hindsight API contract
- Background dispatches and continuation
- Mesh integration
- Index of all documentation
- Plugin contract
Mesh support
diploid-agent can participate in the cross-harness mesh via the diploid-mesh plugin:
- Receives Ed25519-signed
[mesh]webhooks on/mesh/receive(and the OpenClaw alias/plugins/openclaw-mesh/webhook). - Wakes the diploid runtime with mesh context so the agent can reply.
- Exposes MCP tools (
mesh_send,mesh_list,mesh_register,mesh_sync,mesh_publish,mesh_health,mesh_deregister). - Enforces
reply=yes/no/endsemantics:reply=nonudges the model to avoid replying,reply=endhard-blocksmesh_send, and DSNs are recorded without a turn. - Nudges and hard-caps
mesh_sendcalls per ACP turn viaharness.mesh.max_sends_per_turnandharness.mesh.max_message_in_turn_suggestion. - Strengthens prompt discipline with a top-of-prompt
SYSTEM — MESH REPLY RULECTA that commands the agent to usemesh_sendfor replies and to keep mesh content out of normal assistant text. - Can mirror sent mesh messages back to Telegram as
System: [mesh] ...notices viaharness.notifications.mesh_telegram_float. - Shares the same
mesh-peer-registryand local vault format withhermes-meshandopenclaw-mesh, so a diploid agent can exchange messages with Hermes and OpenClaw agents using the same envelope and signatures.
See docs/mesh.md and the diploid-mesh README for install, vault setup, and harness.yaml configuration.
Important caveats
- Authentication is handled by the configured engine (
devin auth loginor Devin Desktop whenprovider: diploid). The harness only works if the user running it is already authenticated, or ifWINDSURF_API_KEY/ACP_API_KEYis supplied inconfig/secrets.env. - An ACP session's model is set at creation. Switching models starts a new session, but the harness re-injects the conversation transcript + memory.
- The HTTP ingress is intended for a trusted/private network (
127.0.0.1by default). If you expose it externally, setHARNESS_API_KEYinconfig/secrets.envand send it in theX-API-Keyheader onPOSTand live runtime configGETrequests (e.g./task/config,/waker/config,/timer/config,/notifications/config). OtherGETendpoints and Telegram's/webhookremain open. TELEGRAM_BOT_TOKENlives inconfig/secrets.envonly; that file is gitignored and the poller does not log the token.
Compliance note
This harness is an automation layer on top of a single Devin/Cognition account that you already pay for. It does not share credentials, bypass authentication, circumvent access controls, or expose paid features for free. It is designed to be used by one operator with their own account and their own CLI session.
Cognition's Acceptable Use Policy (June 2026, "Building with our Services — Agentic Use") explicitly contemplates agents taking autonomous actions — writing and executing code, interacting with third-party systems — under these requirements, which this harness is built to satisfy:
- Operator accountability — you are responsible for every action taken by agents running under your account.
- Human oversight — the harness is a chat/HTTP interface to a session you can observe and interrupt; do not wire it to irreversible production actions without review and confirmation mechanisms.
- No credential sharing — one account, one operator, no multi-tenant access to your subscription.
- No circumvention — nothing in the harness overrides Devin's own security measures or access controls.
- Third-party ToS respect — agents driven through this harness must not interact with other systems in ways that violate those systems' terms (scraping, abuse, unauthorized access). Route agents only against systems you own or are authorized to use.
If you fork or redistribute this project, keep this section intact: the compliance story is part of the design, not an afterthought. Do not market the harness as "free Devin" or as a way to bypass paid tiers — it is a way to get more value from a subscription you already hold.
Source layout
The top-level packages were split in Phase 4/5 and Phase 6 so each major responsibility lives in a focused module:
diploid_agent/runtime/agent_runtime.py— the thin service container and turn orchestrator (the oldConversationHarness).diploid_agent/runtime/*.py— focused runtime collaborators:store.py— chat/session persistence.metrics.py— metrics, health, and prometheus formatting.config_manager.py— live runtime configuration overrides.outbox.py— outbox queue and notification delivery.mcp_skills.py— MCP and skill enablement.plugins.py— plugin lifecycle, incidents, and sandbox.prompts.py— first/follow-up prompt building and model resolution.subagent.py— background subagent start/completion/status.planning.py— plan and dispatch wake helpers.actions.py— public command-style actions.
diploid_agent/turn/— ACP per-turn engine:controller.py— turn coordinator.process.py— mainprocess()turn loop.session.py— new/resume/branch session management.rehydrate.py— stale session recovery and ACP resume.dispatch.py— background dispatch and continue-turn.notifier.py— streaming_NotifyStreamand_OutboxHeartbeat.
diploid_agent/acp_client/— ACP JSON-RPC transport and process lifecycle:client.py— publicAcpClientsession/prompt API.transport.py— low-levelAcpTransport(subprocess, JSON-RPC reader).watchdog.py—PromptWatchdogstall detection and recovery.control.py— Unix-socket listener for agent restart requests.sandbox.py— isolatedHOMEand fakesystemctlwrappers.errors.py,types.py,utils.py— shared helpers.
diploid_agent/transport/telegram/— Telegram long-polling bot:poller.py—TelegramPollercomposingTelegramCommandMixin,TelegramSenderMixin, andTelegramStateMixin.commands.py,sender.py,state.py— the three mixins.workers.py—TurnWorkerandDeliveryWorker.
diploid_agent/transport/http/— FastAPI harness:app.py—create_app,HttpTransport,main.routes/*.py— domain-grouped route handlers.models.py— request/response Pydantic models.
diploid_agent/memory.py/memory_mcp.py— transcript and long-term memory.diploid_agent/mcp.py— MCP server resolution and per-chat enablement.diploid_agent/skills.py— skill discovery and chat-scoped skill loading.diploid_agent/plugins/— state plugin lifecycle and manager.
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file diploid_agent-0.6.0.tar.gz.
File metadata
- Download URL: diploid_agent-0.6.0.tar.gz
- Upload date:
- Size: 343.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15a1fc8ef46f4945b029067060c83906ce66d6cd2476de9c6032b8b3a120f45c
|
|
| MD5 |
cb9b6f39f834f4cd112462444b0a84bc
|
|
| BLAKE2b-256 |
7c5a860164f8eadda92ef73c747b6ab9eab82709eafa37cc0109e09fd27bac04
|
Provenance
The following attestation bundles were made for diploid_agent-0.6.0.tar.gz:
Publisher:
publish.yml on emiltsoi/diploid-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
diploid_agent-0.6.0.tar.gz -
Subject digest:
15a1fc8ef46f4945b029067060c83906ce66d6cd2476de9c6032b8b3a120f45c - Sigstore transparency entry: 2763794644
- Sigstore integration time:
-
Permalink:
emiltsoi/diploid-agent@63c54079a4d8e84b38265b92d2b9b2875ee2f56d -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/emiltsoi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@63c54079a4d8e84b38265b92d2b9b2875ee2f56d -
Trigger Event:
push
-
Statement type:
File details
Details for the file diploid_agent-0.6.0-py3-none-any.whl.
File metadata
- Download URL: diploid_agent-0.6.0-py3-none-any.whl
- Upload date:
- Size: 279.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
830da50597db832d6ca5afa5169d5221ff9fbc73b8365d7bcfe5cfbf5958cd99
|
|
| MD5 |
b42f54ae382fa5fde2c97ed94be10ee3
|
|
| BLAKE2b-256 |
04915c82ab6713138a109047b244de34a496033041d00009cd50fba1c7c819e6
|
Provenance
The following attestation bundles were made for diploid_agent-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on emiltsoi/diploid-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
diploid_agent-0.6.0-py3-none-any.whl -
Subject digest:
830da50597db832d6ca5afa5169d5221ff9fbc73b8365d7bcfe5cfbf5958cd99 - Sigstore transparency entry: 2763794694
- Sigstore integration time:
-
Permalink:
emiltsoi/diploid-agent@63c54079a4d8e84b38265b92d2b9b2875ee2f56d -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/emiltsoi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@63c54079a4d8e84b38265b92d2b9b2875ee2f56d -
Trigger Event:
push
-
Statement type: