Skip to content

Feat/workflow contract - #43

Merged
eschaar merged 25 commits into
mainfrom
feat/workflow-contract
May 9, 2026
Merged

Feat/workflow contract#43
eschaar merged 25 commits into
mainfrom
feat/workflow-contract

Conversation

@eschaar

@eschaar eschaar commented May 9, 2026

Copy link
Copy Markdown
Owner

Summary

Ships the workflow contract source-of-truth feature (ADR-023) and the vstack migrate command (ADR-026).

Workflow contract:

  • workflow: block seeded in .vstack/config.yaml by vstack install, with stage, gate, hitl, and handoffs.prompt for all six roles
  • Agent config.yaml drops the handoffs: block; each agent now carries a handoff_prompt: string; the generator reads the workflow config to produce the full handoff section in .agent.md
  • baseline: flag on output artifacts; agents explicitly distinguish baseline docs (living) from per-session deliverables
  • defaults: block in agent config.yaml for shared frontmatter fields
  • Nested block YAML support in the frontmatter parser (required for multi-line handoff_prompt:)
  • Hand-rolled YAML parser replaced with PyYAML

Docs migration:

  • vstack migrate command moves docs artifact files between major versions with --dry-run, --from, --to flags
  • src/vstack/_migrations/v2_to_v3.yaml migration record (architecture.md, design.md, report files)
  • ADR-026 documents the docs path stability policy

Documentation:

  • README and README-pypi updated with install/upgrade guide (quickstart, patch/minor upgrade, major upgrade with vstack migrate)
  • Roadmap updated: workflow contract source-of-truth marked shipped

Why

Before this change, pipeline configuration was scattered across six agent configs. There was no central, machine-readable description of the pipeline order, gate policy, or handoff prompts. This blocked the future planner orchestrator (ADR-024) and made handoff prompts tightly coupled to stage names.

vstack migrate is the upgrade path for projects on an older major version — without it, docs files would be silently left at stale paths after a major upgrade.

Version impact

  • None
  • Patch
  • Minor
  • Major

eschaar added 15 commits May 9, 2026 01:26
Extend FrontmatterParser.parse_yaml to accumulate nested key/value
blocks inside object-list items (e.g. handoffs: under stages[]).

Empty-value 4-space keys trigger accumulation of subsequent 6-space-
indented lines, stored as a stripped raw string for callers to re-parse.
A trailing nested block left open at EOF is correctly flushed.

This is the prerequisite for reading workflow.stages[].handoffs from
.vstack/config.yaml without a full YAML library.
…fact flag

Move handoffs and artifacts from top-level agent config.yaml into a
defaults: block. The generator extracts this block and re-exposes it
at the expected paths, keeping generated agent.md output identical.

Add baseline: true flag on output artifact entries. Flagged items are
rendered into a dedicated '### baseline docs you maintain' table with
an explicit instruction to keep them current — separating living docs
from per-session deliverables.

Add AGENT_ARTIFACTS_BASELINE template token and inject it into all six
role agent templates.

Refactor AgentGenerator:
- _extract_defaults(): re-parses raw indented string from minimal parser
- load_artifact_config(): merges defaults into config, resolves handoffs
- _resolve_handoffs(): derives next agent from workflow stage order
- _resolve_output_entries(): now an instance method; adds baseline field
- _build_baseline_section(): renders the new baseline table
- _build_handoffs(): compatibility shim kept for test call sites

HANDOFF_ITEM_SCHEMA: agent field changed to required=False (target is
now derived from workflow stage order, not hardcoded in config).
Add a workflow: block to the project config schema. It defines the full
pipeline as an ordered list of stages with gate, hitl, and handoffs fields.

Stage schema:
  role:     agent name (required)
  gate:     required | optional | skip
  hitl:     always | on-change | never
  handoffs: single dict or list of dicts with prompt, agent?, label?

hitl semantics:
  always    — pipeline pauses for human approval before handoff
  on-change — pause only when the stage made changes (default for optional)
  never     — pipeline continues without human approval (explicit opt-out)

CLI changes:
  CommandLineInterface._read_workflow_stages(): reads and parses workflow
    block from .vstack/config.yaml; preserves hitl field when present
  CommandLineInterface._parse_stage_handoffs(): normalises handoffs to
    [{prompt, agent, label}] list; supports dict, list, and legacy
    handoff_prompt flat-key forms
  CommandService: accepts workflow_stages kwarg, passes to AgentGenerator
  InitCommand._warn_unknown_workflow_roles(): emits stderr warning for
    stage roles with no matching agent template (non-fatal)

