Metadata and Workflow Schema (Developer Reference)
This document is the single reference for the two YAML schemas that connect this repository together:
skills-index.yaml— single source of truth for skill metadataworkflows/*.yaml— operational workflow manifests
Both schemas are versioned (schema_version: 1). New fields are always additive. Existing fields are never repurposed. If a meaning needs to change, add a new field and deprecate the old one.
The companion validator is scripts/validate_skills_index.py.
For the operational side — when to run which generator, how to clear a failing drift gate, and what the scheduled launchd agents do — see maintenance-runbook.md.
1. skills-index.yaml
1.1 Top-level shape
schema_version: 1
categories:
- market-regime
- core-portfolio
- swing-opportunity
- trade-planning
- trade-memory
- strategy-research
- advanced-satellite
- meta
skills:
- id: <skill-id>
display_name: <Human Title>
category: <one of categories>
status: production | beta | experimental | deprecated
operational_role:
type: standalone | workflow_step | internal_component | research_only
rationale: <why intentionally outside workflows> # standalone only
knowledge_only: true # required only for script-free production skills
verification:
instruction_contract: passed | not_verified | not_applicable
unit_tests: passed | not_verified | not_applicable
workflow_contract: passed | not_verified | not_applicable
end_to_end_replay: passed | not_verified | not_applicable
data_provenance: passed | not_verified | not_applicable
financial_logic_review: passed | not_verified | not_applicable
empirical_validation: passed | not_verified | not_applicable
security_review: passed | not_verified | not_applicable
summary: >-
One-sentence description.
timeframe: daily | weekly | event-driven | research | unknown
difficulty: beginner | intermediate | advanced | unknown
integrations:
- id: <integration-id>
type: <integration_type>
requirement: <requirement>
note: <free-text>
inputs: [<input-name>, ...]
outputs: [<output-name>, ...]
workflows: [<workflow-id>, ...]
1.2 Required vs best-effort fields
Required (validator hard-blocks under all strictness levels):
| Field | Rule | Error code |
|---|---|---|
id |
Equals the directory name under skills/. Must match SKILL.md frontmatter name. |
IDX003, IDX004 |
display_name |
Non-empty string. Index-owned (NOT cross-checked against frontmatter). | — |
category |
One of the 8 enum values above. | IDX005 |
status |
One of production / beta / experimental / deprecated. |
IDX006 |
summary |
Non-empty string. | IDX009 |
knowledge_only: true is the explicit CI exemption marker for a production
skill that has no executable Python files at any depth under scripts/. The field
must be boolean, may only be true on a production skill, and conflicts with any
scripts/**/*.py file outside tests/, other than __init__.py (IDX014).
Executable skills must ship canonical tests and cannot use this marker to
bypass the test gate.
Best-effort (warn-only by default; required under --strict-metadata):
| Field | Rule |
|---|---|
timeframe |
One of the enum values. unknown allowed under default / --strict-workflows (warn); rejected under --strict-metadata. |
difficulty |
One of the enum values. unknown allowed under default / --strict-workflows (warn); rejected under --strict-metadata. |
integrations |
List form (see §1.4). May be empty for skills with no dependencies, but prefer [{id: local_calculation, type: calculation, requirement: not_required}]. |
inputs |
List of strings. Empty list ([]) allowed under default / --strict-workflows (warn); --strict-metadata requires at least one entry. |
outputs |
List of strings. Empty list ([]) allowed under default / --strict-workflows (warn); --strict-metadata requires at least one entry. |
workflows |
List of unique, non-empty workflow ID strings. Malformed lists warn by default and error under --strict-workflows; missing files follow the same severity tiers. |
operational_role |
Exact mapping described in §1.4. Missing warns by default and errors under --strict-metadata; malformed present mappings always error. |
As of 2026-05-12 the canonical skills-index.yaml populates timeframe, difficulty, inputs, and outputs for all 54 skills, and --strict-metadata is enforced in CI + the pre-push hook. New skill entries must satisfy --strict-metadata to merge.
1.3 verification schema
The block has exactly eight axes. Values are passed, not_verified, or not_applicable.
Production entries must include the complete block. A missing production block is a warning in
default and --strict-workflows modes and an error under --strict-metadata. If any entry includes
the block, its type, exact keys, and enum values are hard-validated in every mode (IDX013).
Non-production entries may omit it, but partial declarations are not allowed.
The extension is additive and keeps schema_version: 1; existing fields are not repurposed. See
production-verification.md for the evidence rules, axis criteria,
baseline, and live issue gate.
1.4 operational_role schema
Every skill declares exactly one operational role:
| Type | Meaning |
|---|---|
workflow_step |
Referenced by a workflow manifest’s required_skills, optional_skills, or steps[].skill. |
standalone |
Intentionally invoked directly and not referenced by a workflow. Requires a non-empty, skill-specific rationale. |
internal_component |
Invoked by another skill or repository pipeline rather than offered as an end-user workflow step. |
research_only |
Exploratory or backtest tooling outside the daily operational loop. |
The mapping has an exact shape: standalone roles contain type and
rationale; every other role contains only type. Missing roles warn in
default and --strict-workflows modes and fail under --strict-metadata
(IDX015). Malformed mappings fail in every mode.
Under --strict-workflows, the validator constructs the canonical forward set
from the union of every manifest’s required_skills, optional_skills, and
steps[].skill. A skill is workflow_step if and only if that set is non-empty
(WF015), and its index-owned workflows value must be a list of unique,
non-empty strings that equals the forward set (WF016). Malformed values are
reported as one stable WF016 finding rather than coerced or iterated.
1.5 integrations schema
integrations:
- id: <integration-id> # alpaca, fmp, finviz, holdings_csv, websearch, local_calculation, ...
type: <integration_type> # see enum below
requirement: <requirement> # see enum below
note: <free-text> # short justification or pointer
integration_type enum:
| Value | Use for |
|---|---|
broker |
Alpaca, IBKR, brokerage APIs |
market_data |
FMP, yfinance, paid market data APIs |
screener |
FINVIZ Elite and similar screener APIs |
web |
WebSearch / WebFetch / general HTTP |
local_file |
CSV / YAML / JSON / Markdown inputs from local disk |
image |
Chart screenshots and other image inputs |
mcp |
MCP servers (non-broker) |
calculation |
Pure local computation, no I/O |
none |
Edge case: no integration record applies |
unknown |
Bootstrap could not determine; flagged for owner review |
requirement enum:
| Value | Meaning |
|---|---|
required |
Skill cannot run without this integration |
recommended |
Works without it but degraded |
optional |
Alternative input path |
not_required |
Explicit “no dependency” marker; pair with type: none or type: calculation |
unknown |
Bootstrap could not determine; flagged for owner review |
not_used is intentionally NOT supported — in a list form, integrations that aren’t used are simply omitted.
type: calculation vs type: none:
type: calculation— skill performs deterministic local computation. Pair withid: local_calculationandrequirement: not_required. Use this forposition-sizerand similar pure-compute skills.type: none— explicit “no integration record applies”. Reserved for edge cases. Prefercalculationwhenever the skill does any computation.
1.6 workflows field semantics
Each entry is a workflow id (= filename in workflows/ minus .yaml).
- Default mode: missing workflow files emit a warning. Useful while bootstrapping.
--strict-workflows: missing workflow files are errors (WF001).
This field is the back-reference. The forward reference (workflow → skill) is in each workflows/<id>.yaml’s required_skills / optional_skills / steps.
1.6 Governance rules
display_nameis index-owned. Validator does NOT cross-check it againstSKILL.mdfrontmatter. Onlyid↔ frontmatternameparity is enforced.- Deprecated skills stay in the index.
status: deprecatedentries remain. They are excluded from.skillbundles and from any workflow’srequired_skills, but they remain queryable. Skills physically removed fromskills/are also removed from the index. bootstrap_skills_index.pyis a one-time migration helper. After PR1 merges,skills-index.yamlis the source of truth; do not re-run bootstrap as a sync tool.CLAUDE.mdbecomes a downstream consumer of the index.- No CLAUDE.md ↔ index parity check. Once the index exists, the CLAUDE.md API matrix is informational. A future PR may regenerate the matrix from the index, but the index is canonical.
2. Workflow manifests (workflows/*.yaml)
2.1 Top-level shape
schema_version: 1
id: <workflow-id> # must equal filename (sans .yaml)
display_name: <Human Title>
display_name_ja: <Japanese Human Title>
cadence: daily | weekly | monthly | ad-hoc
estimated_minutes: <int>
target_users: [<user-persona>, ...]
difficulty: beginner | intermediate | advanced
api_profile: no-api-basic | fmp-required | alpaca-required | mixed
when_to_run: >-
<prose>
when_to_run_ja: >-
<Japanese prose>
when_not_to_run: >-
<prose>
when_not_to_run_ja: >-
<Japanese prose>
required_skills: [<skill-id>, ...]
optional_skills: [<skill-id>, ...]
prerequisite_workflows: # informational only, NOT validated
- id: <workflow-id>
artifact: <artifact-id> # which upstream artifact this workflow expects
rationale: <why>
rationale_ja: <Japanese why>
manual_inputs: # optional external/manual JSON contracts
- id: <input-id>
required: true | false
used_by_steps: [<step-number>, ...]
schema_ref: <repo-relative-path>
description: <what supplies this input and how it degrades>
description_ja: <Japanese description>
artifacts:
- id: <artifact-id>
produced_by_step: <step-number>
required: true | false
downstream_hints: [<workflow-id>, ...] # informational only, NOT validated
steps:
- step: <int>
name: <step-title>
name_ja: <Japanese step-title>
skill: <skill-id>
optional: true | false # default false
consumes: [<artifact-id>, ...]
produces: [<artifact-id>, ...]
decision_gate: true | false
decision_question: >-
<question, required when decision_gate is true>
decision_question_ja: >-
<Japanese question, required when decision_gate is true>
depends_on: [<step-number>, ...] # only earlier steps
manual_review:
- <prose, one item per line>
manual_review_ja: # same length and order as manual_review
- <Japanese prose, one item per line>
journal_destination: <skill-id>
# Only on monthly-performance-review:
final_outputs:
- id: <output-id>
description: <human-facing description>
description_ja: <Japanese human-facing description>
Japanese workflow documentation is fail-closed: every human-facing field shown
above must have a non-empty Japanese counterpart. manual_review_ja must match
manual_review item-for-item. generate_workflow_docs.py --lang ja never falls
back to English. Machine-readable workflow, skill, artifact, status, enum, CLI,
file, and API-profile identifiers remain unchanged and are rendered as code.
Japanese prose may be written as a multi-line folded scalar (>-) for
readability. YAML joins those wrapped lines with a space, so the generator drops
any space whose neighbours are both CJK/full-width characters. A space bordering
ASCII — an inline `code` span, a latin word — is intentional and preserved,
and English prose is unaffected.
2.2 Internal-consistency rules
These are validated under --strict-workflows:
| Rule | Error code |
|---|---|
Workflow filename equals id |
WF002 |
Every step.skill exists in skills-index.yaml |
WF003 |
depends_on references only earlier steps |
WF004 |
decision_gate: true step has non-empty decision_question |
WF005 |
journal_destination resolves to a skill id |
WF006 |
consumes artifacts produced by an earlier step |
WF007 |
No deprecated skill in required_skills |
WF008 |
Every required_skills entry appears in at least one non-optional step |
WF009 |
Every non-optional step.skill appears in required_skills |
WF010 |
Workflow file referenced by an index entry’s workflows: exists |
WF001 |
| Required artifact produced before the final step is not consumed by any later step | WF013 |
| Human-facing Japanese workflow prose is missing, empty, wrongly typed, or list-misaligned | WF014 |
2.3 consumes: semantics — “use if available”, not “required input”
A step’s consumes: list documents which artifacts the step may read if those artifacts have been produced by earlier steps. It is NOT a hard “required input” declaration.
Concretely:
- If a consumed artifact’s producing step is non-optional, the artifact will always exist at execution time. Treat it as required input.
- If a consumed artifact’s producing step is optional (
optional: true), the consuming step must handle the case where the artifact is absent. The consuming skill itself decides graceful-degrade behavior — e.g.exposure-coachruns with or withouttop_risk_report.
Validator rules:
- The artifact must be declared in
artifacts:✅ (WF007) - The artifact must be produced by an earlier step ✅ (
WF007) - Whether the producing step actually executes at runtime — NOT validated. That is operational concern, not schema concern.
A step’s consumes: list MAY NOT reference an artifact produced in a later step (covered by WF007).
Why no optional_consumes: field? In a list-form schema, “use if available” is the default for any artifact whose producer is optional: true. Adding a parallel field would duplicate information already encoded in artifacts[].produced_by_step + steps[].optional. If a future case needs hard “required input” semantics, that will be added as a new field — never by repurposing consumes:.
2.4 prerequisite_workflows: — informational ordering hint
Some workflows depend on the output of another workflow (e.g. swing-opportunity-daily should only run after a non-restrictive market-regime-daily exposure decision). To make this dependency machine-readable for a future Navigator, declare it at the top of the workflow:
prerequisite_workflows:
- id: market-regime-daily
artifact: exposure_decision
rationale: >-
Swing trade entries require a non-restrictive exposure decision. Skip this
workflow on cash-priority days.
This field is informational only. The validator does NOT check that the named workflow exists, that the artifact exists, or that the prerequisite ran today. It is the trader’s responsibility (and, eventually, the Navigator’s) to enforce ordering.
If a hard inter-workflow contract is needed in the future, it will be added as a new field — never by repurposing prerequisite_workflows.
2.5 manual_inputs: — explicit external input contract
Use manual_inputs when an optional step reads operator-supplied data that is
not produced by an earlier step in the same workflow. Keep each distinct JSON
shape separate and point schema_ref to its canonical skill reference. The
field is rendered in workflow documentation but is informational in schema v1;
the named skill remains responsible for validating the input at runtime.
Do not place a manual input in steps[].consumes: consumes is reserved for
artifacts produced by an earlier step in the same workflow.
2.6 downstream_hints is informational
artifacts[].downstream_hints is a free-form list of workflow IDs that might consume the artifact downstream. The validator does not check this. It is meant for human navigation and (future) Navigator hints.
If a hard inter-workflow contract is needed later, add a separate field (e.g. consumed_by_workflows: [...] with strict semantics). Never repurpose downstream_hints.
2.7 one_of: is intentionally NOT in v1
Cases like “either vcp-screener or canslim-screener” are expressed by:
- Promoting one to
required_skills - Demoting the alternative to
optional_skills
Adding one_of: later is an additive schema change.
3. Skillset manifests (skillsets/*.yaml)
A skillset is a category-scoped skill bundle tied to the workflow(s) that
operationalize it. A skillset id is exactly a skills-index.yaml category
(so the Trading Skills Navigator maps a recommendation’s dominant category
straight to its manifest). Validated by scripts/validate_skillsets.py
(pre-commit / pre-push / CI), error codes SK001–SK013.
3.1 Top-level shape
schema_version: 1
id: market-regime # == filename stem == a skills-index category
display_name: Market Regime
category: market-regime # == id (validated equal)
timeframe: daily # daily | weekly | event-driven | research
difficulty: beginner # beginner | intermediate | advanced
api_profile: no-api-basic # no-api-basic | fmp-required | alpaca-required | mixed
target_users: [<persona>, ...] # non-empty
when_to_use: >-
<prose, non-empty>
when_not_to_use: >-
<prose, non-empty — echoes the related workflow's when_not_to_run>
required_skills: [<skill-id>, ...] # non-empty
recommended_skills: [<skill-id>, ...] # may be empty
optional_skills: [<skill-id>, ...] # may be empty
related_workflows: [<workflow-id>, ...] # non-empty; resolves to workflows/<id>.yaml
3.2 Naming normalization (Vision → category id)
PROJECT_VISION.md §12 lists a trade-memory-loop skillset candidate. The
category id is trade-memory (a skills-index.yaml category); the file is
skillsets/trade-memory.yaml, and trade-memory-loop appears under
related_workflows. A skillset id MUST be a skills-index category — never a
workflow id. Do not create skillsets/trade-memory-loop.yaml.
3.3 required_skills ⊆ related_workflows coherence
The validator enforces a live coherence contract (and its pre-commit hook
fires on workflows/*.yaml, so a workflow edit that desyncs a skillset is a
hard error):
| Rule | Error code |
|---|---|
id ≠ filename stem |
SK001 |
id not a canonical category, or category ≠ id |
SK002 |
Missing/invalid scalar (schema_version/display_name/timeframe/difficulty/api_profile) |
SK003 |
Missing/blank when_to_use / when_not_to_use |
SK004 |
List fields: target_users/required_skills/related_workflows non-empty list[str]; recommended_skills/optional_skills keys required (list[str], may be empty — a missing key is an error) |
SK005 |
A listed skill not in skills-index.yaml |
SK006 |
required_skills contains a deprecated skill |
SK007 |
required/recommended/optional not pairwise disjoint |
SK008 |
A related_workflows id has no workflows/<id>.yaml |
SK009 |
⋃(related workflow.required_skills) ⊄ this skillset’s required_skills |
SK010 |
Single related workflow: required_skills set ≠ that workflow’s |
SK011 |
api_profile does not cover a related workflow’s: mixed covers all; any profile covers a no-api-basic workflow; otherwise the provider must match exactly (fmp-required ≠ alpaca-required even though same tier) |
SK012 |
no-api-basic skillset lists a paid-required skill in required/recommended/optional |
SK013 |
4. Validator strictness levels
# Default — pre-commit mode
python3 scripts/validate_skills_index.py
# PR2+ — CI / pre-push mode
python3 scripts/validate_skills_index.py --strict-workflows
# Future — tighten metadata completeness
python3 scripts/validate_skills_index.py --strict-metadata
| Check group | default | --strict-workflows |
--strict-metadata |
|---|---|---|---|
| Index ↔ folder bijection | ✅ | ✅ | ✅ |
Frontmatter name ↔ index id |
✅ | ✅ | ✅ |
| Required fields populated | ✅ | ✅ | ✅ |
category / status enum |
✅ | ✅ | ✅ |
No duplicate id |
✅ | ✅ | ✅ |
integrations[].requirement enum |
✅ | ✅ | ✅ |
summary non-empty |
✅ | ✅ | ✅ |
workflows[] references resolve |
⚠️ warn | ✅ error | ✅ error |
| Workflow internal-consistency rules | — | ✅ error | ✅ error |
timeframe / difficulty not unknown |
⚠️ warn | ⚠️ warn | ✅ error |
inputs / outputs populated |
⚠️ warn | ⚠️ warn | ✅ error |
unknown integrations allowed |
⚠️ warn | ⚠️ warn | ❌ rejected |
operational_role present |
⚠️ warn | ⚠️ warn | ✅ error |
| Operational role ↔ workflow forward/back parity | — | ✅ error | — |
Malformed workflows back-reference list |
⚠️ warn | ✅ error | ⚠️ warn |
5. Error code catalog
Index-level (always strict)
| Code | Meaning |
|---|---|
IDX001 |
Duplicate skill id |
IDX002 |
Index entry exists but skills/<id>/ does not |
IDX003 |
skills/<id>/ exists but no index entry |
IDX004 |
SKILL.md frontmatter name ≠ index id |
IDX005 |
Invalid category |
IDX006 |
Invalid status |
IDX007 |
Invalid integration_type |
IDX008 |
Invalid requirement |
IDX009 |
Empty summary |
IDX010 |
Missing or wrong schema_version (must equal 1) |
IDX011 |
Missing categories: block, or contents do not match the canonical 8 |
Tiered severity (default warn → strict-metadata error)
| Code | Meaning |
|---|---|
IDX012 |
Integration uses an unknown marker (id / type / requirement); flagged for owner review. Warning by default; error under --strict-metadata. |
IDX013 |
Missing production verification block, or a present block has the wrong type, missing/unknown axes, or invalid enum values. Missing block warns by default and errors under --strict-metadata; malformed present blocks always error. |
IDX014 |
Invalid knowledge_only type, non-production use, or conflict with executable Python scripts. |
IDX015 |
Missing or malformed operational_role; missing warns by default and errors under --strict-metadata, malformed present mappings always error. |
Workflow-level (strict-workflows)
| Code | Meaning |
|---|---|
WF001 |
Workflow file referenced by index does not exist |
WF002 |
Workflow id does not match filename |
WF003 |
Step skill not found in skills-index.yaml |
WF004 |
depends_on references a future step |
WF005 |
decision_gate: true step missing decision_question |
WF006 |
journal_destination does not resolve to a skill |
WF007 |
Step consumes an artifact before it is produced |
WF008 |
Deprecated skill listed in required_skills |
WF009 |
required_skills entry never appears in a non-optional step |
WF010 |
Non-optional step skill missing from required_skills |
WF011 |
required_skills / optional_skills entry not in skills-index.yaml |
WF012 |
artifacts[].produced_by_step does not match the corresponding step’s produces (either direction) |
WF013 |
Required artifact produced before the final step is not consumed by any later step |
WF014 |
Required Japanese workflow prose is missing, empty, wrongly typed, or manual_review_ja is not aligned |
WF015 |
operational_role.type is not workflow_step exactly when workflow forward references exist |
WF016 |
Index workflows is not a unique, non-empty list[str], or its back-references do not exactly match manifest forward references |
Skillset-level (scripts/validate_skillsets.py, always strict)
| Code | Meaning |
|---|---|
SK001 |
Manifest id ≠ filename stem |
SK002 |
id not a canonical skills-index category, or category ≠ id |
SK003 |
Missing/invalid scalar (schema_version/display_name/timeframe/difficulty/api_profile) |
SK004 |
Missing/blank when_to_use / when_not_to_use |
SK005 |
List-field violation: target_users/required_skills/related_workflows not a non-empty list[str]; or recommended_skills/optional_skills key absent or not a list[str] (empty list allowed, missing key is not) |
SK006 |
A required/recommended/optional skill not in skills-index.yaml |
SK007 |
A required_skills id is deprecated |
SK008 |
required/recommended/optional not pairwise disjoint |
SK009 |
A related_workflows id has no workflows/<id>.yaml |
SK010 |
⋃(related workflow.required_skills) not a subset of this skillset’s required_skills |
SK011 |
Single related workflow: required_skills set ≠ that workflow’s |
SK012 |
api_profile does not cover a related workflow’s (mixed covers all; any covers no-api-basic; else exact provider match — fmp-required ≠ alpaca-required) |
SK013 |
no-api-basic skillset lists a paid-required skill (fmp/finviz/alpaca @ required) anywhere |
SK-PARSE / SK-MISSING |
YAML parse error / skillsets/ absent (absent dir = OK) |
6. Migration policy
- Schema changes: bump
schema_versiononly when the change is breaking. Additive fields do NOT bump. - Field renames: never. Add a new field, deprecate the old one in a comment, remove after one full version cycle.
- Enum value additions: allowed without bumping
schema_version. Validator must accept unknown enum values gracefully (warn, never crash). - Removed fields: marked
deprecatedin this doc for one cycle before deletion.
See also: project memory feedback_schema_evolution.md — “Add new schema fields, never mutate existing ones for backward compat”.