---
name: istari-folders
description: >
  Organize an Istari Digital System's Resources into folders on SDK 13.x — read paths off
  TrackedResource.path, and use Client folder_path for writes (the Istari facade commit
  cannot set folders). Use for "put files in folders", "organize the system tree", or
  folder_path encoding.
metadata:
  sdk: istari-digital-client
  sdk-major: "13"
  sdk-range: ">=13.0,<14"
  skill-version: "13.0.0"
  verified-sdk: "13.0.1"
  verified-registry: "demo 11.1.0"
  verified-on: "2026-09-06"
---

# istari-folders

A folder is **not an object** — it is a path prefix on a tracked Resource, per commit (so per branch). Concept page: [Folders](/markdown-source/current/intro/key-concepts/systems.md).

## What the facade can and cannot do (13.x)

| Need                         | `Istari` facade                                                                 | `Client` (same package)                                   |
| ---------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| **read** a folder            | `f.path` on `TrackedResource` from `branch.files()`                             | `tracked_file.path`                                       |
| **write** a folder at commit | `branch.commit()` has no folder argument and drops folders of carried Resources | `NewTrackedFile(folder_path=…)` in `create_configuration` |

On any System with folders, writes go through `Client` (same `Configuration` as `istari-connect`).

## Helpers

```python
import re
HEX = re.compile(r"^[0-9a-f]{32}$")

def label(name: str) -> str:
    if not name.isascii():
        raise ValueError(f"ASCII only: {name!r}")
    return re.sub(r"[^A-Za-z0-9]", lambda m: f"_{ord(m.group()):02X}", name)

def folder_path(system_id: str, *names: str) -> str:
    return ".".join([system_id.replace("-", ""), *(label(n) for n in names)])

def folders_of(path: str | None) -> list[str]:
    return [] if not path else [s for s in path.split(".")[1:] if not HEX.match(s)]

def decode(seg: str) -> str:
    return re.sub(r"_([0-9A-F]{2})", lambda m: chr(int(m.group(1), 16)), seg)
```

Keep `[A-Za-z0-9]` literal; everything else `_XX` (uppercase hex). A bare `-` / space / `_` returns HTTP 400. ASCII only.

The **read** form ends in the Resource's id; the **write** form must not. Passing the read form back appends the id as another folder. Segments recovered from a stored path are already encoded — do not `label()` them again.

## Commit into folders

```python
from istari_digital_client import (
    Client, NewSystemConfiguration, NewTrackedFile, TrackedFileSpecifierType, UpdateTag,
)
legacy = Client(cfg)
base = client.systems.get(SID).get_branch("baseline")
keep = [
    NewTrackedFile(
        specifier_type=TrackedFileSpecifierType(f.specifier_type),
        file_id=f.file_id,
        pinned_file_revision_id=f.pinned_file_revision_id,
        **({"folder_path": ".".join([SID.replace("-", ""), *folders_of(f.path)])}
           if folders_of(f.path) else {}),
    )
    for f in base.files()
]
new = NewTrackedFile(
    specifier_type=TrackedFileSpecifierType.LATEST,
    file_id=new_resource.file_id,
    folder_path=folder_path(SID, "10-Analysis"),
)
cfg_ = legacy.create_configuration(
    SID, NewSystemConfiguration(name=f"commit {ts}", tracked_files=keep + [new]),
)
snap = next(
    x for x in legacy.list_snapshots(SID, page=1, size=100).items
    if x.configuration_id == cfg_.id
)
tag = next(t for t in legacy.list_tags(SID).items if t.tag == "baseline")
legacy.update_tag(tag.id, UpdateTag(snapshot_id=snap.id))
```

Worked example: `/quickstart/istari_quickstart.py`. Layout conventions: `istari-structure`.
