@polymorphism-tech/morph-spec 8.33.0 → 8.35.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/CLAUDE.md +22 -2
- package/README.md +2 -2
- package/bin/morph-spec.js +43 -5
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +20 -22
- package/framework/agents.json +198 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +238 -10
- 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/README.md +11 -1
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
- package/framework/hooks/claude-code/core/chain.js +27 -7
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-context.js +83 -14
- package/framework/hooks/claude-code/core/worktree-create.js +206 -0
- package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
- package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
- package/framework/hooks/shared/command-tokens.js +23 -0
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/git-paths.js +25 -0
- package/framework/hooks/shared/hook-response.js +17 -0
- package/framework/hooks/shared/morph-cli.js +134 -0
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/rules/infrastructure-standards.md +3 -1
- 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 +28 -7
- 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 +20 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +38 -4
- 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 +1504 -830
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +256 -160
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +217 -193
- package/framework/standards/ai-agents/context-providers.md +261 -188
- 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 +351 -250
- package/framework/standards/ai-agents/mcp-tools.md +250 -197
- 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 +241 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
- package/framework/standards/ai-agents/modalities-vision.md +205 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +260 -231
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +293 -158
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +143 -135
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +280 -249
- package/framework/standards/ai-agents/sweet-spot.md +129 -109
- 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 +90 -0
- package/framework/templates/REGISTRY.json +14 -1
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- 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/infrastructure/docker/docker-compose.template.yml +24 -14
- package/package.json +3 -3
- package/src/commands/e2e/e2e.js +91 -40
- package/src/commands/project/doctor.js +143 -6
- package/src/commands/project/env.js +95 -0
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +274 -53
- 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/delete.js +8 -0
- package/src/commands/state/finish.js +360 -53
- 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/state-manager.js +9 -1
- package/src/dashboard/app/pages/HookInspector.jsx +430 -418
- package/src/dashboard/scanner/hooks-scanner.js +5 -1
- 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/e2e/compose-env.js +55 -14
- package/src/lib/e2e/stack-teardown.js +115 -0
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/architecture-scanner.js +2 -1
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/graph/graphifyignore.js +2 -1
- package/src/lib/installers/effects.js +193 -0
- 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/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +431 -37
- package/src/lib/validators/verify-runner.js +590 -28
- package/src/lib/worktree/dirs.js +32 -0
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +69 -11
- package/src/lib/worktree/worktree-audit.js +70 -3
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/file-copier.js +28 -16
- package/src/utils/hooks-installer.js +75 -29
- package/src/utils/skills-installer.js +194 -187
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Agent Archetypes — as quatro formas de agente, e como escolher uma
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 1 (on-keyword)
|
|
5
|
+
> **Keywords:** agent archetype, arquetipo de agente, qual forma de agente, conversational agent, one-shot agent, batch agent, vision agent, chat with history
|
|
6
|
+
> **Read by Claude in:** plan (escolher a forma do agente antes de quebrar a feature em tasks)
|
|
7
|
+
|
|
8
|
+
**Verified against:** Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs` e `.../Conversation/ConversationStore.cs` (arquétipos **one-shot em batch** e **chat com histórico** — o conversacional com fases NÃO tem lastro no kit, e `ai-agents-conversational-agent-with-phases` declara isso no próprio cabeçalho). O arquétipo visão + structured output é **verificação documental** — o ai-kit não exercita multimodalidade. Last-verified: 2026-09-08.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Como escolher
|
|
13
|
+
|
|
14
|
+
`ai-agents-sweet-spot` decide **se** o caso pede um agente ou um workflow. Este standard entra
|
|
15
|
+
depois, e decide **qual forma** o agente tem. A escolha é de **fase de plano**: ela muda o desenho
|
|
16
|
+
das tasks, não uma linha de implementação.
|
|
17
|
+
|
|
18
|
+
Três perguntas, nesta ordem — a primeira que responder "sim" fecha a escolha:
|
|
19
|
+
|
|
20
|
+
| # | Pergunta | Se sim |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| 1 | O agente atende **um interlocutor ao longo do tempo**, e o que ele pode fazer muda conforme o ponto da conversa? | **Conversacional com fases** — e leia `ai-agents-conversational-agent-with-phases` |
|
|
23
|
+
| 2 | O agente é chamado por um **job**, produz um artefato e morre? | **One-shot em batch** |
|
|
24
|
+
| 3 | A entrada é **imagem ou PDF** e a saída precisa ser um objeto tipado? | **Visão com structured output** |
|
|
25
|
+
|
|
26
|
+
Se a resposta a (1) for sim, a **propriedade do histórico** ainda é uma decisão separada — é o
|
|
27
|
+
quarto arquétipo, e ele não é alternativo ao primeiro (ver `## Lastro de campo por arquétipo`).
|
|
28
|
+
|
|
29
|
+
**Regra que atravessa os quatro:** o agente referencia **alias**, nunca id de modelo
|
|
30
|
+
(`ai-agents-providers-model-registry`), e a forma escolhida não muda isso.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Conversacional com fases
|
|
35
|
+
|
|
36
|
+
Agente **único** que atende uma conversa inteira. As quatro propriedades que definem a forma:
|
|
37
|
+
|
|
38
|
+
- **Fase derivada por turno**, de estado observável, nunca de memória do modelo.
|
|
39
|
+
- **Allow-list de tools por fase** — o agente na fase de triagem não enxerga a tool de fechamento.
|
|
40
|
+
- **Teto de tool calls por turno**, com uma tool isenta quando existe uma ação que sempre pode
|
|
41
|
+
acontecer.
|
|
42
|
+
- **Dono do próprio histórico** — o agente decide o que entra no contexto do turno.
|
|
43
|
+
|
|
44
|
+
Multiplicar agentes por fase é o erro clássico aqui: cada fase vira um agente, e a transição entre
|
|
45
|
+
fases vira orquestração que ninguém pediu. A fase é **dado do turno**, não identidade de agente.
|
|
46
|
+
|
|
47
|
+
O aprofundamento — compositor de turno determinístico, os dois estágios da allow-list, `RequireAny`
|
|
48
|
+
e o teto — está em `ai-agents-conversational-agent-with-phases`, que declara fonte única.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## One-shot em batch
|
|
53
|
+
|
|
54
|
+
Agente **recriado por invocação**. Prompt e alias entram como **parâmetro de método**, nunca como
|
|
55
|
+
estado do objeto; o agente não sobrevive à chamada.
|
|
56
|
+
|
|
57
|
+
- Sempre dentro de um **job assíncrono**, nunca no caminho de uma requisição HTTP síncrona.
|
|
58
|
+
- Saída tipada com `RunAsync<T>` — quem chama um agente de batch quer um objeto, não um parágrafo.
|
|
59
|
+
- Construir agente é barato: recrie por invocação em vez de guardar um vivo e mutá-lo.
|
|
60
|
+
|
|
61
|
+
O ai-kit compila exatamente essa forma: `AgentFactory.Create(AgentSpec)` recebe **a spec inteira
|
|
62
|
+
como argumento** e devolve um `AIAgent` novo, sem estado compartilhado entre chamadas.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Visão com structured output
|
|
67
|
+
|
|
68
|
+
Entrada multimodal (`DataContent`/`UriContent`) + saída tipada na **mesma** chamada. PDF é
|
|
69
|
+
rasterizado e vai **uma chamada por página** — não existe "mandar o PDF inteiro" que não seja isso
|
|
70
|
+
por baixo.
|
|
71
|
+
|
|
72
|
+
> **Este arquétipo tem ZERO exemplares no acervo.** Ele é forma **projetada**, não destilada. Os
|
|
73
|
+
> dois standards que o sustentam existem separadamente (`ai-agents-modalities-vision` e
|
|
74
|
+
> `ai-agents-structured-output`); o que não existe é um caso de campo que combine os dois numa
|
|
75
|
+
> chamada só. Quem for o primeiro a implementá-lo deve tratar as afirmações desta seção como
|
|
76
|
+
> hipótese a verificar, e voltar aqui com a medição.
|
|
77
|
+
|
|
78
|
+
Os anti-padrões que o acervo **já cometeu** — duas meias-formas e uma implementação quebrada:
|
|
79
|
+
|
|
80
|
+
| Meia-forma medida | O que faltou |
|
|
81
|
+
|---|---|
|
|
82
|
+
| Visão **sem** structured — a resposta volta em prosa e alguém a interpreta depois | O parser vira dívida invisível: o formato da prosa é contrato não declarado |
|
|
83
|
+
| Structured **sem** visão — o objeto é tipado, mas a imagem foi descrita antes por outra chamada | Duas chamadas pagas onde uma bastava, e o erro de descrição não é mais rastreável até a origem |
|
|
84
|
+
| Foto entregue como **URL dentro do texto** do prompt, JSON pedido em prosa, e a resposta limpa com strip de code fence | Isto não é o arquétipo: é o que se faz quando ele não é conhecido. `DataContent`/`UriContent` existem para isso, e `ChatResponseFormatJson` para o outro lado |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Chat com histórico
|
|
89
|
+
|
|
90
|
+
A **conversa** é a fonte de verdade, e a projeção papel → `ChatMessage` acontece **num lugar só**.
|
|
91
|
+
|
|
92
|
+
- Carregar e anexar histórico é responsabilidade de um tipo dedicado, não do handler.
|
|
93
|
+
- A projeção (quem é `user`, quem é `assistant`, o que vira `system`) é uma função pura, testável
|
|
94
|
+
sem banco.
|
|
95
|
+
- Duas projeções em dois arquivos é como um histórico começa a divergir de si mesmo.
|
|
96
|
+
|
|
97
|
+
O ai-kit compila esta forma em `ConversationStore` (um `ChatHistoryProvider` sobre a interface
|
|
98
|
+
mínima `IConversationStore` — `LoadAsync`/`AppendAsync`, sem tipo de banco na assinatura).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Lastro de campo por arquétipo
|
|
103
|
+
|
|
104
|
+
Esta seção existe porque um catálogo que finge quatro instâncias iguais envelhece mal. O lastro é
|
|
105
|
+
desigual, e saber **onde** ele é fino é parte da informação:
|
|
106
|
+
|
|
107
|
+
| Arquétipo | Exemplares no acervo | Observação |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| Conversacional com fases | **1**, em produção | Fonte única; o standard dedicado declara isso no próprio corpo |
|
|
110
|
+
| One-shot em batch | **3** | O mais lastreado dos quatro, e por isso o de menor risco |
|
|
111
|
+
| Visão com structured output | **0** | Forma projetada. Existem duas meias-formas, e uma implementação quebrada |
|
|
112
|
+
| Chat com histórico | **1** | E é **o mesmo agente** do primeiro arquétipo |
|
|
113
|
+
|
|
114
|
+
**Consequência de forma, e é a razão desta tabela:** o primeiro e o quarto arquétipos **não são
|
|
115
|
+
independentes**. São dois eixos do mesmo agente — *fases* (o que ele pode fazer agora) × *propriedade
|
|
116
|
+
do histórico* (quem decide o que ele lembra). Um agente conversacional que delega o histórico a um
|
|
117
|
+
framework externo continua sendo o primeiro arquétipo, e perdeu o quarto. Escolher "conversacional"
|
|
118
|
+
não escolhe o histórico; escolher "chat com histórico" não escolhe as fases.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Anti-patterns
|
|
123
|
+
|
|
124
|
+
| Anti-pattern | Por quê é problema | Solução |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| Um agente por fase da conversa | A transição entre fases vira orquestração que ninguém pediu, e o histórico se fragmenta | Um agente, fase como dado do turno |
|
|
127
|
+
| Agente one-shot guardado como singleton e mutado entre chamadas | Estado vaza entre invocações independentes; um prompt de uma execução aparece na seguinte | Recrie por invocação — a construção é barata |
|
|
128
|
+
| Agente de batch no caminho síncrono de uma requisição | Latência de LLM no p99 do endpoint, sem retry e sem visibilidade | Job assíncrono |
|
|
129
|
+
| Imagem descrita numa chamada e tipada noutra | Duas chamadas pagas, e o erro de descrição deixa de ser rastreável | Uma chamada multimodal com `RunAsync<T>` |
|
|
130
|
+
| Projeção papel → `ChatMessage` em dois arquivos | Os dois divergem, e o bug aparece como "o agente esqueceu" | Uma projeção, pura e testável |
|
|
131
|
+
| Escolher o arquétipo na implementação | A forma muda o desenho das tasks; escolhida depois, ela vira refactor | Escolha na fase de plano, e registre em `decisions.md` |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Checklist (verifiable by morph-eval)
|
|
136
|
+
|
|
137
|
+
- [ ] O arquétipo escolhido está **nomeado** no `decisions.md` da feature, com a pergunta de
|
|
138
|
+
`## Como escolher` que o decidiu
|
|
139
|
+
- [ ] Nenhum agente novo por fase de conversa — a fase é dado do turno
|
|
140
|
+
- [ ] Agente one-shot recebe prompt e alias como parâmetro, e não guarda estado entre invocações
|
|
141
|
+
- [ ] Agente de batch roda em job assíncrono
|
|
142
|
+
- [ ] Se há multimodalidade com saída tipada: uma única chamada, com `DataContent`/`UriContent`
|
|
143
|
+
- [ ] A projeção papel → `ChatMessage` existe em exatamente um lugar
|
|
144
|
+
- [ ] O agente referencia alias do registry, nunca id de modelo
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## References
|
|
149
|
+
|
|
150
|
+
- `ai-agents-sweet-spot` — decide **se** é agente ou workflow; este standard só entra depois
|
|
151
|
+
- `ai-agents-conversational-agent-with-phases` — o aprofundamento do primeiro arquétipo
|
|
152
|
+
- `ai-agents-agent-spec` — a forma do agente **como dado**, comum aos quatro
|
|
153
|
+
- `ai-agents-providers-model-registry` — alias, e por que o modelo não aparece na spec
|
|
154
|
+
- `ai-agents-structured-output` — `RunAsync<T>` e o schema da saída
|
|
155
|
+
- `ai-agents-modalities-vision` — a metade multimodal do terceiro arquétipo
|
|
156
|
+
- `ai-agents-agent-session` — histórico e reducers, quando o quarto arquétipo cresce
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/agent-archetypes.md v1.0 (2026-09-08)*
|
|
@@ -1,160 +1,256 @@
|
|
|
1
|
-
# Agent Session —
|
|
2
|
-
|
|
3
|
-
> **Scope:** stacks=["dotnet"]
|
|
4
|
-
> **Layer:** 2 (on-keyword)
|
|
5
|
-
> **Keywords:** agent session, chat history, conversation state, chat reducer, history truncation,
|
|
6
|
-
> **Read by Claude in:** implement
|
|
7
|
-
|
|
8
|
-
**Verified against:**
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
# Agent Session — de onde vem o histórico de conversa
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 2 (on-keyword)
|
|
5
|
+
> **Keywords:** agent session, chat history, conversation state, chat reducer, history truncation, ChatHistoryProvider, conversa própria, multi-tenant
|
|
6
|
+
> **Read by Claude in:** implement
|
|
7
|
+
|
|
8
|
+
**Verified against:** Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs` (a subclasse de `ChatHistoryProvider`, a invariante de instância única e o teste de duas sessões concorrentes). Nomes de tipo e membros medidos por reflexão sobre `Microsoft.Agents.AI.Abstractions` 1.20.0 em 2026-09-08. Last-verified: 2026-09-08.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## O critério — de onde o histórico deve vir
|
|
13
|
+
|
|
14
|
+
**Primeiro o critério, depois o veredito.** Existem três fontes possíveis de histórico e elas não são intercambiáveis:
|
|
15
|
+
|
|
16
|
+
| Situação | Fonte de histórico |
|
|
17
|
+
|---|---|
|
|
18
|
+
| A conversa carrega **campos de negócio** além das mensagens (usage por turno, título, dossiê, takeover humano, estado de funil) | **Conversa própria** — tabela/`jsonb` do projeto, exposta por um `ChatHistoryProvider` seu |
|
|
19
|
+
| O histórico precisa ser **visível e manipulável pela aplicação** (retry, remontagem, auditoria, edição, exclusão a pedido do titular) | **Conversa própria** |
|
|
20
|
+
| Multi-tenant com isolamento por chave composta e retenção sob controle do projeto | **Conversa própria** |
|
|
21
|
+
| O histórico é **só** a lista de mensagens, sem campo próprio, e o projeto não precisa lê-lo fora do agente | `AgentSession` + um `ChatHistoryProvider` embutido |
|
|
22
|
+
| Protótipo, CLI, demo, teste | `InMemoryChatHistoryProvider` |
|
|
23
|
+
|
|
24
|
+
**O veredito da casa: conversa própria como default.** Isso não é preferência — é a aplicação do critério acima aos projetos que existem. GHLBrain, MORPH_OS e ProspectPRO caem todos nas três primeiras linhas.
|
|
25
|
+
|
|
26
|
+
### Os três fundamentos, cada um citável
|
|
27
|
+
|
|
28
|
+
1. **ADR D3 do GHLBrain.** O estado da conversa vive em `conv_checkpoints`; `ToolAgentRunner.cs:126-130` roda *"sem AgentThread/sessão"* — porque o **retry depende de o histórico ser visível e manipulável**: para repetir uma volta que falhou é preciso remontar as mensagens, e não dá para remontar o que está dentro de um objeto opaco. Registrado em `docs/specs/elevacao-9/brutos/scan-ghlbrain.md:58` e `:599`.
|
|
29
|
+
2. **Doc oficial de self-hosting** (learn.microsoft.com/en-us/agent-framework/hosting/self-hosting/, `ms.date` 2026-08-17), verbatim: *"MAF doesn't include a general-purpose durable session store"*. Produção exige implementação própria. A mesma página recomenda, em conversa longa, um `ChatHistoryProvider` externo **com mensagens por linha** em vez de reescrever a sessão inteira a cada turno.
|
|
30
|
+
3. **Padrão do canal** (Rasmus, parte 06 — "Conversations"): objeto de conversa próprio do domínio, com **um** mapper para `ChatMessage`. A conversa é entidade de negócio; o formato do provedor é detalhe de transporte.
|
|
31
|
+
|
|
32
|
+
### Ligação com a Responses API
|
|
33
|
+
|
|
34
|
+
Com `store: true` (o default da Responses API) **o histórico foge para o provedor** e passa a existir em dois lugares. Quem usa conversa própria mantém **`store: false`** — ver `ai-agents-setup` §*`store: false` é a regra da casa*, incluindo o atalho `AsIChatClientWithStoredOutputDisabled`.
|
|
35
|
+
|
|
36
|
+
### Quando NÃO há histórico nenhum
|
|
37
|
+
|
|
38
|
+
- Agente one-shot (gerador, executor, classificador) — recebe uma entrada, devolve uma saída, fim.
|
|
39
|
+
- Agentes stateless em pipeline multi-agent — cada chamada é independente.
|
|
40
|
+
- Jobs de batch/background — não há conversa.
|
|
41
|
+
|
|
42
|
+
> *"Add Memory/Session because agents need state"* é um anti-pattern de `sweet-spot.md`.
|
|
43
|
+
> Só adicione histórico quando o critério acima aponta para ele.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## A API
|
|
48
|
+
|
|
49
|
+
**Pacote:** `Microsoft.Agents.AI` 1.20.0 (base; já requerido por `ai-agents-setup`).
|
|
50
|
+
|
|
51
|
+
**Tipos e membros — medidos por reflexão sobre as assemblies do pin em 2026-09-08:**
|
|
52
|
+
|
|
53
|
+
| Tipo | Onde | O que é |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `AgentSession` | `Microsoft.Agents.AI.Abstractions` | Container de estado da conversa. Abstrato; tem `StateBag`. Criado por `agent.CreateSessionAsync()` |
|
|
56
|
+
| `ChatClientAgentSession` | `Microsoft.Agents.AI` | A `AgentSession` do `ChatClientAgent`; acrescenta `ConversationId` (o id gerenciado pelo serviço, quando há) |
|
|
57
|
+
| `ChatHistoryProvider` | `Microsoft.Agents.AI.Abstractions` | Classe **abstrata** a estender. Pontos de override: `ProvideChatHistoryAsync(InvokingContext, ct)` e `StoreChatHistoryAsync(InvokedContext, ct)` (ambos `protected`). Declare também `StateKeys` |
|
|
58
|
+
| `ProviderSessionState<TState>` | `Microsoft.Agents.AI.Abstractions` | Estado **por sessão**, com `GetOrInitializeState(session)` / `SaveState(session, state)` e um `StateKey` |
|
|
59
|
+
| `InMemoryChatHistoryProvider` | `Microsoft.Agents.AI.Abstractions` | Implementação embutida. `sealed`. Aceita `ChatReducer` e `ReducerTriggerEvent` |
|
|
60
|
+
| `IChatReducer`, `MessageCountingChatReducer`, `SummarizingChatReducer` | `Microsoft.Extensions.AI` | Redutores de histórico |
|
|
61
|
+
| `ChatClientAgentOptions` | `Microsoft.Agents.AI` | Aceita `ChatHistoryProvider`, `AIContextProviders`, e as três flags de conflito (`Throw/Warn/ClearOnChatHistoryProviderConflict`) |
|
|
62
|
+
| `agent.SerializeSessionAsync(session, …)` / `agent.DeserializeSessionAsync(JsonElement, …)` | `AIAgent` | Serialização da sessão. **São `…Async` e devolvem `ValueTask`** — não existe forma síncrona |
|
|
63
|
+
|
|
64
|
+
> **Correções contra a versão anterior deste standard, todas medidas:** `AgentThread` e `GetNewThread()` **não existem** em `Microsoft.Agents.AI` 1.20.0 (renomeados para `AgentSession` / `CreateSessionAsync` ainda no ciclo pré-GA, `preview.260128.1` e `preview.260205.1`); `CosmosChatHistoryProvider` **não está** em `Microsoft.Agents.AI` nem em `Microsoft.Agents.AI.Abstractions` 1.20.0 — se existir, é de outro pacote, e este standard não afirma qual.
|
|
65
|
+
|
|
66
|
+
### A forma de um provider de conversa própria
|
|
67
|
+
|
|
68
|
+
```csharp
|
|
69
|
+
using Microsoft.Agents.AI;
|
|
70
|
+
using Microsoft.Extensions.AI;
|
|
71
|
+
|
|
72
|
+
public sealed class ConversationStore : ChatHistoryProvider
|
|
73
|
+
{
|
|
74
|
+
public const string ConversationIdStateKey = "app.conversation.id";
|
|
75
|
+
|
|
76
|
+
private readonly IConversationStore _store; // o acervo do PROJETO
|
|
77
|
+
private readonly ProviderSessionState<string> _conversationId; // estado POR SESSÃO
|
|
78
|
+
|
|
79
|
+
public ConversationStore(IConversationStore store)
|
|
80
|
+
{
|
|
81
|
+
ArgumentNullException.ThrowIfNull(store);
|
|
82
|
+
_store = store;
|
|
83
|
+
_conversationId = new ProviderSessionState<string>(NewConversationId, ConversationIdStateKey);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Declara a chave que este provider ocupa no estado da sessão — é o que permite
|
|
87
|
+
// ao MAF serializar e retomar uma sessão sem adivinhar o que é de quem.
|
|
88
|
+
public override IReadOnlyList<string> StateKeys => [ConversationIdStateKey];
|
|
89
|
+
|
|
90
|
+
protected override async ValueTask<IEnumerable<ChatMessage>> ProvideChatHistoryAsync(
|
|
91
|
+
InvokingContext context, CancellationToken cancellationToken = default)
|
|
92
|
+
=> await _store.LoadAsync(GetConversationId(context.Session!), cancellationToken);
|
|
93
|
+
|
|
94
|
+
protected override async ValueTask StoreChatHistoryAsync(
|
|
95
|
+
InvokedContext context, CancellationToken cancellationToken = default)
|
|
96
|
+
{
|
|
97
|
+
// Volta que falhou não é gravada: persistir o pedido sozinho deixaria a conversa
|
|
98
|
+
// terminando numa pergunta que o modelo nunca respondeu.
|
|
99
|
+
if (context.InvokeException is not null) return;
|
|
100
|
+
|
|
101
|
+
List<ChatMessage> round = [.. context.RequestMessages ?? [], .. context.ResponseMessages ?? []];
|
|
102
|
+
if (round.Count == 0) return;
|
|
103
|
+
|
|
104
|
+
// Grava SÓ esta volta. Regravar o histórico inteiro a cada turno faz a conversa
|
|
105
|
+
// crescer em progressão geométrica.
|
|
106
|
+
await _store.AppendAsync(GetConversationId(context.Session!), round, cancellationToken);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public string GetConversationId(AgentSession session)
|
|
110
|
+
=> _conversationId.GetOrInitializeState(session);
|
|
111
|
+
|
|
112
|
+
private static string NewConversationId(AgentSession? session)
|
|
113
|
+
=> session is ChatClientAgentSession { ConversationId.Length: > 0 } managed
|
|
114
|
+
? managed
|
|
115
|
+
: Guid.CreateVersion7().ToString("n"); // ordenável no tempo; NewGuid fragmenta índice
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### A invariante de instância — o erro que só aparece em produção
|
|
120
|
+
|
|
121
|
+
**O provider é UMA instância compartilhada por todas as sessões.** Ele não pode ter campo de estado por conversa. O único estado por sessão vive em `ProviderSessionState<TState>`, **dentro da `AgentSession`**.
|
|
122
|
+
|
|
123
|
+
Um campo de instância guardando "a conversa atual" é como o histórico de um usuário aparece na tela de outro — e a falha **não** aparece em teste de uma sessão só: aparece sob concorrência, em produção, como vazamento de dado alheio. O canário é um teste com **duas sessões concorrentes** sobre o **mesmo** provider.
|
|
124
|
+
|
|
125
|
+
### Histórico embutido, quando o critério aponta para ele
|
|
126
|
+
|
|
127
|
+
```csharp
|
|
128
|
+
AIAgent agent = _modelRegistry.GetChatClient("text-default")
|
|
129
|
+
.AsAIAgent(new ChatClientAgentOptions
|
|
130
|
+
{
|
|
131
|
+
ChatOptions = new() { Instructions = "You are a helpful assistant." },
|
|
132
|
+
ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
|
|
133
|
+
{
|
|
134
|
+
ChatReducer = new MessageCountingChatReducer(20),
|
|
135
|
+
ReducerTriggerEvent = ChatReducerTriggerEvent.AfterMessageAdded,
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Exemplo C# — agente conversacional com session persistente entre turnos
|
|
143
|
+
|
|
144
|
+
```csharp
|
|
145
|
+
using Microsoft.Agents.AI;
|
|
146
|
+
using Microsoft.Extensions.AI;
|
|
147
|
+
|
|
148
|
+
// ── 1. Registrar o agente conversacional no DI ──
|
|
149
|
+
// Program.cs
|
|
150
|
+
builder.Services.AddSingleton<IConversationStore, PostgresConversationStore>();
|
|
151
|
+
builder.Services.AddSingleton<ChatHistoryProvider>(sp =>
|
|
152
|
+
new ConversationStore(sp.GetRequiredService<IConversationStore>())); // UMA instância
|
|
153
|
+
|
|
154
|
+
builder.Services.AddSingleton<AIAgent>(sp =>
|
|
155
|
+
{
|
|
156
|
+
var registry = sp.GetRequiredService<ModelRegistry>();
|
|
157
|
+
return registry.GetChatClient("text-default")
|
|
158
|
+
.AsAIAgent(new ChatClientAgentOptions
|
|
159
|
+
{
|
|
160
|
+
ChatOptions = new() { Instructions = "You are a helpful customer support assistant." },
|
|
161
|
+
ChatHistoryProvider = sp.GetRequiredService<ChatHistoryProvider>(),
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ── 2. Uma session por conversa (por conexão SignalR, por "start chat") ──
|
|
166
|
+
public class ChatService(AIAgent _agent, ConversationStore _history)
|
|
167
|
+
{
|
|
168
|
+
public async Task<AgentSession> StartConversationAsync(string conversationId)
|
|
169
|
+
{
|
|
170
|
+
var session = await _agent.CreateSessionAsync();
|
|
171
|
+
_history.BindConversation(session, conversationId); // a conversa JÁ EXISTE como registro
|
|
172
|
+
return session;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public async Task<string> SendMessageAsync(AgentSession session, string userMessage)
|
|
176
|
+
{
|
|
177
|
+
AgentResponse response = await _agent.RunAsync(userMessage, session);
|
|
178
|
+
return response.Text;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Retomar depois de um restart: a sessão é dado, não objeto vivo.
|
|
182
|
+
public async Task<AgentSession> ResumeAsync(JsonElement serialized)
|
|
183
|
+
=> await _agent.DeserializeSessionAsync(serialized);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── 3. Uso ──
|
|
187
|
+
AgentSession session = await chatService.StartConversationAsync(conversation.Id);
|
|
188
|
+
string reply1 = await chatService.SendMessageAsync(session, "Meu pedido #1234 está atrasado.");
|
|
189
|
+
string reply2 = await chatService.SendMessageAsync(session, "E quando foi despachado?");
|
|
190
|
+
// O contexto do segundo turno veio do ACERVO DO PROJETO — legível por relatório, por auditoria,
|
|
191
|
+
// e apagável a pedido do titular.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## History management
|
|
197
|
+
|
|
198
|
+
O histórico cresce a cada turno. Quando estoura o context window do modelo, as chamadas falham ou truncam silenciosamente.
|
|
199
|
+
|
|
200
|
+
| Estratégia | Quando usar | Como implementar |
|
|
201
|
+
|------------|-------------|------------------|
|
|
202
|
+
| **Sliding window** | Conversas longas com contexto recente suficiente | `MessageCountingChatReducer(n)` no `InMemoryChatHistoryProvider`, ou um `LIMIT` na query do provider próprio |
|
|
203
|
+
| **Token budget** | Modelos com context window pequeno | Contar tokens acumulados no provider próprio e truncar |
|
|
204
|
+
| **Summarization** | Histórico precisa de contexto antigo comprimido | `SummarizingChatReducer`, ou compaction (`ai-agents-context-providers` §Compaction) |
|
|
205
|
+
|
|
206
|
+
**Com conversa própria, a truncation é query, não middleware.** `ProvideChatHistoryAsync` devolve o que a query devolveu — `ORDER BY created_at DESC LIMIT n` já é o sliding window, e é auditável no banco. `IChatReducer` existe para o caminho embutido.
|
|
207
|
+
|
|
208
|
+
**Grave só a volta corrente.** `StoreChatHistoryAsync` recebe `RequestMessages` + `ResponseMessages` **daquela** volta; regravar o histórico completo a cada turno é crescimento geométrico do acervo.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Anti-patterns
|
|
213
|
+
|
|
214
|
+
| Anti-pattern | Problema |
|
|
215
|
+
|-------------|----------|
|
|
216
|
+
| **Estado por sessão em campo do provider** | O provider é **uma** instância para todas as sessões. Um campo `_currentConversation` faz o histórico de um usuário aparecer para outro. Estado por sessão vive em `ProviderSessionState<TState>`, com `StateKey` declarado em `StateKeys`. A falha não aparece em teste de uma sessão só |
|
|
217
|
+
| **`ConversationId` / `previous_response_id` como fronteira de autorização** | Esses ids são escopados à **chave da API**, não ao usuário: quem tem o id e a chave lê a conversa. Mapeie o id do cliente para o id do serviço e **verifique o dono** antes de retomar |
|
|
218
|
+
| Adicionar histórico em agente one-shot (gerador, executor) | Complexidade desnecessária; estado nunca é usado |
|
|
219
|
+
| Histórico ilimitado sem truncation | Context window estoura; custo aumenta linearmente com a conversa |
|
|
220
|
+
| Compartilhar uma `AgentSession` entre múltiplos usuários | Histórico de um usuário vaza para outro |
|
|
221
|
+
| Usar `InMemoryChatHistoryProvider` sem criar uma `AgentSession` por conversa | A isolation depende de `AgentSession` — sempre uma session por conversa, via `agent.CreateSessionAsync()` |
|
|
222
|
+
| Gravar a volta que falhou | Sem resposta, a conversa termina numa pergunta não respondida; a volta seguinte a repete e o usuário se vê duplicado |
|
|
223
|
+
| Deixar `store` ligado tendo histórico próprio | Duas cópias da conversa, uma delas fora do seu controle de retenção |
|
|
224
|
+
|
|
225
|
+
> **Nota de migração (histórico, não API viva):** `AgentThread` → `AgentSession` e `GetNewThread()` → `CreateSessionAsync()` aconteceram **antes** do GA de 2026-04-02. Código escrito contra os nomes antigos não compila com o pin. Não são alternativas: são nomes mortos.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Checklist (verifiable by morph-eval)
|
|
230
|
+
|
|
231
|
+
- [ ] A escolha da fonte de histórico foi feita pela tabela de critério — e está escrita em `decisions.md` quando não foi a conversa própria
|
|
232
|
+
- [ ] O provider de histórico é registrado como **uma** instância compartilhada
|
|
233
|
+
- [ ] Nenhum campo de instância do provider guarda estado de conversa; o que é por sessão está em `ProviderSessionState<TState>` e declarado em `StateKeys`
|
|
234
|
+
- [ ] Existe teste com **duas sessões concorrentes** sobre o mesmo provider
|
|
235
|
+
- [ ] Uma `AgentSession` por conversa/usuário — nunca compartilhada
|
|
236
|
+
- [ ] `ConversationId` nunca é usado como prova de autorização; o dono é verificado antes de retomar
|
|
237
|
+
- [ ] `store: false` quando o histórico é próprio
|
|
238
|
+
- [ ] Estratégia de truncation definida (query com `LIMIT`, reducer ou summarization)
|
|
239
|
+
- [ ] Agentes one-shot (gerador, executor, classificador) **não** recebem histórico
|
|
240
|
+
- [ ] Nenhuma ocorrência de `AgentThread` / `GetNewThread()` no código
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## References
|
|
245
|
+
|
|
246
|
+
- `ai-agents-setup` — packages, DI base, `store: false`
|
|
247
|
+
- `ai-agents-sweet-spot` — decisão de quando adicionar estado ao agente
|
|
248
|
+
- `ai-agents-context-providers` — a mesma invariante de instância, e compaction
|
|
249
|
+
- `ai-agents-middleware-patterns` — pipeline e middleware
|
|
250
|
+
- Reference implementation (compila contra o pin): `templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs`
|
|
251
|
+
- MAF Conversations / storage: https://learn.microsoft.com/en-us/agent-framework/agents/conversations/storage (`ms.date` 2026-07-01)
|
|
252
|
+
- MAF self-hosting: https://learn.microsoft.com/en-us/agent-framework/hosting/self-hosting/ (`ms.date` 2026-08-17)
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/agent-session.md v2.0 (2026-09-08)*
|