Seed .vstack/config.yaml and the project template with the default
six-stage pipeline (commented-out in the template; active in this repo).
ADR-023 (workflow contract): documents the workflow: block added to
.vstack/config.yaml as the source of truth for pipeline stage order,
gate policy, hitl policy, and handoff prompts. Covers the baseline:
flag on output artifacts and the defaults: restructuring in agent
config.yaml. Supersedes the handoff-in-agent-config approach.

ADR-024 (subagent orchestration): documents the decision to use VS Code
native subagents (runSubagent tool, agents: frontmatter) for the
orchestrated pipeline. Defines the planner agent as coordinator. Rejects
scripts/runner.py, MCP orchestrator, and planner-only alternatives.
Supersedes ADR-004.

ADR-004: status changed to 'superseded by ADR-024'; amendment added
explaining that the original blocker (no platform support for subagents)
was resolved when Microsoft shipped the runSubagent tool in May 2026.

overview.md: ADR-004 row marked superseded; ADR-023 and ADR-024 rows
added to the decision table; pipeline reference links updated.

roadmap.md: workflow contract and orchestrated pipeline rows moved from
candidate to in progress with ADR references.
…ormatting

Regenerate all .github/ artifacts from updated templates:
- Agent files reflect the new defaults: config structure, baseline
  artifact tables, and workflow-derived handoff targets
- All skill/instruction/prompt VSTACK-META footers bumped to vstack_version 3.0.0

Update .vstack/vstack.json with new checksums and install timestamp.

Fix CHANGELOG.md formatting: replace * bullets with - bullets and remove
double blank lines between sections (mdformat compliance).
Introduce pyyaml>=6.0 as the sole runtime dependency and replace the
hand-rolled state-machine parser (~200 lines) in frontmatter/parser.py
with yaml.safe_load. Keeps the public API unchanged (parse, parse_yaml).

Pre-processes bare '- *' list items to '- ''*''' before passing to
yaml.safe_load, since VS Code uses '*' as a wildcard in agents: lists
and PyYAML treats bare '*' as an alias marker.

Also quote YAML-special leading characters ('*', '&', '!') in list items
emitted by the serializer, fix int version comparison in cli/init.py
(PyYAML parses 'version: 20260421027' as int), and remove dead-code
string-reparse fallbacks from agents/generator.py and cli/interface.py.

Add ADR-025 (pyyaml-runtime-dependency) and amend ADR-006 with a scope
note clarifying it covers skill template content only, not pip deps.

pyproject.toml: add pyyaml>=6.0 to [project] dependencies and to
[testenv] deps (tox uses package=skip so it does not auto-install).
Re-run 'vstack install' after serializer fix: all six role agent files
now emit '- ''*''' instead of '- *' in the agents: list, which is valid
YAML and safe for downstream yaml.safe_load parsing.
@eschaar
eschaar force-pushed the feat/workflow-contract branch from 1c65872 to 4ce284c Compare May 9, 2026 19:50
@eschaar
eschaar requested a review from Copilot May 9, 2026 19:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR centralizes workflow/pipeline configuration into a project-owned workflow: block (seeded in .vstack/config.yaml) and updates agent generation to derive handoffs and “baseline docs” sections from that contract. It also adds a new vstack migrate CLI command and a packaged _migrations/ record format to move docs/ artifacts across major-version path changes, while replacing the custom frontmatter YAML parser with PyYAML.

Changes:

  • Introduce workflow-contract-driven agent generation (handoffs derived from workflow.stages, plus baseline: true output grouping).
  • Add vstack migrate command with YAML migration records, CLI wiring, and tests.
  • Replace the hand-rolled YAML frontmatter parser with PyYAML and update docs/readmes accordingly.

Reviewed changes

