Conversation
Make recovery from a dead MCP transport one command. Both launchers now take a snapshot every 2h (configurable) and one final snapshot on graceful shutdown; --resume resolves <snapshot-dir>/<agent_id>.plut so users no longer need to remember paths. New pluto_health MCP tool distinguishes adapter failures from server-unreachable cases. - src_py/utils/snapshot_helper.py: AutoSnapshotter, resolve_resume_path, clean_snapshots — shared by MCP friend and AgentFriend. - agent_mcp_friend: --resume / --snapshot-dir / --no-auto-snapshot / --auto-snapshot-interval / --clean-snapshots; auto-snapshot wired through FastMCP lifespan with final shutdown snapshot; pluto_health tool pings /health + reports last-snapshot metadata. - agent_friend: same flags; PlutoConnection.save_snapshot_files passthrough; AutoSnapshotter started after connect, stopped in finally. - launchers: forward new flags; --resume prints a role-reselection reminder (snapshots restore identity + locks but not the role prompt). - docs: refreshed pluto-mcp-friend.md and pluto-agent-friend.md with TL;DR recovery recipe, flag tables, and pluto_health entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A wiped/cleaned Pluto server has no memory of prior tokens, but agents holding those tokens had no way to know until their next call returned "session_not_found." This adds a boot-unique server_epoch (UUID minted at app:start) that the client caches on register and compares against /health on demand. A mismatch means the server was restarted and the held token is dead — surfaced up-front via pluto_health so agents can diagnose "session lost" without first failing a real call. - pluto_app: generate server_epoch in persistent_term at boot; expose via server_epoch/0. - pluto_http_listener: emit server_epoch on /health, /ping, and the three register response branches. - PlutoHttpClient: cache resp["server_epoch"] on register; add fetch_server_epoch() helper. - pluto_health tool: include live + cached epoch; set agent_registered=false and server_restarted=true with a recovery_hint when they differ. - pluto_session: include cached server_epoch. - docs: explain server_epoch semantics + the new health fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The polling loop now probes /health every ~30s and triggers a re-register up front when the live server_epoch differs from the one cached on register. Avoids the lag where the wrapper only notices a server restart after the next message-bearing call returns 404. Reuses the existing _reregister path used for session_not_found, so lock semantics (lost_locks) and seq cursor reset are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PlutoMCPFriend.sh prints a recovery note on launch and the docs explain why the MCP friend can't self-heal (stdio child of Claude Code) versus PlutoAgentFriend (owns its process, auto-reregisters on epoch change). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The HTTP listener forced Connection: close on every response and the Python client opened a fresh socket per call via urllib.urlopen. Under routine traffic (3 agents, 1s peek loop each, server-epoch probes), the kernel filled with 16k TIME_WAIT entries on port 9202 in ~18 minutes, blocking new connections with SYN_SENT timeouts. Server side (pluto_http_listener.erl): - handle_connection now loops via serve/2 with a 60s idle timeout between requests on the same socket. - send_http_response/4 negotiates Connection: keep-alive vs close per RFC 7230 §6.3 (HTTP/1.1 default keep-alive; HTTP/1.0 default close). - send_http_response/3 stays as a back-compat alias defaulting to close — used by do_long_poll's direct-write path. Client side (pluto_client.py): - New _HTTPConnectionPool: LifoQueue-backed bounded pool of http.client.HTTPConnection (size 4), context-managed acquire, per-call socket read timeout, stale-conn auto-retry once on RemoteDisconnected/ConnectionResetError/BrokenPipeError. - PlutoHttpClient routes everything through _request, which sets Connection: keep-alive, drains the response body so the socket is reusable, and falls back to a fresh conn on stale-disconnect. - long_poll passes its extended deadline via _request(timeout=...) instead of mutating self.timeout (race-prone under concurrent callers). - unregister drains and closes the pool. Verification: under a 500-call load against the rebuilt server, netstat -an | grep 9202 | grep TIME_WAIT shows zero new entries — the count is identical before and after. Pre-fix the same load would have added ~500 TIME_WAITs. tests/test_http_keepalive.py: new regression test covering socket reuse under sequential load, concurrent long-poll vs short calls sharing the pool, and the stale-connection retry branch. All three pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Make recovery from a dead MCP transport one command. Both launchers now take a snapshot every 2h (configurable) and one final snapshot on graceful shutdown; --resume resolves /<agent_id>.plut so users no longer need to remember paths. New pluto_health MCP tool distinguishes adapter failures from server-unreachable cases.