@polymorphism-tech/morph-spec 7.4.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +9 -4
- package/framework/MORPH.md +4 -4
- package/framework/agents.json +1 -1
- package/framework/commands/create-umbraco-lp.md +35 -0
- package/framework/commands/migrate-lp-to-umbraco.md +33 -0
- package/framework/commands/morph-archive.md +17 -27
- package/framework/commands/morph-preflight.md +10 -5
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +115 -90
- package/framework/hooks/claude-code/core/protect-spec-files.js +32 -30
- package/framework/hooks/claude-code/core/state-sync.js +16 -48
- package/framework/hooks/shared/state-reader.js +74 -8
- package/framework/hooks/shared/state-writer.js +124 -0
- package/framework/rules/infrastructure-standards.md +4 -4
- package/framework/rules/morph-task-tracking.md +69 -49
- package/framework/rules/morph-workflow.md +192 -191
- package/framework/skills/core/create-umbraco-lp/SKILL.md +272 -0
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +531 -0
- package/framework/skills/core/morph-apply/SKILL.md +15 -15
- package/framework/skills/core/morph-brainstorming/SKILL.md +5 -5
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +5 -4
- package/framework/skills/core/morph-plan/SKILL.md +6 -6
- package/framework/skills/core/morph-proposal/SKILL.md +8 -7
- package/framework/skills/core/morph-review/SKILL.md +3 -3
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +80 -8
- package/framework/standards/ai-agents/durable-workflows-hitl.md +191 -0
- package/framework/standards/ai-agents/rag-custom-pgvector.md +246 -243
- package/framework/standards/ai-agents/setup.md +3 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +170 -0
- package/framework/standards/ai-agents/workflows.md +190 -187
- package/framework/standards/infrastructure/docker/{easypanel-deploy.md → coolify-deploy.md} +49 -20
- package/framework/templates/README.md +7 -7
- package/framework/templates/REGISTRY.json +15 -17
- package/framework/templates/context/CONTEXT-FEATURE.md +10 -17
- package/framework/templates/context/CONTEXT.md +181 -180
- package/framework/templates/docs/onboarding.md +122 -118
- package/framework/templates/docs/proposal.md +12 -2
- package/framework/templates/docs/spec.md +228 -228
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +1 -1
- package/framework/templates/infrastructure/docker/coolify.template.json +49 -0
- package/framework/templates/infrastructure/github/README.md +6 -6
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +33 -0
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -80
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +90 -89
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +2 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +44 -43
- package/framework/templates/meta-prompts/validators/checkpoint-validator.md +107 -107
- package/package.json +1 -1
- package/src/commands/project/update.js +415 -368
- package/src/commands/project/worktree.js +212 -210
- package/src/commands/state/advance.js +82 -0
- package/src/commands/state/archive.js +111 -0
- package/src/commands/validation/validate-feature.js +229 -105
- package/src/core/state/state-manager.js +238 -66
- package/src/dashboard/scanner/state-scanner.js +35 -8
- package/src/lib/generators/context-generator.js +12 -8
- package/src/utils/file-copier.js +7 -0
- package/framework/templates/infrastructure/docker/easypanel.template.json +0 -46
- package/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
|
@@ -4,7 +4,7 @@ description: Entende o negócio, os usuários e a arquitetura antes de qualquer
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber um novo request de feature, antes de morph-plan
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-brainstorming
|
|
@@ -87,9 +87,9 @@ O harness conhece o stack por padrão. Não pergunte sobre tecnologia a menos qu
|
|
|
87
87
|
const riskList = getRiskSignals(signals);
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
**Persista a decisão
|
|
91
|
-
`
|
|
92
|
-
(Edit em `.morph/
|
|
90
|
+
**Persista a decisão no `feature.json` autoritativo da feature ANTES de apresentar ou auto-aprovar** — grave
|
|
91
|
+
`gateDecisions.proposal = { pause, reason, signals, trust, decidedAt: <ISO> }` em `.morph/features/{feature}/feature.json`
|
|
92
|
+
(Edit em `.morph/features/**` é permitido pela allowlist). O hook `gate-guard.js` confere depois se a ação (pausar vs auto-aprovar) bateu com esta decisão e avisa em divergência. Sem o registro o guard não verifica — não pule.
|
|
93
93
|
|
|
94
94
|
**Se `pause === true`** (manual mode OU risco detectado em auto), use `AskUserQuestion`:
|
|
95
95
|
|
|
@@ -116,7 +116,7 @@ O harness conhece o stack por padrão. Não pergunte sobre tecnologia a menos qu
|
|
|
116
116
|
**Riscos:** {principal} (sinais detectados: nenhum)
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
No caminho manual, o hook state-sync.js detecta a aprovação automaticamente (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`). Em modo auto sem AskUserQuestion, aprove via `morph-spec approve {feature} proposal --mode auto` (ou `morph-spec advance {feature}` para já criar a pasta 2-plan/) — nunca via Edit à mão de state.json.
|
|
120
120
|
|
|
121
121
|
## Output esperado
|
|
122
122
|
- `.morph/features/{feature}/0-proposal/proposal.md` — proposta aprovada
|
|
@@ -4,7 +4,7 @@ description: Avalia a qualidade de uma tarefa ou fase com rubricas graduadas 0-1
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao completar uma tarefa ou ao finalizar uma fase de implementação
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-eval
|
|
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-implement
|
|
@@ -109,9 +109,10 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
109
109
|
|
|
110
110
|
`tasks.json` é protegido após o Gate 2, mas os campos de progresso (`status`,
|
|
111
111
|
`outputs`, `notes`) continuam graváveis durante a fase implement. `writeTasksJson`
|
|
112
|
-
grava o arquivo
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
grava o arquivo inteiro (Write) e é o caminho mais simples; **uma `Edit` que mexe
|
|
113
|
+
só nesses campos mutáveis também é permitida** — o hook `protect-spec-files` faz
|
|
114
|
+
diff semântico e libera Write ou Edit desde que nenhum campo de contrato do plano
|
|
115
|
+
(`title`, `description`, `dependencies`, `effort`, `doneCriteria`, `group`) mude.
|
|
115
116
|
|
|
116
117
|
9. Quando invocada por `morph-apply`, retornar o controle à skill orquestradora — ela decide qual task vem em seguida via `getReadyTasks(data)`. Quando invocada manualmente, verificar se há próxima task ready e iniciá-la imediatamente aplicando esta skill desde o passo 1 (auto-advance). Pause apenas se encontrar ambiguidade bloqueante não coberta pelo mandate.
|
|
117
118
|
|
|
@@ -4,7 +4,7 @@ description: Guia a produção dos três artefatos de planejamento de uma featur
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -94,9 +94,9 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
94
94
|
const { pause, reason } = shouldPauseGate(config, signals);
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
**Persista a decisão
|
|
98
|
-
`
|
|
99
|
-
(Edit em `.morph/
|
|
97
|
+
**Persista a decisão no `feature.json` autoritativo da feature ANTES de apresentar ou auto-aprovar** — grave
|
|
98
|
+
`gateDecisions.plan = { pause, reason, signals, trust, decidedAt: <ISO> }` em `.morph/features/{feature}/feature.json`
|
|
99
|
+
(Edit em `.morph/features/**` é permitido pela allowlist). Isso deixa o raciocínio de trust auditável no disco: o hook `gate-guard.js` confere depois se a ação tomada (pausar vs auto-aprovar) bateu com esta decisão e avisa em caso de divergência. Sem este registro o guard não tem como verificar — não pule.
|
|
100
100
|
|
|
101
101
|
**Se `pause === true`**, use `AskUserQuestion`:
|
|
102
102
|
|
|
@@ -121,14 +121,14 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
121
121
|
**Tasks:** {N} | Esforço total: {soma} | Caminho crítico: {IDs sem deps paralelas}
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
No caminho manual, o hook state-sync registra a aprovação automaticamente em answers de AskUserQuestion (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`). Em auto sem AskUserQuestion, aprove via `morph-spec approve {feature} plan --mode auto` (ou `morph-spec advance {feature}` para já criar a pasta da próxima fase) — nunca via Edit à mão de state.json.
|
|
125
125
|
|
|
126
126
|
## Output esperado
|
|
127
127
|
|
|
128
128
|
- `.morph/features/{feature}/2-plan/spec.md` — especificação técnica completa
|
|
129
129
|
- `.morph/features/{feature}/2-plan/mandate.md` — contrato de implementação
|
|
130
130
|
- `.morph/features/{feature}/2-plan/tasks.json` — task list estruturada válida contra schema
|
|
131
|
-
- Gate 2
|
|
131
|
+
- Gate 2 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo)
|
|
132
132
|
|
|
133
133
|
## Referências
|
|
134
134
|
|
|
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-proposal
|
|
@@ -17,7 +17,7 @@ Conduzir uma feature do request inicial até o plano aprovado (Gate 2), sem inte
|
|
|
17
17
|
|
|
18
18
|
- Usuário pede "use /morph-proposal {feature}" ou "vamos planejar {feature}".
|
|
19
19
|
- Início de qualquer feature nova num projeto morph-spec.
|
|
20
|
-
- Em sessões retomadas, antes de continuar uma feature em progresso, verificar via `.morph/
|
|
20
|
+
- Em sessões retomadas, antes de continuar uma feature em progresso, verificar via `.morph/features/{feature-name}/feature.json` (estado autoritativo da feature) se ela já passou pelos Gates.
|
|
21
21
|
|
|
22
22
|
## Argumentos
|
|
23
23
|
|
|
@@ -82,11 +82,12 @@ Escopo: {incluído} | Excluído: {exclusões}
|
|
|
82
82
|
Stack: {compliance}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
No caminho manual, o hook `state-sync.js` registra a aprovação automaticamente via PostToolUse quando a `AskUserQuestion` é respondida com "aprovado" (grava o gate no `feature.json` autoritativo, `approvedBy: "manual"`). Em `trust: auto` (sem AskUserQuestion), **aprove via CLI — nunca via Edit à mão de state.json**:
|
|
86
86
|
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
```
|
|
88
|
+
morph-spec approve {feature} proposal --mode auto
|
|
89
|
+
# ou, para já aprovar o Gate 1 e criar a pasta 2-plan/ num passo só:
|
|
90
|
+
morph-spec advance {feature}
|
|
90
91
|
```
|
|
91
92
|
|
|
92
93
|
### 4. Verificar retomada antes de morph-plan
|
|
@@ -128,7 +129,7 @@ Lógica idêntica ao passo 3 (`shouldPauseGate` + AskUserQuestion ou auto-aprova
|
|
|
128
129
|
- `.morph/features/{feature}/2-plan/spec.md`
|
|
129
130
|
- `.morph/features/{feature}/2-plan/mandate.md`
|
|
130
131
|
- `.morph/features/{feature}/2-plan/tasks.json`
|
|
131
|
-
- Gates 1 e 2
|
|
132
|
+
- Gates 1 e 2 registrados aprovados em `.morph/features/{feature}/feature.json` (autoritativo) — via `morph-spec approve`/`morph-spec advance` em auto, ou via hook state-sync no caminho manual.
|
|
132
133
|
|
|
133
134
|
## Referências
|
|
134
135
|
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -88,12 +88,12 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
88
88
|
**Recomendação:** Aprovado
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
No caminho manual, o hook state-sync detecta a aprovação em answers de AskUserQuestion e grava `approvalGates.review = { approved: true, timestamp, approvedBy: "manual" }` no `feature.json` autoritativo da feature. Em auto sem AskUserQuestion, aprove via `morph-spec approve {feature} review --mode auto` — nunca via Edit à mão de state.json.
|
|
92
92
|
|
|
93
93
|
## Output esperado
|
|
94
94
|
- `.morph/features/{feature}/4-review/review-report.md` com scores e recomendação
|
|
95
95
|
- Trace da fase com `phase_summary` preenchido
|
|
96
|
-
- Gate 3
|
|
96
|
+
- Gate 3 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo), na forma canônica `{ approved, timestamp, approvedBy }` — via `morph-spec approve` em auto, ou pelo hook state-sync no caminho manual
|
|
97
97
|
|
|
98
98
|
## Referências
|
|
99
99
|
- `.morph/framework/MORPH.md` — seção 5 (Gate 3), seção 1 (papel Evaluator), Trust Mode
|
|
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-stack-scan
|
|
@@ -4,7 +4,7 @@ description: Localiza e carrega um standard específico do STANDARDS.json sob de
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: quando precisar consultar um standard específico durante a implementação
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
|
@@ -1495,9 +1495,9 @@
|
|
|
1495
1495
|
"digest": "Vitest for unit tests, React Testing Library for component tests. Mock next/navigation with vi.mock. Test Server Actions with direct function calls. Use MSW for API mocking in component tests. Playwright for E2E tests against running app. Never test implementation details — test user behavior and output."
|
|
1496
1496
|
},
|
|
1497
1497
|
{
|
|
1498
|
-
"id": "infrastructure-docker-
|
|
1499
|
-
"name": "
|
|
1500
|
-
"path": "infrastructure/docker/
|
|
1498
|
+
"id": "infrastructure-docker-coolify-deploy",
|
|
1499
|
+
"name": "coolify deploy",
|
|
1500
|
+
"path": "infrastructure/docker/coolify-deploy.md",
|
|
1501
1501
|
"category": "infrastructure",
|
|
1502
1502
|
"subcategory": "docker",
|
|
1503
1503
|
"tags": [
|
|
@@ -1508,17 +1508,20 @@
|
|
|
1508
1508
|
"docker"
|
|
1509
1509
|
],
|
|
1510
1510
|
"aliases": [
|
|
1511
|
-
"
|
|
1511
|
+
"coolify",
|
|
1512
1512
|
"docker deploy",
|
|
1513
1513
|
"self-hosted",
|
|
1514
1514
|
"VPS deployment",
|
|
1515
|
-
"docker compose"
|
|
1515
|
+
"docker compose",
|
|
1516
|
+
"coolify api",
|
|
1517
|
+
"ssh deploy"
|
|
1516
1518
|
],
|
|
1517
1519
|
"anchors": {
|
|
1518
|
-
"setup": "#
|
|
1519
|
-
"deployment": "#zero-downtime-deploys"
|
|
1520
|
+
"setup": "#coolify-service-config",
|
|
1521
|
+
"deployment": "#zero-downtime-deploys",
|
|
1522
|
+
"ssh": "#triggering-a-deploy-via-ssh--api"
|
|
1520
1523
|
},
|
|
1521
|
-
"digest": "
|
|
1524
|
+
"digest": "Coolify is the self-hosted Docker PaaS. No MCP/CLI exists — all access is via SSH into the VPS, hitting the Coolify API at localhost:8000/api/v1 with a server-side token (never printed, never leaves the SSH session). Configure environment variables in Coolify UI/API — not in Dockerfile. Configure /health endpoint for health checks. HTTPS via Let's Encrypt automatic (Traefik). Redeploys trigger via GET /deploy?uuid={uuid} over SSH."
|
|
1522
1525
|
},
|
|
1523
1526
|
{
|
|
1524
1527
|
"id": "infrastructure-neon-setup",
|
|
@@ -1657,6 +1660,75 @@
|
|
|
1657
1660
|
"edit-workflows": "#iterative-editing-workflows"
|
|
1658
1661
|
},
|
|
1659
1662
|
"digest": "Guidelines for generating project images with Gemini Image MCP — prompt patterns, aspect ratios, design-system-aware prompts, iterative editing, and assets.md format."
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
"id": "ai-agents-durable-workflows-hitl",
|
|
1666
|
+
"name": "durable workflows hitl",
|
|
1667
|
+
"path": "ai-agents/durable-workflows-hitl.md",
|
|
1668
|
+
"category": "ai-agents",
|
|
1669
|
+
"subcategory": null,
|
|
1670
|
+
"tags": [
|
|
1671
|
+
"ai-agents",
|
|
1672
|
+
"maf",
|
|
1673
|
+
"workflow",
|
|
1674
|
+
"durability"
|
|
1675
|
+
],
|
|
1676
|
+
"stacks": [
|
|
1677
|
+
"dotnet"
|
|
1678
|
+
],
|
|
1679
|
+
"aliases": [
|
|
1680
|
+
"durable workflow",
|
|
1681
|
+
"checkpoint",
|
|
1682
|
+
"checkpointing",
|
|
1683
|
+
"human in the loop",
|
|
1684
|
+
"hitl",
|
|
1685
|
+
"requestport",
|
|
1686
|
+
"requestinfoevent",
|
|
1687
|
+
"resume workflow",
|
|
1688
|
+
"long running workflow",
|
|
1689
|
+
"approval workflow",
|
|
1690
|
+
"icheckpointstore"
|
|
1691
|
+
],
|
|
1692
|
+
"anchors": {
|
|
1693
|
+
"when": "#quando-usar-este-standard",
|
|
1694
|
+
"hitl": "#human-in-the-loop-via-requestport",
|
|
1695
|
+
"checkpointing": "#checkpointing--durável-sem-depender-de-azure",
|
|
1696
|
+
"durable-task": "#azure-durable-task-extension--escape-hatch-not-default"
|
|
1697
|
+
},
|
|
1698
|
+
"digest": "HITL via RequestPort — a workflow node that pauses and waits for an external (human) response via RequestInfoEvent. Checkpointing is GA and Azure-free: CheckpointManager.CreateInMemory() for tests, CreateJson(FileSystemJsonCheckpointStore) for local-disk durability, or a custom ICheckpointStore (recommended against the project's own Postgres). Microsoft.Agents.AI.DurableTask (Azure Durable Task Scheduler) is documented as an escape hatch only, not adopted as default."
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
"id": "ai-agents-vector-data-extensions",
|
|
1702
|
+
"name": "vector data extensions",
|
|
1703
|
+
"path": "ai-agents/vector-data-extensions.md",
|
|
1704
|
+
"category": "ai-agents",
|
|
1705
|
+
"subcategory": null,
|
|
1706
|
+
"tags": [
|
|
1707
|
+
"ai-agents",
|
|
1708
|
+
"maf",
|
|
1709
|
+
"rag",
|
|
1710
|
+
"vector-data"
|
|
1711
|
+
],
|
|
1712
|
+
"stacks": [
|
|
1713
|
+
"dotnet"
|
|
1714
|
+
],
|
|
1715
|
+
"aliases": [
|
|
1716
|
+
"microsoft extensions vectordata",
|
|
1717
|
+
"vectorstorecollection",
|
|
1718
|
+
"vector store abstraction",
|
|
1719
|
+
"data ingestion",
|
|
1720
|
+
"ingestion pipeline",
|
|
1721
|
+
"semantic kernel connectors exception",
|
|
1722
|
+
"pgvector connector",
|
|
1723
|
+
"vectorstorewriter"
|
|
1724
|
+
],
|
|
1725
|
+
"anchors": {
|
|
1726
|
+
"naming-trap": "#a-pegadinha-de-nomenclatura-crítico--leia-antes-de-tudo",
|
|
1727
|
+
"maturity": "#maturidade-atual-verificado-2026-07-06",
|
|
1728
|
+
"decision-matrix": "#decision-matrix--custom-pgvector-vs-vectordata",
|
|
1729
|
+
"data-ingestion": "#microsoftextensionsdataingestion-preview--pipeline-pattern-para-adoção-futura"
|
|
1730
|
+
},
|
|
1731
|
+
"digest": "Microsoft.Extensions.VectorData.Abstractions (GA) is the provider-agnostic vector-store abstraction; Microsoft.Extensions.DataIngestion (preview) is the companion ETL pipeline. Concrete connectors (Postgres, Qdrant, SQL Server...) ship under Microsoft.SemanticKernel.Connectors.* package names despite having nothing to do with Semantic Kernel — an explicit exception to ai-agents-setup's SK ban. Default RAG storage stays ai-agents-rag-custom-pgvector; adopt VectorData only for genuine multi-backend portability, since the Postgres connector is still prerelease."
|
|
1660
1732
|
}
|
|
1661
1733
|
],
|
|
1662
1734
|
"sharedAliases": {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# MAF Workflows — Checkpointing & Human-in-the-Loop
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 1 (on-keyword)
|
|
5
|
+
> **Keywords:** durable workflow, checkpoint, checkpointing, human in the loop, hitl, requestport, requestinfoevent, resume workflow, long running workflow, approval workflow, icheckpointstore, filesystemjsoncheckpointstore
|
|
6
|
+
> **Read by Claude in:** plan (when `ai-agents-workflows` identifies a genuine HITL or long-running need) + implement (wiring `RequestPort` / `CheckpointManager`)
|
|
7
|
+
|
|
8
|
+
**Verified against:** Microsoft.Agents.AI 1.0 GA (`Microsoft.Agents.AI.Workflows` namespace) + Microsoft.Agents.AI.DurableTask (prerelease, Azure-coupled — documented as an escape hatch, not adopted as default). Last-verified: 2026-07-06.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quando usar este standard
|
|
13
|
+
|
|
14
|
+
Read `ai-agents-sweet-spot` and `ai-agents-workflows` first. This standard applies only once `ai-agents-workflows` has justified a Workflow **and** the reason is one of:
|
|
15
|
+
|
|
16
|
+
- Long-running execution with a human-in-the-loop (HITL) approval step.
|
|
17
|
+
- A workflow whose state must survive a process restart (deploy, crash, restart) between steps.
|
|
18
|
+
|
|
19
|
+
If neither applies, a plain workflow (or no workflow at all — see `ai-agents-sweet-spot`) is enough; do not add checkpointing or `RequestPort` "just in case."
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Human-in-the-Loop via `RequestPort`
|
|
24
|
+
|
|
25
|
+
HITL is modeled with `RequestPort` — a special node in the workflow graph that pauses execution and waits for an external (human) response instead of processing automatically.
|
|
26
|
+
|
|
27
|
+
```csharp
|
|
28
|
+
using Microsoft.Agents.AI.Workflows;
|
|
29
|
+
|
|
30
|
+
// Request type the port sends out, response type it expects back.
|
|
31
|
+
RequestPort<ApprovalRequest, bool> approvalPort =
|
|
32
|
+
RequestPort.Create<ApprovalRequest, bool>("RefundApproval");
|
|
33
|
+
|
|
34
|
+
var workflow = new WorkflowBuilder(refundAnalyzerAgent)
|
|
35
|
+
.AddEdge(refundAnalyzerAgent, approvalPort)
|
|
36
|
+
.AddEdge(approvalPort, refundProcessorExecutor)
|
|
37
|
+
.WithOutputFrom(refundProcessorExecutor)
|
|
38
|
+
.Build();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Running the workflow, the caller watches for `RequestInfoEvent` and sends the human's answer back — the framework routes the response to the executor that made the request:
|
|
42
|
+
|
|
43
|
+
```csharp
|
|
44
|
+
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input);
|
|
45
|
+
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
|
46
|
+
{
|
|
47
|
+
switch (evt)
|
|
48
|
+
{
|
|
49
|
+
case RequestInfoEvent requestEvt:
|
|
50
|
+
// Surface requestEvt.Request to a human (dashboard, email, Slack, ...).
|
|
51
|
+
// This is where execution pauses until an external answer arrives.
|
|
52
|
+
bool approved = await GetHumanDecisionAsync(requestEvt.Request);
|
|
53
|
+
await run.SendResponseAsync(requestEvt.Request.CreateResponse(approved));
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case WorkflowOutputEvent outputEvt:
|
|
57
|
+
Console.WriteLine($"Workflow completed: {outputEvt.Data}");
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Agent tool-approval uses the same mechanism.** When an agent orchestration (sequential, concurrent, group chat) calls a tool marked as requiring approval, the workflow pauses and emits a `RequestInfoEvent` whose payload is a `ToolApprovalRequestContent` instead of a custom request type — no separate API to learn. See `ai-agents-multi-agent-patterns` for agent orchestration patterns this composes with.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Checkpointing — durável sem depender de Azure
|
|
68
|
+
|
|
69
|
+
Checkpointing is part of the **base, GA** `Microsoft.Agents.AI.Workflows` namespace — no extra/preview package required. A checkpoint captures the full workflow state at each superstep boundary: executor state, pending messages, and pending `RequestPort` requests (so a pending approval survives a restore).
|
|
70
|
+
|
|
71
|
+
### Three storage options
|
|
72
|
+
|
|
73
|
+
| Store | Durability | When to use |
|
|
74
|
+
|-------|-----------|-------------|
|
|
75
|
+
| `CheckpointManager.CreateInMemory()` | In-process only, lost on restart | Tests, demos, short-lived workflows |
|
|
76
|
+
| `CheckpointManager.CreateJson(new FileSystemJsonCheckpointStore(dir))` | Local disk, survives process restart | Single-instance VPS deployments — no Azure, no preview package |
|
|
77
|
+
| Custom `ICheckpointStore` implementation | Whatever backend you implement it against | **Recommended for production** — implement against the project's own Neon Postgres table, matching the ownership model already used by `ai-agents-rag-custom-pgvector` |
|
|
78
|
+
|
|
79
|
+
```csharp
|
|
80
|
+
using Microsoft.Agents.AI.Workflows;
|
|
81
|
+
|
|
82
|
+
// Local-disk durability — no Azure dependency:
|
|
83
|
+
var checkpointFolder = Directory.CreateDirectory("./checkpoints");
|
|
84
|
+
CheckpointManager checkpointManager = CheckpointManager.CreateJson(
|
|
85
|
+
new FileSystemJsonCheckpointStore(checkpointFolder));
|
|
86
|
+
|
|
87
|
+
StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input, checkpointManager);
|
|
88
|
+
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
|
89
|
+
{
|
|
90
|
+
if (evt is SuperStepCompletedEvent stepEvt)
|
|
91
|
+
{
|
|
92
|
+
CheckpointInfo? checkpoint = stepEvt.CompletionInfo?.Checkpoint;
|
|
93
|
+
// Persist checkpoint.CheckpointId alongside the business record
|
|
94
|
+
// (e.g. the refund request row) so it can be resumed after a restart.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Resuming after a restart — rehydrate into a **new** run instance from the last known checkpoint:
|
|
100
|
+
|
|
101
|
+
```csharp
|
|
102
|
+
StreamingRun resumedRun = await InProcessExecution.ResumeStreamingAsync(
|
|
103
|
+
newWorkflowInstance, savedCheckpoint, checkpointManager);
|
|
104
|
+
|
|
105
|
+
await foreach (WorkflowEvent evt in resumedRun.WatchStreamAsync())
|
|
106
|
+
{
|
|
107
|
+
// A pending RequestPort request is re-emitted here as a RequestInfoEvent —
|
|
108
|
+
// the paused approval survives the restart.
|
|
109
|
+
if (evt is RequestInfoEvent requestEvt) { /* re-surface to the human */ }
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Custom executor state
|
|
114
|
+
|
|
115
|
+
An executor with instance state beyond the message flow must opt in explicitly:
|
|
116
|
+
|
|
117
|
+
```csharp
|
|
118
|
+
internal sealed class RefundAnalyzerExecutor() : Executor("RefundAnalyzer")
|
|
119
|
+
{
|
|
120
|
+
private const string StateKey = "AnalysisHistory";
|
|
121
|
+
private List<string> _history = [];
|
|
122
|
+
|
|
123
|
+
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken ct = default)
|
|
124
|
+
=> context.QueueStateUpdateAsync(StateKey, _history);
|
|
125
|
+
|
|
126
|
+
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken ct = default)
|
|
127
|
+
=> _history = await context.ReadStateAsync<List<string>>(StateKey);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `ICheckpointStore` — Neon/Postgres-backed (recommended production pattern)
|
|
132
|
+
|
|
133
|
+
Implementing `ICheckpointStore` against the project's own Postgres database keeps checkpoint durability inside the same infrastructure the rest of the project already trusts (see `infrastructure-neon-setup`), instead of introducing a new storage dependency:
|
|
134
|
+
|
|
135
|
+
```csharp
|
|
136
|
+
public sealed class PostgresCheckpointStore(NpgsqlDataSource dataSource) : ICheckpointStore
|
|
137
|
+
{
|
|
138
|
+
// Persist/retrieve checkpoint blobs in a workflow_checkpoints table
|
|
139
|
+
// (workflow_id, checkpoint_id, payload jsonb/bytea, created_at).
|
|
140
|
+
// Implement per the ICheckpointStore contract — see Microsoft.Agents.AI.Workflows docs.
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> **Security:** checkpoint storage is a trust boundary. Never restore a checkpoint from an untrusted or unauthenticated source — a tampered checkpoint can inject arbitrary workflow state.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Azure Durable Task extension — escape hatch, not default
|
|
149
|
+
|
|
150
|
+
`Microsoft.Agents.AI.DurableTask` (prerelease) adds durability backed by the **Azure Durable Task Scheduler**, including cost-efficient multi-day HITL waits with zero compute cost while paused. Even its "bring-your-own-compute / self-hosted" mode still requires a Durable Task Scheduler backend (`Microsoft.DurableTask.*.AzureManaged` packages) — self-hosted means your own worker process, not an Azure-free backend.
|
|
151
|
+
|
|
152
|
+
This framework deploys to VPS/Coolify by default and does not adopt Azure as a default dependency (see `ai-agents-setup` §3.4 on Azure OpenAI). For that reason, `Microsoft.Agents.AI.DurableTask` is **not adopted as default** here. The `CheckpointManager` + custom `ICheckpointStore` pattern above covers the same durability need without an Azure dependency. Only reach for the Durable Task extension when a specific client already runs on Azure and needs multi-day/serverless HITL waits — record that decision in `decisions.md`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Anti-patterns
|
|
157
|
+
|
|
158
|
+
| Anti-pattern | Why it's wrong | Right way |
|
|
159
|
+
|--------------|----------------|-----------|
|
|
160
|
+
| Adding a `Workflow` with `RequestPort` but no `CheckpointManager` | A pending human approval is lost if the process restarts while waiting | Wire a persistent `CheckpointManager` (file-based or custom `ICheckpointStore`) whenever a HITL wait can outlive a single process lifetime |
|
|
161
|
+
| Reaching for `Microsoft.Agents.AI.DurableTask` by default | Adds an Azure Durable Task Scheduler dependency this framework's VPS-first stack doesn't otherwise need | Use base `CheckpointManager` + custom `ICheckpointStore`; reserve the Durable Task extension for projects already committed to Azure |
|
|
162
|
+
| Storing checkpoints in a public or unauthenticated location | Checkpoint storage is a trust boundary — a tampered checkpoint can inject arbitrary state on restore | Restrict read/write access to the checkpoint store the same way you would a secrets store |
|
|
163
|
+
| Skipping `OnCheckpointingAsync`/`OnCheckpointRestoredAsync` on an executor with instance state | State silently resets to defaults after a resume, corrupting workflow logic | Any executor with fields beyond the message flow must implement both methods |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Checklist (verifiable by morph-eval)
|
|
168
|
+
|
|
169
|
+
- [ ] `ai-agents-workflows`' HITL or long-running criterion is documented in `decisions.md` before this standard is applied.
|
|
170
|
+
- [ ] Every HITL wait point uses `RequestPort` + handles `RequestInfoEvent`.
|
|
171
|
+
- [ ] A `CheckpointManager` is wired whenever the workflow can pause across a process restart — `CreateInMemory()` is acceptable only for tests/demos.
|
|
172
|
+
- [ ] If a custom `ICheckpointStore` was implemented, it persists to infrastructure already owned by the project (e.g., Neon Postgres), not a new ad hoc store.
|
|
173
|
+
- [ ] Executors with instance state implement `OnCheckpointingAsync` and `OnCheckpointRestoredAsync`.
|
|
174
|
+
- [ ] `Microsoft.Agents.AI.DurableTask` is present only if the project already runs on Azure and the choice is recorded in `decisions.md`.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## References
|
|
179
|
+
|
|
180
|
+
- `ai-agents-workflows` — when a Workflow (and therefore this standard) is justified
|
|
181
|
+
- `ai-agents-sweet-spot` — single agent vs workflow decision
|
|
182
|
+
- `ai-agents-multi-agent-patterns` — agent orchestrations that share the tool-approval HITL mechanism
|
|
183
|
+
- `ai-agents-setup` — the VPS-first, non-Azure-default stance this standard follows for durability
|
|
184
|
+
- `infrastructure-neon-setup` — Postgres connection patterns for a custom `ICheckpointStore`
|
|
185
|
+
- Human-in-the-loop (HITL) | Microsoft Learn: https://learn.microsoft.com/en-us/agent-framework/workflows/human-in-the-loop
|
|
186
|
+
- Checkpoints | Microsoft Learn: https://learn.microsoft.com/en-us/agent-framework/workflows/checkpoints
|
|
187
|
+
- Durable Extension | Microsoft Learn: https://learn.microsoft.com/en-us/agent-framework/integrations/durable-extension
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/durable-workflows-hitl.md v1.0 (2026-07-06)*
|