@polymorphism-tech/morph-spec 8.34.2 → 8.35.1
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/CLAUDE.md +22 -5
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -4
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +16 -17
- package/framework/agents.json +197 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +236 -11
- package/framework/evals/proof-rubrics/README.md +76 -0
- package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
- package/framework/evals/proof-rubrics/fiacao.md +54 -0
- package/framework/evals/proof-rubrics/guard-cego.md +52 -0
- package/framework/evals/proof-rubrics/media-claims.md +109 -0
- package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
- package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
- package/framework/evals/rubrics/architecture.md +30 -0
- package/framework/evals/rubrics/code-quality.md +26 -0
- package/framework/evals/rubrics/test-coverage.md +20 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-env-inject.js +43 -0
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/e2e-check.js +135 -22
- package/framework/hooks/shared/payload-utils.js +15 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/schemas/ai-pin.schema.json +97 -0
- package/framework/schemas/config.schema.json +107 -0
- package/framework/schemas/feature.schema.json +23 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +26 -5
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +18 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +33 -2
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +166 -9
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/PROVENANCE.json +81 -3
- package/framework/standards/STANDARDS.json +1483 -839
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +164 -68
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +61 -37
- package/framework/standards/ai-agents/context-providers.md +97 -24
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
- package/framework/standards/ai-agents/cost-and-budget.md +256 -0
- package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
- package/framework/standards/ai-agents/evals-with-cache.md +289 -0
- package/framework/standards/ai-agents/guardrails.md +222 -0
- package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
- package/framework/standards/ai-agents/mcp-server.md +119 -18
- package/framework/standards/ai-agents/mcp-tools.md +85 -32
- package/framework/standards/ai-agents/media-pipeline.md +352 -0
- package/framework/standards/ai-agents/media-video.md +267 -0
- package/framework/standards/ai-agents/middleware-patterns.md +39 -5
- package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
- package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
- package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
- package/framework/standards/ai-agents/modalities-vision.md +6 -2
- package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +38 -9
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +174 -39
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +27 -19
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +34 -3
- package/framework/standards/ai-agents/sweet-spot.md +23 -3
- package/framework/standards/ai-agents/testing-ai.md +236 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
- package/framework/standards/ai-agents/workflows.md +27 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +13 -0
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
- package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
- package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
- package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
- package/framework/templates/dotnet/ai-kit/README.md +128 -0
- package/framework/templates/dotnet/ai-kit/global.json +9 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +3 -3
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/e2e/e2e.js +15 -2
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +148 -6
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +76 -4
- package/src/commands/state/advance.js +24 -2
- package/src/commands/state/approve.js +33 -1
- package/src/commands/state/create.js +72 -11
- package/src/commands/state/finish.js +379 -24
- package/src/commands/state/gate-check.js +190 -6
- package/src/commands/state/score.js +36 -1
- package/src/commands/validation/verify.js +134 -25
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +9 -1
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +182 -0
- package/src/lib/ai/ai-project-scan.js +347 -0
- package/src/lib/ai/code-audit.js +181 -0
- package/src/lib/ai/comment-stripper.js +184 -0
- package/src/lib/ai/pin-drift.js +138 -0
- package/src/lib/ai/registry-audit.js +285 -0
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/e2e/compose-env.js +50 -4
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/git/pr-files.js +56 -0
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/effects.js +193 -0
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/templates-reconcile.js +3 -3
- package/src/lib/risk-detector.js +284 -0
- package/src/lib/standards/verified-against.js +276 -0
- package/src/lib/tasks/eval-node.js +485 -0
- package/src/lib/tasks/linux-runner.js +297 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +410 -33
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/verify-runner.js +583 -28
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +24 -0
- package/src/lib/worktree/worktree-audit.js +30 -1
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +11 -4
- package/src/utils/hooks-installer.js +29 -26
- package/src/utils/skills-installer.js +36 -29
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Conversational Agent with Phases — um agente, fase como dado do turno
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 2 (sob demanda)
|
|
5
|
+
> **Keywords:** agente conversacional, fases do agente, allow-list de tools, RequireAny, ChatToolMode, compositor de turno, teto de tool calls, turno de conversa
|
|
6
|
+
> **Read by Claude in:** implement (ao construir ou alterar um agente que atende uma conversa ao longo do tempo)
|
|
7
|
+
|
|
8
|
+
**Verified against:** Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08). `ChatToolMode.RequireAny` foi **medido por reflexão sobre `Microsoft.Extensions.AI.Abstractions` 10.9.0** em 2026-09-08. O restante é **verificação documental**, sem cláusula `provado por` — o ai-kit ainda não traz um compositor de turno, e o padrão é destilado de um único repositório de campo (ver a nota de fonte única abaixo). Last-verified: 2026-09-08.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
> **Nota de fonte única, declarada no corpo de propósito.** Todo o lastro de campo deste standard vem
|
|
13
|
+
> de **um** repositório em produção. Não há segundo exemplar no acervo para confirmar nem para
|
|
14
|
+
> contradizer. Isso não invalida o padrão — ele roda, e roda há tempo suficiente para ter dor
|
|
15
|
+
> registrada —, mas muda como lê-lo: as **formas** aqui são fortes; os **números** (um teto de 8,
|
|
16
|
+
> dois estágios de poda) são o que aquele produto precisou, não um universal. Onde este arquivo diz
|
|
17
|
+
> "medido", leia "medido ali".
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quando este arquétipo
|
|
22
|
+
|
|
23
|
+
`ai-agents-agent-archetypes` decide a forma; este arquivo é o aprofundamento da primeira. Ele vale
|
|
24
|
+
quando as quatro condições valem juntas:
|
|
25
|
+
|
|
26
|
+
1. Há **um interlocutor** e uma conversa que dura mais de um turno.
|
|
27
|
+
2. O que o agente **pode fazer** muda conforme o ponto da conversa.
|
|
28
|
+
3. Existe estado observável de onde derivar esse ponto — não a memória do modelo.
|
|
29
|
+
4. Toda ação com efeito no mundo é uma **tool**, não uma frase.
|
|
30
|
+
|
|
31
|
+
Falhando (2), você tem um agente simples com histórico e não precisa de nada aqui. Falhando (4), o
|
|
32
|
+
teto de tool calls e a allow-list perdem o sentido, porque a ação escapa pelo texto.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Compositor de turno determinístico
|
|
37
|
+
|
|
38
|
+
**A peça central, e a mais reaproveitável.** Um `static class` puro — sem I/O, sem relógio, sem
|
|
39
|
+
banco — que recebe o estado do turno e devolve **um plano de turno**: as instruções, a lista de
|
|
40
|
+
tools permitidas, e os limites daquele turno.
|
|
41
|
+
|
|
42
|
+
Três propriedades definem o padrão:
|
|
43
|
+
|
|
44
|
+
**(1) Prompt decomposto em blocos nomeados.** O prompt não nasce como uma string; nasce como uma
|
|
45
|
+
lista de blocos com nome (`persona`, `contexto do lead`, `regras da fase`, `histórico`, …). O nome
|
|
46
|
+
serve para medir: dá para dizer que bloco cresceu, que bloco entrou, que bloco não deveria estar ali.
|
|
47
|
+
|
|
48
|
+
**(2) O texto enviado É a concatenação dos blocos, derivada sem cache.** Esta é a invariante, e ela
|
|
49
|
+
é deliberadamente cara: nada de guardar a string montada ao lado dos blocos. A razão está escrita no
|
|
50
|
+
código de campo: *"para a telemetria não poder mentir"*. Se o texto enviado e o texto registrado
|
|
51
|
+
pudessem divergir — e um cache é exatamente um lugar onde divergem —, então o hash do prompt não
|
|
52
|
+
prova mais nada, e a rastreabilidade inteira vira decoração. É o que amarra este standard a
|
|
53
|
+
`ai-agents-observability-patterns`.
|
|
54
|
+
|
|
55
|
+
**(3) Determinístico e puro, portanto testável sem infraestrutura.** Um compositor que lê banco não
|
|
56
|
+
é testável em ciclo curto, e um compositor não testado é onde a regra de fase silenciosamente para
|
|
57
|
+
de valer.
|
|
58
|
+
|
|
59
|
+
**A dor que o compositor mata**, medida: montagem de prompt em **três lugares** e o filtro de
|
|
60
|
+
allow-list em **três lugares** dentro do mesmo produto. Nenhum dos três está errado isoladamente;
|
|
61
|
+
juntos, garantem que uma regra nova entre em dois e esqueça o terceiro.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Allow-list por fase
|
|
66
|
+
|
|
67
|
+
A poda acontece **em dois estágios, ambos em código**, e a ordem importa:
|
|
68
|
+
|
|
69
|
+
| Estágio | Pergunta | Efeito |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| 1 | Esta tool pertence a **esta fase**? | Poda por fase |
|
|
72
|
+
| 2 | Este **tenant/plano** tem esta capacidade? | Poda por capacidade |
|
|
73
|
+
|
|
74
|
+
Regras que o campo fixou, e que valem repetir porque cada uma custou um bug:
|
|
75
|
+
|
|
76
|
+
- O mapa de capacidade é **declarativo e puro** — uma tabela, não um `switch` com efeitos.
|
|
77
|
+
- **Tool ausente do mapa é disponível.** O default é permitir, e a razão é operacional: um mapa
|
|
78
|
+
incompleto que nega por omissão derruba funcionalidade em produção toda vez que alguém adiciona
|
|
79
|
+
uma tool e esquece a linha.
|
|
80
|
+
- **A poda preserva a ordem** da lista original. Ordem de tools é entrada do modelo; embaralhá-la
|
|
81
|
+
entre turnos introduz variância que ninguém pediu.
|
|
82
|
+
|
|
83
|
+
Cobertura de referência no campo: 52 casos de teste **só** sobre o segundo estágio. Não é excesso —
|
|
84
|
+
é o estágio em que um erro é invisível (a tool some, o modelo se adapta, e ninguém percebe até a
|
|
85
|
+
reclamação).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Exigir tool no turno
|
|
90
|
+
|
|
91
|
+
Quando **toda** ação do agente é uma tool, um turno que não chama tool nenhuma é, por construção,
|
|
92
|
+
um turno perdido.
|
|
93
|
+
|
|
94
|
+
**`ChatToolMode.RequireAny` existe.** Medido por reflexão sobre
|
|
95
|
+
`Microsoft.Extensions.AI.Abstractions` 10.9.0 em 2026-09-08: `ChatToolMode` expõe `Auto`, `None`,
|
|
96
|
+
`RequireAny` (do tipo `RequiredChatToolMode`) e o método `RequireSpecific(string functionName)`. A
|
|
97
|
+
restrição de uso é a esperada: é preciso haver ao menos uma tool em `ChatOptions.Tools`.
|
|
98
|
+
|
|
99
|
+
**Este standard o recomenda como forma default para esse caso** — e para no ponto exato em que uma
|
|
100
|
+
recomendação viraria prescrição cega.
|
|
101
|
+
|
|
102
|
+
### A alternativa não é um defeito, e o critério de saída é parte da regra
|
|
103
|
+
|
|
104
|
+
A alternativa medida em campo é a **segunda passada**: quando a primeira não chama tool, o código
|
|
105
|
+
reenvia com uma mensagem fixa pedindo a ação. É fácil chamar isso de gambiarra; os fatos não
|
|
106
|
+
sustentam:
|
|
107
|
+
|
|
108
|
+
- Tem **7 testes dedicados**.
|
|
109
|
+
- Tem um vocabulário fechado de **quatro desfechos**, incluindo `recovered` — ou seja, o caso "a
|
|
110
|
+
segunda passada salvou o turno" é **contado**, não suposto.
|
|
111
|
+
- **Preserva o desfecho da primeira passada** quando a segunda falha no transporte, em vez de
|
|
112
|
+
transformar um erro de rede em "o agente não fez nada".
|
|
113
|
+
- É chamado **no próprio código** de *"migração futura pendente de métrica"*.
|
|
114
|
+
|
|
115
|
+
Transformar uma migração que o código deixou aberta de propósito numa regra é adiantar uma decisão
|
|
116
|
+
que ninguém mediu. Então o standard escreve o **critério de saída**:
|
|
117
|
+
|
|
118
|
+
> **Migre para `RequireAny` quando a taxa de `recovered` não pagar o custo da segunda chamada.** Os
|
|
119
|
+
> dois números existem: a contagem de `recovered` sobre o total de turnos, e o custo por turno da
|
|
120
|
+
> chamada extra (`ai-agents-cost-and-budget`). Se `recovered` é raro, a segunda passada é seguro
|
|
121
|
+
> caro; se é frequente, ela está compensando um prompt fraco, e `RequireAny` vai expor isso como
|
|
122
|
+
> erro em vez de escondê-lo como recuperação.
|
|
123
|
+
|
|
124
|
+
**E o que se perde na migração**, para que a decisão seja informada: a recuperação best-effort
|
|
125
|
+
desaparece (um turno que hoje se salva passa a falhar), e o **acumulador de uso que soma as duas
|
|
126
|
+
passadas** perde a razão de existir — se ele for removido junto, a contabilidade de custo daquele
|
|
127
|
+
turno muda de forma, o que precisa ser conferido antes e depois.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Teto de tool calls e isenta
|
|
132
|
+
|
|
133
|
+
Um agente que pode chamar tools em loop precisa de um teto, e o teto precisa de uma **isenta**.
|
|
134
|
+
|
|
135
|
+
- **Teto por turno** — um número de voltas de tool calling. Referência de campo: 8.
|
|
136
|
+
- **Uma tool isenta**, tipicamente a que fala com o interlocutor: com ela configurada, o loop tolera
|
|
137
|
+
N negações pós-teto antes de encerrar o turno. Sem isenta, bater o teto significa **o agente parar
|
|
138
|
+
de responder no meio**, que é o pior desfecho possível numa conversa.
|
|
139
|
+
- **O valor efetivo vem da configuração**, por tenant ou por produto, não de uma constante.
|
|
140
|
+
- Bater o teto é **evento observável**, não um `return` silencioso.
|
|
141
|
+
|
|
142
|
+
Nota de camada: o teto de voltas é **nível de agente** (uma opção do pipeline de invocação de
|
|
143
|
+
funções), não uma propriedade das opções de chamada. Confundir os dois faz o teto ser enviado ao
|
|
144
|
+
provider, onde ele não significa nada.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Agente auxiliar em sequência
|
|
149
|
+
|
|
150
|
+
**Etapa fixa do turno roda em sequência, no código. Aninhar um agente numa tool call é decisão
|
|
151
|
+
própria, com critério — não o default.**
|
|
152
|
+
|
|
153
|
+
### A fronteira com `multi-agent-patterns`, escrita para não virar contradição
|
|
154
|
+
|
|
155
|
+
`ai-agents-multi-agent-patterns` é o dono da composição entre agentes, e ele **recomenda**
|
|
156
|
+
Agent-as-Tool (`agent.AsAIFunction()` — API real, medida no pin: `AIAgentExtensions.AsAIFunction`
|
|
157
|
+
existe em `Microsoft.Agents.AI` 1.20.0), com teto de **≤ 2 níveis** de aninhamento. Isso **não**
|
|
158
|
+
contradiz este arquivo, e a fronteira é uma pergunta só:
|
|
159
|
+
|
|
160
|
+
| Pergunta | Resposta | Forma |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| **Quem decide se o auxiliar roda?** O **modelo**, turno a turno | Agent-as-Tool é o padrão: `AsAIFunction()`, ≤ 2 níveis (`ai-agents-multi-agent-patterns`) | aninhado, deliberadamente |
|
|
163
|
+
| O **código** — é etapa fixa do turno, sempre acontece | **sequência no código**, nunca aninhada | o orquestrador é o código |
|
|
164
|
+
|
|
165
|
+
O caso deste standard é quase sempre o segundo: numa conversa com fases, o auxiliar (persona,
|
|
166
|
+
sumarização, extração) é **etapa fixa**, não escolha do modelo. Aninhá-lo paga o preço abaixo sem
|
|
167
|
+
comprar a única coisa que o aninhamento oferece — deixar o modelo decidir.
|
|
168
|
+
|
|
169
|
+
**O preço, concretamente:** o custo do agente interno aparece como custo de uma tool (logo, some da
|
|
170
|
+
contabilidade por agente); o teto de tool calls do externo não governa o interno; o timeout do turno
|
|
171
|
+
passa a cobrir duas chamadas de LLM em série; e o span do interno pendura-se sob `execute_tool`, onde
|
|
172
|
+
nenhum painel de agente procura. Quando a decisão **é** do modelo, esses quatro custos continuam
|
|
173
|
+
valendo — a diferença é que aí eles foram escolhidos, e o `decisions.md` diz por quê.
|
|
174
|
+
|
|
175
|
+
Os dois lados de campo, e é raro ter os dois:
|
|
176
|
+
|
|
177
|
+
| Lado | O que o acervo mostra |
|
|
178
|
+
|---|---|
|
|
179
|
+
| **Declarado** | Um projeto escreve o contrato explicitamente: *"nenhum agente chama outro; nesting é anti-padrão nomeado"* |
|
|
180
|
+
| **Medido como problema** | Outro projeto roda um agente de persona **dentro** de uma tool call do agente principal — e a persona é etapa **fixa** daquele turno, isto é, o segundo caso da tabela acima |
|
|
181
|
+
|
|
182
|
+
A composição legítima — quando ela é mesmo necessária — é assunto de
|
|
183
|
+
`ai-agents-multi-agent-patterns`, e este standard não a repete: aponta.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Anti-patterns
|
|
188
|
+
|
|
189
|
+
| Anti-pattern | Por quê é problema | Solução |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| Um agente por fase | A transição vira orquestração; o histórico fragmenta | Um agente, fase como dado do turno |
|
|
192
|
+
| Fase derivada da memória do modelo | O modelo "esquece" a fase, e o bug não é reproduzível | Derive de estado observável, a cada turno |
|
|
193
|
+
| Montagem de prompt em mais de um lugar | Regra nova entra em dois e esquece o terceiro | Um compositor, puro |
|
|
194
|
+
| String de prompt guardada em cache ao lado dos blocos | A telemetria passa a poder mentir; o hash não prova nada | Derive sem cache, sempre |
|
|
195
|
+
| Allow-list negando por omissão | Tool nova sem linha no mapa derruba funcionalidade em produção | Ausente do mapa = disponível |
|
|
196
|
+
| Poda que reordena as tools | Variância de entrada que ninguém pediu | Preserve a ordem |
|
|
197
|
+
| Teto de tool calls sem isenta | O agente para de responder no meio da conversa | Isenta configurada, com tolerância pós-teto |
|
|
198
|
+
| Teto tratado como opção de chamada | Vai ao provider, onde não significa nada | É opção do pipeline, nível de agente |
|
|
199
|
+
| `RequireAny` adotado sem medir `recovered` | Troca uma recuperação medida por uma falha nova | Aplique o critério de saída antes |
|
|
200
|
+
| Etapa FIXA do turno aninhada numa tool call | Paga custo, teto, timeout e span quebrados sem comprar nada — ninguém precisava decidir | Sequência, no código |
|
|
201
|
+
| Agent-as-Tool adotado sem registrar a decisão, ou além de 2 níveis | Os quatro custos viram surpresa, e o 3º nível é sinal de que devia ser sequência | Decisão no `decisions.md`, teto ≤ 2 níveis (`ai-agents-multi-agent-patterns`) |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Checklist (verifiable by morph-eval)
|
|
206
|
+
|
|
207
|
+
- [ ] Existe **um** compositor de turno, puro e sem I/O, e nenhuma segunda montagem de prompt
|
|
208
|
+
- [ ] O prompt é composto de blocos nomeados, e o texto enviado é derivado sem cache
|
|
209
|
+
- [ ] A fase é derivada de estado observável a cada turno
|
|
210
|
+
- [ ] A allow-list poda por fase e por capacidade, nessa ordem, preservando a ordem das tools
|
|
211
|
+
- [ ] Tool ausente do mapa de capacidade é tratada como **disponível**
|
|
212
|
+
- [ ] Há teto de tool calls por turno, com tool isenta, e bater o teto é observável
|
|
213
|
+
- [ ] O teto está no nível de agente, não nas opções de chamada
|
|
214
|
+
- [ ] Se `RequireAny` foi adotado: a taxa de `recovered` e o custo da segunda chamada estão no
|
|
215
|
+
`decisions.md`. Se não foi: o critério de saída está escrito
|
|
216
|
+
- [ ] Nenhuma etapa **fixa** do turno é invocada de dentro de uma tool call. Onde há aninhamento, quem decide é o **modelo**, está registrado no `decisions.md`, e respeita o teto de ≤ 2 níveis de `ai-agents-multi-agent-patterns`
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## References
|
|
221
|
+
|
|
222
|
+
- `ai-agents-agent-archetypes` — o catálogo das quatro formas; este é o aprofundamento da primeira
|
|
223
|
+
- `ai-agents-multi-agent-patterns` — dono da composição entre agentes; a seção de auxiliar aponta
|
|
224
|
+
para lá em vez de duplicá-lo
|
|
225
|
+
- `ai-agents-agent-spec` — o agente como dado, e a regra "nulo = não envia"
|
|
226
|
+
- `ai-agents-observability-patterns` — a rastreabilidade que a invariante do compositor torna
|
|
227
|
+
confiável
|
|
228
|
+
- `ai-agents-cost-and-budget` — o custo da segunda chamada, que é metade do critério de saída
|
|
229
|
+
- `ai-agents-testing-ai` — como exercitar compositor, allow-list e teto sem rede
|
|
230
|
+
|
|
231
|
+
> **Lacuna declarada, não escondida.** O par compilável deste standard seria um
|
|
232
|
+
> `TurnPlanComposer` em `templates/dotnet/ai-kit/src/Morph.AiKit/Agents/`, com o teste de
|
|
233
|
+
> concatenação sem cache em `tests/Morph.AiKit.Tests/`. **Ele ainda não existe** — nem no ai-kit
|
|
234
|
+
> atual nem no plano que o construiu. Por isso o cabeçalho deste arquivo declara verificação
|
|
235
|
+
> documental e **não** carimba `provado por`: prova inexistente é pior que prova ausente.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/conversational-agent-with-phases.md v1.0 (2026-09-08)*
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Cost and Budget — tarifa, cálculo e teto de gasto de um agente
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 1 (on-keyword)
|
|
5
|
+
> **Keywords:** custo de llm, cost middleware, UsageCostMiddleware, teto de custo, budget middleware, tarifa por token, cost_usd, orçamento do agente
|
|
6
|
+
> **Read by Claude in:** implement (ao registrar custo ou impor teto) e troubleshoot (fatura maior que o esperado, custo nulo no relatório)
|
|
7
|
+
|
|
8
|
+
**Verified against:** Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs`, `.../Middleware/UsageCostRecord.cs` e `.../Providers/ModelAlias.cs` — o middleware, o record de custo não-nulo e a conta de tarifa são o que o kit compila. A ausência de qualquer propriedade de preço em `UsageDetails` foi **medida por reflexão sobre `Microsoft.Extensions.AI.Abstractions` 10.9.0** em 2026-09-08. O teto de orçamento é **verificação documental** — o ai-kit não traz um middleware de orçamento. Last-verified: 2026-09-08.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Tarifa no registry
|
|
13
|
+
|
|
14
|
+
**O SDK dá tokens e não dá dinheiro.** Medido por reflexão sobre
|
|
15
|
+
`Microsoft.Extensions.AI.Abstractions` 10.9.0 em 2026-09-08, `UsageDetails` expõe:
|
|
16
|
+
|
|
17
|
+
`InputTokenCount` · `OutputTokenCount` · `TotalTokenCount` · `CachedInputTokenCount` ·
|
|
18
|
+
`ReasoningTokenCount` · os pares de texto e áudio (`InputTextTokenCount`, `InputAudioTokenCount`,
|
|
19
|
+
`OutputTextTokenCount`, `OutputAudioTokenCount`) · `AdditionalCounts` · e o método
|
|
20
|
+
`Add(UsageDetails)`.
|
|
21
|
+
|
|
22
|
+
**Nenhuma propriedade de preço.** Nem tarifa, nem moeda, nem custo. Isso não é lacuna do pacote: é a
|
|
23
|
+
divisão certa. A tarifa muda por contrato, por região e por semana; um SDK que a embutisse estaria
|
|
24
|
+
errado no dia seguinte.
|
|
25
|
+
|
|
26
|
+
**Logo a tarifa é do projeto, e mora por alias no registry** — o mesmo lugar onde já moram provider,
|
|
27
|
+
modelo e protocolo. A forma que o kit compila:
|
|
28
|
+
|
|
29
|
+
| Campo | Significado |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `InputPerMTok` | USD por 1M de tokens de entrada |
|
|
32
|
+
| `OutputPerMTok` | USD por 1M de tokens de saída |
|
|
33
|
+
| `CachedInputPerMTok` | USD por 1M de tokens de entrada servidos do cache. **Nulo = não há desconto declarado**, e então token cacheado é cobrado como entrada normal. Nulo **nunca** significa "de graça" |
|
|
34
|
+
|
|
35
|
+
**E a tarifa precisa de data — que NÃO cabe no `pricing`.** Tarifa envelhece, e um preço sem data é
|
|
36
|
+
um número que ninguém sabe se ainda vale. Mas o exemplar estampado acima **recusa** um campo de data:
|
|
37
|
+
medido em 2026-09-08, `ModelRegistry` valida o objeto `pricing` contra uma allowlist fechada de
|
|
38
|
+
exatamente três chaves (`inputPerMTok`, `outputPerMTok`, `cachedInputPerMTok`) e chama
|
|
39
|
+
`RejectUnknownProperties` — qualquer quarta chave **derruba a carga do registry**. A allowlist do
|
|
40
|
+
alias também não tem chave de data.
|
|
41
|
+
|
|
42
|
+
Onde a data mora, então:
|
|
43
|
+
|
|
44
|
+
| Opção | Custo |
|
|
45
|
+
|---|---|
|
|
46
|
+
| **Comentário JSONC no registry**, ao lado do bloco `pricing` | Zero: o parser do exemplar já roda com `JsonCommentHandling.Skip`, então o comentário é lido e descartado sem erro. É a opção default |
|
|
47
|
+
| Campo novo na allowlist (`verifiedAt`) | Muda `.cs` — decisão de projeto, não deste standard |
|
|
48
|
+
| Arquivo de procedência separado, versionado ao lado do registry | Serve quando a tarifa vem de contrato e precisa de histórico |
|
|
49
|
+
|
|
50
|
+
**Nunca** escreva a data como uma quarta chave dentro de `pricing` sem antes abrir a allowlist: o
|
|
51
|
+
registry para de carregar, e o sintoma (falha na inicialização) não aponta para a tarifa.
|
|
52
|
+
|
|
53
|
+
**Alias sem tarifa precisa dizer por quê.** O registry do kit **recusa a carga** de um alias que não
|
|
54
|
+
declare nem tarifa nem uma dispensa escrita (`pricingWaiver`) — modelo local, por exemplo, onde a
|
|
55
|
+
conta é a máquina e não o token. O esquecimento deixou de ser um caminho possível: ou há tarifa, ou
|
|
56
|
+
há motivo.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## O middleware de custo
|
|
61
|
+
|
|
62
|
+
**Um middleware, não uma fórmula copiada.**
|
|
63
|
+
|
|
64
|
+
A dor, literal e contada: num projeto do acervo a **mesma expressão de custo** aparece em **cinco
|
|
65
|
+
call-sites, distribuídos em quatro arquivos** (um dos arquivos hospeda dois agentes). E o dado já
|
|
66
|
+
era único — o diagnóstico do próprio projeto registra: *"o SDK expõe as contagens de token, mas não
|
|
67
|
+
preços; a duplicação é do cálculo, não do dado"*.
|
|
68
|
+
|
|
69
|
+
Duas cópias da mesma conta divergem. A divergência aparece na fatura, que é o pior lugar para
|
|
70
|
+
descobri-la.
|
|
71
|
+
|
|
72
|
+
A forma: um `DelegatingChatClient` que lê o `Usage` da resposta, chama **a** conta (a que já existe,
|
|
73
|
+
no tipo de tarifa) e publica **uma linha por chamada** num sink de custo. O middleware não contém
|
|
74
|
+
aritmética de tarifa — ele converte, chama e carimba.
|
|
75
|
+
|
|
76
|
+
### Onde ele entra no pipeline, e por que a ordem não é estética
|
|
77
|
+
|
|
78
|
+
O encaixe de custo fica **acima** da invocação de funções, com a telemetria de chat **abaixo** dela:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
usage-cost -> function-invocation -> OpenTelemetry -> cliente do provider
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
O motivo é medido, não argumentado. **Acima** do cliente que invoca funções, o middleware vê **uma**
|
|
85
|
+
resposta cujo `Usage` já traz o total de todas as voltas de tool, e publica **uma** linha por
|
|
86
|
+
chamada. **Abaixo** dele, veria as respostas parciais — e leria o mesmo objeto de uso que o próprio
|
|
87
|
+
cliente de invocação **muta** para acumular, o que faria a mesma conta dar resultado diferente
|
|
88
|
+
conforme a hora de ler.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Armadilhas de fórmula
|
|
93
|
+
|
|
94
|
+
Três formas de errar a conta com todos os números certos:
|
|
95
|
+
|
|
96
|
+
**(1) `ReasoningTokenCount` é cobrado como saída.** Uma fórmula que soma apenas entrada + saída sem
|
|
97
|
+
conferir **como aquele provider bilha o raciocínio** subestima o custo de todo modelo de
|
|
98
|
+
raciocínio — exatamente os mais caros.
|
|
99
|
+
|
|
100
|
+
**(2) `CachedInputTokenCount` tem tarifa própria, e ignorá-lo superestima.** O detalhe que derruba
|
|
101
|
+
implementações: **o cacheado é subconjunto da entrada**, não uma parcela a mais. Somar entrada +
|
|
102
|
+
cacheado conta o mesmo token duas vezes. A conta correta separa a parte cacheada da parte cheia:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
custo = (entrada - cacheado) * tarifaEntrada
|
|
106
|
+
+ cacheado * (tarifaCache ?? tarifaEntrada)
|
|
107
|
+
+ saida * tarifaSaida
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
E quando não há tarifa de cache declarada, **toda** a entrada é cobrada pela tarifa de entrada —
|
|
111
|
+
nunca de graça.
|
|
112
|
+
|
|
113
|
+
**(3) Taxa fixa por operação hospedada não aparece em `UsageDetails`.** Busca na web, interpretador
|
|
114
|
+
de código e afins são cobrados por **operação**, não por token. Precedente de campo: um campo
|
|
115
|
+
dedicado de custo de busca no registro de alias, com vinte linhas de comentário explicando que
|
|
116
|
+
ignorá-lo subestimaria o teto. Se o agente usa uma dessas, a conta por token está estruturalmente
|
|
117
|
+
incompleta, e o standard exige que isso seja declarado em vez de descoberto.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## cost_usd nunca nulo
|
|
122
|
+
|
|
123
|
+
**`cost_usd` nunca é nulo.** Alias sem tarifa registra tokens e custo **zero com a ausência
|
|
124
|
+
sinalizada** — nunca `null`, nunca um número inventado.
|
|
125
|
+
|
|
126
|
+
Um custo nulo atravessa três camadas e vira um buraco no relatório, e ninguém consegue distinguir
|
|
127
|
+
"não custou" de "não medimos". A distinção precisa viajar **junto** com o número, num estado
|
|
128
|
+
explícito. Os três estados que o kit compila:
|
|
129
|
+
|
|
130
|
+
| Estado | Significado | Pode somar numa fatura? |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `Computed` | Tarifa do alias aplicada sobre uso reportado. O único em que o valor é **medição** | Sim, sem ressalva |
|
|
133
|
+
| `Waived` | Alias sem tarifa **com dispensa escrita**. Zero por decisão registrada, e o motivo viaja junto | Sim, ciente de que é zero por decisão |
|
|
134
|
+
| `UsageMissing` | O provider não reportou uso. Os tokens são desconhecidos, logo o custo também: o zero é **ausência de medição**, não gratuidade | Só com a ressalva explícita |
|
|
135
|
+
|
|
136
|
+
**Precedente bom, do acervo:** um projeto trata uso nulo como zero com o comentário *"0 é o fallback
|
|
137
|
+
honesto (custo subestimado, nunca inventado)"* — e persiste custo em **todos** os caminhos, inclusive
|
|
138
|
+
nos de falha: um agente que devolve "não encontrado" devolve também *"o custo já pago visível"*.
|
|
139
|
+
|
|
140
|
+
**Precedente ruim, e é regressão viva:** outro projeto grava `cost_usd = null` **hoje, em
|
|
141
|
+
produção**, porque a tabela de preços sai vazia da configuração. O código está correto; a
|
|
142
|
+
configuração está vazia; e o resultado é que o produto não sabe quanto gasta.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Teto de orçamento
|
|
147
|
+
|
|
148
|
+
**A unidade é parâmetro.** Por lead, por tenant, por operação, por dia — o standard não inventa uma
|
|
149
|
+
"unidade de negócio" que nenhum projeto do acervo tem. O que ele fixa é a **forma** do teto.
|
|
150
|
+
|
|
151
|
+
O acervo tem exatamente **um** teto duro (um limite de custo por lead) e ele revela uma dor de
|
|
152
|
+
**lugar**, não de unidade: o teto mora **dentro de uma tool**, e por isso não vale para o pipeline
|
|
153
|
+
inteiro. Tudo o que a tool não executa passa por fora dele. Os demais "tetos" do acervo são
|
|
154
|
+
instrução textual ao modelo, chamados **no próprio código** de *"freio soft"* — o que é uma
|
|
155
|
+
descrição honesta: o modelo não é um mecanismo de controle de gasto.
|
|
156
|
+
|
|
157
|
+
**As quatro regras:**
|
|
158
|
+
|
|
159
|
+
1. **O teto mora no pipeline, não dentro de uma tool.** Um teto que só uma tool respeita mede uma
|
|
160
|
+
fração do gasto.
|
|
161
|
+
2. **Alerta antes de bloquear.** Um limiar de aviso, com a unidade e o valor acumulado, dá ao
|
|
162
|
+
operador a chance de agir; um bloqueio sem aviso prévio aparece como incidente.
|
|
163
|
+
3. **O bloqueio devolve ao modelo uma frase acionável** — *"orçamento esgotado, conclua com o que
|
|
164
|
+
você já tem"* — e **nunca uma exceção**. Uma exceção no meio de um turno perde o trabalho já
|
|
165
|
+
pago; uma frase permite fechar a conversa com o que existe.
|
|
166
|
+
4. **A unidade e o valor são configuração**, e o valor efetivo aparece no registro do turno, para
|
|
167
|
+
que "por que bloqueou" tenha resposta.
|
|
168
|
+
|
|
169
|
+
> **Lacuna declarada.** O par compilável desta seção seria um `BudgetMiddleware` em
|
|
170
|
+
> `templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/`. **Ele não existe** — nem no ai-kit atual
|
|
171
|
+
> nem no plano que o construiu. Por isso o cabeçalho declara esta parte como documental.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Medir o total, não o piso
|
|
176
|
+
|
|
177
|
+
**Um middleware no pipeline de chat mede o que passa pelo pipeline. O que não passa por ele precisa
|
|
178
|
+
ser declarado.**
|
|
179
|
+
|
|
180
|
+
O caso medido, e ele é o alerta inteiro: um projeto declara o contrato de *"no máximo 2 chamadas de
|
|
181
|
+
LLM por turno"* — e a própria documentação do projeto reconhece que essa medição é **piso, não
|
|
182
|
+
total**. Não são contadas: transcrição de áudio, visão (uma chamada por imagem e **uma por página**
|
|
183
|
+
de PDF), embeddings de memória, embeddings de base de conhecimento, análise de memória e
|
|
184
|
+
sumarização. Um turno com um PDF de três páginas chega a **cerca de nove** chamadas ao provider.
|
|
185
|
+
|
|
186
|
+
A regra: faça o inventário das chamadas ao provider que **não** passam pelo pipeline instrumentado,
|
|
187
|
+
e escreva-o. Um número que mede metade do gasto e se apresenta como total é pior que nenhum número,
|
|
188
|
+
porque produz confiança.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Fronteira com observability-patterns
|
|
193
|
+
|
|
194
|
+
`ai-agents-observability-patterns` é o **dono canônico** de OpenTelemetry: setup, atributos de span,
|
|
195
|
+
os nomes `gen_ai.*`, enrichment e a tabela de monitoramento. Ele continua sendo o lugar de "como
|
|
196
|
+
instrumentar".
|
|
197
|
+
|
|
198
|
+
**Este** standard é dono de: a **tarifa** (onde mora, com que forma, com que data), o **cálculo**
|
|
199
|
+
(a conta, e as três armadilhas), o **estado** do custo (`Computed`/`Waived`/`UsageMissing`) e o
|
|
200
|
+
**teto** de gasto.
|
|
201
|
+
|
|
202
|
+
A delegação é **de mão dupla** e está escrita nos dois arquivos: quem chega pelo lado da
|
|
203
|
+
observabilidade procurando tarifa é mandado para cá; quem chega aqui procurando atributo de span é
|
|
204
|
+
mandado para lá. Nenhum dos dois duplica o outro.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Anti-patterns
|
|
209
|
+
|
|
210
|
+
| Anti-pattern | Por quê é problema | Solução |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| Fórmula de custo copiada em cada agente | Duas cópias divergem, e a divergência aparece na fatura | Uma conta, num tipo; um middleware que a chama |
|
|
213
|
+
| Tarifa embutida no código | Muda por contrato e por semana; vira literal errado | Tarifa por alias no registry |
|
|
214
|
+
| Tarifa sem data de verificação | Ninguém sabe se o preço ainda vale | Data ao lado do valor — em comentário JSONC, não como quarta chave do `pricing` |
|
|
215
|
+
| Alias sem tarifa e sem motivo escrito | O custo some em silêncio | Ou tarifa, ou dispensa escrita — o registry recusa o resto |
|
|
216
|
+
| `cost_usd` nulo | Buraco no relatório; "não custou" e "não medimos" viram a mesma coisa | Zero com estado explícito |
|
|
217
|
+
| Zero anônimo (sem estado) | Some a diferença entre dispensa e ausência de medição | `Computed` / `Waived` / `UsageMissing` |
|
|
218
|
+
| Somar entrada + cacheado | Conta o mesmo token duas vezes: o cacheado é subconjunto | Separe a parte cheia da cacheada |
|
|
219
|
+
| Ignorar `ReasoningTokenCount` | Subestima justamente os modelos mais caros | Confira como o provider bilha o raciocínio |
|
|
220
|
+
| Ignorar taxa fixa de operação hospedada | A conta por token é estruturalmente incompleta | Declare a taxa, ou declare a incompletude |
|
|
221
|
+
| Teto dentro de uma tool | Mede uma fração do gasto do pipeline | Teto no pipeline |
|
|
222
|
+
| Teto como instrução textual ao modelo | O modelo não é mecanismo de controle de gasto | Código, com alerta antes do bloqueio |
|
|
223
|
+
| Bloqueio por exceção | Perde o trabalho já pago no meio do turno | Frase acionável de volta ao modelo |
|
|
224
|
+
| Contar só o que passa pelo pipeline e chamar de total | Produz confiança num número que mede metade | Inventário escrito do que não passa |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Checklist (verifiable by morph-eval)
|
|
229
|
+
|
|
230
|
+
- [ ] A conta de custo existe em **um** lugar; nenhum agente a repete
|
|
231
|
+
- [ ] A tarifa mora por alias no registry, e a **data** dela está registrada FORA do objeto `pricing` (comentário JSONC, campo próprio na allowlist, ou arquivo de procedência) — a allowlist do exemplar tem exatamente 3 chaves e recusa uma quarta
|
|
232
|
+
- [ ] Nenhum alias sem tarifa e sem motivo escrito
|
|
233
|
+
- [ ] O middleware de custo está **acima** da invocação de funções no pipeline
|
|
234
|
+
- [ ] `cost_usd` nunca é nulo, e cada linha carrega o estado (`Computed`/`Waived`/`UsageMissing`)
|
|
235
|
+
- [ ] A fórmula trata o cacheado como subconjunto da entrada
|
|
236
|
+
- [ ] `ReasoningTokenCount` e taxas fixas de operação hospedada estão tratados ou declarados
|
|
237
|
+
- [ ] Custo é persistido também nos caminhos de falha
|
|
238
|
+
- [ ] Se há teto: ele está no pipeline, alerta antes de bloquear, e o bloqueio devolve frase, não exceção
|
|
239
|
+
- [ ] Existe inventário escrito das chamadas ao provider que **não** passam pelo pipeline instrumentado
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## References
|
|
244
|
+
|
|
245
|
+
- `ai-agents-observability-patterns` — dono de OpenTelemetry, dos atributos `gen_ai.*` e da tabela
|
|
246
|
+
de monitoramento; a delegação de tarifa e teto é recíproca
|
|
247
|
+
- `ai-agents-providers-model-registry` — Layer 0: o schema do documento onde a tarifa mora
|
|
248
|
+
- `ai-agents-llm-runtime-defaults` — timeout, retry e o pin de SDK; a segunda chamada paga cujo custo
|
|
249
|
+
entra no critério de saída de `ai-agents-conversational-agent-with-phases`
|
|
250
|
+
- `ai-agents-middleware-patterns` — o **mecanismo** de middleware de chat; este standard usa, não repete
|
|
251
|
+
- `ai-agents-service-tier-flex` — reduzir tarifa em trabalho assíncrono
|
|
252
|
+
- `ai-agents-batching` — reduzir tarifa processando N prompts em lote
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/cost-and-budget.md v1.0 (2026-09-08)*
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> **Scope:** stacks=["dotnet"]
|
|
4
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`)
|
|
5
|
+
> **Keywords:** durable workflow, checkpoint, checkpointing, human in the loop, hitl, requestport, requestinfoevent, resume workflow, long running workflow, approval workflow, icheckpointstore, filesystemjsoncheckpointstore, tool approval, ApprovalRequiredAIFunction, ToolApprovalAgent
|
|
6
|
+
> **Read by Claude in:** plan (when `ai-agents-workflows` identifies a genuine HITL or long-running need, **or** when a single agent has an irreversible tool) + implement (wiring `RequestPort` / `CheckpointManager` / `ApprovalRequiredAIFunction`)
|
|
7
7
|
|
|
8
|
-
**Verified against:** Microsoft.Agents.AI 1.0
|
|
8
|
+
**Verified against:** Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); `Microsoft.Agents.AI.Workflows` 1.20.0 e `Microsoft.Agents.AI.DurableTask` `1.16.0-preview.260730.1` **não estão no pin** — versões apuradas no nuget.org em 2026-09-08. **Verificação documental**, sem cláusula `provado por`: o ai-kit não exercita workflows nem aprovação de tool. Os nomes de tipo e membros de aprovação foram **medidos por reflexão sobre as assemblies do pin** em 2026-09-08. Last-verified: 2026-09-08.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -64,6 +64,87 @@ await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
67
|
+
## Aprovação de tool SEM workflow — o caso do agente único
|
|
68
|
+
|
|
69
|
+
**A fronteira, primeiro.** Aprovação de tool e `RequestPort` não são o mesmo mecanismo, e usar o segundo onde bastava o primeiro é montar um grafo para pausar uma chamada:
|
|
70
|
+
|
|
71
|
+
| Situação | Mecanismo |
|
|
72
|
+
|---|---|
|
|
73
|
+
| **Um agente**, uma tool de efeito irreversível (cobrar, enviar proposta, apagar) | `ApprovalRequiredAIFunction` — **sem workflow nenhum** |
|
|
74
|
+
| Um grafo com etapa humana entre executores, ou espera de dias | `RequestPort` + `CheckpointManager` |
|
|
75
|
+
|
|
76
|
+
O caso do agente único é o que a casa realmente tem (MORPH_OS: billing, envio de proposta) e é o que faltava neste standard.
|
|
77
|
+
|
|
78
|
+
### O ciclo, medido em `Microsoft.Extensions.AI` 10.9.0
|
|
79
|
+
|
|
80
|
+
```csharp
|
|
81
|
+
// 1. Embrulhe a tool. ApprovalRequiredAIFunction : DelegatingAIFunction.
|
|
82
|
+
AIFunction charge = AIFunctionFactory.Create(BillingTools.ChargeCustomer);
|
|
83
|
+
AITool guarded = new ApprovalRequiredAIFunction(charge);
|
|
84
|
+
|
|
85
|
+
AIAgent agent = registry.GetChatClient("text-default")
|
|
86
|
+
.AsAIAgent(instructions: "…", tools: [guarded]);
|
|
87
|
+
|
|
88
|
+
AgentSession session = await agent.CreateSessionAsync();
|
|
89
|
+
AgentResponse response = await agent.RunAsync("Cobre o cliente 42.", session);
|
|
90
|
+
|
|
91
|
+
// 2. O run TERMINA pedindo aprovação — não bloqueia, não espera.
|
|
92
|
+
// ToolApprovalRequestContent.ToolCall é um ToolCallContent (medido; NÃO é
|
|
93
|
+
// FunctionCallContent — este último aparece em ToolAutoApprovalRuleContext).
|
|
94
|
+
var pending = response.Messages
|
|
95
|
+
.SelectMany(m => m.Contents)
|
|
96
|
+
.OfType<ToolApprovalRequestContent>()
|
|
97
|
+
.ToList();
|
|
98
|
+
|
|
99
|
+
// 3. Responda NA MESMA SESSÃO. CreateResponse(bool approved, string? reason).
|
|
100
|
+
if (pending.Count > 0)
|
|
101
|
+
{
|
|
102
|
+
var approvals = pending.Select(r => (AIContent)r.CreateResponse(approved: true, reason: "aprovado por lucas@…"));
|
|
103
|
+
response = await agent.RunAsync(new ChatMessage(ChatRole.User, [.. approvals]), session);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 4. REPITA o passo 2 até não restar pendência. Uma volta pode pedir OUTRA tool.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Os quatro pontos que erram na primeira tentativa:**
|
|
110
|
+
|
|
111
|
+
1. **O run termina, não bloqueia.** Quem espera um `await` que "pausa" escreve um laço que nunca roda.
|
|
112
|
+
2. **A resposta vai na MESMA sessão.** Sessão nova = a aprovação não casa com pedido nenhum.
|
|
113
|
+
3. **Checar pendência depois de *cada* run**, num laço — não uma vez.
|
|
114
|
+
4. **`ToolCall` é `ToolCallContent`.** Tipar como `FunctionCallContent` não compila.
|
|
115
|
+
|
|
116
|
+
### `ToolApprovalAgent` — quando as regras se repetem
|
|
117
|
+
|
|
118
|
+
Estável desde **1.14** (2026-07-21). Envolve o agente e centraliza a fila de pedidos e as regras de "sempre aprovar":
|
|
119
|
+
|
|
120
|
+
```csharp
|
|
121
|
+
AIAgent approving = agent.AsBuilder()
|
|
122
|
+
.UseToolApproval(new ToolApprovalAgentOptions
|
|
123
|
+
{
|
|
124
|
+
AutoApprovalRules = [ctx => ctx.FunctionCallContent.Name is "GetInvoice" or "ListPlans"],
|
|
125
|
+
MaxAutoApprovalIterations = 10,
|
|
126
|
+
})
|
|
127
|
+
.Build();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`ToolAutoApprovalRuleContext` entrega `Agent`, `Session`, `FunctionCallContent`, `RequestMessages` e `RunOptions` — a regra decide com o contexto inteiro, não só com o nome. `ToolApprovalAgent.AllToolsAutoApprovalRule` é o atalho "aprove tudo" (use em teste, não em produção). Para uma aprovação que vale para as próximas chamadas iguais, `ToolApprovalRequestContentExtensions.CreateAlwaysApproveToolResponse(...)` / `CreateAlwaysApproveToolWithArgumentsResponse(...)`.
|
|
131
|
+
|
|
132
|
+
### Mudança de 1.14 que QUEBRA código
|
|
133
|
+
|
|
134
|
+
A partir da 1.14 as respostas de aprovação passaram a ser **vinculadas ao request** — a reconciliação deixou de ser "qualquer resposta serve para qualquer pedido pendente". Código escrito contra a 1.0 precisa ser **revisto**, não apenas recompilado.
|
|
135
|
+
|
|
136
|
+
O rastro disso está na própria API do pin: `ChatClientAgentOptions` expõe `DisableApprovalResponseBinding` (medido em 1.20.0) — uma flag que só existe porque o *binding* passou a ser o comportamento padrão. Ligá-la é voltar ao comportamento antigo, conscientemente.
|
|
137
|
+
|
|
138
|
+
> **Nota de migração (histórico, não API viva):** `FunctionApprovalRequestContent` / `FunctionApprovalResponseContent` → `ToolApprovalRequestContent` / `ToolApprovalResponseContent`, renomeados no RC5 (2026-04-01), antes do GA. Os nomes antigos não existem no pin.
|
|
139
|
+
|
|
140
|
+
### Quando aprovar — e quando aprovação não é a proteção certa
|
|
141
|
+
|
|
142
|
+
**Peça aprovação quando a tool tem:** efeito colateral externo (cobrança, e-mail, contrato, deploy), dado sensível na saída, efeito **irreversível**, ou escopo amplo (apagar em lote, alterar preço).
|
|
143
|
+
|
|
144
|
+
**E quando NÃO serve:** no turno de WhatsApp **o lead não é o aprovador**. Mandar um `ToolApprovalRequestContent` para quem está do outro lado da conversa é pedir ao interessado que autorize a si mesmo. Ali a proteção é **guard determinístico no choke-point** — a tool checa o estado do funil, o limite de valor e a permissão do operador **em código**, antes de agir, e recusa com motivo. Aprovação humana é para quando existe um humano com autoridade **diferente** da de quem falou.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
67
148
|
## Checkpointing — durável sem depender de Azure
|
|
68
149
|
|
|
69
150
|
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).
|
|
@@ -147,6 +228,8 @@ public sealed class PostgresCheckpointStore(NpgsqlDataSource dataSource) : IChec
|
|
|
147
228
|
|
|
148
229
|
## Azure Durable Task extension — escape hatch, not default
|
|
149
230
|
|
|
231
|
+
> **Mudança estrutural na 1.17:** a integração Durable Task / Azure Functions foi **extraída para um repositório separado** — deixou de acompanhar o ciclo de release do `Microsoft.Agents.AI`. Isso se vê no próprio versionamento: em 2026-09-08 a última versão de `Microsoft.Agents.AI.DurableTask` no nuget.org é **`1.16.0-preview.260730.1`**, enquanto o pacote base já está em 1.20.0 — quatro minors atrás, e ainda preview. A conclusão desta seção **não muda**: não adotar por padrão. O motivo ganhou um segundo pé: além do acoplamento com Azure, a extensão agora **não é atualizada junto** com o framework.
|
|
232
|
+
|
|
150
233
|
`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
234
|
|
|
152
235
|
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`.
|
|
@@ -161,6 +244,11 @@ This framework deploys to VPS/Coolify by default and does not adopt Azure as a d
|
|
|
161
244
|
| 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
245
|
| 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
246
|
| 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 |
|
|
247
|
+
| **Montar um `Workflow` só para pausar uma tool de um agente único** | Um grafo inteiro, com checkpoint store, para o que `ApprovalRequiredAIFunction` resolve em duas linhas | `ApprovalRequiredAIFunction` — §Aprovação de tool SEM workflow |
|
|
248
|
+
| **Responder a aprovação numa sessão nova** | A resposta não casa com o pedido; o run recomeça pedindo de novo | Mesma `AgentSession` |
|
|
249
|
+
| **Checar `ToolApprovalRequestContent` uma vez só** | Uma volta aprovada pode pedir OUTRA tool; a segunda pendência passa despercebida | Laço: checar depois de **cada** run até zerar |
|
|
250
|
+
| **Pedir aprovação ao lead no turno de WhatsApp** | O interessado autorizando a si mesmo não é controle nenhum | Guard determinístico no choke-point da tool |
|
|
251
|
+
| Subir de 1.0 para ≥1.14 sem revisar a reconciliação de aprovações | Desde a 1.14 a resposta é **vinculada ao request**; o código antigo casa errado | Revisar; `DisableApprovalResponseBinding` é opt-out consciente, não default |
|
|
164
252
|
|
|
165
253
|
---
|
|
166
254
|
|
|
@@ -171,7 +259,11 @@ This framework deploys to VPS/Coolify by default and does not adopt Azure as a d
|
|
|
171
259
|
- [ ] A `CheckpointManager` is wired whenever the workflow can pause across a process restart — `CreateInMemory()` is acceptable only for tests/demos.
|
|
172
260
|
- [ ] 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
261
|
- [ ] 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
|
|
262
|
+
- [ ] `Microsoft.Agents.AI.DurableTask` is present only if the project already runs on Azure and the choice is recorded in `decisions.md` — ciente de que, desde a 1.17, o pacote vive em repositório separado e ficou para trás do framework.
|
|
263
|
+
- [ ] Toda tool com efeito irreversível está embrulhada em `ApprovalRequiredAIFunction` **ou** tem um guard determinístico documentado — e a escolha entre os dois está escrita.
|
|
264
|
+
- [ ] A resposta de aprovação é enviada na **mesma** `AgentSession`, num laço que checa pendências depois de cada run.
|
|
265
|
+
- [ ] Nenhum `Workflow` foi criado apenas para pausar uma tool de um agente único.
|
|
266
|
+
- [ ] Nenhuma ocorrência de `FunctionApprovalRequestContent` / `FunctionApprovalResponseContent` (nomes pré-GA).
|
|
175
267
|
|
|
176
268
|
---
|
|
177
269
|
|
|
@@ -185,7 +277,8 @@ This framework deploys to VPS/Coolify by default and does not adopt Azure as a d
|
|
|
185
277
|
- Human-in-the-loop (HITL) | Microsoft Learn: https://learn.microsoft.com/en-us/agent-framework/workflows/human-in-the-loop
|
|
186
278
|
- Checkpoints | Microsoft Learn: https://learn.microsoft.com/en-us/agent-framework/workflows/checkpoints
|
|
187
279
|
- Durable Extension | Microsoft Learn: https://learn.microsoft.com/en-us/agent-framework/integrations/durable-extension
|
|
280
|
+
- Release notes 1.14 (2026-07-21) e 1.17 do `microsoft/agent-framework` — graduação do `ToolApprovalAgent` e extração do Durable Task (compilados em `docs/specs/elevacao-9/brutos/research-maf.md:312-331`, lidos 2026-09-07)
|
|
188
281
|
|
|
189
282
|
---
|
|
190
283
|
|
|
191
|
-
*MORPH-SPEC by Polymorphism Tech — ai-agents/durable-workflows-hitl.md
|
|
284
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/durable-workflows-hitl.md v2.0 (2026-09-08)*
|