Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 39 additions & 16 deletions hindsight-integrations/aider/hindsight_aider/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ def do_retain(client: Any, config: AiderConfig, bank_id: str, transcript: str) -
logger.warning("Hindsight retain failed: %s", e)


def _close_client(client: Any) -> None:
"""Best-effort close of the Hindsight client's HTTP session.

The client wraps an aiohttp session; leaving it open prints
"Unclosed connector" warnings after aider exits.
"""
close = getattr(client, "close", None)
if callable(close):
try:
close()
except Exception: # pragma: no cover - cleanup best-effort
pass


def _default_run_aider(cmd: list[str]) -> int:
"""Run aider, inheriting stdio so the session is interactive."""
try:
Expand All @@ -119,25 +133,34 @@ def run(
) -> int:
"""Recall -> run aider -> retain. Returns Aider's exit code."""
config = config or load_config()
# Track whether we created the client so we can close it on exit. A
# test-injected client is left for the caller to manage.
owns_client = client is None
client = client or resolve_client(config)
bank_id = resolve_bank_id(config)

memory_path = Path(config.memory_filename)
history_path = Path(config.chat_history_file)

injected = False
if config.auto_recall:
query = compose_recall_query(aider_args, config.recall_default_query)
injected = do_recall(client, config, bank_id, query, memory_path)

prev_size = history_size(history_path)

cmd = build_aider_command(config, aider_args, memory_path if injected else None)
code = (run_aider or _default_run_aider)(cmd)

if config.auto_retain:
transcript = format_transcript(read_history_delta(history_path, prev_size))
if transcript:
do_retain(client, config, bank_id, transcript)

return code
try:
injected = False
if config.auto_recall:
query = compose_recall_query(aider_args, config.recall_default_query)
injected = do_recall(client, config, bank_id, query, memory_path)

prev_size = history_size(history_path)

cmd = build_aider_command(config, aider_args, memory_path if injected else None)
code = (run_aider or _default_run_aider)(cmd)

if config.auto_retain:
transcript = format_transcript(read_history_delta(history_path, prev_size))
if transcript:
do_retain(client, config, bank_id, transcript)

return code
finally:
# Close the HTTP session we opened so aiohttp doesn't print
# "Unclosed connector" warnings after aider exits.
if owns_client:
_close_client(client)
2 changes: 1 addition & 1 deletion hindsight-integrations/aider/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "hindsight-aider"
version = "0.1.0"
version = "0.1.1"
description = "Aider integration for Hindsight - persistent long-term memory across pair-programming sessions"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
18 changes: 18 additions & 0 deletions hindsight-integrations/aider/tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,21 @@ def test_passes_through_exit_code(self, tmp_path):
cfg = _config(tmp_path)
client = make_client([])
assert run([], config=cfg, client=client, run_aider=lambda cmd: 3) == 3

def test_closes_client_it_created(self, tmp_path, monkeypatch):
"""A client run() resolves itself is closed on exit (avoids the aiohttp
'Unclosed connector' warning)."""
import hindsight_aider.runner as runner_mod

client = make_client([])
monkeypatch.setattr(runner_mod, "resolve_client", lambda config: client)
cfg = _config(tmp_path, auto_recall=False, auto_retain=False)
run([], config=cfg, run_aider=lambda cmd: 0) # no client= -> run() owns it
client.close.assert_called_once()

def test_injected_client_not_closed(self, tmp_path):
"""A test/caller-injected client is left for the caller to manage."""
client = make_client([])
cfg = _config(tmp_path, auto_recall=False, auto_retain=False)
run([], config=cfg, client=client, run_aider=lambda cmd: 0)
client.close.assert_not_called()
2 changes: 1 addition & 1 deletion hindsight-integrations/aider/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions hindsight-integrations/openhands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ open local server).
> snippet to paste instead of touching the file. `hindsight-openhands init
> --print-only` shows the snippet + rule anytime.

### Running the OpenHands Docker app?

The containerized OpenHands app loads MCP servers from its **UI settings**, not
from a project `config.toml`. So add the server in **Settings → MCP** as a
**Streamable HTTP** server (not SSE — Hindsight's endpoint is streamable HTTP):

- **URL:** `http://host.docker.internal:8888/mcp/<bank>/` (use `host.docker.internal`,
not `localhost`, so the container can reach a Hindsight server on your host;
launch the app with `--add-host host.docker.internal:host-gateway`)
- **API key:** your `hsk_...` for Cloud, or none for an open local server

The MCP tools load when a conversation starts. The `AGENTS.md` rule still applies
when you open the project as a repository.

## Commands

| Command | Description |
Expand Down
5 changes: 5 additions & 0 deletions hindsight-integrations/openhands/hindsight_openhands/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
apply_to_config,
build_shttp_server,
default_config_path,
mcp_endpoint_url,
remove_from_config,
render_snippet,
)
Expand Down Expand Up @@ -106,6 +107,10 @@ def cmd_init(args: argparse.Namespace) -> None:
print(f" Wrote recall/retain rule to {outcome.agents_md_path}")
print("\nDone. Start OpenHands in this project — the hindsight MCP tools")
print("(recall/retain/reflect) are available and used automatically.")
print("\nRunning the OpenHands Docker app? It reads MCP servers from its UI")
print("settings, not this config.toml — add the server in Settings -> MCP as")
print(f"a Streamable HTTP (shttp) server, URL {mcp_endpoint_url(cfg.hindsight_api_url, cfg.bank_id)}")
print("(use http://host.docker.internal:<port> so the container can reach a local server).")


def cmd_status(args: argparse.Namespace) -> None:
Expand Down
2 changes: 1 addition & 1 deletion hindsight-integrations/openhands/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "hindsight-openhands"
version = "0.1.0"
version = "0.1.1"
description = "OpenHands integration for Hindsight - persistent long-term memory via MCP"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion hindsight-integrations/openhands/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading