Skip to content

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 anchor
  • trails/config/trailblaze.yaml is the workspace manifest
  • trails/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:

  • gmail
  • wikipedia
  • android-settings
  • consumer-payment-app
  • merchant-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:

  • web
  • android
  • ios

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.yaml content becomes part of a trailmap manifest
  • tomorrow’s trailmap.yaml owns 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 is
  • tools — atomic capabilities
  • toolsets — grouped capabilities
  • waypoints — named, assertable locations/states
  • routes or segments — deterministic movement between waypoints
  • trails — 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-app as app-specific trailmaps
  • company-suite as 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:

  • use
  • extend
  • replace

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 anchor
  • trails/config/trailblaze.yaml as workspace manifest
  • trails/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

  1. Introduce local file-based target trailmaps and trailmap.yaml.
  2. Move standalone target YAML semantics into the trailmap manifest.
  3. Add workspace composition of trailmaps (use / extend / replace).
  4. Make waypoints and routes/segments first-class trailmap artifacts.
  5. Shift scripted-tool discovery to declarative YAML metadata with lazy JS/TS execution.
  6. 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.