honcho-cli 0.1.4__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/.gitignore +4 -0
  2. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/CHANGELOG.md +23 -0
  3. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/PKG-INFO +15 -3
  4. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/README.md +13 -1
  5. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/pyproject.toml +2 -2
  6. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/__init__.py +1 -1
  7. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/_help.py +9 -4
  8. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/conclusion.py +133 -28
  9. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/peer.py +52 -11
  10. honcho_cli-0.2.0/src/honcho_cli/commands/scope.py +262 -0
  11. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/setup.py +9 -2
  12. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/workspace.py +83 -3
  13. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/common.py +41 -0
  14. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/config.py +14 -1
  15. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/main.py +2 -0
  16. honcho_cli-0.2.0/src/honcho_cli/recall.py +94 -0
  17. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_commands.py +214 -0
  18. honcho_cli-0.2.0/tests/test_conclusion_attribution.py +142 -0
  19. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_config.py +10 -1
  20. honcho_cli-0.2.0/tests/test_recall.py +62 -0
  21. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/scripts/generate_cli_docs.py +0 -0
  22. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/branding.py +0 -0
  23. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/__init__.py +0 -0
  24. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/config_cmd.py +0 -0
  25. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/message.py +0 -0
  26. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/session.py +0 -0
  27. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/commands/stack.py +0 -0
  28. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/__init__.py +0 -0
  29. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/docker.py +0 -0
  30. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/env.py +0 -0
  31. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/health.py +0 -0
  32. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/profile.py +0 -0
  33. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/setup.py +0 -0
  34. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/templates/__init__.py +0 -0
  35. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/templates/docker-compose.yml +0 -0
  36. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/local/templates/init.sql +0 -0
  37. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/oauth.py +0 -0
  38. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/output.py +0 -0
  39. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/update_check.py +0 -0
  40. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/src/honcho_cli/validation.py +0 -0
  41. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/__init__.py +0 -0
  42. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/conftest.py +0 -0
  43. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_common.py +0 -0
  44. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_local.py +0 -0
  45. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_oauth.py +0 -0
  46. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_output.py +0 -0
  47. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_setup.py +0 -0
  48. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_start.py +0 -0
  49. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/tests/test_validation.py +0 -0
  50. {honcho_cli-0.1.4 → honcho_cli-0.2.0}/uv.lock +0 -0
@@ -196,3 +196,7 @@ grafana-data/
196
196
 
197
197
  # Claude Code addon stuff
198
198
  .omc
199
+
200
+ # Sandbox real-provider credentials (see sandbox/real.env.example)
201
+ sandbox/real.env
202
+ sandbox/.state.env
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-09-21
11
+
12
+ ### Added
13
+
14
+ - CLI requests carry `X-Honcho-Host: honcho-cli/<version> (<platform>)` so telemetry counts them as CLI traffic rather than direct SDK use (#1181)
15
+ - Conclusion attribution on `honcho conclusion list` and `honcho conclusion search` (Honcho v3.2.0+): every row now carries `level`, `source_ids` and `times_derived`, so an extracted fact is distinguishable from a dreamed one. Table output shows a premise count; `--json` keeps the full id list, ready to pipe into `honcho conclusion get` (#952)
16
+ - `honcho conclusion get <id>...` fetches conclusions by ID from anywhere in the workspace, with no observer/observed pair required. Feed it a conclusion's `source_ids` to walk a reasoning chain down to the explicit facts underneath it; IDs that no longer exist are reported rather than failing the command (#952)
17
+ - `honcho conclusion derived <id>` lists what was built on top of a conclusion — the same edge walked upward. Worth checking before deleting or correcting a fact (#952)
18
+ - `--level` on `honcho conclusion list` and `honcho conclusion search`, and `--derived-from` on `honcho conclusion list` (#952)
19
+ - `--evidence` on `honcho peer chat` and `honcho workspace chat` (Honcho v3.2.0+): reports the conclusions and messages the dialectic read and the tools it called. Evidence is collated from what the agent accessed rather than reported by the model, so it over-reports and costs no extra model tokens. Messages are summarised per session, since evidence carries their identity but not their text (#1129)
20
+
21
+ ### Changed
22
+
23
+ - Requires `honcho-ai` 2.5.0 (was 2.4.0), which carries the attribution fields and the evidence-bearing chat response
24
+
25
+ ## [0.1.5] - 2026-09-09
26
+
27
+ ### Added
28
+
29
+ - `honcho scope` command group: `list`, `create`, `inspect`, `sessions`, `add-sessions`, `remove-session`, `status` (Honcho v3.1.0+) (#1162)
30
+ - `honcho workspace chat` for reasoned questions across all peers (Honcho v3.1.0+) (#1150)
31
+ - `--scope` on `honcho workspace chat` and `honcho peer chat`, and `--sessions` on `honcho peer chat`, to confine recall (Honcho v3.1.0+) (#1151)
32
+
10
33
  ## [0.1.4] - 2026-08-26
11
34
 
12
35
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: honcho-cli
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: A terminal for Honcho — memory that reasons.
5
5
  Project-URL: Homepage, https://github.com/plastic-labs/honcho
6
6
  Project-URL: Repository, https://github.com/plastic-labs/honcho
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Topic :: Software Development :: Libraries
15
15
  Requires-Python: >=3.11
16
16
  Requires-Dist: click>=8.0.0
17
- Requires-Dist: honcho-ai>=2.0.0
17
+ Requires-Dist: honcho-ai>=2.5.0
18
18
  Requires-Dist: httpx>=0.27.0
19
19
  Requires-Dist: rich>=13.0.0
20
20
  Requires-Dist: typer>=0.15.0
@@ -100,6 +100,7 @@ honcho stop --wipe # also delete volumes
100
100
  | `honcho workspace list` | List accessible workspaces |
101
101
  | `honcho workspace create <id>` | Create or get a workspace |
102
102
  | `honcho workspace inspect` | Peers, sessions, config for a workspace |
103
+ | `honcho workspace chat <query>` | Query the dialectic across all peers (optional `-s` / `--scope` / `--reasoning`) |
103
104
  | `honcho workspace search <query>` | Search messages across workspace |
104
105
  | `honcho workspace queue-status` | Deriver queue status (filter with `--observer` / `--sender`) |
105
106
  | `honcho workspace delete <id>` | Delete a workspace. Use `--dry-run` to preview, `--cascade` to also delete sessions, `--yes` to skip the confirm prompt |
@@ -112,7 +113,7 @@ honcho stop --wipe # also delete volumes
112
113
  | `honcho peer create <id>` | Create or get a peer |
113
114
  | `honcho peer inspect <id>` | Card, session count, recent conclusions |
114
115
  | `honcho peer card <id>` | Raw peer card content |
115
- | `honcho peer chat <query>` | Query the dialectic about a peer (peer via `-p` / `HONCHO_PEER_ID`) |
116
+ | `honcho peer chat <query>` | Query the dialectic about a peer (peer via `-p` / `HONCHO_PEER_ID`; optional `--scope` / `--sessions`) |
116
117
  | `honcho peer representation <id>` | Formatted representation |
117
118
  | `honcho peer search <query>` | Search a peer's messages (peer via `-p` / `HONCHO_PEER_ID`) |
118
119
  | `honcho peer get-metadata <id>` / `set-metadata` | Metadata operations |
@@ -133,6 +134,17 @@ honcho stop --wipe # also delete volumes
133
134
  | `honcho session get-metadata <id>` / `set-metadata` | Metadata operations |
134
135
  | `honcho session delete <id>` | Destructive; requires `--yes` |
135
136
 
137
+ ### Scopes
138
+
139
+ | Command | Description |
140
+ |---------|-------------|
141
+ | `honcho scope list` | List scopes in the workspace |
142
+ | `honcho scope create <name>` | Create or get a scope (`--sessions id,...` to add members, `--metadata`) |
143
+ | `honcho scope inspect <name>` | Metadata, member sessions, backfill summary. Never creates a missing scope |
144
+ | `honcho scope sessions <name>` | Member sessions, longest-standing first |
145
+ | `honcho scope add-sessions <name> <ids...>` / `remove-session <name> <id>` | Membership; history backfills asynchronously |
146
+ | `honcho scope status <name>` | Per-session backfill state — recall is complete once nothing is `pending` |
147
+
136
148
  ### Messages
137
149
 
138
150
  | Command | Description |
@@ -75,6 +75,7 @@ honcho stop --wipe # also delete volumes
75
75
  | `honcho workspace list` | List accessible workspaces |
76
76
  | `honcho workspace create <id>` | Create or get a workspace |
77
77
  | `honcho workspace inspect` | Peers, sessions, config for a workspace |
78
+ | `honcho workspace chat <query>` | Query the dialectic across all peers (optional `-s` / `--scope` / `--reasoning`) |
78
79
  | `honcho workspace search <query>` | Search messages across workspace |
79
80
  | `honcho workspace queue-status` | Deriver queue status (filter with `--observer` / `--sender`) |
80
81
  | `honcho workspace delete <id>` | Delete a workspace. Use `--dry-run` to preview, `--cascade` to also delete sessions, `--yes` to skip the confirm prompt |
@@ -87,7 +88,7 @@ honcho stop --wipe # also delete volumes
87
88
  | `honcho peer create <id>` | Create or get a peer |
88
89
  | `honcho peer inspect <id>` | Card, session count, recent conclusions |
89
90
  | `honcho peer card <id>` | Raw peer card content |
90
- | `honcho peer chat <query>` | Query the dialectic about a peer (peer via `-p` / `HONCHO_PEER_ID`) |
91
+ | `honcho peer chat <query>` | Query the dialectic about a peer (peer via `-p` / `HONCHO_PEER_ID`; optional `--scope` / `--sessions`) |
91
92
  | `honcho peer representation <id>` | Formatted representation |
92
93
  | `honcho peer search <query>` | Search a peer's messages (peer via `-p` / `HONCHO_PEER_ID`) |
93
94
  | `honcho peer get-metadata <id>` / `set-metadata` | Metadata operations |
@@ -108,6 +109,17 @@ honcho stop --wipe # also delete volumes
108
109
  | `honcho session get-metadata <id>` / `set-metadata` | Metadata operations |
109
110
  | `honcho session delete <id>` | Destructive; requires `--yes` |
110
111
 
112
+ ### Scopes
113
+
114
+ | Command | Description |
115
+ |---------|-------------|
116
+ | `honcho scope list` | List scopes in the workspace |
117
+ | `honcho scope create <name>` | Create or get a scope (`--sessions id,...` to add members, `--metadata`) |
118
+ | `honcho scope inspect <name>` | Metadata, member sessions, backfill summary. Never creates a missing scope |
119
+ | `honcho scope sessions <name>` | Member sessions, longest-standing first |
120
+ | `honcho scope add-sessions <name> <ids...>` / `remove-session <name> <id>` | Membership; history backfills asynchronously |
121
+ | `honcho scope status <name>` | Per-session backfill state — recall is complete once nothing is `pending` |
122
+
111
123
  ### Messages
112
124
 
113
125
  | Command | Description |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "honcho-cli"
3
- version = "0.1.4"
3
+ version = "0.2.0"
4
4
  description = "A terminal for Honcho — memory that reasons."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -19,7 +19,7 @@ classifiers = [
19
19
  dependencies = [
20
20
  "click>=8.0.0",
21
21
  "typer>=0.15.0",
22
- "honcho-ai>=2.0.0",
22
+ "honcho-ai>=2.5.0",
23
23
  "rich>=13.0.0",
24
24
  "httpx>=0.27.0",
25
25
  ]
@@ -1,3 +1,3 @@
1
1
  """Honcho CLI — a terminal for Honcho."""
2
2
 
3
- __version__ = "0.1.4"
3
+ __version__ = "0.2.0"
@@ -80,30 +80,35 @@ def print_welcome(console: Console) -> None:
80
80
  ("[dim]pattern[/dim]", r"[dim]honcho <command> \[args] \[-w workspace] \[-p peer] \[-s session][/dim]"),
81
81
  ("[dim]example[/dim]", "[dim]honcho peer chat \"what does alice prefer?\" -p alice -w agents[/dim]"),
82
82
  ("", ""),
83
- ("workspace", "list · create · search · delete · inspect · queue-status"),
83
+ ("workspace", "list · create · search · chat · delete · inspect · queue-status"),
84
84
  ("peer", "list · create · search · inspect · card · chat"),
85
85
  ("", "get-metadata · set-metadata · representation"),
86
86
  ("session", "list · create · search · delete · inspect · view · add-peers"),
87
87
  ("", "context · get-metadata · set-metadata · peers"),
88
88
  ("", "remove-peers · representation · summaries"),
89
+ ("scope", "list · create · inspect · sessions · add-sessions"),
90
+ ("", "remove-session · status"),
89
91
  ("message", "list · create · get"),
90
92
  ("conclusion", "list · create · search · delete"),
91
93
  ("config", "inspect current configuration"),
92
94
  ]
93
95
  memory_rows = [
96
+ ("honcho workspace chat \"...\" -w <workspace>", "query the Dialectic across all peers"),
94
97
  ("honcho peer chat \"...\" -p <peer> -w <workspace>", "query the Dialectic about a peer"),
95
98
  ("honcho peer inspect -p <peer> -w <workspace>", "dashboard: peer card + recent conclusions + configuration"),
96
99
  ("honcho peer representation -p <peer> -w <workspace>", "global peer representation"),
97
100
  ("honcho peer representation -p <peer> -w <workspace> -s <session>", "session-scoped peer representation"),
101
+ ("honcho scope list -w <workspace>", "which session sets exist; inspect one for members + backfill"),
102
+ ("honcho peer chat \"...\" -p <peer> --scope <scope>", "answer from one scope's sessions only"),
98
103
  ("honcho peer card -p <peer> -w <workspace>", "synthesized identity: traits, preferences, instructions"),
99
104
  ("honcho conclusion list -p <peer> -w <workspace>", "browse peer conclusions"),
100
105
  ("honcho session view / context -s <session>", "transcript, or what an agent would see"),
101
106
  ("honcho workspace queue-status", "is the deriver processing?"),
102
107
  ]
103
108
  option_rows = [
104
- ("-w / --workspace", "scope to a workspace"),
105
- ("-p / --peer", "scope to a peer"),
106
- ("-s / --session", "scope to a session"),
109
+ ("-w / --workspace", "target a workspace"),
110
+ ("-p / --peer", "target a peer"),
111
+ ("-s / --session", "target a session"),
107
112
  ("--json", "force JSON output for scripts and agents"),
108
113
  ("--help", "show help for any command (e.g. honcho peer --help)"),
109
114
  ]
@@ -18,6 +18,43 @@ app = typer.Typer(cls=HonchoTyperGroup, help="List, search, create, and delete p
18
18
  add_common_options(app)
19
19
 
20
20
 
21
+ _LEVELS = ("explicit", "deductive", "inductive", "contradiction")
22
+
23
+ #: Columns carrying a conclusion's attribution, shown alongside the content.
24
+ _ATTRIBUTION_COLUMNS = ["id", "level", "source_ids", "times_derived", "content"]
25
+
26
+
27
+ def _format_conclusion(c, workspace_id: str | None) -> dict:
28
+ """Shape a Conclusion for output.
29
+
30
+ ``level``, ``source_ids`` and ``times_derived`` are the attribution the
31
+ server started returning in Honcho v3.2.0. In table mode ``source_ids``
32
+ collapses to a count, since the ids are nanoids and a list of them makes
33
+ the row unreadable; JSON mode keeps the full list so it can be piped back
34
+ into ``honcho conclusion get``.
35
+ """
36
+ source_ids = c.source_ids or []
37
+ return {
38
+ "id": c.id,
39
+ "level": c.level,
40
+ "source_ids": source_ids if use_json() else len(source_ids),
41
+ "times_derived": c.times_derived,
42
+ "content": c.content if use_json() else c.content[:160],
43
+ "workspace_id": workspace_id,
44
+ "observer_id": c.observer_id,
45
+ "observed_id": c.observed_id,
46
+ "session_id": c.session_id,
47
+ "created_at": str(c.created_at),
48
+ }
49
+
50
+
51
+ def _validate_level(level: str | None) -> str | None:
52
+ if level and level not in _LEVELS:
53
+ print_error("INVALID_LEVEL", f"--level must be one of: {', '.join(_LEVELS)}")
54
+ raise typer.Exit(1)
55
+ return level
56
+
57
+
21
58
  def _require_observer(observer: str | None) -> str:
22
59
  """Resolve observer peer ID; emit combined error if peer+workspace both missing."""
23
60
  config = get_resolved_config()
@@ -39,6 +76,16 @@ def list_conclusions(
39
76
  observer: Optional[str] = typer.Option(None, "--observer", help="Observer peer ID"),
40
77
  observed: Optional[str] = typer.Option(None, "--observed", help="Observed peer ID"),
41
78
  limit: int = typer.Option(10, "--limit", help="Max results"),
79
+ level: Optional[str] = typer.Option(
80
+ None,
81
+ "--level",
82
+ help="Only this reasoning level: explicit, deductive, inductive, contradiction",
83
+ ),
84
+ derived_from: Optional[str] = typer.Option(
85
+ None,
86
+ "--derived-from",
87
+ help="Only conclusions derived from this conclusion ID",
88
+ ),
42
89
  workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
43
90
  peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
44
91
  json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
@@ -46,31 +93,27 @@ def list_conclusions(
46
93
  """List conclusions."""
47
94
 
48
95
  handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
96
+ _validate_level(level)
49
97
  observer = _require_observer(observer)
50
98
  client, config = get_client()
51
99
 
52
100
  p = client.peer(observer)
53
101
 
102
+ filters: dict = {}
103
+ if level:
104
+ filters["level"] = level
105
+ if derived_from:
106
+ filters["source_ids"] = {"contains": derived_from}
107
+
54
108
  try:
55
109
  if observed:
56
110
  scope = p.conclusions_of(observed)
57
111
  else:
58
112
  scope = p.conclusions
59
113
 
60
- conclusions = scope.list(size=limit).items
61
- items = [
62
- {
63
- "id": c.id,
64
- "content": c.content if use_json() else c.content[:200],
65
- "workspace_id": config.workspace_id,
66
- "observer_id": c.observer_id,
67
- "observed_id": c.observed_id,
68
- "session_id": c.session_id,
69
- "created_at": str(c.created_at),
70
- }
71
- for c in conclusions
72
- ]
73
- print_result(items, columns=["id", "content", "workspace_id", "observer_id", "observed_id", "session_id", "created_at"], title="Conclusions")
114
+ conclusions = scope.list(size=limit, filters=filters or None).items
115
+ items = [_format_conclusion(c, config.workspace_id) for c in conclusions]
116
+ print_result(items, columns=_ATTRIBUTION_COLUMNS + ["observed_id", "created_at"], title="Conclusions")
74
117
  except Exception as e:
75
118
  _handle_error(e, "conclusion", "list")
76
119
 
@@ -81,6 +124,11 @@ def search(
81
124
  observer: Optional[str] = typer.Option(None, "--observer", help="Observer peer ID"),
82
125
  observed: Optional[str] = typer.Option(None, "--observed", help="Observed peer ID"),
83
126
  top_k: int = typer.Option(10, help="Max results"),
127
+ level: Optional[str] = typer.Option(
128
+ None,
129
+ "--level",
130
+ help="Only this reasoning level: explicit, deductive, inductive, contradiction",
131
+ ),
84
132
  workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
85
133
  peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
86
134
  json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
@@ -88,6 +136,7 @@ def search(
88
136
  """Semantic search over conclusions."""
89
137
 
90
138
  handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
139
+ _validate_level(level)
91
140
  observer = _require_observer(observer)
92
141
  client, config = get_client()
93
142
 
@@ -99,24 +148,80 @@ def search(
99
148
  else:
100
149
  scope = p.conclusions
101
150
 
102
- results = scope.query(query, top_k=top_k)
103
- items = [
104
- {
105
- "id": c.id,
106
- "content": c.content if use_json() else c.content[:200],
107
- "workspace_id": config.workspace_id,
108
- "observer_id": c.observer_id,
109
- "observed_id": c.observed_id,
110
- "session_id": c.session_id,
111
- "created_at": str(c.created_at),
112
- }
113
- for c in results
114
- ]
115
- print_result(items, columns=["id", "content", "workspace_id", "session_id", "created_at"], title=f"Conclusion search: {query}")
151
+ results = scope.query(query, top_k=top_k, filters={"level": level} if level else None)
152
+ items = [_format_conclusion(c, config.workspace_id) for c in results]
153
+ print_result(items, columns=_ATTRIBUTION_COLUMNS + ["created_at"], title=f"Conclusion search: {query}")
116
154
  except Exception as e:
117
155
  _handle_error(e, "conclusion", "search")
118
156
 
119
157
 
158
+ @app.command()
159
+ def get(
160
+ conclusion_ids: list[str] = typer.Argument(help="Conclusion IDs to fetch"),
161
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
162
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
163
+ ) -> None:
164
+ """Fetch conclusions by ID, from anywhere in the workspace.
165
+
166
+ Pass a conclusion's source_ids to see the premises it was derived from,
167
+ and repeat to walk a reasoning chain down to the explicit facts it rests
168
+ on. IDs that no longer exist are reported rather than failing the command.
169
+ """
170
+
171
+ handle_cmd_flags(json_output=json_output, workspace=workspace)
172
+ client, config = get_client()
173
+
174
+ for cid in conclusion_ids:
175
+ validate_resource_id(cid, "conclusion")
176
+
177
+ try:
178
+ conclusions = client.conclusions.get_many(list(conclusion_ids))
179
+ found = {c.id for c in conclusions}
180
+ missing = [cid for cid in conclusion_ids if cid not in found]
181
+
182
+ items = [_format_conclusion(c, config.workspace_id) for c in conclusions]
183
+ print_result(items, columns=_ATTRIBUTION_COLUMNS + ["observed_id", "created_at"], title="Conclusions")
184
+ if missing:
185
+ print_error(
186
+ "MISSING_CONCLUSIONS",
187
+ f"Not in this workspace (consolidated or deleted): {', '.join(missing)}",
188
+ )
189
+ except Exception as e:
190
+ _handle_error(e, "conclusion", "get")
191
+
192
+
193
+ @app.command()
194
+ def derived(
195
+ conclusion_id: str = typer.Argument(help="The premise conclusion ID"),
196
+ limit: int = typer.Option(10, "--limit", help="Max results"),
197
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
198
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
199
+ ) -> None:
200
+ """List the conclusions derived FROM a conclusion.
201
+
202
+ Walks the reasoning tree upward (premise -> conclusion); `conclusion get`
203
+ on a conclusion's source_ids walks it downward. Worth checking before
204
+ deleting or correcting a fact.
205
+ """
206
+
207
+ handle_cmd_flags(json_output=json_output, workspace=workspace)
208
+ validate_resource_id(conclusion_id, "conclusion")
209
+ client, config = get_client()
210
+
211
+ try:
212
+ page = client.conclusions.list(
213
+ size=limit, filters={"source_ids": {"contains": conclusion_id}}
214
+ )
215
+ items = [_format_conclusion(c, config.workspace_id) for c in page.items]
216
+ print_result(
217
+ items,
218
+ columns=_ATTRIBUTION_COLUMNS + ["observed_id", "created_at"],
219
+ title=f"Derived from {conclusion_id}",
220
+ )
221
+ except Exception as e:
222
+ _handle_error(e, "conclusion", "derived")
223
+
224
+
120
225
  @app.command()
121
226
  def create(
122
227
  content: str = typer.Argument(help="Conclusion content or JSON payload"),
@@ -9,12 +9,13 @@ import typer
9
9
 
10
10
  from honcho.api_types import PeerConfig
11
11
 
12
- from honcho_cli.commands.workspace import _config_to_dict, _handle_error, _raw_list
12
+ from honcho_cli.commands.workspace import _config_to_dict, _handle_chat_error, _handle_error, _raw_list
13
13
  from honcho_cli.output import print_error, print_result, use_json
14
+ from honcho_cli.recall import parse_csv_repeatable, reject_incompatible_recall, scope_for_sdk
14
15
  from honcho_cli.validation import validate_resource_id
15
16
 
16
17
  from honcho_cli._help import HonchoTyperGroup
17
- from honcho_cli.common import add_common_options, get_client, get_resolved_config, handle_cmd_flags
18
+ from honcho_cli.common import add_common_options, format_evidence, get_client, get_flag_overrides, get_resolved_config, handle_cmd_flags
18
19
 
19
20
  app = typer.Typer(cls=HonchoTyperGroup, help="List, create, chat with, search, and manage peers and their representations.")
20
21
  add_common_options(app)
@@ -130,6 +131,21 @@ def chat(
130
131
  query: str = typer.Argument(help="Question to ask about the peer"),
131
132
  target: Optional[str] = typer.Option(None, help="Target peer for perspective"),
132
133
  reasoning: Optional[str] = typer.Option(None, "--reasoning", "-r", help="Reasoning level: minimal, low, medium, high, max"),
134
+ scope: Optional[list[str]] = typer.Option(
135
+ None,
136
+ "--scope",
137
+ help="Recall only from this scope. Repeat or comma-separate for several (explicit conclusions only). Excludes -s and --sessions.",
138
+ ),
139
+ sessions: Optional[list[str]] = typer.Option(
140
+ None,
141
+ "--sessions",
142
+ help="Recall only from these session IDs (repeat or comma-separate); explicit conclusions only. Excludes -s and --scope.",
143
+ ),
144
+ evidence: bool = typer.Option(
145
+ False,
146
+ "--evidence",
147
+ help="Also report what the answer was built from: the conclusions and messages read and the tools called.",
148
+ ),
133
149
  workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
134
150
  peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
135
151
  session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
@@ -139,25 +155,50 @@ def chat(
139
155
 
140
156
  _REASONING_LEVELS = ("minimal", "low", "medium", "high", "max")
141
157
  if reasoning and reasoning not in _REASONING_LEVELS:
142
- from honcho_cli.output import print_error
143
158
  print_error("INVALID_REASONING", f"--reasoning must be one of: {', '.join(_REASONING_LEVELS)}")
144
159
  raise typer.Exit(1)
145
160
 
146
161
  handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer, session=session)
147
162
  pid = _get_peer_id(None)
148
163
  client, config = get_client()
149
- p = client.peer(pid)
164
+ scope_names = parse_csv_repeatable(scope, kind="scope", flag="--scope")
165
+ session_allowlist = parse_csv_repeatable(sessions, kind="session", flag="--sessions")
166
+ session_id = config.session_id or None
167
+ reject_incompatible_recall(
168
+ session_id=session_id,
169
+ scope=scope_names,
170
+ sessions=session_allowlist,
171
+ session_from_env=not get_flag_overrides()["session"],
172
+ )
173
+
174
+ chat_kwargs: dict[str, object] = {
175
+ "target": target,
176
+ "session": session_id,
177
+ "reasoning_level": reasoning or None,
178
+ }
179
+ scope_arg = scope_for_sdk(scope_names)
180
+ if scope_arg is not None:
181
+ chat_kwargs["scope"] = scope_arg
182
+ if session_allowlist is not None:
183
+ chat_kwargs["sessions"] = session_allowlist
150
184
 
151
185
  try:
152
- response = p.chat(
153
- query,
154
- target=target,
155
- session=config.session_id or None,
156
- reasoning_level=reasoning or None,
186
+ p = client.peer(pid)
187
+ if not evidence:
188
+ response = p.chat(query, **chat_kwargs)
189
+ print_result({"peer_id": pid, "query": query, "response": response})
190
+ return
191
+ result = p.chat(query, include_evidence=True, **chat_kwargs)
192
+ print_result(
193
+ {
194
+ "peer_id": pid,
195
+ "query": query,
196
+ "response": result.content,
197
+ "evidence": format_evidence(result.evidence),
198
+ }
157
199
  )
158
- print_result({"peer_id": pid, "query": query, "response": response})
159
200
  except Exception as e:
160
- _handle_error(e, "peer", pid)
201
+ _handle_chat_error(e, "peer", pid)
161
202
 
162
203
 
163
204
  @app.command()