Files & Mirrors
Files in Portuni live in two places at once: the remote (the source of
truth for the team) and the local mirror on each device. The metadata
row in files binds a node to a remote location; the path on the current
device is derived from the per-device mirror root, the file’s remote_path,
and the node’s sync_key. There is no persisted local_path column on
files – it would go stale across devices and renames.
portuni_mirror
Section titled “portuni_mirror”Create a local folder for a node on this device and register it.
| Parameter | Type | Required | Description |
|---|---|---|---|
node_id |
string | yes | Node ID |
targets |
string[] | yes | Mirror targets (only "local" supported in Phase 1) |
custom_path |
string | no | Override default path |
Default path: {PORTUNI_WORKSPACE_ROOT}/{org-slug}/{type-plural}/{node-sync-key}/
(organizations mirror directly to {PORTUNI_WORKSPACE_ROOT}/{org-slug}/).
Locally, every mirror gets the outputs/, wip/, resources/
subdirectories. The org-plural subfolders (projects/, processes/,
areas/, principles/) are scaffolded on the remote when an
organization is mirrored; locally they appear only as parent directories
once child nodes are mirrored.
Returns: { node_id, local_path, subdirs, remote_scaffold, scope_config } —
remote_scaffold lists the remote folders created and the resolved
remote_name; scope_config lists the per-mirror config files written.
Mirror registrations are per device. Each machine keeps its own copy
of the registry in {PORTUNI_WORKSPACE_ROOT}/.portuni/sync.db; the shared
Turso DB does NOT store per-device paths.
portuni_store
Section titled “portuni_store”Copy a file into the node’s local mirror, upload it via the routed remote,
and persist a files row + file_state cache.
| Parameter | Type | Required | Description |
|---|---|---|---|
node_id |
string | yes | Node ID |
local_path |
string | yes | Absolute path of the source file on this device |
status |
enum | no | wip (default) or output |
subpath |
string | no | Optional subfolder within the section |
The file is copied to {mirror}/wip/... or {mirror}/outputs/... based
on status (or detected from the source path if it already lives inside
the mirror), then uploaded to the remote at
{org-sync-key}/{type-plural}/{node-sync-key}/{section}/{subpath}/{filename}.
The remote is resolved through remote_routing (priority-ordered).
The sync_key-anchored path means renaming a node does NOT change the
remote location, so existing references stay valid.
Returns: { file_id, remote_name, remote_path, local_path, hash }
portuni_pull
Section titled “portuni_pull”Two modes:
file_id– download the remote version into the mirror, refresh the local hash cache. Used to restore a deleted local copy or pull a teammate’s update.node_id– preview only. Classifies each file asunchanged | updated | conflict | orphan | nativewithout modifying anything. Use this before pulling to see what would change.
| Parameter | Type | Required | Description |
|---|---|---|---|
file_id |
string | one of | File ID (download mode) |
node_id |
string | one of | Node ID (preview mode) |
force |
boolean | no | Download mode only. Overwrite the local file even when it has unpushed local changes. Default false |
portuni_list_files
Section titled “portuni_list_files”List files across all nodes with optional filtering. Each row includes a
derived local_path (from the current mirror + remote_path +
sync_key); it is null when the node has no mirror on this device.
local_path is the node’s real mirror for the home node and its
depth-1 neighbours (the seatbelt grants read on those real paths). For an
ad-hoc in-scope node (deeper than depth-1) it is null — the files are
not on disk; read their content with portuni_read_file (below). See
disk read scope.
Scope gating: with node_id the node must be in session scope (out of
scope returns scope_expansion_required). Without node_id the call is
a global query — mode-gated, and results are restricted to the session
scope set (empty scope returns an empty array) unless the mode is
permissive. The same gating applies to the other list tools
(portuni_list_events, …).
| Parameter | Type | Required | Description |
|---|---|---|---|
node_id |
string | no | Filter by node |
status |
enum | no | Filter by status (wip or output) |
limit |
number | no | Max rows, newest first (default 500, max 2000) |
Returns: Array of files, each with: id, node_id, node_name,
filename, status, remote_name, remote_path,
current_remote_hash, last_pushed_at, is_native_format, the derived
local_path, and updated_at.
portuni_read_file
Section titled “portuni_read_file”Read a file’s content from an in-scope node that the seatbelt does not
expose on disk — an ad-hoc node reached by deeper graph traversal (beyond
the home node and its depth-1 neighbours, whose folders you read natively
via the local_path returned by portuni_get_context / portuni_get_node).
The server reads the live file from the node’s local mirror and returns it,
so there is no stale copy and no .portuni-scope staging.
| Parameter | Type | Required | Description |
|---|---|---|---|
node_id |
string | yes | Node the file belongs to |
path |
string | yes | File path within the node, e.g. wip/notes.md |
Returns the file content as UTF-8 text, or [binary file, N bytes, base64]
followed by base64 for non-text files. Scope-gated exactly like
portuni_get_node: reading a node not yet in scope returns
scope_expansion_required (call portuni_expand_scope first). Errors when
the node has no mirror on this device (portuni_pull it first), the file
does not exist, or the file exceeds the 1 MB inline limit.
portuni_status
Section titled “portuni_status”Scan tracked files and (optionally) discover new local / new remote files. Call this at session end when files were touched, before major migrations, or whenever the user asks about sync state.
| Parameter | Type | Required | Description |
|---|---|---|---|
node_id |
string | no | Restrict to one node |
remote_name |
string | no | Restrict to one remote |
include_discovery |
boolean | no | Walk the mirror + list the remote for new files (default: true) |
Returns: classified buckets (clean, push_candidates, pull_candidates,
conflicts, orphan, native, new_local, new_remote,
deleted_local, deleted_remote, moved).
portuni_list_remotes / portuni_setup_remote / portuni_set_routing_policy
Section titled “portuni_list_remotes / portuni_setup_remote / portuni_set_routing_policy”Manage the pluggable remote backends and the priority-ordered routing
rules that pick a remote for each (node_type, org_slug) combination.
See concepts/mirrors for the per-device mirror model.