Skip to main content
Version: 2026.09

Folders in a System

Arrange the Resources tracked by a System into a tree from Python (istari-digital-client 13.2.1). A folder is a path prefix on a tracked Resource in a commit, not an object you create: the web app shows the names you typed, and the API stores an encoded path.

On 13.0.x, the branch method used below is named branch.files() rather than branch.resources(); everything else on this page is the same.

For what folders are and when a Subsystem fits better, see Systems and Subsystems. For the same task in the browser, see Folders.

Prerequisites​

  • A configured Client — see SDK setup.
  • Editor (or above) on the target System.

Path encoding​

The write form is the System id without hyphens, then one dot-separated segment per folder level. Letters and digits stay literal; every other ASCII character becomes _XX — two uppercase hex digits of its code:

You meanStored as
10-Analysis10_2DAnalysis
tier 1 nativetier_201_20native
v1_extractedv1_5Fextracted

Folder names must be ASCII. A bare -, space, or _ in a segment is rejected.

import re

def folder_path(system_id: str, *names: str) -> str:
"""The `<32-hex system id>.<label>…` form that NewTrackedFile(folder_path=…) expects."""
def label(name: str) -> str:
if not name.isascii():
raise ValueError(f"folder names must be ASCII: {name!r}")
return re.sub(r"[^A-Za-z0-9]", lambda m: f"_{ord(m.group()):02X}", name)

return ".".join([system_id.replace("-", ""), *(label(n) for n in names)])

Reading a path back​

branch.resources() returns the branch's tracked Resources, each carrying a path. That path ends with the Resource's own id as a final segment (<system-hex>.<folders…>.<file-hex>); the path you write must not include it, so strip the trailing 32-hex segment before reusing a stored path:

def folders_of(path: str | None) -> list[str]:
"""The encoded folder segments of a tracked Resource path, without the file-id tail."""
hex32 = re.compile(r"^[0-9a-f]{32}$")
return [] if not path else [s for s in path.split(".")[1:] if not hex32.match(s)]

def decode_label(segment: str) -> str:
"""`10_2DAnalysis` -> `10-Analysis`, the name the web app shows."""
return re.sub(r"_([0-9A-F]{2})", lambda m: chr(int(m.group(1), 16)), segment)

Committing into a folder​

branch.commit() on the Istari facade takes only add and remove: it cannot set a folder, and it rebuilds every carried tracked file without one, so existing folders are dropped. Commit folder layout with Client instead — build a configuration in which every tracked Resource carries its folder_path, then move the branch tag to the snapshot that configuration produced.

Both clients take the same Configuration, so one credential serves the branch read and the commit:

from istari_digital_client import (Client, NewSystemConfiguration, NewTrackedFile,
TrackedFileSpecifierType, UpdateTag)
from istari_digital_client.sdk import Istari

istari, client = Istari(configuration), Client(configuration)

system = istari.systems.get(system_id)
baseline = system.get_branch("baseline")

# Carry what is already on the branch, each file keeping its own folder.
keep = [
NewTrackedFile(
specifier_type=TrackedFileSpecifierType(tracked.specifier_type),
file_id=tracked.file_id,
pinned_file_revision_id=tracked.pinned_file_revision_id,
**({"folder_path": folder_path(system.id, *(decode_label(s) for s in folders_of(tracked.path)))}
if folders_of(tracked.path) else {}),
)
for tracked in baseline.resources()
]

# Add the new Resource into 10-Model/. Omit folder_path to put it at the root.
new = [NewTrackedFile(specifier_type=TrackedFileSpecifierType.LATEST,
file_id=resource.file_id,
folder_path=folder_path(system.id, "10-Model"))]

configuration_ = client.create_configuration(
system.id, NewSystemConfiguration(name="add bracket", tracked_files=keep + new))
snapshot = next(s for s in client.list_snapshots(system.id, page=1, size=100).items
if s.configuration_id == configuration_.id)
tag = next(t for t in client.list_tags(system.id).items if t.tag == "baseline")
client.update_tag(tag.id, UpdateTag(snapshot_id=snapshot.id))

Every Resource you leave out of tracked_files leaves the branch, and every one you carry without a folder_path moves to the root — so build keep from the branch you are about to replace rather than from memory.

A runnable version of all of this is istari_quickstart_13_2_1.py, and the istari-folders skill is the same recipe for an AI coding agent.