Copilot reviewed 112 out of 113 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/vstack/skills/test_templates.py Update frontmatter parser call style
tests/vstack/frontmatter/test_parser.py Expand PyYAML-based parser coverage
tests/vstack/cli/test_registry.py Move registry tests to CLI interface method
tests/vstack/cli/test_parser.py Assert migrate subcommand presence
tests/vstack/cli/test_migrate.py New tests for vstack migrate
tests/vstack/cli/test_interface.py Add workflow stage parsing tests + wiring updates
tests/vstack/cli/test_init.py Add unknown workflow role warning tests
tests/vstack/cli/test_helpers.py Update normalize helper call site
tests/vstack/cli/test_catalog.py Assert migrate command in catalog
tests/vstack/artifacts/test_generator.py Tighten schema validation test input
tests/vstack/agents/test_role_wiring.py Update wiring expectations for workflow-generated handoffs
tests/vstack/agents/test_generation.py Seed workflow config for handoff generation test
src/vstack/utils.py New hashing utilities module
src/vstack/manifest/store.py Move hashing helpers + manifest preservation helpers into class
src/vstack/manifest/init.py Adjust manifest package exports
src/vstack/frontmatter/serializer.py Quote YAML-special list items
src/vstack/frontmatter/parser.py Replace custom YAML parsing with PyYAML
src/vstack/constants.py Add MIGRATIONS_ROOT package path
src/vstack/cli/uninstall.py Use new manifest preservation helpers + base normalize
src/vstack/cli/service.py Pass workflow stages into AgentGenerator
src/vstack/cli/registry.py Remove standalone registry builder (deleted)
src/vstack/cli/parser.py Add migrate subcommand args
src/vstack/cli/migrate.py Implement docs migration command
src/vstack/cli/interface.py Read workflow stages + build registry internally
src/vstack/cli/init.py Warn on unknown workflow roles + update manifest helper calls
src/vstack/cli/helpers.py Remove normalize helper module (deleted)
src/vstack/cli/catalog.py Register migrate command
src/vstack/cli/base.py Add _normalize_targeted_names helper
src/vstack/artifacts/generator.py Reuse a shared FrontmatterParser instance
src/vstack/agents/generator.py Generate handoffs/baseline sections from workflow + defaults blocks
src/vstack/agents/config.py Make handoffs[].agent optional in schema
src/vstack/_templates/project/.vstack/config.yaml Seed commented workflow contract template
src/vstack/_templates/agents/tester/template.md Insert baseline section placeholder
src/vstack/_templates/agents/tester/config.yaml Move artifacts/handoffs under defaults: + baseline paths
src/vstack/_templates/agents/release/template.md Insert baseline section placeholder
src/vstack/_templates/agents/release/config.yaml Move artifacts under defaults:
src/vstack/_templates/agents/product/template.md Insert baseline section placeholder
src/vstack/_templates/agents/product/config.yaml Move artifacts/handoffs under defaults: + baseline paths
src/vstack/_templates/agents/engineer/template.md Insert baseline section placeholder
src/vstack/_templates/agents/engineer/config.yaml Move artifacts/handoffs under defaults:
src/vstack/_templates/agents/designer/template.md Insert baseline section placeholder
src/vstack/_templates/agents/designer/config.yaml Move artifacts/handoffs under defaults: + baseline paths
src/vstack/_templates/agents/architect/template.md Insert baseline section placeholder
src/vstack/_templates/agents/architect/config.yaml Move artifacts/handoffs under defaults: + baseline paths
src/vstack/_migrations/v2_to_v3.yaml Add v2→v3 docs move record
src/vstack/_migrations/README.md Document migration record schema
README.md Add install/upgrade/migrate guidance + PyYAML runtime note
README-pypi.md Mirror install/upgrade/migrate guidance for PyPI
pyproject.toml Add PyYAML runtime dep + include _migrations in package
poetry.lock Move PyYAML to main deps + add types-PyYAML
docs/product/roadmap.md Mark workflow contract shipped + update orchestration status
docs/product/requirements.md Update runtime dependency statement
docs/architecture/overview.md Update NFRs + ADR index entries
docs/architecture/adr/026-docs-artifact-migration-policy.md New ADR for docs path stability + migration policy
docs/architecture/adr/025-pyyaml-runtime-dependency.md New ADR for PyYAML dependency decision
docs/architecture/adr/024-subagent-orchestration.md New ADR superseding ADR-004
docs/architecture/adr/023-workflow-contract.md New ADR defining workflow contract
docs/architecture/adr/006-no-runtime-dependency.md Scope clarification + orchestration update
docs/architecture/adr/004-option-a-to-b-pipeline.md Mark superseded + add amendment
CHANGELOG.md Reformat existing entries (bulleted lists)
.vstack/vstack.json Refresh manifest install timestamp/checksums
.vstack/config.yaml Add workflow contract block to repo config
.github/agents/architect.agent.md Regenerated agent output (handoffs/baseline + wildcard quoting)
.github/agents/designer.agent.md Regenerated agent output (handoffs/baseline + wildcard quoting)
.github/agents/engineer.agent.md Regenerated agent output (handoffs/baseline + wildcard quoting)
.github/agents/product.agent.md Regenerated agent output (handoffs/baseline + wildcard quoting)
.github/agents/release.agent.md Regenerated agent output (baseline placeholder + wildcard quoting)
.github/agents/tester.agent.md Regenerated agent output (handoffs label change + wildcard quoting)
.github/skills/adr/SKILL.md Regenerated skill output
.github/skills/analyse/SKILL.md Regenerated skill output
.github/skills/architecture/SKILL.md Regenerated skill output
.github/skills/aws-cli/SKILL.md Regenerated skill output
.github/skills/cicd/SKILL.md Regenerated skill output
.github/skills/cloudformation/SKILL.md Regenerated skill output
.github/skills/code-review/SKILL.md Regenerated skill output
.github/skills/codeql/SKILL.md Regenerated skill output
.github/skills/concise/SKILL.md Regenerated skill output
.github/skills/consult/SKILL.md Regenerated skill output
.github/skills/container/SKILL.md Regenerated skill output
.github/skills/conventional-commit/SKILL.md Regenerated skill output
.github/skills/debug/SKILL.md Regenerated skill output
.github/skills/dependabot/SKILL.md Regenerated skill output
.github/skills/dependency/SKILL.md Regenerated skill output
.github/skills/design/SKILL.md Regenerated skill output
.github/skills/docs/SKILL.md Regenerated skill output
.github/skills/explore/SKILL.md Regenerated skill output
.github/skills/gdpr/SKILL.md Regenerated skill output
.github/skills/gh-issues/SKILL.md Regenerated skill output
.github/skills/gh-release/SKILL.md Regenerated skill output
.github/skills/guardrails/SKILL.md Regenerated skill output
.github/skills/helm/SKILL.md Regenerated skill output
.github/skills/incident/SKILL.md Regenerated skill output
.github/skills/inspect/SKILL.md Regenerated skill output
.github/skills/k8s/SKILL.md Regenerated skill output
.github/skills/migrate/SKILL.md Regenerated skill output
.github/skills/onboard/SKILL.md Regenerated skill output
.github/skills/openapi/SKILL.md Regenerated skill output
.github/skills/performance/SKILL.md Regenerated skill output
.github/skills/postmortem/SKILL.md Regenerated skill output
.github/skills/pr/SKILL.md Regenerated skill output
.github/skills/rancher/SKILL.md Regenerated skill output
.github/skills/rca/SKILL.md Regenerated skill output
.github/skills/refactor/SKILL.md Regenerated skill output
.github/skills/release-notes/SKILL.md Regenerated skill output
.github/skills/requirements/SKILL.md Regenerated skill output
.github/skills/secret-scan/SKILL.md Regenerated skill output
.github/skills/security/SKILL.md Regenerated skill output
.github/skills/terraform/SKILL.md Regenerated skill output
.github/skills/terragrunt/SKILL.md Regenerated skill output
.github/skills/threat-model/SKILL.md Regenerated skill output
.github/skills/verify/SKILL.md Regenerated skill output
.github/skills/vision/SKILL.md Regenerated skill output

