Skip to content

Add Hermes and writable Agy adapters - #2

Open
KennyUMN wants to merge 8 commits into
GodrezJr2:mainfrom
KennyUMN:add-agy-write-target
Open

KennyUMN wants to merge 8 commits into
GodrezJr2:mainfrom
KennyUMN:add-agy-write-target

Conversation

@KennyUMN

@KennyUMN KennyUMN commented Sep 23, 2026 •

Copy link
Copy Markdown

Summary

Combines the Hermes adapter and writable Antigravity adapter work into one pull request.

  • Add Hermes Agent as a fully readable and writable adapter.
  • Fix the Kimi updatedAt timestamp crash during sync.
  • Make Antigravity (agy) writable by mirroring foreign chats into its native Other store.
  • Add hop agy chat selection instead of automatically opening the newest conversation.
  • Keep explicit selection available with hop agy <number|title>.
  • Keep hop sync-agy running after an individual seed failure.
  • Convert vanished Hermes mirror sessions into a controlled HopError instead of an endless raw-error retry loop.
  • Add hop refresh as an alias for hop sync when manually catching up a live chat.

Verified

  • python -m unittest discover -s tests -v: 31 tests pass.
  • Python syntax and git diff --check pass.
  • End-to-end picker test uses the real hop wrapper and a fake agy executable, confirming the selected thread is passed to the handoff.
  • hop refresh --dry-run --days 1 detected the live "Fix hop agy auto-resuming handoff" chat.
  • hop sync indexed that live chat, and it is now visible in hop agy.
  • Audit probes cover Hermes create/update, title collisions, missing stores, vanished mirrors, and Agy batch failure continuation.
  • CI is currently action_required pending maintainer approval, not failed.

Honest limits

Agy mirrors are readable and continue as text, but do not preserve agy-native protobuf tool state. The current implementation uses the readable JSONL transcript and summary store rather than reconstructing agy's proprietary execution trajectory.

Mirrors chats into Hermes' SQLite store ($HERMES_HOME/state.db) so they show
up in Hermes' own /resume, and reads Hermes chats into every other agent.

Writes sessions + messages rows directly (the schema is stable and already
maintained by AFTER-INSERT triggers), marked via origin_json so hop never
re-adopts its own mirror. model is left NULL on purpose: --resume reuses the
stored model, and a placeholder like 'hop-import' makes the mirror unresumable.

Also fixes two pre-existing bugs found while wiring this up:
- kimi: updated was the raw ISO string from state.json, so sync's
  sort-by-updated crashed with str/int comparison (TypeError).
- kimi: list() now converts it with core.iso_ms.

Tests: 3 new (Hermes mirror round-trip + title collision, reading a real
session, WAL store without -wal/-shm sidecars; Kimi ISO updatedAt).
…ision, delete)

1. write() on a store that does not exist yet (Hermes installed but never run)
   raised a raw OperationalError, which is not a HopError, so sync queued the
   chat for retry on every turn forever. It now raises HopError('run Hermes
   once so its state.db exists') before touching anything, like Gemini/Kimi.
2. A title collision retried with no title at all, leaving mirrors untitled
   (Hermes requires unique titles). It now retries with ' · <last 6 of id>'.
3. delete() (hop forget) and retitle() crashed when ~/.hermes was gone. The
   existence check moved into connect() so every rw caller treats a missing
   store as a no-op.

Also from the same review:
- started_at is the first message's time, not the last.
- The delete in write() moved inside the transaction block.
- list() uses one grouped query for message counts instead of two per session.
- Tests: per-test store reset (no test depends on another having run), the WAL
  test seeds its own session, HOP renamed to HERMES_HOME_DIR.
- Docs: pyproject description/keywords and the README 'Tested on' line mention
  Hermes. Hero image left for the maintainer's make_hero.py.
- Added 3 tests: missing store -> HopError + delete no-op, title collision
  keeps a suffixed title, WAL store without sidecars.
agy owns a protobuf execution trajectory (conversations/<cid>.db) that hop does not write,
but a conversation's readable history is a JSONL transcript under brain/<cid>/ and its picker
index is the conversation_summaries table. Writing those two, with app_data_dir != 'antigravity-cli',
puts a mirror under agy's resume-picker 'Other' tab (verified: rows with app_data_dir='antigravity-cli'
are the 'CLI' tab; every other app_data_dir is 'Other').

- write(): emit the transcript JSONL (the exact inverse of read()) + a summary row, so a mirror is
  readable in agy and continues as text. # ponytail: no protobuf trajectory, so no agy-native tool
  state; upgrade via agy's importConversationCmd if bit-identical resume matters.
- delete()/retitle(): remove the transcript + row / rename the label, so hop forget is reversible.
- Mirror detection: rows are tagged samethread and list() sets is_hop, so sync never re-imports its
  own copy (no loop).
- title == preview == the picker label, matching real agy rows.
- write() raises HopError (not a raw OperationalError) if agy has never run.

Tests: write/read round-trip, 'Other'-tab shape (app_data_dir != cli, empty project/parent, depth 0),
is_hop no-loop, delete removes both, missing store -> HopError. 19 tests green on py3.9 and py3.12
with the exact CI commands. Docs updated where they claimed agy was unwritable.
Copilot AI lite review requested due to automatic review settings September 23, 2026 11:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

