feat: adding peer config

This commit is contained in:
ajspig 2026-04-08 09:35:42 -04:00
parent a881313212
commit f9e73a58bd
4 changed files with 394 additions and 14 deletions

View File

@ -27,6 +27,14 @@ honcho peer list
honcho peer inspect <peer_id>
honcho session messages <session_id> --last 20
# Peer management
honcho peer create <peer_id>
honcho peer create <peer_id> --observe-me --metadata '{"role": "user"}'
honcho peer get-metadata <peer_id>
honcho peer set-metadata <peer_id> --metadata '{"role": "user"}'
honcho peer representation <peer_id>
honcho peer representation <peer_id> --search-query "preferences" --max-conclusions 20
# Debug
honcho peer card <peer_id>
honcho conclusion search "topic" --observer <peer_id>

View File

@ -1,7 +1,8 @@
"""Peer commands: list, inspect, card, chat, search."""
"""Peer commands: list, inspect, card, chat, search, create, metadata, representation."""
from __future__ import annotations
import json
from typing import Optional
import typer
@ -177,3 +178,130 @@ def search(
print_result(items, columns=["id", "session_id", "content", "created_at"], title=f"Peer search: {query}")
except Exception as e:
_handle_error(e, "peer", pid)
@app.command("create")
def create_peer(
peer_id: str = typer.Argument(help="Peer ID to create or get"),
observe_me: Optional[bool] = typer.Option(None, "--observe-me/--no-observe-me", help="Whether Honcho will form a representation of this peer"),
metadata: Optional[str] = typer.Option(None, "--metadata", help="JSON metadata to associate with the peer"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Create or get a peer."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
from honcho.api_types import PeerConfig
handle_cmd_flags(json_output=json_output, workspace=workspace)
pid = validate_resource_id(peer_id, "peer")
client, config = get_client()
parsed_metadata = None
if metadata:
try:
parsed_metadata = json.loads(metadata)
except json.JSONDecodeError as e:
from honcho_cli.output import print_error
print_error("INVALID_JSON", f"--metadata must be valid JSON: {e}", {})
raise typer.Exit(1)
peer_config = PeerConfig(observe_me=observe_me) if observe_me is not None else None
try:
p = client.peer(pid, configuration=peer_config, metadata=parsed_metadata)
result = {
"peer_id": p.id,
"metadata": parsed_metadata,
"configuration": {"observe_me": observe_me} if observe_me is not None else None,
}
print_result(result)
except Exception as e:
_handle_error(e, "peer", pid)
@app.command("get-metadata")
def get_metadata(
peer_id: Optional[str] = typer.Argument(None, help="Peer ID (uses default if omitted)"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Get metadata for a peer."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
pid = _get_peer_id(peer_id)
client, config = get_client()
p = client.peer(pid)
try:
result = p.get_metadata()
print_result({"peer_id": pid, "metadata": result})
except Exception as e:
_handle_error(e, "peer", pid)
@app.command("set-metadata")
def set_metadata(
peer_id: Optional[str] = typer.Argument(None, help="Peer ID (uses default if omitted)"),
metadata: str = typer.Option(..., "--metadata", "-m", help="JSON metadata to set (e.g. '{\"key\": \"value\"}')"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Set metadata for a peer."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
pid = _get_peer_id(peer_id)
client, config = get_client()
try:
parsed = json.loads(metadata)
except json.JSONDecodeError as e:
from honcho_cli.output import print_error
print_error("INVALID_JSON", f"metadata must be valid JSON: {e}", {})
raise typer.Exit(1)
p = client.peer(pid)
try:
p.set_metadata(parsed)
print_result({"peer_id": pid, "metadata": parsed})
except Exception as e:
_handle_error(e, "peer", pid)
@app.command()
def representation(
peer_id: Optional[str] = typer.Argument(None, help="Peer ID (uses default if omitted)"),
target: Optional[str] = typer.Option(None, help="Target peer to get representation about"),
session: Optional[str] = typer.Option(None, help="Scope representation to a session"),
search_query: Optional[str] = typer.Option(None, help="Semantic search query to filter conclusions"),
max_conclusions: Optional[int] = typer.Option(None, help="Maximum number of conclusions to include"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Get the formatted representation for a peer."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
pid = _get_peer_id(peer_id)
client, config = get_client()
p = client.peer(pid)
try:
result = p.representation(
target=target,
session=session,
search_query=search_query,
max_conclusions=max_conclusions,
)
print_result({"peer_id": pid, "target": target, "representation": result})
except Exception as e:
_handle_error(e, "peer", pid)

View File

@ -1,8 +1,9 @@
"""Session commands: list, inspect, messages, context, summaries."""
"""Session commands: list, inspect, messages, context, summaries, peers, search, representation, metadata."""
from __future__ import annotations
from typing import Optional
import json
from typing import List, Optional
import typer
@ -197,3 +198,223 @@ def summaries(
print_result(result)
except Exception as e:
_handle_error(e, "session", sid)
@app.command()
def delete(
session_id: Optional[str] = typer.Argument(None, help="Session ID (uses default if omitted)"),
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Delete a session and all its data. Destructive — requires --yes or interactive confirm."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session)
sid = _get_session_id(session_id)
client, config = get_client()
if not yes:
typer.confirm(f"Delete session '{sid}' and all its messages, conclusions, and queue items?", abort=True)
sess = client.session(sid)
try:
sess.delete()
status(f"Session '{sid}' deleted")
print_result({"deleted": sid})
except Exception as e:
_handle_error(e, "session", sid)
@app.command("peers")
def session_peers(
session_id: Optional[str] = typer.Argument(None, help="Session ID (uses default if omitted)"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""List peers in a session."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session)
sid = _get_session_id(session_id)
client, config = get_client()
sess = client.session(sid)
try:
peers = sess.peers()
items = [{"id": p.id} for p in peers]
print_result(items, columns=["id"], title=f"Session peers ({sid})")
except Exception as e:
_handle_error(e, "session", sid)
@app.command("add-peers")
def add_peers(
peer_ids: List[str] = typer.Argument(help="Peer IDs to add to the session"),
session_id: Optional[str] = typer.Option(None, "--session", "-s", help="Session ID (uses default if omitted)"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Add peers to a session."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session_id)
sid = _get_session_id(session_id)
client, config = get_client()
sess = client.session(sid)
try:
sess.add_peers(peer_ids)
print_result({"session_id": sid, "added_peers": peer_ids})
except Exception as e:
_handle_error(e, "session", sid)
@app.command("remove-peers")
def remove_peers(
peer_ids: List[str] = typer.Argument(help="Peer IDs to remove from the session"),
session_id: Optional[str] = typer.Option(None, "--session", "-s", help="Session ID (uses default if omitted)"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Remove peers from a session."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session_id)
sid = _get_session_id(session_id)
client, config = get_client()
sess = client.session(sid)
try:
sess.remove_peers(peer_ids)
print_result({"session_id": sid, "removed_peers": peer_ids})
except Exception as e:
_handle_error(e, "session", sid)
@app.command()
def search(
query: str = typer.Argument(help="Search query"),
session_id: Optional[str] = typer.Option(None, help="Session ID (uses default if omitted)"),
limit: int = typer.Option(10, help="Max results"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Search messages in a session."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session)
sid = _get_session_id(session_id)
client, config = get_client()
sess = client.session(sid)
try:
results = sess.search(query, limit=limit)
items = [
{
"id": m.id,
"peer_id": m.peer_id,
"content": m.content[:200],
"created_at": str(m.created_at),
}
for m in results
]
print_result(items, columns=["id", "peer_id", "content", "created_at"], title=f"Session search: {query}")
except Exception as e:
_handle_error(e, "session", sid)
@app.command()
def representation(
peer_id: str = typer.Argument(help="Peer ID to get representation for"),
session_id: Optional[str] = typer.Option(None, help="Session ID (uses default if omitted)"),
target: Optional[str] = typer.Option(None, help="Target peer (what peer_id knows about target)"),
search_query: Optional[str] = typer.Option(None, help="Semantic search query to filter conclusions"),
max_conclusions: Optional[int] = typer.Option(None, help="Maximum number of conclusions to include"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Get the representation of a peer within a session."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session)
sid = _get_session_id(session_id)
client, config = get_client()
sess = client.session(sid)
try:
result = sess.representation(
peer_id,
target=target,
search_query=search_query,
max_conclusions=max_conclusions,
)
print_result({"session_id": sid, "peer_id": peer_id, "target": target, "representation": result})
except Exception as e:
_handle_error(e, "session", sid)
@app.command("get-metadata")
def get_metadata(
session_id: Optional[str] = typer.Argument(None, help="Session ID (uses default if omitted)"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Get metadata for a session."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session)
sid = _get_session_id(session_id)
client, config = get_client()
sess = client.session(sid)
try:
result = sess.get_metadata()
print_result({"session_id": sid, "metadata": result})
except Exception as e:
_handle_error(e, "session", sid)
@app.command("set-metadata")
def set_metadata(
metadata: str = typer.Argument(help="JSON metadata to set (e.g. '{\"key\": \"value\"}')"),
session_id: Optional[str] = typer.Option(None, help="Session ID (uses default if omitted)"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Set metadata for a session."""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session)
sid = _get_session_id(session_id)
client, config = get_client()
try:
parsed = json.loads(metadata)
except json.JSONDecodeError as e:
from honcho_cli.output import print_error
print_error("INVALID_JSON", f"metadata must be valid JSON: {e}", {})
raise typer.Exit(1)
sess = client.session(sid)
try:
sess.set_metadata(parsed)
print_result({"session_id": sid, "metadata": parsed})
except Exception as e:
_handle_error(e, "session", sid)

View File

@ -113,11 +113,16 @@ def inspect(
@app.command()
def delete(
workspace_id: str = typer.Argument(help="Workspace ID to delete"),
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt (for scripted/agent use)"),
cascade: bool = typer.Option(False, "--cascade", help="Delete all sessions before deleting the workspace"),
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Delete a workspace. Destructive — requires --yes or interactive confirm."""
"""Delete a workspace. Use --dry-run first to see what will be deleted.
Requires --yes to skip confirmation, or will prompt interactively.
If sessions exist, requires --cascade to delete them first.
"""
from honcho_cli.common import handle_cmd_flags
from honcho_cli.main import get_client
@ -125,26 +130,44 @@ def delete(
validate_resource_id(workspace_id, "workspace")
client, config = get_client()
client = _with_workspace(client, workspace_id)
ws_client = _with_workspace(client, workspace_id)
# Always fetch sessions for dry-run or cascade
raw_sessions = _raw_list(ws_client.sessions()) if (dry_run or cascade) else []
if dry_run:
sessions = list(client.sessions())
peers = list(client.peers())
print_result({
"dry_run": True,
"workspace_id": workspace_id,
"sessions_to_delete": len(sessions),
"peers_to_delete": len(peers),
"sessions_to_delete": len(raw_sessions),
"session_ids": [s.id for s in raw_sessions],
"warning": "This action cannot be undone.",
})
return
if not yes:
typer.confirm(f"Delete workspace '{workspace_id}' and all its data?", abort=True)
if cascade and raw_sessions:
typer.confirm(
f"Delete workspace '{workspace_id}' and {len(raw_sessions)} session(s)? This cannot be undone.",
abort=True,
)
else:
typer.confirm(f"Delete workspace '{workspace_id}'? This cannot be undone.", abort=True)
try:
client.delete_workspace(workspace_id)
status(f"Workspace '{workspace_id}' deleted")
print_result({"deleted": workspace_id})
deleted_sessions = []
if cascade and raw_sessions:
for s in raw_sessions:
ws_client.session(s.id).delete()
deleted_sessions.append(s.id)
status(f"Deleted session '{s.id}'")
ws_client.delete_workspace(workspace_id)
status(f"Workspace '{workspace_id}' deletion accepted (processing in background)")
result = {"deleted_workspace": workspace_id, "status": "accepted"}
if cascade:
result["deleted_sessions"] = deleted_sessions
print_result(result)
except Exception as e:
_handle_error(e, "workspace", workspace_id)