Resource discovery

Find and manage resources and their loading rules.

Prompts, skills, and executable extensions share one filesystem resolver. Resource-specific parsers own their schemas; the resolver owns the cross-cutting local safety and precedence contract. Built-in auto, light, and dark appearances are available through /theme without theme files. Named theme files follow the separate bounded theme loader.

Locations and precedence#

Kind Global Trusted project Explicit option
Prompt ~/.octet/prompts/*.{md,toml} .octet/prompts/*.{md,toml} --prompt-template <file-or-dir>
Skill Ordered user roots Ordered project roots --skill-dir
Extension ~/.octet/extensions/*/extension.toml .octet/extensions/*/extension.toml --extension-dir

Roots are visited global, project, then explicit in option order. An explicit Pi-compatible prompt source may be one .md/.toml file or a directory. Later definitions with the same resource name win, and the shadowed path remains in the diagnostic snapshot. Scans and result ordering are deterministic. A valid package-manager install.json admits an installed bundle's nested skills/ root; merely copying an unmanaged extension directory does not. Bundle skills have lower precedence than ~/.octet/skills, remain inactive until explicitly loaded, and disappear from the next discovery snapshot after package removal.

Workspace resources are ignored until --workspace-trusted is present. Explicit paths are an intentional user choice for that invocation. Executable extensions add a second boundary: discovery and workspace trust still do not launch code. Installed executable extensions remain disabled by default. Full access (unsafe_host, the default) implicitly trusts the selected validated source, so an explicitly enabled extension needs no extra trust flag. Trust and enablement are separate; implicit trust never writes a grant to configuration. Process startup still requires full access and the independent process gate.

--safe-mode removes implicit trust and blocks executable startup even when an explicit grant exists. It does not sandbox extensions or allow approval to bypass the unsafe_host floor. A project config cannot create a persistent executable trust grant. Bare persistent trust names apply only to the global extension directory; project and explicit sources require an exact absolute name@.../extension.toml grant to persist trust. --trust-extension name grants explicit trust only for that invocation, never enablement. These grants do not transfer between sources or override safe-mode execution policy. The extension directory name must match the manifest name, and source, compatibility, bundle, and artifact validation remain mandatory.

If octet cannot resolve an absolute user home directory, global configuration and global resources are disabled with a diagnostic. It never falls back to the invocation directory and reclassifies project files as user-owned resources.

Skill roots#

Skill discovery uses this low-to-high precedence order:

  1. User: ~/.agents/skills, then ~/.pi/agent/skills, then managed extension skills, then ~/.octet/skills.
  2. Trusted project: .agents/skills roots from the workspace through the invocation directory, then the invocation directory's .pi/skills, then the workspace's .octet/skills. These project roots require --workspace-trusted.
  3. Explicit: --skill-dir sources in command-line option order.

A project root that is also a user skill root is scanned only in the user tier. For example, starting in your home directory keeps ~/.agents/skills and ~/.octet/skills user-installed, without an untrusted-project warning for those same roots. This does not trust the workspace: distinct project roots, including nested .agents/skills and the invocation's .pi/skills, remain gated. Root symlinks are still rejected.

Later definitions replace earlier definitions of the same skill name; collisions are recorded in discovery diagnostics. Discovery does not activate a skill. The octet-native entrypoints remain ~/.octet/skills/*/SKILL.md, .octet/skills/*/SKILL.md, and managed ~/.octet/extensions/*/skills/*/SKILL.md.

These lookup locations do not promise full Agent Skills/Pi parser compatibility or additional symlink support. Parser-specific shapes and symlink behavior for those additional roots require their exact source contract.

Skill catalog budgets#

Discovery accepts at most 32 KiB of YAML frontmatter per skill and has a 4096-entry per-root scan limit. Those are per-input limits, not global catalog limits. Across all roots, the retained discovery catalog additionally has these caps:

  • 1024 UTF-8 bytes per description, including an ellipsis when shortened. Only the metadata excerpt is shortened; the source file and instruction body are unchanged. The full description allocation is not retained.
  • 256 descriptors / 256 KiB of descriptor payload bytes, whichever is reached first. Payload counts text fields, encoded paths, and JSON-serialized arbitrary metadata, not allocator overhead. Admission follows deterministic root/candidate order. Later definitions still replace earlier ones of the same ID; if a larger replacement does not fit, its predecessor is removed rather than advertised as the winner.
  • 64 KiB of rendered model-catalog text, including XML escaping, framing, paths, and any cap notice. Rendering uses ID/path order and stops before the first entry that does not fit. Names, location paths, XML entities, and closing tags are never cut. disable-model-invocation skills remain excluded.

Description caps and omitted counts appear in discovery diagnostics. Catalog omission is not deactivation: winning source locations remain indexed, so a known /skill:NAME or /skills load NAME can still load an omitted skill with the usual trust, required-tool, symlink, and 256 KiB file limits. An omitted header is parsed on demand; a changed skill ID requires rediscovery. Listing and search use the bounded descriptors, not the complete source index. These limits bound retained descriptors and model context, not total discovery work or RSS: the lightweight source index and diagnostics still grow with discovered inputs.

Reads and diagnostics#

For octet-native resource roots, selected files, and directory entrypoints, the existing resolver contract requires regular, non-symlink filesystem objects. Parser reads use descriptor-bound no-follow opens and fixed byte limits:

Kind Maximum parser input
Prompt 512 KiB
Skill entrypoint 256 KiB
Extension manifest selected by the product resource resolver 256 KiB

Prompt expansion, skill resource reads, extension protocol messages, and session files have their own narrower purpose-specific limits after discovery. The lower-level ExtensionManifest::load API has a separate 64 KiB default; product discovery reads the selected manifest through the 256 KiB resolver bound and then calls ExtensionManifest::parse. Invalid UTF-8, invalid names, inaccessible roots, rejected links, oversized files, parser failures, and precedence decisions become inspectable diagnostics. One broken customization does not prevent the core binary from starting.

Automatic reload suppresses repeated resource/bootstrap and keybinding problems per checked component. A successful check clears that component's remembered problem, allowing a later recurrence to appear; skipped checks do not clear it. Explicit commands still report their diagnostics, and actual work losses are never suppressed as duplicate configuration warnings.

Reload#

Each discovery pass produces an immutable generation snapshot. Consumers build a complete replacement from the new snapshot and swap only after validation, so an in-flight prompt never observes half of a reload.

  • /skills reload refreshes the shared prompt/skill resource boundary.
  • /extensions reload handshakes replacement processes under the default full-access policy when the independent process gate permits startup; safe mode leaves executable extension processes stopped.
  • /reload performs full product resource discovery and rebuilds the active customization boundary.