Foreign Document Access
Cross-project document access within a Tenant via a dedicated, read-centric tool family
foreign_*. Replaces the existingcross_doc_*family and unifies it with the new read path.
1. Motivation & Delimitation
The normal case in Vancetope is strict Project isolation: doc_* tools implicitly operate within the current Project of the Think Process, and the Scope cascade never looks sideways (architektur-scopes-clients.md — “nothing is visible between Projects; exchange only via the common parent level”). This is a statement about automatic memory/context visibility, not an access prohibition.
However, there is a real need for explicit, authorized on-demand access across Project boundaries:
- “Look in Project XY, we solved it that way there — I’d like that here too.”
- “Copy the search results from Project XY here.”
foreign_* addresses exactly this: it is not memory bleed, but a tool that a model must consciously grasp, gated by the normal permission chain. The common parent level of two Projects is the Tenant — foreign_* remains consistently Tenant-internal.
Previously, only Eddie (as a Hub agent with “Spot”/workingProjectId) could effectively access foreign Projects. With foreign_*, access becomes an engine-neutral, permission-driven feature — in particular, Arthur should be able to read foreign Projects without gaining write permissions there.
2. Core Invariant
A Process may read any Project where its Subject is READER — but only write where it has CREATE.
The “read-only for Arthur” guarantee thus arises not from tool selection, but from the permission: Arthur is typically only a READER on foreign Projects, so any write to a foreign Project will fail-closed due to missing CREATE/DELETE. This is consistent with the Vancetope rule “Enforcement points never speak roles” (no if(isAdmin()), no Engine-Role-Gate for documents) — see permission-system.md.
3. Tool Interface
All tools are server tools (@Component in vance-brain), deferred (discoverable via tool_list), and are enabled per Recipe via allowedToolsAdd. projectId/fromProjectId are mandatory — this deliberately distinguishes the family from the implicit doc_* tools.
| Tool | Purpose | Permission |
|---|---|---|
foreign_project_list(query?) |
List readable Projects; optional query filters by Name/Title. Without query = all readable. |
ProjectService.listReadableBy |
foreign_doc_list(projectId, folder?) |
List documents of a foreign Project (optionally under folder). |
Project READ |
foreign_doc_search(projectId, query) |
Metadata/path search (Title/Summary/Tags/Path) in the foreign Project. No RAG/Vector Query. | Project READ |
foreign_doc_read(projectId, path\|id) |
Read content of a foreign Document. | Project READ + Document READ |
foreign_doc_copy(fromProjectId, fromPath, toProjectId?, toPath?) |
Copy; toProjectId default = current Project, toPath default = fromPath. |
READ on source + CREATE on target |
foreign_doc_move(fromProjectId, fromPath, toProjectId?, toPath?) |
Copy-then-trash. For Orchestrators (Eddie/Trillian), not in Arthur’s Recipe. | READ+DELETE source + CREATE target |
3.1 Why one foreign_doc_copy instead of two
Direction is parametric, not a second tool. The most common case (“from XY here”) is foreign_doc_copy(fromProjectId=XY, fromPath=…) with toProjectId omitted. Eddie’s old case “current → foreign” is the same call with toProjectId set. This keeps the number of commands small (stated goal) and cleanly derives the read-only guarantee from the permission.
3.2 Copy Semantics
foreign_doc_copy/foreign_doc_move create a fresh copy: content plus selected meta, no lineageId link across Project boundaries (behavior like Kit-Apply). The target Document is independent in the new Project. Writing is done as WriteReason.USER (target path is caller-controlled).
4. Security Constraints
- Tenant-internal: no cross-Tenant access. Target/source outside the Tenant → error (
CROSS_PROJECT_NOT_IN_TENANT), analogous to the existingDocLinkTool. - Filtered lists do not mean “does not exist”:
foreign_project_listand the genericproject_listreturn what the caller is allowed to read — not what exists. An agent that infers non-existence from absence and reports it upstream creates a phantom Project from a missing grant (actually happened, seetrillian-engine.md§9a context).project_listtherefore states it in the result itself (filteredByPermissions+note) and in its description; it does not reveal names or total numbers. - System Projects filtered: Projects with
_prefix (_vance,_tenant,_user_*) do not appear inforeign_project_listand are rejected as targets for copy/move (as withcross_doc_copytoday).foreign_doc_list/_searchhide_vance/…paths to prevent configuration/Manual documents from leaking across Projects. - Access denied: missing READ on the target Project →
CROSS_PROJECT_DENIED(tool error message, no Process abort). - Enforcement point: Project resolution + READ check centrally analogous to
EddieContext.resolveProject(theprojectIdparameter is caller-controllable, hence a hardPermissionService.enforceper call).
5. Engine Assignment
- Arthur: Recipe receives tools 1–5 (
foreign_project_list,foreign_doc_list/search/read,foreign_doc_copy) viaallowedToolsAdd.foreign_doc_movenot. Effectively read-only on foreign Projects due to permission. - Eddie / Trillian: additionally receive
foreign_doc_move(write/move orchestration). No Engine-Role-Gate — enablement occurs purely via the respective Recipe.
6. Relationship to the existing cross_doc_* family
cross_doc_list_projects, cross_doc_copy, cross_doc_move are migrated to foreign_*:
| old | new |
|---|---|
cross_doc_list_projects |
foreign_project_list |
cross_doc_copy |
foreign_doc_copy |
cross_doc_move |
foreign_doc_move |
Reason for the name change: foreign_* immediately implies “an other Project” and generalizes across object types (foreign_doc_* + foreign_project_*), whereas cross_doc_list_projects is already inconsistent (doc-prefix, but lists Projects). Recipes and Manual Hooks that reference the old names will be updated (see implementation plan planning/foreign-document-access.md).
7. Deliberate Non-Goals (v1)
- No Cross-Project RAG/Vector Search —
foreign_doc_searchis metadata/path search. Semantic search across Project boundaries remains a RAG Non-Goal (rag.md). - No Knowledge Graph across Project boundaries — remains KG roadmap.
- No origin/update linking of copied documents (no Lineage across Projects). A later “update from source” would be a v2 extension.