Comment thread src/vstack/frontmatter/serializer.py
Comment thread .github/skills/vision/SKILL.md
Comment thread src/vstack/frontmatter/parser.py Outdated
Comment thread src/vstack/agents/generator.py
Comment thread src/vstack/_migrations/README.md Outdated
Comment thread docs/architecture/adr/026-docs-artifact-migration-policy.md Outdated
Comment thread docs/architecture/adr/023-workflow-contract.md
Comment thread src/vstack/cli/service.py
Comment thread src/vstack/manifest/__init__.py
eschaar added 10 commits May 10, 2026 00:14
…t/list serialization

Restores the class-level static-method call form FrontmatterParser.parse(...)
and FrontmatterParser.parse_yaml(...) that was broken by the OOP-cleanup
commit.  Updates every call site in artifacts/generator.py, cli/interface.py,
and cli/service.py.

Fixes FrontmatterSerializer._append_raw_field to use yaml.dump for dict and
list values instead of str(), which was emitting invalid Python-repr YAML
(e.g. {'owner': 'vstack', ...}) into generated agent frontmatter.

Adds tests for dict-value and list-value raw-field serialization.
…grate

Implements the documented fallback in AgentGenerator._resolve_handoffs:
when no workflow stages are configured but a handoff_prompt is present,
returns a single generic handoff entry without an agent: key, preserving
v3 behaviour.  Previously the method returned [] in all no-workflow cases,
discarding the agent's own prompt.

