orchestratr
reference

path grammar and glob patterns

the one grammar every surface derives from (segments, paths, patterns, the rand placeholder, loop names, run ids), plus the anchored matching rules, reserved names, and resolution order.

every surface (CLI validation, the SDK, the socket schema) derives from one grammar block. it is defined nowhere else, and no surface uses ad-hoc string matching or SQL LIKE. for the conceptual walkthrough, see identity: paths and uuids.

the grammar

segment   = [a-z0-9_]{1,64}
path      = segment ("/" segment)*        # ≤ 8 segments, ≤ 256 chars total;
                                          # the last segment is the name
abs_path  = "/" path                      # anchored to the root
pattern   = path where any segment may    # wildcards are whole segments only:
            be "*" or "**"                # a/b/*, a/*/b, a/**, a/**/b, no
                                          # partial forms (phase_*); bulk verbs
                                          # only; a trailing bare "/" is invalid
{rand}    = creation-only placeholder     # replaced with 5 random [a-z0-9] chars
                                          # anywhere in --name/--path values
loop name = segment                       # one segment, mandatory
run id    = "r" + 5 [a-z0-9]              # r82c9s, generated, never user-chosen
run path  = loop_name "/" run_id

a segment is lowercase alphanumeric plus underscore, up to 64 characters. a path is at most 8 segments and at most 256 characters total. a depth violation is invalid_request with details.reason: "path_too_deep" (carrying the effective path and count).

anchored matching

patterns are resolved against the caller's scope first (a leading / skips that), then matched anchored against the full path. review/* can never match reviewer/x.

  • * matches any characters except /: one whole segment.
  • ** matches any characters including /: any number of segments (one or more).

both may appear anywhere and more than once (*/review/* is legal). a bare * means every agent at the current level; a bare ** means everything under the scope. there are no partial-segment forms like phase_*. a trailing bare slash (review/) is invalid syntax.

so review/* matches agents directly under review (one level, not nested ones), and review/** matches everything under review at any depth, never the agent named review itself. self plus subtree is two targets: kill review "review/**".

the stored matcher preserves exactly these semantics: no raw string-prefix scans, no SQL LIKE, because _ is both a legal name character and a LIKE wildcard.

where patterns are accepted

verbaccepts
wait, kill, ls, top (filter)patterns and uuids (<pattern|uuid>)
send, logs, attachexact target only (<path|uuid>); wildcards rejected

quote patterns in the shell (kill "review/**") so your shell does not expand them against real files first.

the {rand} placeholder

same path means same agent slot, by definition: there is no silent auto-suffixing. when you want a fresh path each run, use {rand}: anywhere in a --name or --path value, it is replaced with 5 random lowercase alphanumeric characters before validation.

--path "review_{rand}/file_1"   →  review_u38c8/file_1  this run
                                   review_o90j8/file_1  the next

{rand} is creation-only. it never appears in a selector.

reserved level-1 names

some top-level path segments are owned by orcr, and are also rejected as loop names:

level-1 segmentyou may create paths here?owned bywhat it is
idlenoorcrthe parking workspace (GC)
unmanagednoorcragents you started by hand, auto-tracked
an active loop's nameonly from inside that loop's runsthe loopits runs and their agents
an ended loop's nameyesfree againhistory stays reachable by uuid

reserved names apply at level 1 only: inside a scope, --name idle resolving to review/idle is fine. loop protection applies to creation only: observing agents under an active loop (ls, wait, logs, top) always works, and kill "/nightly/**" works too (with the normal confirmation) as the escape hatch for a runaway loop. while loop nightly is active, nothing outside its own runs can create an agent anywhere under nightly/**, whether via a relative path or an absolute /nightly/….

enforcement order

every check runs on the effective path (the relative input already resolved against the caller's scope), in this order:

  1. parse the input.
  2. resolve against the caller's scope, unless the path is absolute.
  3. validate grammar and depth: invalid_request, details.reason: "path_too_deep".
  4. reserved level-1 check: invalid_request, details.reason: "reserved_name".
  5. active-loop ownership.
  6. active path uniqueness: state_conflict, details.reason: "path_in_use" with the occupying {uuid, path, status}.

steps 3 through 6 happen in one BEGIN IMMEDIATE transaction, which is what makes concurrent spawns safe.

resolution and uniqueness

a full uuid contains dashes, which names never do, so it resolves to its row directly, active or ended. a bare target is tried as a path first (the active agent, else the most recent ended agent with that path); only if nothing matches as a path is it tried as a uuid prefix of 8 or more hex characters.

results say which one you got: JSON carries resolved: "active" | "latest_ended". send, attach, and kill act on active agents only.

a full path must be unique among active agents (any non-ended status, including lost, which reserves its path until reconciliation confirms the terminal is gone). this is enforced by a partial unique index. paths of ended agents are reusable: the uuid is what stays unique forever. for older reuses of a path, use the uuid from ls --all.

On this page