Target Trailmaps: Local-First Packaging for Target-Aware Capabilities¶
Context¶
Trailblaze’s current external-config story is converging on a clear workspace model:
trails/is the workspace anchortrails/config/trailblaze.yamlis the workspace manifesttrails/config/is the artifact directory
That is a good base, but the current top-level config unit is still too narrow. A
targets/*.yaml file can describe how to detect an app/site and which tools/toolsets to load,
but the real thing we want to share is bigger than that:
- target detection (
gmail.com, Android package name, iOS bundle id) - tools and toolsets
- waypoints
- short recorded navigation segments between waypoints
- runnable trails
- optional JS/TS implementations backing some tools
This matters for both testing and agentic device control. A user may never author a test, but still benefits enormously if Trailblaze can recognize “this is Gmail” and immediately load better tools, stronger selectors, known-good waypoints, and reusable navigation segments.
Decision¶
Core model¶
Trailblaze will move toward a target-trailmap model.
- A target remains the runtime concept: the thing Trailblaze is acting against.
- A target trailmap is the reusable, composable, distributable unit.
Examples:
gmailwikipediaandroid-settingsconsumer-payment-appmerchant-point-of-sale
Each target trailmap is self-describing and target-aware. It already knows how to recognize itself on supported platforms and what capabilities it exports once active.
One trailmap may span multiple platforms¶
A target trailmap may support:
webandroidios
or only one of them.
The trailmap is the published/local dependency unit; the runtime resolves a more specific target
variant such as gmail:web or gmail:android.
Trailmaps replace standalone authored targets/*.yaml¶
The current standalone target YAML shape is too small to be the long-term top-level unit.
Going forward, the authored unit should become trailmap.yaml, not a sibling targets/foo.yaml.
In other words:
- today’s
target.yamlcontent becomes part of a trailmap manifest - tomorrow’s
trailmap.yamlowns target detection plus all exported artifacts
This keeps “what is this target?” and “what does this reusable bundle provide?” in one place.
Trailmap ownership is implicit by layout¶
Artifacts should normally inherit their owning trailmap from directory layout rather than repeating that ownership in every file.
For example:
trails/config/
trailmaps/
gmail/
trailmap.yaml
tools/
inputText.yaml
toolsets/
waypoints/
routes/
trails/
In this shape, tools/inputText.yaml implicitly belongs to the gmail trailmap because it lives
under that trailmap’s root.
This matches how package ownership works in other ecosystems:
- Python modules inherit package ownership from import path and directory structure
- TypeScript modules inherit package ownership from the package manifest and file location
The system should still be able to represent ownership explicitly in generated or resolved output,
but authored trailmap content should not require repetitive trailmap: gmail declarations in each file.
Waypoints are first-class¶
Waypoints are not a sidecar to trails. They are a core primitive alongside tools.
The model becomes:
target— what this thing istools— atomic capabilitiestoolsets— grouped capabilitieswaypoints— named, assertable locations/statesroutesorsegments— deterministic movement between waypointstrails— runnable authored flows and validations
Trails become one consumer of this structure rather than the only organizing surface.
Declarative metadata, dynamic implementation¶
Trailmap discovery and composition must stay cheap.
That means:
- YAML is the source of truth for discovery and metadata
- JS/TS is the source of truth for behavior
Trailblaze must not need to execute TypeScript just to discover what tools a trailmap contains.
Tool metadata should be declared in YAML. JS/TS implementations are referenced by that metadata and executed lazily at runtime. Optional generators may help authors keep YAML and TS in sync, but runtime discovery should remain declarative.
Resolve is automatic; build is lazy¶
Users should not be required to run a separate resolve command during normal use.
Instead:
- trailmap resolution is automatic
- resolution is cheap
- expensive JS/TS build work is lazy and cached
The split is:
- resolve: load manifests, merge dependencies, compute active capabilities, validate ids, materialize normalized metadata
- build: only when a JS/TS implementation is actually needed and no usable runtime artifact is already present
Prebuilt JS remains the preferred runtime artifact when available. Source TS is a convenient authoring surface, not a requirement for runtime startup.
Resolve produces one flattened runtime tree¶
Runtime should not operate directly on an unresolved forest of trailmaps.
Instead, authored inputs are compiled into one resolved output tree, similar to how a JVM build turns many source files and dependencies into one set of generated classes or one packaged JAR.
That means:
- source trailmaps can be modular and layered
- meta-trailmaps can depend on other trailmaps
- runtime sees one clean, collision-checked output tree
Conceptually:
workspace/
├── trails/
│ ├── config/
│ │ ├── trailblaze.yaml
│ │ └── trailmaps/
│ │ ├── consumer-payment-app/
│ │ ├── merchant-point-of-sale/
│ │ ├── dashboard-admin-app/
│ │ └── company-suite/
│ └── ... trail files ...
└── .trailblaze/
└── resolved/
├── manifest.yaml
├── targets/
├── tools/
├── toolsets/
├── waypoints/
├── routes/
├── trails/
└── mcp/
The resolved tree is the runtime source of truth. Duplicate ids and ambiguous ownership should be handled during resolve, not left for runtime to guess about.
Local-first scope¶
Trailblaze does not need a registry, remote installer, version solver, or signatures to prove this model.
The first implementation target is purely file-based local trailmaps.
Recommended workspace shape:
your-workspace/
└── trails/
├── config/
│ ├── trailblaze.yaml
│ └── trailmaps/
│ ├── gmail/
│ │ ├── trailmap.yaml
│ │ ├── tools/
│ │ ├── toolsets/
│ │ ├── waypoints/
│ │ ├── routes/
│ │ ├── trails/
│ │ └── mcp/
│ └── wikipedia/
│ └── ...
└── ... trail files ...
trails/config/trailblaze.yaml then composes the trailmaps the workspace wants to use.
That composition should allow both leaf trailmaps and aggregate meta-trailmaps:
consumer-payment-app,merchant-point-of-sale,dashboard-admin-appas app-specific trailmapscompany-suiteas a meta-trailmap that depends on those trailmaps and resolves into one flattened output
This mirrors dependency-management systems where published package identity is unique, but local symbol names can remain simple inside a package.
Initial merge semantics should support:
useextendreplace
Forking should be the escape hatch, not the default extension story.
Relationship to the older tool naming decision¶
Tool Naming Convention described the flat global tool namespace that Trailblaze still uses today.
That older decision remains historically accurate for the current runtime model, but part of its original rationale is no longer the right long-term constraint:
- previously, tool naming was tightly coupled to finding the backing Kotlin class during serialization and registration
- today, class-backed tools are described by YAML and can point to fully-qualified class names directly
So the old document is still relevant as a description of the current flat runtime namespace, but it should no longer be treated as the main design argument against trailmap ownership or trailmap-local simple names in the future.
Why this is the right cut¶
This preserves the value of the current config-alignment work while opening a clean path to a much stronger ecosystem:
- local workspaces stay simple
- downstream consumers can dogfood the model by moving app-specific behavior out of the binary
- open-source examples can ship as real trailmaps
- future publishing is mostly packaging/distribution work, not a redesign of the runtime model
What lands now vs later¶
What should land with the current PR¶
The workspace/config alignment work is worth merging now:
trails/as workspace anchortrails/config/trailblaze.yamlas workspace manifesttrails/config/as filesystem artifact directory- shared resolver/constants
- generated docs describing the current binary behavior
That PR should not wait on the full trailmap model.
What should be tracked as follow-up work¶
- Introduce local file-based target trailmaps and
trailmap.yaml. - Move standalone target YAML semantics into the trailmap manifest.
- Add workspace composition of trailmaps (
use / extend / replace). - Make waypoints and routes/segments first-class trailmap artifacts.
- Shift scripted-tool discovery to declarative YAML metadata with lazy JS/TS execution.
- Dogfood the trailmap model by migrating downstream app-specific behavior out of forked binaries.
Naming¶
The naming stack locked by this decision:
- target — runtime identity
- target trailmap — reusable/distributable unit
- waypoint — named app/site location or state
- route or segment — movement between waypoints
- trail — runnable authored flow
Deferred¶
Explicitly deferred until the local model proves itself:
- remote registry
- versioned public package distribution
- trust scoring / signatures
- semver constraint solving
- automatic download/install based on target detection
These are packaging and ecosystem problems. The important near-term work is to make the local trailmap shape, merge rules, and lazy runtime behavior solid.