Removes self._parser instance from AgentGenerator.__init__; all call sites
now use FrontmatterParser class-level static methods directly.

Adds a None guard in cli/migrate.py _detect_installed_major so it does not
raise AttributeError when ManifestFile.read() returns None.

Updates agent generator tests to cover the fallback case and the static
call form.
preserve_existing_entry() and preserved_manifest_entries() were removed
from vstack.manifest.__all__ as part of the OOP-cleanup refactor, but both
are part of the public API and removing them is a backward-incompatible
change for the current minor bump.

Re-adds both as module-level wrapper functions in manifest/__init__.py that
delegate to Manifest.preserve_existing_entry() and Manifest.preserved_entries()
respectively and emit DeprecationWarning(stacklevel=2) so callers see the
warning at their own call site.  Both are listed in __all__ with a comment
noting they will be dropped in the next minor release.

Adds TestDeprecatedModuleLevelHelpers with three tests covering delegation,
warning emission, and the None-manifest edge case.
…ons README

ADR-023: corrects section 2 — agents use defaults.handoffs.prompt (nested
block), not a flat handoff_prompt: key.  Adds a real config.yaml example.
Marks section 4 (project-level artifact overrides) as deferred / not
implemented in this release rather than describing an unimplemented feature
as if it were shipped.

ADR-026: updates status to accepted and documents the shipped vstack migrate
command with its CLI flags (--target, --from, --to, --dry-run).

src/vstack/_migrations/README.md: removes 'not yet implemented' language;
describes the actual migrate command behaviour and flags.
Regenerates all .github/skills/*/SKILL.md files and .vstack/vstack.json
after the frontmatter serializer fix (dict/list metadata fields now emit
valid block YAML instead of Python repr strings).
…signer, release

product, architect, and designer agents lacked execute, preventing them from
running terminal commands (find, git log, OpenAPI validators, doc aggregation).
release lacked web, needed for CI-status, GitHub release, and PyPI checks.

All six agents now have: read search edit execute web vscode todo agent.
The agent tool is already present everywhere — subagent invocations are
already wired correctly via the agents: ["*"] field.
…l configs

The agentskills.io spec defines allowed-tools as an experimental field that
pre-approves a space-separated set of tool names for a skill, preventing
scope creep when a skill is loaded into an agent that has broader permissions.

SKILL_SCHEMA gains a new FieldSpec("allowed-tools") entry, slotted between
the metadata and argument-hint fields.

Skill tool tiers:
- read-only / analysis (analyse, code-review, concise, consult, explore, inspect):
  execute read search
- network-dependent (aws-cli, codeql, dependency, gh-issues, gh-release, pr,
  secret-scan): execute read search edit web
- all others (31 skills): execute read search edit
…g changes

Artifact changes driven by:
- execute added to product, architect, designer agents
- web added to release agent
- allowed-tools field added to all 44 skill configs

Generated via: python3 -m vstack install
…to candidate

The workflow contract (workflow.stages, gate, hitl, handoffs) ships in this
release, satisfying the prerequisite for ADR-024. However the planner agent
and worker-agent wiring are not yet implemented; no planner template exists
in src/vstack/_templates/agents/.

Align the feature table (was 'in progress') with the detail section header
(already 'candidate') and update the detail body to reference the shipped
prerequisite and list concrete next steps.
… security.yml env

Node.js 24 is now the supported runtime for GitHub Actions JavaScript runners.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 was only set in publish.yml; add it to
check, commit, verify, security, release, and automerge.

Also:
- security.yml was missing POETRY_VIRTUALENVS_IN_PROJECT=true despite running
  poetry install; without it the venv lands in the global cache dir, making
  the 'cache: poetry' step in setup-python point at the wrong location
- publish.yml had a dead 'cache: poetry' / 'cache-dependency-path' on the
  setup-python step; publish only runs 'poetry build', never 'poetry install',
  so no venv is created and the cache is never populated or restored
@eschaar
eschaar merged commit b2e6ccd into main May 9, 2026
16 checks passed
@eschaar
eschaar deleted the feat/workflow-contract branch May 9, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants