MCP Task Orchestrator · Field guidePlate 4 of 9

The rules are YAML, and they compose

A schema says which notes an item type must carry at each phase. Traits add cross-cutting requirements to any type. Both live in .taskorchestrator/config.yaml, so changing the process never means changing code or prompts.

From config to the gate an item actually faces

The item

type: feature-task
traits: needs-migration-review

Its schema

work_item_schemas:
  feature-task:
    default_traits: [needs-security-review]
    notes:
      - { key: requirements, role: queue, required: true }
      - { key: implementation-notes, role: work, required: true }

Traits

traits:
  needs-security-review:
    notes:
      - { key: security-assessment, role: review,
          required: true, skill: security-review }
  needs-migration-review:
    notes:
      - { key: migration-assessment, role: queue,
          required: true, skill: migration-review }

Resolved schema for this item

queue
  • requirementsfrom the base schema
  • migration-assessmentfrom the per-item trait. Routes the author to the migration-review skill.
work
  • implementation-notesfrom the base schema
review
  • security-assessmentfrom the default trait. Because a review-phase note now exists, the item passes through review.

Type first, then tags, then default

The item's type selects a schema. With no type match, its first matching tag does. Failing both, the default schema applies. With no default, the item advances freely.

Default traits, then per-item traits

A schema's default_traits apply to every item of the type. Traits set on a single item layer on after. The first trait to declare a key keeps it.

Traits add, they never relax

A trait note whose key already exists in the base schema is dropped. A trait cannot make a required note optional or move it to another phase.

A trait carries six signals

Traits are orchestration signals. Note requirements are only the first of them.

Notes

Which notes must exist, in which phase. Enforced at the gate.

notes:
  - key: migration-assessment
    role: queue
    required: true

Guidance

How to write the note. Handed to the agent at the moment it is about to fill it.

guidance: "Cover table
  recreation and data
  migration strategy."

Skill routing

Names a skill to run first, so the note follows a defined evaluation framework.

skill: "migration-review"

Resources

Shared resources the item needs. An exclusive one is leased when the item enters work, and a second item's start is rejected until it frees.

resources:
  - staging-db
  - key: deploy-token
    mode: advisory

Dispatch

Which agent, model and effort should pick up each phase. Returned to the orchestrator with the transition.

dispatch:
  review:
    agent: reviewer
    effort: high

Seats

Named roles inside a phase. A trait can add a seat to any type it is applied to. See the next figure.

seats:
  - name: test-author
    phase: work

Keeping notes short

A note is what the next agent reads in place of the whole conversation, so its value depends on staying small. A schema can cap each note, and the server can warn or refuse when a body runs over.

note_limits:
  mode: reject            # warn (default) | reject

work_item_schemas:
  bug-fix:
    notes:
      - key: diagnosis
        role: queue
        required: true
        maxLength: 6000   # characters

A cap per note

maxLength is a backstop on one note. Guidance text usually names a much smaller target.

Warn or reject

In warn mode an over-long note is stored with a warning on the result. In reject mode that note fails with NOTE_BODY_TOO_LONG and the rest of the batch is still written. A project can set its own mode.

Long artifacts go by file

For test output, diffs and logs, an agent passes bodyFromFile and the server reads the file itself. The text never passes through the model's context.

Seats split a phase between agents

A phase is often more than one agent's job. A schema or trait can name the seats inside each phase: which seat enters the phase, which seat owns each required note, and in what order they work.

queue work review planner diagnosis test-plan independent_of: a different actor must write test-manifest implementer enters the phase implementation-notes after test-author added by a trait test-manifest after orchestrator session-tracking reads_exclude: the test author is told not to read implementation-notes or session-tracking. reviewer review-checklist test-independence-audit
Each box is a seat with the notes it owns. The seat layout is this project's own bug-fix schema with the needs-test-author trait. The independent_of bracket follows the reference example in the config documentation.

The server serves seats to the orchestrator as signals, reports missing notes grouped by the seat that owns them, and can check that an independent_of note was written by a different actor. Plate 6 covers seats in full.

A config with no seats anywhere behaves exactly as before. Seats are optional at every level.

Resource leases: a gate on shared things

Some work needs something only one item can use at a time: a staging slot, a test database, a deploy window. A trait can declare that resource, and the server holds a lease on it for as long as the item is in work. There is no acquire or release call. The lease rides on advance_item.

Item A Item B start in work · holds the lease on staging-db complete lease released start rejected: resource_unavailable, retry after 30 s start in work · holds the lease time
The rejection is transient, not a gate failure: Item B's notes are fine. The agent should work on something else and try again later. The response names the contended key and never says who holds it.
resources:                  # optional registry
  staging-db:
    description: "Shared staging database"
    defaultTtlSeconds: 1800

traits:
  needs-staging:
    resources:
      - staging-db          # exclusive by default
      - key: deploy-token
        mode: advisory      # recorded, never locked

Exclusive or advisory

exclusive admits one item at a time. advisory takes no lock and only records that the item used the resource. Reserve exclusive for things that cannot be shared. A credential several agents can use at once should be advisory.

Held for the whole work phase

Taken on start or resume into work. Released on any exit: complete, cancel, block or reopen. It expires after an hour by default (24 hours at most) and cannot be renewed. Nothing sweeps an expired lease. The next contender simply gets it.

Task-level items only

Put exclusive resources on leaf task types. A feature container can sit in work for days and would hold the lock the whole time.

One namespace per server

Keys are server-wide, so on a clash the global registry entry beats a project's. This is the one place the global layer wins. Prefix keys per project to avoid accidents.

Operator controls

The REST API lists active leases and their history, and an admin can force-release one. RESOURCE_LEASES_ENFORCED=false switches acquisition off.

There is no queue and no fairness: whoever retries first after release gets the lease. A lease is checked on entry to work only. It is a coordination aid for cooperating agents.

One server, many projects, two layers

Task Orchestrator server project-a / config.yaml project-b / config.yaml config-sync Per-root config (database) root A · schemas, traits root B · schemas, traits hot-reloads, no restart global config.yaml located by AGENT_CONFIG_DIR mounted Global layer the floor · read once at startup project rules fallback Schema resolver picks a project by item.rootId then type, tag, default
The workspace file is the source of truth. The plugin's config-sync hook pushes it at session start and whenever it changes, and the server rejects a push from a stale checkout. A solo developer gets this too: one persistent server serves every repository with its own rules.

Lookup order for an item's type, by schema_resolution mode

Exact match on typeThe layer's default schema Per-root
Project schema named for the type
Project default
Global
Global schema named for the type
Global default