@polymorphism-tech/morph-spec 8.2.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -99
- package/bin/morph-spec.js +13 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +76 -135
- package/docs/QUICKSTART.md +35 -51
- package/framework/CLAUDE.md +3 -2
- package/framework/MORPH.md +20 -2
- package/framework/agents.json +3 -1
- package/framework/hooks/README.md +3 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +102 -0
- package/framework/hooks/shared/build-check.js +8 -1
- package/framework/hooks/shared/payload-utils.js +16 -2
- package/framework/hooks/shared/phase-utils.js +2 -0
- package/framework/hooks/shared/telemetry-logger.js +75 -0
- package/framework/rules/ai-agents-standards.md +1 -0
- package/framework/schemas/feature.schema.json +0 -5
- 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 +12 -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 +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +22 -10
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +19 -12
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +43 -1
- package/framework/standards/ai-agents/observability-patterns.md +156 -0
- package/framework/standards/ai-agents/production.md +3 -47
- package/framework/templates/REGISTRY.json +0 -93
- package/package.json +1 -1
- package/src/commands/observability/telemetry.js +75 -0
- package/src/commands/project/worktree.js +150 -36
- package/src/commands/state/score.js +13 -2
- package/src/commands/tasks/dag.js +11 -3
- package/src/core/paths/output-schema.js +28 -0
- package/src/core/state/state-manager.js +0 -128
- package/src/dashboard/app/pages/LiveState.jsx +0 -43
- package/src/lib/detectors/design-system-detector.js +0 -43
- package/src/lib/generators/index.js +0 -1
- package/src/lib/stack/stack-profile.js +40 -9
- package/src/lib/tasks/tasks-json-parser.js +21 -0
- package/src/lib/tasks/test-runner.js +26 -4
- package/src/lib/telemetry/telemetry-reader.js +103 -0
- package/src/lib/validators/validation-runner.js +13 -74
- package/src/utils/hooks-installer.js +27 -3
- package/framework/templates/context/CONTEXT-FEATURE.md +0 -269
- package/framework/templates/context/CONTEXT.md +0 -181
- package/src/lib/agents/metrics-aggregator.js +0 -56
- package/src/lib/agents/metrics-calculator.js +0 -125
- package/src/lib/generators/metadata-extractor.js +0 -385
|
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-implement
|
|
@@ -59,11 +59,14 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
59
59
|
Done quando: [doneCriteria do tasks.json]
|
|
60
60
|
|
|
61
61
|
Contrato do relatório final:
|
|
62
|
-
1.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
1. GRAVE seu relatório em `.morph/features/{feature}/3-implement/reports/[ID].md`
|
|
63
|
+
ANTES de encerrar — esse arquivo é o valor de retorno autoritativo desta
|
|
64
|
+
sessão. A sua última mensagem de texto é cortesia best-effort: pode não
|
|
65
|
+
chegar inteira ao orquestrador (o evento recebido às vezes é só um
|
|
66
|
+
idle_notification, sem texto). Ainda assim NÃO chame SendMessage: ele
|
|
67
|
+
gera um evento a mais além da notificação de término, custando um turno
|
|
68
|
+
inútil ao orquestrador. Relatório ausente, oco, ou sem os 4 itens abaixo,
|
|
69
|
+
é falha de protocolo.
|
|
67
70
|
2. NÃO toque em tasks.json, feature.json, state.json nem no board de sessão
|
|
68
71
|
(TaskCreate/TaskUpdate) — todo estado é gravado exclusivamente pelo
|
|
69
72
|
orquestrador.
|
|
@@ -74,11 +77,18 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
74
77
|
commite na branch do worktree como último ato antes do relatório
|
|
75
78
|
e reporte a branch — nunca faça merge.
|
|
76
79
|
|
|
77
|
-
Seu relatório
|
|
78
|
-
(
|
|
79
|
-
(4) auto-score 0-10.
|
|
80
|
+
Seu relatório (no arquivo, e na mensagem final quando ela chega) deve conter
|
|
81
|
+
APENAS: (1) o que foi feito em 2-3 frases, (2) arquivos modificados,
|
|
82
|
+
(3) problemas encontrados e como foram resolvidos, (4) auto-score 0-10.
|
|
80
83
|
```
|
|
81
|
-
Após
|
|
84
|
+
Após o retorno do dispatch: **leia primeiro** `.morph/features/{feature}/3-implement/reports/[ID].md`
|
|
85
|
+
— é o valor de retorno autoritativo. Trate a mensagem de texto do sub-agent, se chegou inteira,
|
|
86
|
+
como um atalho que economiza essa leitura — nunca como a única fonte. Arquivo ausente é falha de
|
|
87
|
+
protocolo (registre no recap e re-despache/complete a partir do que o sub-agent já fez, se
|
|
88
|
+
recuperável). O auto-score é **triagem, não aprovação** — se < 7, re-despache com o feedback
|
|
89
|
+
específico (ou corrija inline) antes de prosseguir. Em qualquer caso, a task só avança quando
|
|
90
|
+
`morph-eval` der score ≥ 9 (barra única do MORPH.md §0). Integre o relatório ao recap.md e vá
|
|
91
|
+
para o passo 7.
|
|
82
92
|
|
|
83
93
|
**Se não — siga para o passo 5.**
|
|
84
94
|
|
|
@@ -136,6 +146,8 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
136
146
|
|
|
137
147
|
## Output esperado
|
|
138
148
|
- Código implementado conforme o standard consultado (direto ou via sub-agent)
|
|
149
|
+
- Quando despachada: `.morph/features/{feature}/3-implement/reports/{taskId}.md` — o relatório
|
|
150
|
+
autoritativo do sub-agent, canal durável (passo 4)
|
|
139
151
|
- `.morph/features/{feature}/3-implement/recap.md` atualizado com entrada da task concluída
|
|
140
152
|
- `.morph/features/{feature}/2-plan/tasks.json` com status da task atualizado para `done`
|
|
141
153
|
- `.morph/features/{feature}/2-plan/decisions.md` atualizado se houver decisão não trivial
|
|
@@ -4,7 +4,7 @@ description: Guia a produção dos três artefatos de planejamento de uma featur
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-proposal
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -56,18 +56,22 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
56
56
|
seguindo as bandas 0-10 de cada uma.
|
|
57
57
|
NÃO reescreva código. NÃO toque em tasks.json, feature.json nem no board
|
|
58
58
|
de sessão.
|
|
59
|
-
Contrato do relatório final
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
Contrato do relatório final: GRAVE seu relatório em
|
|
60
|
+
`.morph/features/{feature}/4-review/evaluator-report.md` ANTES de
|
|
61
|
+
encerrar — esse arquivo é o valor de retorno autoritativo desta
|
|
62
|
+
sessão. A sua última mensagem de texto é cortesia best-effort: pode
|
|
63
|
+
não chegar inteira ao orquestrador (o evento recebido às vezes é só
|
|
64
|
+
um idle_notification, sem texto). Ainda assim NÃO chame SendMessage:
|
|
65
|
+
ele gera um evento a mais além da notificação de término, custando
|
|
66
|
+
um turno inútil ao orquestrador.
|
|
67
|
+
Seu relatório (no arquivo, e na mensagem final quando ela chega)
|
|
68
|
+
deve conter: score 0-10 por dimensão, score composto (arch×0.30 +
|
|
69
|
+
contracts×0.25 + quality×0.25 + tests×0.20), e feedback priorizado
|
|
70
|
+
(bloqueantes primeiro) para qualquer dimensão < 9. Relatório
|
|
71
|
+
ausente, oco, ou sem esses itens, é falha de protocolo.
|
|
68
72
|
```
|
|
69
73
|
- **Fallback (tipo não registrado):** se `morph-evaluator` não estiver na sua lista de subagent types, despache `general-purpose` colando o campo `persona` do agente `evaluator` (Read em `.morph/framework/agents.json`) no topo do prompt — e **registre o fallback no review-report** (seção de método).
|
|
70
|
-
- Os scores por dimensão e o composto do review-report vêm **desse
|
|
74
|
+
- **Leia primeiro** `.morph/features/{feature}/4-review/evaluator-report.md` — é o valor de retorno autoritativo, não a mensagem de texto do dispatch (que é cortesia, útil só quando chega inteira). Os scores por dimensão e o composto do review-report vêm **desse arquivo**, não da sua auto-avaliação. Se o arquivo vier ausente, oco, ou sem os scores por dimensão, registre a falha de protocolo na seção de método do review-report e re-despache.
|
|
71
75
|
|
|
72
76
|
5. Produza `review-report.md` em `.morph/features/{feature}/4-review/` com:
|
|
73
77
|
- Scores por dimensão (arquitetura, contratos, qualidade, cobertura)
|
|
@@ -117,7 +121,10 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
117
121
|
No caminho manual, o hook state-sync detecta a aprovação em answers de AskUserQuestion e grava `approvalGates.review = { approved: true, timestamp, approvedBy: "manual" }` no `feature.json` autoritativo da feature. Em auto sem AskUserQuestion, aprove via `morph-spec approve {feature} review --mode auto` — nunca via Edit à mão de state.json.
|
|
118
122
|
|
|
119
123
|
## Output esperado
|
|
120
|
-
- `.morph/features/{feature}/4-review/
|
|
124
|
+
- `.morph/features/{feature}/4-review/evaluator-report.md` — relatório bruto e autoritativo do
|
|
125
|
+
`morph-evaluator` despachado no passo 4 (canal durável, ver MORPH.md §6)
|
|
126
|
+
- `.morph/features/{feature}/4-review/review-report.md` — síntese do orquestrador a partir do
|
|
127
|
+
evaluator-report, com scores e recomendação
|
|
121
128
|
- Trace da fase com `phase_summary` preenchido
|
|
122
129
|
- Gate 3 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo), na forma canônica `{ approved, timestamp, approvedBy }` — via `morph-spec approve` em auto, ou pelo hook state-sync no caminho manual
|
|
123
130
|
|
|
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-stack-scan
|
|
@@ -4,7 +4,7 @@ description: Localiza e carrega um standard específico do STANDARDS.json sob de
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: quando precisar consultar um standard específico durante a implementação
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"mcp": "#mcp-integration-model-context-protocol",
|
|
30
30
|
"observability": "#observability--opentelemetry"
|
|
31
31
|
},
|
|
32
|
-
"digest": "Production panorama for MAF agents. Middleware (3 levels)
|
|
32
|
+
"digest": "Production panorama for MAF agents. Middleware (3 levels), MCP integration, and observability/OpenTelemetry are summarized here and treated canonically in ai-agents-middleware-patterns / ai-agents-mcp-tools / ai-agents-mcp-server / ai-agents-observability-patterns. Owns: A2A protocol via Microsoft.Agents.AI.A2A; Redis semantic + HybridCache caching; security and production checklists."
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"id": "ai-agents-setup",
|
|
@@ -309,6 +309,40 @@
|
|
|
309
309
|
},
|
|
310
310
|
"digest": "Three middleware levels intercept agent behavior: Agent Run (whole invocation), Function Calling (individual tool calls), IChatClient (raw LLM calls). Register via .AsBuilder().Use(...). Patterns: telemetry, resilience (retry/rate-limit), anti-hallucination (validate tool args before next())."
|
|
311
311
|
},
|
|
312
|
+
{
|
|
313
|
+
"id": "ai-agents-observability-patterns",
|
|
314
|
+
"name": "observability patterns",
|
|
315
|
+
"path": "ai-agents/observability-patterns.md",
|
|
316
|
+
"category": "ai-agents",
|
|
317
|
+
"subcategory": null,
|
|
318
|
+
"tags": [
|
|
319
|
+
"ai-agents",
|
|
320
|
+
"maf",
|
|
321
|
+
"observability"
|
|
322
|
+
],
|
|
323
|
+
"stacks": [
|
|
324
|
+
"dotnet"
|
|
325
|
+
],
|
|
326
|
+
"aliases": [
|
|
327
|
+
"observability",
|
|
328
|
+
"opentelemetry",
|
|
329
|
+
"telemetry",
|
|
330
|
+
"logging",
|
|
331
|
+
"tracing",
|
|
332
|
+
"log investigation",
|
|
333
|
+
"troubleshooting",
|
|
334
|
+
"aspire dashboard",
|
|
335
|
+
"correlation id",
|
|
336
|
+
"agent observability",
|
|
337
|
+
"agent telemetry"
|
|
338
|
+
],
|
|
339
|
+
"anchors": {
|
|
340
|
+
"setup": "#setup--opentelemetry",
|
|
341
|
+
"enrichment": "#enrichment--o-que-preencher-em-spans-e-logs",
|
|
342
|
+
"investigation": "#investigação-e-análise--quando-algo-dá-errado"
|
|
343
|
+
},
|
|
344
|
+
"digest": "OpenTelemetry setup for MAF agents (AgentOpenTelemetryConsts.DefaultSourceName, traces+metrics+logging). Enrichment: correlation_id propagation across async boundaries, gen_ai.* span attributes, structured ILogger scopes, sensitive-data logging rules. Investigation workflow for production incidents: anchor on correlation_id/time window, drill from root span to children, check metrics before theorizing, reproduce with Debug in staging (never production)."
|
|
345
|
+
},
|
|
312
346
|
{
|
|
313
347
|
"id": "ai-agents-modalities-vision",
|
|
314
348
|
"name": "modalities vision",
|
|
@@ -1756,6 +1790,14 @@
|
|
|
1756
1790
|
"ai-agents-production",
|
|
1757
1791
|
"ai-agents-middleware-patterns"
|
|
1758
1792
|
],
|
|
1793
|
+
"agent observability": [
|
|
1794
|
+
"ai-agents-production",
|
|
1795
|
+
"ai-agents-observability-patterns"
|
|
1796
|
+
],
|
|
1797
|
+
"agent telemetry": [
|
|
1798
|
+
"ai-agents-production",
|
|
1799
|
+
"ai-agents-observability-patterns"
|
|
1800
|
+
],
|
|
1759
1801
|
"when not to use workflow": [
|
|
1760
1802
|
"ai-agents-workflows",
|
|
1761
1803
|
"ai-agents-sweet-spot"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Observability Patterns — OpenTelemetry, enrichment, investigation
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 1 (on-keyword)
|
|
5
|
+
> **Keywords:** observability, telemetry, opentelemetry, logging, tracing, log investigation, troubleshooting, aspire dashboard, span, correlation id
|
|
6
|
+
> **Read by Claude in:** implement (wiring telemetry into an agent) and troubleshoot (investigating a production issue)
|
|
7
|
+
|
|
8
|
+
**Verified against:** MAF 1.0 GA. Last-verified: 2026-07-22.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Por que isso importa
|
|
13
|
+
|
|
14
|
+
Um agente MAF em produção não é uma função — é uma cadeia (LLM call → tool call → sub-agent → RAG/DB) que pode ter qualquer profundidade. Sem telemetria estruturada, um bug em produção ("o agente respondeu errado", "a automação não disparou") é indistinguível de uma caixa-preta: não dá pra saber qual chamada falhou, quanto cada etapa levou, ou qual tool retornou o quê. Este standard cobre as três partes do problema: **como instrumentar** (setup), **o que registrar** (enrichment) e **como investigar** (análise) — nessa ordem, porque setup sem enrichment produz traces vazios, e enrichment sem prática de investigação não ajuda ninguém no momento do incidente.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Setup — OpenTelemetry
|
|
19
|
+
|
|
20
|
+
```csharp
|
|
21
|
+
using OpenTelemetry;
|
|
22
|
+
using OpenTelemetry.Trace;
|
|
23
|
+
using OpenTelemetry.Metrics;
|
|
24
|
+
using Microsoft.Agents.AI;
|
|
25
|
+
|
|
26
|
+
// Habilitar em agents individuais
|
|
27
|
+
agent.WithOpenTelemetry();
|
|
28
|
+
|
|
29
|
+
// Program.cs — setup completo
|
|
30
|
+
builder.Services.AddOpenTelemetry()
|
|
31
|
+
.WithTracing(tracing =>
|
|
32
|
+
{
|
|
33
|
+
tracing.AddSource(AgentOpenTelemetryConsts.DefaultSourceName); // MAF 1.0 GA source
|
|
34
|
+
tracing.AddSource(builder.Environment.ApplicationName); // seus spans customizados (ver Enrichment)
|
|
35
|
+
tracing.AddAspNetCoreInstrumentation();
|
|
36
|
+
tracing.AddHttpClientInstrumentation();
|
|
37
|
+
tracing.AddOtlpExporter();
|
|
38
|
+
})
|
|
39
|
+
.WithMetrics(metrics =>
|
|
40
|
+
{
|
|
41
|
+
metrics.AddMeter(AgentOpenTelemetryConsts.DefaultSourceName);
|
|
42
|
+
metrics.AddAspNetCoreInstrumentation();
|
|
43
|
+
metrics.AddHttpClientInstrumentation();
|
|
44
|
+
metrics.AddRuntimeInstrumentation();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
builder.Logging.AddOpenTelemetry(logging =>
|
|
48
|
+
{
|
|
49
|
+
logging.IncludeFormattedMessage = true;
|
|
50
|
+
logging.IncludeScopes = true; // necessário para correlação — ver Enrichment
|
|
51
|
+
logging.AddOtlpExporter();
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **Source name:** sempre `AgentOpenTelemetryConsts.DefaultSourceName` de `Microsoft.Agents.AI`.
|
|
56
|
+
> **NÃO usar** o source string `"Experimental.[pacote-antigo].*"` — pertence à família `Microsoft.Extensions.AI` 9.x, incompatível com MAF 1.0 GA.
|
|
57
|
+
> **Produção:** desabilite dados sensíveis — `EnableSensitiveTelemetryData = false` no agent — para não vazar prompt/response completos no backend de export.
|
|
58
|
+
|
|
59
|
+
### Aspire Dashboard (dev/local)
|
|
60
|
+
|
|
61
|
+
Visualiza em tempo real: fluxo completo de conversas entre agents, ciclos de tool call (Model → Tool → Model), token usage e latência por chamada, error traces e retry patterns. Para ambientes hospedados (Coolify), exporte via OTLP para o backend de observability do projeto — ver `infrastructure-docker-coolify-deploy` para o setup de Serilog/JSON console log que alimenta o mesmo pipeline de agregação.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Enrichment — o que preencher em spans e logs
|
|
66
|
+
|
|
67
|
+
Setup sem enrichment produz traces tecnicamente corretos mas operacionalmente inúteis — você vê "uma chamada ao LLM aconteceu", não "por que ela demorou 8s" ou "qual feature/task gerou essa chamada". Preencha sempre:
|
|
68
|
+
|
|
69
|
+
### Correlação entre trace, feature e requisição
|
|
70
|
+
|
|
71
|
+
```csharp
|
|
72
|
+
using var activity = Activity.Current;
|
|
73
|
+
activity?.SetTag("morph.feature", featureName);
|
|
74
|
+
activity?.SetTag("morph.task_id", taskId);
|
|
75
|
+
activity?.SetTag("morph.correlation_id", correlationId); // mesmo ID em toda a cadeia de uma requisição de usuário
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Sem um `correlation_id` propagado manualmente através de chamadas assíncronas/filas (Hangfire, background jobs), você não consegue religar "o usuário reclamou às 14:32" a um trace específico — o `TraceId` do OpenTelemetry sozinho não sobrevive a um boundary de fila.
|
|
79
|
+
|
|
80
|
+
### Atributos de custo e uso do modelo
|
|
81
|
+
|
|
82
|
+
```csharp
|
|
83
|
+
activity?.SetTag("gen_ai.request.model", modelAlias); // alias do Model Registry, nunca o nome hardcoded
|
|
84
|
+
activity?.SetTag("gen_ai.usage.prompt_tokens", usage.PromptTokens);
|
|
85
|
+
activity?.SetTag("gen_ai.usage.completion_tokens", usage.CompletionTokens);
|
|
86
|
+
activity?.SetTag("gen_ai.response.finish_reason", finishReason);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Nomeação `gen_ai.*` segue a convenção semântica OpenTelemetry para GenAI — mantém compatibilidade se o backend de observability tiver dashboards prontos para essa convenção.
|
|
90
|
+
|
|
91
|
+
### Logging estruturado com `ILogger` — sempre com scope
|
|
92
|
+
|
|
93
|
+
```csharp
|
|
94
|
+
using (_logger.BeginScope(new Dictionary<string, object>
|
|
95
|
+
{
|
|
96
|
+
["FeatureId"] = featureName,
|
|
97
|
+
["TaskId"] = taskId,
|
|
98
|
+
["AgentId"] = agentPersonaId
|
|
99
|
+
}))
|
|
100
|
+
{
|
|
101
|
+
_logger.LogInformation("Tool call {ToolName} started with args {Args}", toolName, args);
|
|
102
|
+
// ... chamada ...
|
|
103
|
+
_logger.LogInformation("Tool call {ToolName} completed in {ElapsedMs}ms", toolName, elapsed);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- **Sempre** parâmetros nomeados no template (`{ToolName}`, não string interpolada) — permite querying estruturado no backend em vez de grep de texto livre.
|
|
108
|
+
- **Nunca** logar o conteúdo completo do prompt/response em `Information` — isso é PII/dado sensível do cliente por padrão. Logar em `Debug` apenas, e só se `EnableSensitiveTelemetryData` estiver habilitado explicitamente para o ambiente.
|
|
109
|
+
- **Nível de log por tipo de evento:** `Information` para início/fim de tool call e decisões do agent; `Warning` para retry/fallback de provider; `Error` apenas para falhas que geram resposta degradada ao usuário (não para toda exceção capturada e tratada).
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Investigação e análise — quando algo dá errado
|
|
114
|
+
|
|
115
|
+
Workflow para diagnosticar um incidente relatado ("o agente X respondeu errado/lento/nada às HH:MM"):
|
|
116
|
+
|
|
117
|
+
1. **Ancore no correlation_id ou janela de tempo.** Se o relato tem um identificador (número de WhatsApp, feature, conversationId), busque por ele nos atributos (`morph.correlation_id`, `gen_ai.*`) — não pelo texto do log. Sem identificador, restrinja pela janela de tempo + `morph.feature`/`AgentId` antes de olhar qualquer trace individual.
|
|
118
|
+
2. **Suba do trace raiz para os spans filhos.** O span raiz mostra a duração total; os filhos (`Function Calling`, `IChatClient`) mostram onde o tempo/erro realmente está. Não assuma — meça. Um "agente lento" é quase sempre UM tool call específico (chamada de API externa, query sem índice), não o LLM.
|
|
119
|
+
3. **Correlacione com métricas antes de teorizar.** Confira a tabela "What to Monitor" abaixo pelo período do incidente — um pico de `tool call failure rate` ou `token usage` aponta a causa mais rápido que ler traces individuais um a um.
|
|
120
|
+
4. **Reproduza com Debug ligado, nunca em produção.** Se a causa não estiver clara nos atributos estruturados, habilite `Debug` (com dados sensíveis, se necessário) num ambiente de staging com o mesmo input — nunca habilite log de prompt/response completo em produção para investigar.
|
|
121
|
+
5. **Documente a causa raiz em `decisions.md`** se a investigação levar a uma mudança de arquitetura (novo middleware de retry, novo timeout, novo cache) — não só no commit message.
|
|
122
|
+
|
|
123
|
+
### What to Monitor
|
|
124
|
+
|
|
125
|
+
| Metric | Purpose | Alert Threshold |
|
|
126
|
+
|--------|---------|------------------|
|
|
127
|
+
| Token usage per request | Cost control | > 10K tokens/request |
|
|
128
|
+
| Latency per agent call | Performance | > 5s for simple tasks |
|
|
129
|
+
| Tool call failure rate | Reliability | > 5% failures |
|
|
130
|
+
| Cache hit rate | Cost optimization | < 50% (tune strategy) |
|
|
131
|
+
| Model drift | Quality | Evaluation score drop > 10% |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Checklist (verifiable by morph-eval)
|
|
136
|
+
|
|
137
|
+
- [ ] OpenTelemetry configurado com `AgentOpenTelemetryConsts.DefaultSourceName` (traces + metrics + logging)
|
|
138
|
+
- [ ] `EnableSensitiveTelemetryData = false` fora de dev/staging
|
|
139
|
+
- [ ] Todo tool call relevante tem `correlation_id` propagado manualmente através de boundaries assíncronos (filas, background jobs)
|
|
140
|
+
- [ ] Logging via `ILogger` com `BeginScope` estruturado, nunca string interpolada em templates de log
|
|
141
|
+
- [ ] Nenhum log em `Information`/produção contém prompt/response completo
|
|
142
|
+
- [ ] Alertas configurados para os thresholds da tabela "What to Monitor" relevantes ao projeto
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## References
|
|
147
|
+
|
|
148
|
+
- `ai-agents-production` — panorama de produção; esta seção era resumida lá, tratamento canônico agora aqui
|
|
149
|
+
- `ai-agents-middleware-patterns` — middleware de telemetria no nível `IChatClient` (`TelemetryMiddleware`)
|
|
150
|
+
- `infrastructure-docker-coolify-deploy` — Serilog/JSON console log para o pipeline de agregação em produção
|
|
151
|
+
- [OpenTelemetry Semantic Conventions for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/)
|
|
152
|
+
- [Agent Observability](https://learn.microsoft.com/agent-framework/user-guide/observability/)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/observability-patterns.md v1.0 (2026-07-22)*
|
|
@@ -179,54 +179,9 @@ public async Task<WeatherResponse> GetWeatherAsync(
|
|
|
179
179
|
|
|
180
180
|
## Observability / OpenTelemetry
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
using OpenTelemetry;
|
|
184
|
-
using OpenTelemetry.Trace;
|
|
185
|
-
using OpenTelemetry.Metrics;
|
|
186
|
-
using Microsoft.Agents.AI;
|
|
187
|
-
|
|
188
|
-
// Enable on individual agents
|
|
189
|
-
agent.WithOpenTelemetry();
|
|
190
|
-
|
|
191
|
-
// Program.cs — full setup
|
|
192
|
-
builder.Services.AddOpenTelemetry()
|
|
193
|
-
.WithTracing(tracing =>
|
|
194
|
-
{
|
|
195
|
-
tracing.AddSource(AgentOpenTelemetryConsts.DefaultSourceName); // MAF 1.0 GA source
|
|
196
|
-
tracing.AddSource(builder.Environment.ApplicationName);
|
|
197
|
-
tracing.AddAspNetCoreInstrumentation();
|
|
198
|
-
tracing.AddHttpClientInstrumentation();
|
|
199
|
-
tracing.AddOtlpExporter();
|
|
200
|
-
})
|
|
201
|
-
.WithMetrics(metrics =>
|
|
202
|
-
{
|
|
203
|
-
metrics.AddMeter(AgentOpenTelemetryConsts.DefaultSourceName); // MAF 1.0 GA meter
|
|
204
|
-
metrics.AddAspNetCoreInstrumentation();
|
|
205
|
-
metrics.AddHttpClientInstrumentation();
|
|
206
|
-
metrics.AddRuntimeInstrumentation();
|
|
207
|
-
});
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
> **Source name:** sempre `AgentOpenTelemetryConsts.DefaultSourceName` de `Microsoft.Agents.AI`.
|
|
211
|
-
> **NÃO usar** o source string `"Experimental.[pacote-antigo].*"` — pertence à família `Microsoft.Extensions.AI 9.x`, incompatível com MAF 1.0 GA.
|
|
212
|
-
|
|
213
|
-
### Aspire Dashboard
|
|
214
|
-
|
|
215
|
-
Visualiza traces de agents incluindo:
|
|
216
|
-
- Fluxo completo de conversas entre agents
|
|
217
|
-
- Tool call cycles (Model → Tool → Model)
|
|
218
|
-
- Token usage e latência por chamada
|
|
219
|
-
- Error traces e retry patterns
|
|
220
|
-
|
|
221
|
-
### What to Monitor
|
|
182
|
+
Setup, enrichment (span attributes, correlation IDs, structured logging) e o workflow de investigação/análise de logs em produção têm **tratamento canônico** em **`ai-agents-observability-patterns`**. Não duplicado aqui.
|
|
222
183
|
|
|
223
|
-
|
|
224
|
-
|--------|---------|----------------|
|
|
225
|
-
| Token usage per request | Cost control | > 10K tokens/request |
|
|
226
|
-
| Latency per agent call | Performance | > 5s for simple tasks |
|
|
227
|
-
| Tool call failure rate | Reliability | > 5% failures |
|
|
228
|
-
| Cache hit rate | Cost optimization | < 50% (tune strategy) |
|
|
229
|
-
| Model drift | Quality | Evaluation score drop > 10% |
|
|
184
|
+
> Em produção: source name sempre `AgentOpenTelemetryConsts.DefaultSourceName`; `EnableSensitiveTelemetryData = false` fora de dev/staging. Ver `ai-agents-observability-patterns` para o setup completo, o que preencher em cada span/log, e o workflow de investigação quando um incidente for reportado.
|
|
230
185
|
|
|
231
186
|
---
|
|
232
187
|
|
|
@@ -262,6 +217,7 @@ Visualiza traces de agents incluindo:
|
|
|
262
217
|
- `ai-agents-sweet-spot` — decisão de arquitetura antes de middleware
|
|
263
218
|
- `ai-agents-providers-model-registry` — Model Registry para `_modelRegistry.GetChatClient(...)`
|
|
264
219
|
- `ai-agents-middleware-patterns` — **tratamento canônico** de middleware (código dos 3 níveis, telemetria, resilência, anti-hallucination)
|
|
220
|
+
- `ai-agents-observability-patterns` — **tratamento canônico** de observability (setup OpenTelemetry, enrichment de spans/logs, workflow de investigação)
|
|
265
221
|
- `ai-agents-mcp-tools` — agente MAF consumindo MCP servers externos
|
|
266
222
|
- `ai-agents-mcp-server` — projeto expondo capacidades como MCP server
|
|
267
223
|
- `ai-agents-rag-custom-pgvector` — implementação completa de busca semântica (referida em Caching)
|
|
@@ -109,55 +109,6 @@
|
|
|
109
109
|
"technology": "handlebars",
|
|
110
110
|
"validatePlaceholders": true
|
|
111
111
|
},
|
|
112
|
-
{
|
|
113
|
-
"id": "context",
|
|
114
|
-
"kind": "template",
|
|
115
|
-
"name": "Project Context",
|
|
116
|
-
"path": "context/CONTEXT.md",
|
|
117
|
-
"location": "framework",
|
|
118
|
-
"category": "context",
|
|
119
|
-
"phase": "proposal",
|
|
120
|
-
"required": false,
|
|
121
|
-
"description": "Project-level context with tech stack, active features, agents, standards",
|
|
122
|
-
"outputName": "CONTEXT.md",
|
|
123
|
-
"placeholders": [
|
|
124
|
-
"AGENT_COUNT",
|
|
125
|
-
"AGENT_TEAMS_DISPLAY_MODE",
|
|
126
|
-
"AGENT_TEAMS_THRESHOLDS",
|
|
127
|
-
"APP_INSIGHTS",
|
|
128
|
-
"AZURE_LOCATION",
|
|
129
|
-
"CONTAINER_ENV",
|
|
130
|
-
"COOLIFY_APP_URL",
|
|
131
|
-
"COOLIFY_SERVER",
|
|
132
|
-
"DEVOPS_AREA_PATH",
|
|
133
|
-
"DEVOPS_BUILD_PIPELINE",
|
|
134
|
-
"DEVOPS_ORG",
|
|
135
|
-
"DEVOPS_PROJECT",
|
|
136
|
-
"DEVOPS_RELEASE_PIPELINE",
|
|
137
|
-
"DEVOPS_WIKI_NAME",
|
|
138
|
-
"DOTNET_VERSION",
|
|
139
|
-
"EF_VERSION",
|
|
140
|
-
"INFERRED_STANDARDS",
|
|
141
|
-
"MORPH_VERSION",
|
|
142
|
-
"NEON_PROJECT_ID",
|
|
143
|
-
"NEON_REGION",
|
|
144
|
-
"NEXTJS_VERSION",
|
|
145
|
-
"OPENAI_MODEL",
|
|
146
|
-
"PROJECT_DESCRIPTION",
|
|
147
|
-
"PROJECT_NAME",
|
|
148
|
-
"PROJECT_TYPE",
|
|
149
|
-
"REPOSITORY_URL",
|
|
150
|
-
"SQL_DATABASE",
|
|
151
|
-
"SQL_SERVER",
|
|
152
|
-
"TIMESTAMP",
|
|
153
|
-
"UI_LIBRARY"
|
|
154
|
-
],
|
|
155
|
-
"stackSpecific": false,
|
|
156
|
-
"engine": "handlebars",
|
|
157
|
-
"deprecated": false,
|
|
158
|
-
"technology": "handlebars",
|
|
159
|
-
"validatePlaceholders": true
|
|
160
|
-
},
|
|
161
112
|
{
|
|
162
113
|
"id": "architecture",
|
|
163
114
|
"kind": "template",
|
|
@@ -176,50 +127,6 @@
|
|
|
176
127
|
"technology": "handlebars",
|
|
177
128
|
"validatePlaceholders": true
|
|
178
129
|
},
|
|
179
|
-
{
|
|
180
|
-
"id": "context-feature",
|
|
181
|
-
"kind": "template",
|
|
182
|
-
"name": "Feature Context",
|
|
183
|
-
"path": "context/CONTEXT-FEATURE.md",
|
|
184
|
-
"location": "framework",
|
|
185
|
-
"category": "context",
|
|
186
|
-
"phase": "proposal",
|
|
187
|
-
"required": false,
|
|
188
|
-
"description": "Feature-level context with progress, agents, tasks, decisions, validations",
|
|
189
|
-
"outputName": "CONTEXT-FEATURE.md",
|
|
190
|
-
"placeholders": [
|
|
191
|
-
"ACTIVE_AGENTS_COUNT",
|
|
192
|
-
"AGENT_TEAMS_DECISION",
|
|
193
|
-
"COMPLEXITY",
|
|
194
|
-
"CONTRACTS_PATH",
|
|
195
|
-
"CURRENT_TASK",
|
|
196
|
-
"DECISIONS_PATH",
|
|
197
|
-
"DESCRIPTION",
|
|
198
|
-
"FEATURE_ID",
|
|
199
|
-
"FEATURE_NAME",
|
|
200
|
-
"MORPH_VERSION",
|
|
201
|
-
"PHASE",
|
|
202
|
-
"PHASE_DESCRIPTION",
|
|
203
|
-
"PROPOSAL_PATH",
|
|
204
|
-
"RECAP_PATH",
|
|
205
|
-
"SPEC_PATH",
|
|
206
|
-
"STATUS",
|
|
207
|
-
"TASKS_BLOCKED",
|
|
208
|
-
"TASKS_COMPLETED",
|
|
209
|
-
"TASKS_IN_PROGRESS",
|
|
210
|
-
"TASKS_PATH",
|
|
211
|
-
"TASKS_PROGRESS",
|
|
212
|
-
"TASKS_TOTAL",
|
|
213
|
-
"TEAM_LEAD",
|
|
214
|
-
"TIMESTAMP",
|
|
215
|
-
"VALIDATION_TIMESTAMP"
|
|
216
|
-
],
|
|
217
|
-
"stackSpecific": false,
|
|
218
|
-
"engine": "handlebars",
|
|
219
|
-
"deprecated": false,
|
|
220
|
-
"technology": "handlebars",
|
|
221
|
-
"validatePlaceholders": true
|
|
222
|
-
},
|
|
223
130
|
{
|
|
224
131
|
"id": "spec-examples",
|
|
225
132
|
"kind": "template",
|
package/package.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `morph-spec telemetry [--json] [--feature <name>] [--limit <n>]`
|
|
3
|
+
*
|
|
4
|
+
* The reader surface over the harness event stream (.morph/logs/events.jsonl,
|
|
5
|
+
* written by the telemetry-log hook). Reconstructs the whole execution path of
|
|
6
|
+
* an NLH session: top skills, personas dispatched, standards (knowledge base)
|
|
7
|
+
* read, the tool mix, and the recent prompts (user + sub-agent).
|
|
8
|
+
*
|
|
9
|
+
* Read-only, always exits 0 (fail-open — an empty stream is an answer).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import { loadTelemetry, aggregateTelemetry } from '../../lib/telemetry/telemetry-reader.js';
|
|
14
|
+
|
|
15
|
+
// The knowledge-base count reflects EXPLICIT reads only. The core standards
|
|
16
|
+
// load via @-refs in rules (Layer 0/1 auto-load) — those are injected by
|
|
17
|
+
// Claude Code's rules system, not via the Read tool, so no hook ever sees them.
|
|
18
|
+
// A low count here means "few on-demand reads", NOT "standards barely used".
|
|
19
|
+
const STANDARDS_CAVEAT =
|
|
20
|
+
'Só reads explícitos (via Read) são observáveis; standards auto-carregados por @-ref nas rules (Layer 0/1) não passam por tool call e ficam invisíveis aqui.';
|
|
21
|
+
|
|
22
|
+
function section(title, rows, fmt) {
|
|
23
|
+
if (!rows.length) return;
|
|
24
|
+
console.log('\n' + chalk.white.bold(title));
|
|
25
|
+
for (const r of rows) console.log(' ' + fmt(r));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {{ json?: boolean, feature?: string, limit?: number, cwd?: string }} [options]
|
|
30
|
+
*/
|
|
31
|
+
export function telemetryCommand(options = {}) {
|
|
32
|
+
const events = loadTelemetry(options.cwd || process.cwd());
|
|
33
|
+
const summary = aggregateTelemetry(events, { feature: options.feature });
|
|
34
|
+
|
|
35
|
+
if (options.json) {
|
|
36
|
+
console.log(JSON.stringify({ ...summary, notes: { standards: STANDARDS_CAVEAT } }));
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (summary.total === 0) {
|
|
41
|
+
const scope = options.feature ? ` para a feature "${options.feature}"` : '';
|
|
42
|
+
console.log(chalk.gray(`Telemetria vazia${scope} — nenhum evento capturado ainda em .morph/logs/events.jsonl.`));
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const scope = options.feature ? chalk.cyan(` · feature ${options.feature}`) : '';
|
|
47
|
+
console.log(chalk.white.bold(`Telemetria do harness — ${summary.total} eventos${scope}`));
|
|
48
|
+
if (summary.features.length) {
|
|
49
|
+
console.log(chalk.gray(`Features: ${summary.features.join(', ')}`));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
section('Skills', summary.skills, s => `${chalk.green(s.name.padEnd(24))} ${s.count}`);
|
|
53
|
+
section('Personas despachadas', summary.personas, p => `${chalk.magenta(p.name.padEnd(24))} ${p.count}`);
|
|
54
|
+
section('Base de conhecimento (standards lidos explicitamente)', summary.standards,
|
|
55
|
+
s => `${chalk.yellow(String(s.count).padStart(3))}× ${s.path}`);
|
|
56
|
+
if (summary.standards.length || summary.tools.some(t => t.name === 'Read')) {
|
|
57
|
+
console.log(chalk.gray(` ⚠ ${STANDARDS_CAVEAT}`));
|
|
58
|
+
}
|
|
59
|
+
section('Mix de tools', summary.tools, t => `${t.name.padEnd(16)} ${t.count}`);
|
|
60
|
+
|
|
61
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 10;
|
|
62
|
+
const prompts = summary.prompts.slice(-limit);
|
|
63
|
+
if (prompts.length) {
|
|
64
|
+
console.log('\n' + chalk.white.bold(`Prompts (últimos ${prompts.length})`));
|
|
65
|
+
for (const p of prompts) {
|
|
66
|
+
const tag = p.source === 'user' ? chalk.blue('user ') : chalk.magenta('agent');
|
|
67
|
+
const where = p.feature ? chalk.gray(` [${p.feature}/${p.phase || '-'}]`) : '';
|
|
68
|
+
const oneLine = String(p.text || '').replace(/\s+/g, ' ').trim();
|
|
69
|
+
const preview = oneLine.length > 120 ? oneLine.slice(0, 117) + '...' : oneLine;
|
|
70
|
+
console.log(` ${tag}${where} ${preview}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|