Files
hermes-agent/tests/plugins/memory/test_honcho_config_schema.py
Erosika f7d5ac3230 feat(honcho): write bot dms into their own a2a session
A DM relayed from another Hermes profile ran as a turn in the recipient's
Bot Chat session. sync_turn wrote the bot's words and the recipient's reply
into that session, and before per-author writes they landed under the
human's peer. The human's representation absorbed conversations the human
never had.

The turn context now marks such turns with scope a2a:<bot id>.
sync_turn routes a bot-authored turn into a separate Honcho session keyed
<session>:a2a:<sanitized bot id>, created with the sender bot as its user
peer, and never writes it into the human's session. The key is deterministic
so every turn from the same bot reaches the same session, and it stays
inside Honcho's 100 character session id limit. Recall still reads the
human's session only.

a2aSessions (host block, then root, default true) turns the routing on.
With it off, bot-authored turns are skipped. A bot turn that names no
author id is skipped as well, because nothing can key its session. Human
turns are unchanged.

get_or_create takes a user_peer_id override so the a2a session's roster is
the bot and the assistant, not the runtime human.
2026-09-10 10:45:57 -07:00

93 lines
3.2 KiB
Python

"""Tests for Honcho's declared config surface."""
from plugins.memory.config_schema import (
KIND_BOOL,
KIND_JSON,
KIND_NUMBER,
KIND_SECRET,
KIND_SELECT,
STORAGE_HONCHO_HOST_BLOCK,
get_provider_config_schema,
)
# The curated set shown in the compact panel; everything else lives in the modal.
INLINE_KEYS = {
"apiKey",
"baseUrl",
"environment",
"workspace",
"peerName",
"aiPeer",
"sessionStrategy",
}
def test_honcho_is_declared():
provider = get_provider_config_schema("honcho")
assert provider is not None
assert provider.label == "Honcho"
assert provider.storage == STORAGE_HONCHO_HOST_BLOCK
# Field keys are unique, and the curated inline set is present.
keys = [field.key for field in provider.fields]
assert len(keys) == len(set(keys))
assert INLINE_KEYS <= set(keys)
def test_inline_fields_are_the_curated_subset():
provider = get_provider_config_schema("honcho")
assert provider is not None
assert {field.key for field in provider.inline_fields()} == INLINE_KEYS
# The modal-only fields are a non-empty remainder.
non_inline = {f.key for f in provider.fields} - INLINE_KEYS
assert {"writeFrequency", "recallMode", "userPeerAliases"} <= non_inline
def test_declares_the_new_field_kinds():
provider = get_provider_config_schema("honcho")
assert provider is not None
by_key = {f.key: f for f in provider.fields}
assert by_key["saveMessages"].kind == KIND_BOOL
assert by_key["a2aSessions"].kind == KIND_BOOL
assert by_key["a2aSessions"].default == "true"
assert by_key["dialecticMaxChars"].kind == KIND_NUMBER
assert by_key["userPeerAliases"].kind == KIND_JSON
assert by_key["recallMode"].allowed_values() == {"hybrid", "context", "tools"}
assert by_key["observationMode"].allowed_values() == {"directional", "unified"}
def test_selects_constrain_their_values():
provider = get_provider_config_schema("honcho")
assert provider is not None
environment = next(f for f in provider.fields if f.key == "environment")
assert environment.kind == KIND_SELECT
# Honcho SDK only accepts local/production; "demo" is not a valid environment.
assert environment.allowed_values() == {"production", "local"}
strategy = next(f for f in provider.fields if f.key == "sessionStrategy")
assert strategy.allowed_values() == {"per-directory", "per-repo", "per-session", "global"}
def test_api_key_is_a_secret_bound_to_env():
provider = get_provider_config_schema("honcho")
assert provider is not None
api_key = next(f for f in provider.fields if f.key == "apiKey")
assert api_key.kind == KIND_SECRET
assert api_key.is_secret is True
assert api_key.env_key == "HONCHO_API_KEY"
def test_root_scoped_fields_are_exactly_the_global_ones():
provider = get_provider_config_schema("honcho")
assert provider is not None
scopes = {f.key: f.scope for f in provider.fields}
root_keys = {k for k, scope in scopes.items() if scope == "root"}
# baseUrl, timeout and sessions live at the config root in Honcho's schema;
# everything else is per-profile host-scoped.
assert root_keys == {"baseUrl", "timeout", "sessions"}