A critical sync registration failure and multiple correctness and cleanup issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 4 Medium severity · 1 Low severity

Open (6)
What changed in this PR

Makes the Antigravity (agy) adapter writable by mirroring chats into its Other tab using JSONL transcripts and summary rows.

Changes:

  • Adds agy write, retitle, delete, and mirror-detection support.
  • Adds persistence and cleanup tests.
  • Updates documentation for agy mirroring and limitations.
File Summary and final review comments
tests/​test_core.py Adds agy persistence, round-trip, metadata, missing-store, and deletion coverage.
src/​samethread/​agents/​antigravity.py Implements writable mirrors. Critical (2 votes): materialize() reads missing res['seen'], preventing index registration. Moderate: update both title and preview on retitle (3 votes); construct valid Windows file URIs (2 votes); preserve user text containing closing delimiters (2 votes); guard incomplete stores before writing (3 votes); raise HopError when deletion cannot clean up (1 vote).
README.md Nit (3 votes): update outdated agy seeding guidance to describe population through hop sync in the Other tab.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/samethread/agents/antigravity.py Outdated
Comment on lines +129 to +130
return self.session(cid, path=path, title=title, sig=file_sig(path), cwd=cwd,
updated=msgs[-1]['ts'] if msgs else 0, is_hop=True)
Comment thread src/samethread/agents/antigravity.py Outdated
Comment on lines +112 to +113
if not os.path.isdir(ROOT):
raise HopError('run agy once so its store exists')
Comment thread src/samethread/agents/antigravity.py Outdated
if m['role'] == 'user':
rec = {'step_index': i, 'source': 'USER_EXPLICIT', 'type': 'USER_INPUT',
'status': 'DONE', 'created_at': ms_iso(m['ts']),
'content': '<USER_REQUEST>\n' + (m['text'] or '') + '\n</USER_REQUEST>'}
Comment thread src/samethread/agents/antigravity.py Outdated
'values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
(cid, label, label, len(msgs),
stamp(msgs[-1]['ts'] if msgs else 0),
json.dumps(['file://' + urllib.parse.quote(cwd or '', safe='/')]),
Comment thread src/samethread/agents/antigravity.py Outdated
Comment on lines +160 to +161
c.execute('update conversation_summaries set title=? where conversation_id=?',
(title or '', (cp.get('id') or '').split(':', 1)[-1]))
Comment thread README.md Outdated
| `hop sync` | Mirror new and changed chats. The hooks run this for you. |
| `hop list [-a]` | Chats for this folder (or all folders), with the exact resume command for every copy. |
| `hop resume <agent> [n]` | Open chat *n* in any agent: `cc`, `oc`, `codex`, `gemini`, `qwen`, `pi`, `kimi`, `agy`, `mmx`. For Antigravity and MiniMax it seeds a new conversation, and its turns flow back to the others. |
| `hop resume <agent> [n]` | Open chat *n* in any agent: `cc`, `oc`, `codex`, `gemini`, `qwen`, `pi`, `kimi`, `agy`, `mmx`. For Antigravity and MiniMax it seeds a new conversation; `hop sync` also writes mirrors into Antigravity's `"Other"` tab. Turns flow back to the others. |
@KennyUMN

Copy link
Copy Markdown
Author

Hermes audit — changes requested

This PR does not currently connect to agy correctly.

Blocking

  • src/samethread/agents/antigravity.py:129-130 — write() returns a session without seen, but sync.materialize() unconditionally reads res['seen'] at src/samethread/sync.py:328. The transcript and summary are written, then sync raises KeyError: 'seen' before registering the mirror. I reproduced this locally.
  • src/samethread/agents/antigravity.py:116-128 — writing only the JSONL transcript and summary row does not register a conversation in agy's native picker/resume. agy's own store requires the trajectory/registry path; the importer path is the sanctioned way to register foreign chats. I created a sandbox mirror without conversations/<cid>.db; agy --conversation audit-mirror --print ... reported conversation "audit-mirror" not found. The current tests only prove SameThread can read back its own JSONL, not that agy can reopen the mirror.

High/medium

  • antigravity.py:64 — list() only opens mode=ro. Against the real agy store while agy was running, this returned OperationalError: unable to open database file; add a safe fallback or equivalent WAL handling.
  • antigravity.py:112-113 — checking only ROOT allows an existing empty agy directory to create an orphan transcript, then fail with a raw sqlite3.OperationalError; guard the database/schema and clean up on failure.
  • antigravity.py:120-122 — literal <USER_REQUEST>...</USER_REQUEST> framing truncates mirrored text containing </USER_REQUEST>; use symmetric escaping/decoding.
  • antigravity.py:160-161 — retitle changes title only, leaving stale preview in the picker row.
  • antigravity.py:147 — Windows paths are encoded as file://C%3A%5C..., which uri_to_path() cannot resolve correctly; construct a valid cross-platform file URI.
  • README.md:105 — hop resume agy no longer seeds when no copy exists because agy is now writable; document that hop sync populates the Other tab instead.

Verification

  • 19 tests pass on Python 3.9 and 3.12.
  • git diff --check passes.
  • No CI checks are reported on the PR.
  • The existing tests do not cover Sync.materialize() or native agy picker registration.

@KennyUMN KennyUMN changed the title Make the agy adapter writable: mirror foreign chats into its 'Other' tab Add Hermes and writable Agy adapters Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants