Skip to content
This repository was archived by the owner on May 31, 2026. It is now read-only.

Commit e11a1c8

Browse files
authored
Merge pull request #6 from basicmachines-co/docs/skill-cross-project
fix: cross-project routing recipe + backfill bm_recent
2 parents d4c7e48 + 1307ec5 commit e11a1c8

3 files changed

Lines changed: 126 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
99
### Added
1010
- **Per-call project routing on every `bm_*` tool.** All eight tools now accept optional `project` (name) and `project_id` (UUID from `bm_projects`) parameters. The agent can write or read against a project other than the Hermes-configured one — useful when the user asks to write into a different cloud project (e.g. a personal `main` project) without reconfiguring the plugin. `project_id` takes precedence over `project`; both fall back to the configured default when omitted. Workspace routing is handled transparently by BM via `project_id` — no separate workspace parameter is needed.
1111
- **`bm_projects` and `bm_workspaces` agent tools.** Promotes the discovery logic previously available only as `/bm-project` and `/bm-workspace` slash commands to agent-facing tools. `bm_projects` returns JSON with `name` and `external_id` (UUID) per project so the agent can hand the UUID to `bm_write` / `bm_read` / etc. via `project_id` — the unambiguous form across cloud workspaces. `bm_workspaces` lists BM Cloud workspaces (name, type, role, default flag). Together with per-call routing, these unblock the workflow Drew's friction note flagged: agent picks the right project + workspace before writing, instead of silently operating against the active Hermes memory project.
12+
- **SKILL.md cross-project workflow** documenting the discovery → route → write → verify recipe end-to-end. Adds a "Permalinks" section covering the three canonical shapes (short, project-qualified, workspace-qualified) and the round-trip property where `bm_write`'s returned permalink self-routes for follow-up reads. A "Cross-project routing" section explains `project` (including workspace-qualified syntax like `"personal/main"`) vs `project_id` and when to use each. Also backfills `bm_recent` documentation (the tool shipped in 0.2.0 but the skill hadn't been updated).
13+
- **SKILL.md "Further reading" section** linking to the official docs at [docs.basicmemory.com](https://docs.basicmemory.com), with raw-markdown URLs (`/raw/<path>.md`) the agent can `WebFetch` on demand for deeper material — knowledge format, observations & relations, memory URL wildcards, semantic search, cloud routing, BM's full MCP tool surface, and the `llms.txt` sitemap.
1214

1315
### Notes
14-
- Addresses the routing and discovery friction in the real-world note "Hermes Basic Memory Cloud Task Experience." A SKILL.md update with the worked discovery → route → write → verify workflow is the remaining follow-up. A proposed `bm_import` tool was evaluated and dropped — `read_file` + `bm_write` already composes the same operation with no new capability, at the cost of one more tool in the surface.
16+
- Addresses the routing, discovery, and documentation gaps in the real-world note "Hermes Basic Memory Cloud Task Experience." A proposed `bm_import` tool was evaluated and dropped — `read_file` + `bm_write` already composes the same operation with no new capability, at the cost of one more tool in the surface.
1517
- The slash commands `/bm-project` and `/bm-workspace` still exist and behave identically — they continue to call `list_memory_projects` / `list_workspaces` directly via the actor. No behavior change for human use.
1618

1719
## [0.2.0] — 2026-05-11

skill/SKILL.md

Lines changed: 118 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ The CLI is fine when you genuinely need a feature these wrappers don't expose (r
2323
| Create / update a note | `bm_write` / `bm_edit` |
2424
| Navigate relations | `bm_context` |
2525
| Move / delete | `bm_move` / `bm_delete` |
26+
| What's been touched lately | `bm_recent` |
27+
| List available projects | `bm_projects` |
28+
| List cloud workspaces | `bm_workspaces` |
2629

2730
## Tool reference
2831

@@ -75,6 +78,103 @@ bm_edit({
7578
### `bm_delete` / `bm_move` — maintenance
7679
Use sparingly. `bm_move` takes `new_folder`.
7780

81+
### `bm_recent` — what's been touched lately
82+
Returns notes updated within a window. Use when there's no specific query yet — e.g. "what was I working on yesterday?"
83+
84+
```
85+
bm_recent({ timeframe: "7d" })
86+
bm_recent({ timeframe: "yesterday", limit: 20 })
87+
bm_recent({ timeframe: "2 weeks", type: "entity" })
88+
```
89+
90+
`timeframe` accepts natural language (`"yesterday"`, `"2 weeks"`, `"last month"`) or compact forms (`"7d"`, `"24h"`). Default is `7d`.
91+
92+
### `bm_projects` — list available projects
93+
Returns name, workspace slug, and `external_id` (UUID) per project across local and cloud. Call this when the user names a project that isn't the active one. Route follow-up tool calls either by workspace-qualified name (`project: "personal/main"`) or by UUID (`project_id: "bf2a4c1e-d77f-..."`) — see Cross-project routing below.
94+
95+
```
96+
bm_projects()
97+
```
98+
99+
### `bm_workspaces` — list BM Cloud workspaces
100+
Workspaces are a BM Cloud concept. Returns name, type, role, and default flag. Pair with `bm_projects` when the same project name might exist in more than one workspace and you need to disambiguate.
101+
102+
```
103+
bm_workspaces()
104+
```
105+
106+
## Permalinks
107+
108+
A permalink is the canonical, URL-friendly identifier for a note. Three shapes exist; the read/write tools accept all of them:
109+
110+
| Shape | Example | When |
111+
|---|---|---|
112+
| **Short** | `decisions/auth-strategy` | Bare `folder/note-slug`. Tools need a `project` (or `project_id`) arg to route — the permalink alone isn't enough. |
113+
| **Project-qualified** | `main/decisions/auth-strategy` | `project-name/folder/note-slug`. Carries enough context to route without a separate `project` arg. |
114+
| **Workspace-qualified** | `personal/main/decisions/auth-strategy` | `workspace-slug/project-name/folder/note-slug`. Fully routes, including across cloud workspaces with same-named projects. |
115+
116+
**Important: the permalink returned by `bm_write` already encodes the routing it needs for follow-up reads.** If you wrote with `project="personal/main"`, you get back `personal/main/folder/note-slug` and can call `bm_read({ identifier: <that permalink> })` with no `project` arg. The permalink self-routes.
117+
118+
`memory://` URLs follow the same shapes: `memory://personal/main/decisions/auth-strategy` is valid. The `memory://` prefix is optional for `bm_read` (any of the three permalink shapes works directly); `bm_context` expects the prefix.
119+
120+
## Cross-project routing
121+
122+
Every read/write tool (`bm_search`, `bm_read`, `bm_write`, `bm_edit`, `bm_context`, `bm_delete`, `bm_move`, `bm_recent`) accepts optional `project` and `project_id`:
123+
124+
- `project` — project name, optionally workspace-qualified. Plain (`"main"`) when the name is globally unique; qualified (`"personal/main"`, `"team-paul/research"`) when you need to pick a specific cloud workspace by slug.
125+
- `project_id` — UUID from `bm_projects` (`external_id` field). The most stable identifier — survives project renames and works across workspaces without qualification. Wins over `project` if both are passed.
126+
127+
Omit both and the call uses the Hermes-configured active project.
128+
129+
```
130+
# Plain project name (unique)
131+
bm_write({ title: "...", folder: "...", content: "...", project: "main" })
132+
133+
# Workspace-qualified name (disambiguates same-named projects across workspaces)
134+
bm_write({ title: "...", folder: "...", content: "...", project: "personal/main" })
135+
136+
# UUID (most stable, survives renames)
137+
bm_write({ title: "...", folder: "...", content: "...", project_id: "bf2a4c1e-d77f-..." })
138+
```
139+
140+
`bm_projects` and `bm_workspaces` themselves do **not** take routing — they list across everything.
141+
142+
## Recipe: writing an existing file into a specific project
143+
144+
When the user asks something like *"save this markdown file to my personal `main` project, return the permalink"*:
145+
146+
1. **Discover the project.** Call `bm_projects()` and find the entry matching the user's described project + workspace. You can route by either the workspace-qualified name (`personal/main`) or the UUID (`external_id`).
147+
148+
```
149+
bm_projects()
150+
# → [{name: "main", external_id: "bf2a4c1e-d77f-4b7a-9c3e-5d8a1f0e2b6d", workspace: "Personal", ...}, ...]
151+
```
152+
153+
If a project name appears in multiple workspaces, use `bm_workspaces()` to confirm which slug you want.
154+
155+
2. **Read the file from disk.** Use Hermes's filesystem tool (not a `bm_*` tool — local files aren't in the graph yet).
156+
157+
3. **Write the note with explicit routing.** Either form works; the workspace-qualified name reads cleaner in logs, the UUID is more durable.
158+
159+
```
160+
bm_write({
161+
title: "StartWithDrew Level 9 Task Queue",
162+
folder: "startwithdrew",
163+
content: <file body>,
164+
project: "personal/main"
165+
})
166+
# → returns "personal/main/startwithdrew/start-with-drew-level-9-task-queue"
167+
# (the returned permalink is workspace-qualified — carries its own routing)
168+
```
169+
170+
4. **Verify by reading back.** No `project` arg needed — the workspace-qualified permalink routes itself.
171+
172+
```
173+
bm_read({ identifier: "personal/main/startwithdrew/start-with-drew-level-9-task-queue" })
174+
```
175+
176+
Return the permalink (and the project name for clarity) to the user.
177+
78178
## When to use each tool
79179

80180
| Situation | Tool |
@@ -83,10 +183,14 @@ Use sparingly. `bm_move` takes `new_folder`.
83183
| User exposes a decision, plan, or meeting outcome | offer to `bm_write` |
84184
| Updating prior work | `bm_edit` (append for time-ordered logs, replace_section for living docs) |
85185
| Exploring related concepts | `bm_context` |
186+
| "What was I working on yesterday?" / no specific query yet | `bm_recent` |
187+
| User names a project that isn't the active one | `bm_projects` → call read/write tool with `project: "workspace/name"` or `project_id: "<uuid>"` |
188+
| Same project name might exist in multiple workspaces | `bm_projects` (+ `bm_workspaces` if needed) → route with workspace-qualified `project` or `project_id` |
189+
| Following up on a freshly-written note | Use the returned permalink directly — it already encodes the routing |
86190

87191
## Note structure
88192

89-
Use consistent markdown:
193+
BM treats `- [category]` lines as **observations** and WikiLink lines under `## Relations` as **relations**. Categories (`[decision]`, `[insight]`, `[risk]`, `[fact]`, `[todo]`, …) and relation types (`relates_to`, `implements`, `depends_on`, `blocks`, …) are open-ended — use what fits the content. YAML frontmatter is supported with `title`, `type`, `tags`, and `permalink` as standard fields; any custom fields are allowed. See the [knowledge format docs](https://docs.basicmemory.com/raw/concepts/knowledge-format.md) for the full convention.
90194

91195
```markdown
92196
# Clear Title
@@ -112,10 +216,6 @@ Background and current situation.
112216
- [ ] Document
113217
```
114218

115-
## Memory URLs
116-
117-
`memory://projects/api-redesign` — direct reference. Used in `bm_context`, `bm_read`. The `memory://` prefix is optional for `bm_read`.
118-
119219
## Behavior guidelines
120220

121221
1. **Search before answering.** If the user asks "what did we decide about X?", run `bm_search` first.
@@ -127,3 +227,16 @@ Background and current situation.
127227
## Footgun
128228

129229
If a note's body contains literal `<memory-context>...</memory-context>` tags, Hermes's streaming output scrubber will eat those tags (and the text between paired ones) when you echo the note verbatim back to the user. Tool *inputs* are unaffected. If you must include such content, fence it in a code block.
230+
231+
## Further reading
232+
233+
Official docs live at [docs.basicmemory.com](https://docs.basicmemory.com). Every page has an AI-friendly raw markdown view at `/raw/<path>.md` (or send `Accept: text/markdown` to the canonical URL). `WebFetch` any of these when you need detail beyond what this skill covers:
234+
235+
- **[Knowledge format](https://docs.basicmemory.com/raw/concepts/knowledge-format.md)** — observation categories, relation types, frontmatter conventions.
236+
- **[Observations & relations](https://docs.basicmemory.com/raw/concepts/observations-and-relations.md)** — how notes form a graph that's searchable and traversable.
237+
- **[Memory URLs](https://docs.basicmemory.com/raw/concepts/memory-urls.md)** — title-based addressing, wildcards (`memory://docs/*`), and routing resolution order.
238+
- **[Projects & folders](https://docs.basicmemory.com/raw/concepts/projects-and-folders.md)** — multi-project layout, folder organization, cloud routing behavior.
239+
- **[Semantic search](https://docs.basicmemory.com/raw/concepts/semantic-search.md)** — how `bm_search` resolves queries (semantic + full-text).
240+
- **[MCP tools reference](https://docs.basicmemory.com/raw/reference/mcp-tools-reference.md)** — Basic Memory's full MCP surface (the `bm_*` tools here are a curated subset).
241+
- **[Cloud routing](https://docs.basicmemory.com/raw/cloud/routing.md)** — local vs cloud project modes, per-project routing setup.
242+
- **[llms.txt index](https://docs.basicmemory.com/llms.txt)** — full sitemap of raw markdown pages, useful when you need to look up a page not listed above.

tests/test_helpers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def test_translate_uses_project_id_override(bm):
314314
"""Agent passes project_id=<uuid> → reaches BM as project_id, with no
315315
project name in the call (would be redundant and risk server-side
316316
precedence surprises)."""
317-
uuid = "01HXYZ123ABC456DEF789GHI"
317+
uuid = "bf2a4c1e-d77f-4b7a-9c3e-5d8a1f0e2b6d"
318318
_, args = bm._translate_args(
319319
"bm_search", {"query": "hi", "project_id": uuid}, "default-proj"
320320
)
@@ -325,7 +325,7 @@ def test_translate_uses_project_id_override(bm):
325325
def test_translate_project_id_wins_when_both_supplied(bm):
326326
"""If the agent passes both, project_id is the more specific identifier
327327
(UUID across workspaces) and takes precedence. Only project_id reaches BM."""
328-
uuid = "01HXYZ123ABC456DEF789GHI"
328+
uuid = "bf2a4c1e-d77f-4b7a-9c3e-5d8a1f0e2b6d"
329329
_, args = bm._translate_args(
330330
"bm_search",
331331
{"query": "hi", "project": "main", "project_id": uuid},
@@ -361,9 +361,9 @@ def test_translate_routing_works_for_every_tool(bm, tool, base_args):
361361
_, out = bm._translate_args(tool, args_with, "default-proj")
362362
assert out["project"] == "main"
363363

364-
args_with_id = dict(base_args, project_id="uuid-1")
364+
args_with_id = dict(base_args, project_id="e1d3a5b8-0492-4c1f-8e7d-2a4b6c8d0e2f")
365365
_, out = bm._translate_args(tool, args_with_id, "default-proj")
366-
assert out["project_id"] == "uuid-1"
366+
assert out["project_id"] == "e1d3a5b8-0492-4c1f-8e7d-2a4b6c8d0e2f"
367367
assert "project" not in out
368368

369369
_, out = bm._translate_args(tool, base_args, "default-proj")
@@ -398,7 +398,7 @@ def test_translate_global_tools_ignore_project_kwargs(bm):
398398
the listing would be worse than ignoring the args."""
399399
_, out = bm._translate_args(
400400
"bm_projects",
401-
{"project": "main", "project_id": "uuid-1"},
401+
{"project": "main", "project_id": "e1d3a5b8-0492-4c1f-8e7d-2a4b6c8d0e2f"},
402402
"default-proj",
403403
)
404404
assert "project" not in out

0 commit comments

Comments
 (0)