@polymorphism-tech/morph-spec 7.4.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +18 -0
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +9 -4
  7. package/framework/MORPH.md +4 -4
  8. package/framework/agents.json +1 -1
  9. package/framework/commands/create-umbraco-lp.md +35 -0
  10. package/framework/commands/migrate-lp-to-umbraco.md +33 -0
  11. package/framework/commands/morph-archive.md +17 -27
  12. package/framework/commands/morph-preflight.md +10 -5
  13. package/framework/hooks/claude-code/core/enforce-phase-writes.js +115 -90
  14. package/framework/hooks/claude-code/core/protect-spec-files.js +32 -30
  15. package/framework/hooks/claude-code/core/state-sync.js +16 -48
  16. package/framework/hooks/shared/state-reader.js +74 -8
  17. package/framework/hooks/shared/state-writer.js +124 -0
  18. package/framework/rules/infrastructure-standards.md +4 -4
  19. package/framework/rules/morph-task-tracking.md +69 -49
  20. package/framework/rules/morph-workflow.md +192 -191
  21. package/framework/skills/core/create-umbraco-lp/SKILL.md +272 -0
  22. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +531 -0
  23. package/framework/skills/core/morph-apply/SKILL.md +15 -15
  24. package/framework/skills/core/morph-brainstorming/SKILL.md +5 -5
  25. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  26. package/framework/skills/core/morph-implement/SKILL.md +5 -4
  27. package/framework/skills/core/morph-plan/SKILL.md +6 -6
  28. package/framework/skills/core/morph-proposal/SKILL.md +8 -7
  29. package/framework/skills/core/morph-review/SKILL.md +3 -3
  30. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  31. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  32. package/framework/standards/STANDARDS.json +80 -8
  33. package/framework/standards/ai-agents/durable-workflows-hitl.md +191 -0
  34. package/framework/standards/ai-agents/rag-custom-pgvector.md +246 -243
  35. package/framework/standards/ai-agents/setup.md +3 -0
  36. package/framework/standards/ai-agents/vector-data-extensions.md +170 -0
  37. package/framework/standards/ai-agents/workflows.md +190 -187
  38. package/framework/standards/infrastructure/docker/{easypanel-deploy.md → coolify-deploy.md} +49 -20
  39. package/framework/templates/README.md +7 -7
  40. package/framework/templates/REGISTRY.json +15 -17
  41. package/framework/templates/context/CONTEXT-FEATURE.md +10 -17
  42. package/framework/templates/context/CONTEXT.md +181 -180
  43. package/framework/templates/docs/onboarding.md +122 -118
  44. package/framework/templates/docs/proposal.md +12 -2
  45. package/framework/templates/docs/spec.md +228 -228
  46. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +1 -1
  47. package/framework/templates/infrastructure/docker/coolify.template.json +49 -0
  48. package/framework/templates/infrastructure/github/README.md +6 -6
  49. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +33 -0
  50. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -80
  51. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +90 -89
  52. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +2 -1
  53. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +44 -43
  54. package/framework/templates/meta-prompts/validators/checkpoint-validator.md +107 -107
  55. package/package.json +1 -1
  56. package/src/commands/project/update.js +415 -368
  57. package/src/commands/project/worktree.js +212 -210
  58. package/src/commands/state/advance.js +82 -0
  59. package/src/commands/state/archive.js +111 -0
  60. package/src/commands/validation/validate-feature.js +229 -105
  61. package/src/core/state/state-manager.js +238 -66
  62. package/src/dashboard/scanner/state-scanner.js +35 -8
  63. package/src/lib/generators/context-generator.js +12 -8
  64. package/src/utils/file-copier.js +7 -0
  65. package/framework/templates/infrastructure/docker/easypanel.template.json +0 -46
  66. package/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
@@ -1,243 +1,246 @@
1
- # RAG Custom with pgvector — Knowledge retrieval you control
2
-
3
- > **Scope:** stacks=["dotnet"]
4
- > **Layer:** 1 (on-keyword)
5
- > **Keywords:** rag, vector search, pgvector, embeddings, custom rag, retrieval, semantic search, knowledge base, RAG tool
6
- > **Read by Claude in:** implement (when an agent needs to retrieve knowledge)
7
-
8
- **Verified against:** MAF 1.0 GA + Microsoft.Extensions.AI. Last-verified: 2026-05-19.
9
-
10
- ---
11
-
12
- ## Por que RAG custom
13
-
14
- Hosted RAG (uploading PDFs to a service) hands control of chunking, embedding, and storage to a third party. You cannot tune chunk size for your document type, cannot switch embedding models without re-indexing, and cannot add tenant isolation at the storage layer. Custom RAG gives you all three.
15
-
16
- - **Chunking:** you choose the strategy per document type (catalog, contract, technical doc). See `data-vector-search-rag-chunking`.
17
- - **Embedding:** provider-agnostic. The embedding model is chosen via the `embeddings` alias in the project's Model Registry (see `ai-agents-providers-model-registry`). The project wires an `IEmbeddingGenerator<string, Embedding<float>>` for that alias — switching from `text-embedding-3-small` to another model is a one-file config change.
18
- - **Storage:** pgvector on Neon — a column you own, indexed by HNSW, with RLS for tenant isolation.
19
-
20
- ---
21
-
22
- ## Arquitetura
23
-
24
- ```
25
- [Document ingestion]
26
- │
27
- ▼
28
- EmbeddingService (generates float vectors via IEmbeddingGenerator)
29
- │
30
- ▼
31
- kb_embeddings table (pgvector column, HNSW index, tenant_id partition)
32
- │
33
- ▼
34
- VectorSearchTool ◄── MAF agent (tool registered via AIFunctionFactory.Create)
35
- │
36
- ▼
37
- Top-k chunks → agent context (agent decides when to call the tool)
38
- ```
39
-
40
- Three pieces:
41
-
42
- 1. **EmbeddingService** — wraps `IEmbeddingGenerator<string, Embedding<float>>` from `Microsoft.Extensions.AI`. Called during indexing (batch) and at query time (single text).
43
- 2. **`kb_embeddings` table** — stores chunk text + `vector(1536)` + `tenant_id` + `metadata jsonb`. HNSW index with `vector_cosine_ops`.
44
- 3. **VectorSearchTool** — a typed MAF tool. A `[Description]`-decorated method the agent can call. Embeds the query, runs cosine-distance SQL, returns top-k chunks as concatenated text.
45
-
46
- ---
47
-
48
- ## Chunking por tipo de documento
49
-
50
- Chunk strategy varies by document type:
51
-
52
- | Document type | Recommended strategy | Chunk size |
53
- |---------------|----------------------|------------|
54
- | Product catalog | Fixed-size | 256–512 tokens |
55
- | Legal contract | Sentence-aware | 256 tokens |
56
- | Technical docs | Semantic (split on headers) | Variable |
57
- | Q&A pairs | Keep whole pair | Whole Q+A |
58
- | Code files | Function boundary | Whole function |
59
-
60
- For implementation of each strategy, see `data-vector-search-rag-chunking`. Do not duplicate chunking code here — that standard is the single source of truth for chunk implementations and the `Chunk` metadata record.
61
-
62
- ---
63
-
64
- ## EmbeddingService
65
-
66
- Reference template: `templates/code/dotnet/ai-agents/EmbeddingService.cs.template`
67
-
68
- The service wraps `IEmbeddingGenerator<string, Embedding<float>>` (from `Microsoft.Extensions.AI`) and exposes two methods:
69
-
70
- ```csharp
71
- // Single text → embedding vector (used at query time)
72
- Task<ReadOnlyMemory<float>> EmbedAsync(string text, CancellationToken ct = default);
73
-
74
- // Batch of texts → list of vectors (used during document indexing)
75
- Task<IReadOnlyList<ReadOnlyMemory<float>>> EmbedBatchAsync(
76
- IReadOnlyList<string> texts, CancellationToken ct = default);
77
- ```
78
-
79
- Both call `GenerateAsync(IEnumerable<string>, ...)` on the injected `IEmbeddingGenerator` (verified via context7 / Microsoft Learn). `GenerateAsync` returns `GeneratedEmbeddings<Embedding<float>>`; each element's `.Vector` property is a `ReadOnlyMemory<float>`.
80
-
81
- **Wiring the `IEmbeddingGenerator`:** the project registers its chosen embedding provider in DI (e.g., via the OpenAI embedding client's `.AsIEmbeddingGenerator()` extension, or the equivalent for another provider). The `embeddings` alias in `model-registry.json` declares the model (`text-embedding-3-small`, dimensions 1536). The `EmbeddingService` is registered as a scoped or singleton service and injected wherever indexing or search happens.
82
-
83
- ```csharp
84
- // Example DI registration (Program.cs, in the project — not in framework)
85
- builder.Services.AddSingleton<IEmbeddingGenerator<string, Embedding<float>>>(sp =>
86
- {
87
- var cfg = sp.GetRequiredService<IConfiguration>();
88
- return new OpenAIClient(cfg["OpenAI:ApiKey"]!)
89
- .GetEmbeddingClient("text-embedding-3-small")
90
- .AsIEmbeddingGenerator();
91
- });
92
- builder.Services.AddScoped<EmbeddingService>();
93
- ```
94
-
95
- ---
96
-
97
- ## A tabela pgvector
98
-
99
- Reference template: `templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template`
100
-
101
- ```sql
102
- CREATE TABLE kb_embeddings (
103
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
104
- tenant_id uuid NOT NULL,
105
- source text NOT NULL, -- document identifier / URL
106
- chunk_index int NOT NULL, -- position within source document
107
- content text NOT NULL, -- chunk text returned to the agent
108
- embedding vector(1536) NOT NULL, -- matches embedding model dimensions
109
- metadata jsonb NOT NULL DEFAULT '{}',
110
- created_at timestamptz NOT NULL DEFAULT now()
111
- );
112
-
113
- -- HNSW index — cosine distance, production-grade recall
114
- CREATE INDEX ix_kb_embeddings_hnsw
115
- ON kb_embeddings
116
- USING hnsw (embedding vector_cosine_ops);
117
-
118
- -- Index for tenant filter (applied in every query)
119
- CREATE INDEX ix_kb_embeddings_tenant
120
- ON kb_embeddings (tenant_id);
121
- ```
122
-
123
- For pgvector extension setup (`CREATE EXTENSION IF NOT EXISTS vector;`, `UseVector()` on `NpgsqlDataSourceBuilder`, HNSW parameter tuning), see `infrastructure-neon-pgvector`.
124
-
125
- > **Dimension match:** `vector(1536)` matches `text-embedding-3-small` (default 1536 dimensions). If the project switches models, re-create the column with the new dimension. Never mix models on a single column.
126
-
127
- ---
128
-
129
- ## VectorSearchTool (tool MAF)
130
-
131
- Reference template: `templates/code/dotnet/ai-agents/VectorSearchTool.cs.template`
132
-
133
- A sealed class with a `[Description]`-decorated method the agent calls when it needs context from the knowledge base.
134
-
135
- ```csharp
136
- // Registration — in the agent setup code
137
- var tool = sp.GetRequiredService<VectorSearchTool>();
138
- var agent = _modelRegistry.GetChatClient("text-default")
139
- .AsAIAgent(
140
- instructions: systemPrompt,
141
- tools: [AIFunctionFactory.Create(tool.SearchAsync)]);
142
- ```
143
-
144
- The tool:
145
- 1. Takes a natural-language `query` string (the only parameter the LLM sees).
146
- 2. Calls `EmbeddingService.EmbedAsync(query)` to get a query vector.
147
- 3. Runs a parameterized SQL `SELECT content FROM kb_embeddings WHERE tenant_id = @tenant ORDER BY embedding <=> @queryVec LIMIT 5`.
148
- 4. Concatenates chunk contents and returns them as a single string — the agent uses this as grounding context.
149
-
150
- The `tenant_id` filter is **never a tool parameter** (that would allow the LLM to cross tenant boundaries). It is injected via a constructor dependency — e.g., a scoped `ITenantContext` or a `Guid currentTenantId` resolved from the authenticated request. See §Multi-tenant below.
151
-
152
- Note on raw Npgsql vs EF Core: this template uses `NpgsqlDataSource` (raw SQL) for the cosine-distance query, since pgvector operators (`<=>`) are easier to express and verify in raw SQL. If the project already wires pgvector through EF Core (`UseVector()`, `HasMethod("hnsw")`), the equivalent `db.Database.SqlQuery<>` form per `infrastructure-neon-pgvector` is equally valid — choose whichever is already in use.
153
-
154
- ---
155
-
156
- ## Exemplo de uso
157
-
158
- ```csharp
159
- // In an agent feature handler (the project's code)
160
- using Microsoft.Agents.AI;
161
- using Microsoft.Extensions.AI;
162
-
163
- public sealed class KnowledgeAgentHandler(
164
- ModelRegistry modelRegistry,
165
- VectorSearchTool searchTool,
166
- IPromptRepository prompts)
167
- {
168
- public async Task<string> AnswerAsync(string question, CancellationToken ct = default)
169
- {
170
- var systemPrompt = await prompts.GetActivePromptAsync("knowledge-agent", ct);
171
-
172
- var agent = modelRegistry.GetChatClient("text-default")
173
- .AsAIAgent(
174
- instructions: systemPrompt,
175
- tools: [AIFunctionFactory.Create(searchTool.SearchAsync)]);
176
-
177
- var response = await agent.RunAsync(question, ct);
178
- return response.Text;
179
- }
180
- }
181
- ```
182
-
183
- The agent decides autonomously when to call `VectorSearchTool.SearchAsync`. It receives the top-k chunks as a string and incorporates them into its response. The handler code stays clean — no manual retrieval loop.
184
-
185
- ---
186
-
187
- ## Multi-tenant
188
-
189
- Each tenant's knowledge is partitioned by `tenant_id`:
190
-
191
- - Every row in `kb_embeddings` has a `tenant_id UUID NOT NULL`.
192
- - Every query filters on `tenant_id = @tenant` before any cosine-distance ordering.
193
- - `VectorSearchTool` resolves `tenant_id` from the authenticated request context (injected at construction time) — **never from a tool parameter the LLM supplies**.
194
- - For database-level enforcement, enable Row-Level Security:
195
-
196
- ```sql
197
- ALTER TABLE kb_embeddings ENABLE ROW LEVEL SECURITY;
198
- -- See the project's RLS policy template (templates/code/sql/postgresql-rls-policy.sql)
199
- -- for the policy pattern that binds app.current_tenant_id to the session.
200
- ```
201
-
202
- > **Rule:** never query across tenants without the filter. One missing `WHERE tenant_id = @tenant` leaks every tenant's data to the requester.
203
-
204
- ---
205
-
206
- ## Anti-patterns
207
-
208
- | Anti-pattern | Why it's wrong | Right way |
209
- |--------------|----------------|-----------|
210
- | Hosted RAG (upload PDFs to a service) | Loses control of chunking, embedding, and storage; vendor lock-in | Custom RAG: own all three layers |
211
- | Embeddings column without an index | Full table scan — 100 ms becomes 30 s at 100k rows | HNSW index on the embedding column always |
212
- | One fixed chunk size for all document types | Contracts need dense sentences; catalogs need short snippets; mixed chunks degrade recall | Choose strategy per document type (see `data-vector-search-rag-chunking`) |
213
- | `tenant_id` as a tool parameter the LLM passes | LLM can be prompted to pass any GUID — cross-tenant data leak | Inject `tenant_id` from auth context at construction; never expose as a model-facing parameter |
214
- | Embedding with model A, querying with model B | Vectors live in different spaces — cosine distance is meaningless | One embedding model per column; re-index when switching models |
215
- | `ORDER BY similarity DESC` in SQL | `<=>` returns distance (0 = identical), not similarity | `ORDER BY embedding <=> @queryVec` (ascending distance = most similar first) |
216
-
217
- ---
218
-
219
- ## Checklist (verifiable by morph-eval)
220
-
221
- - [ ] EmbeddingService (concrete class) registered in DI; it injects IEmbeddingGenerator<string, Embedding<float>> for the project's chosen embedding model.
222
- - [ ] Embedding model declared in `model-registry.json` under the `embeddings` alias.
223
- - [ ] `kb_embeddings` table has `embedding vector(1536)` and HNSW index with `vector_cosine_ops`.
224
- - [ ] `tenant_id` column present and filtered in every query.
225
- - [ ] `VectorSearchTool.SearchAsync` takes only `query` + `ct` — no tenant param visible to the LLM.
226
- - [ ] `VectorSearchTool` registered on the agent via `AIFunctionFactory.Create(tool.SearchAsync)`.
227
- - [ ] No `Microsoft.SemanticKernel` references in the project.
228
- - [ ] Vector dimensions consistent between embedding model and `vector(N)` column definition.
229
- - [ ] RLS enabled on `kb_embeddings` (or equivalent app-level guard documented in `decisions.md`).
230
- - [ ] Re-indexing strategy documented when document content changes (content hash check).
231
-
232
- ---
233
-
234
- ## References
235
-
236
- - `ai-agents-setup` — MAF packages, `AsAIAgent`, `AIFunctionFactory.Create`
237
- - `ai-agents-providers-model-registry` — `embeddings` alias, `IEmbeddingGenerator` wiring
238
- - `data-vector-search-rag-chunking` — chunk strategies per document type
239
- - `infrastructure-neon-pgvector` — extension setup, `UseVector()`, HNSW parameter tuning
240
-
241
- ---
242
-
243
- *MORPH-SPEC by Polymorphism Tech — ai-agents/rag-custom-pgvector.md v1.0 (2026-05-19)*
1
+ # RAG Custom with pgvector — Knowledge retrieval you control
2
+
3
+ > **Scope:** stacks=["dotnet"]
4
+ > **Layer:** 1 (on-keyword)
5
+ > **Keywords:** rag, vector search, pgvector, embeddings, custom rag, retrieval, semantic search, knowledge base, RAG tool
6
+ > **Read by Claude in:** implement (when an agent needs to retrieve knowledge)
7
+
8
+ **Verified against:** MAF 1.0 GA + Microsoft.Extensions.AI. Last-verified: 2026-05-19.
9
+
10
+ ---
11
+
12
+ ## Por que RAG custom
13
+
14
+ Hosted RAG (uploading PDFs to a service) hands control of chunking, embedding, and storage to a third party. You cannot tune chunk size for your document type, cannot switch embedding models without re-indexing, and cannot add tenant isolation at the storage layer. Custom RAG gives you all three.
15
+
16
+ - **Chunking:** you choose the strategy per document type (catalog, contract, technical doc). See `data-vector-search-rag-chunking`.
17
+ - **Embedding:** provider-agnostic. The embedding model is chosen via the `embeddings` alias in the project's Model Registry (see `ai-agents-providers-model-registry`). The project wires an `IEmbeddingGenerator<string, Embedding<float>>` for that alias — switching from `text-embedding-3-small` to another model is a one-file config change.
18
+ - **Storage:** pgvector on Neon — a column you own, indexed by HNSW, with RLS for tenant isolation.
19
+
20
+ > **This is the default.** `Microsoft.Extensions.VectorData` (see `ai-agents-vector-data-extensions`) is Microsoft's official, provider-agnostic vector-store abstraction, but its Postgres connector is still a prerelease package as of last-verified date. Adopt VectorData only when genuine multi-backend portability is required — do not migrate this working implementation to a preview dependency by default.
21
+
22
+ ---
23
+
24
+ ## Arquitetura
25
+
26
+ ```
27
+ [Document ingestion]
28
+ │
29
+ ▼
30
+ EmbeddingService (generates float vectors via IEmbeddingGenerator)
31
+ │
32
+ ▼
33
+ kb_embeddings table (pgvector column, HNSW index, tenant_id partition)
34
+ │
35
+ ▼
36
+ VectorSearchTool ◄── MAF agent (tool registered via AIFunctionFactory.Create)
37
+ │
38
+ ▼
39
+ Top-k chunks → agent context (agent decides when to call the tool)
40
+ ```
41
+
42
+ Three pieces:
43
+
44
+ 1. **EmbeddingService** — wraps `IEmbeddingGenerator<string, Embedding<float>>` from `Microsoft.Extensions.AI`. Called during indexing (batch) and at query time (single text).
45
+ 2. **`kb_embeddings` table** — stores chunk text + `vector(1536)` + `tenant_id` + `metadata jsonb`. HNSW index with `vector_cosine_ops`.
46
+ 3. **VectorSearchTool** — a typed MAF tool. A `[Description]`-decorated method the agent can call. Embeds the query, runs cosine-distance SQL, returns top-k chunks as concatenated text.
47
+
48
+ ---
49
+
50
+ ## Chunking por tipo de documento
51
+
52
+ Chunk strategy varies by document type:
53
+
54
+ | Document type | Recommended strategy | Chunk size |
55
+ |---------------|----------------------|------------|
56
+ | Product catalog | Fixed-size | 256–512 tokens |
57
+ | Legal contract | Sentence-aware | 256 tokens |
58
+ | Technical docs | Semantic (split on headers) | Variable |
59
+ | Q&A pairs | Keep whole pair | Whole Q+A |
60
+ | Code files | Function boundary | Whole function |
61
+
62
+ For implementation of each strategy, see `data-vector-search-rag-chunking`. Do not duplicate chunking code here — that standard is the single source of truth for chunk implementations and the `Chunk` metadata record.
63
+
64
+ ---
65
+
66
+ ## EmbeddingService
67
+
68
+ Reference template: `templates/code/dotnet/ai-agents/EmbeddingService.cs.template`
69
+
70
+ The service wraps `IEmbeddingGenerator<string, Embedding<float>>` (from `Microsoft.Extensions.AI`) and exposes two methods:
71
+
72
+ ```csharp
73
+ // Single text → embedding vector (used at query time)
74
+ Task<ReadOnlyMemory<float>> EmbedAsync(string text, CancellationToken ct = default);
75
+
76
+ // Batch of texts → list of vectors (used during document indexing)
77
+ Task<IReadOnlyList<ReadOnlyMemory<float>>> EmbedBatchAsync(
78
+ IReadOnlyList<string> texts, CancellationToken ct = default);
79
+ ```
80
+
81
+ Both call `GenerateAsync(IEnumerable<string>, ...)` on the injected `IEmbeddingGenerator` (verified via context7 / Microsoft Learn). `GenerateAsync` returns `GeneratedEmbeddings<Embedding<float>>`; each element's `.Vector` property is a `ReadOnlyMemory<float>`.
82
+
83
+ **Wiring the `IEmbeddingGenerator`:** the project registers its chosen embedding provider in DI (e.g., via the OpenAI embedding client's `.AsIEmbeddingGenerator()` extension, or the equivalent for another provider). The `embeddings` alias in `model-registry.json` declares the model (`text-embedding-3-small`, dimensions 1536). The `EmbeddingService` is registered as a scoped or singleton service and injected wherever indexing or search happens.
84
+
85
+ ```csharp
86
+ // Example DI registration (Program.cs, in the project — not in framework)
87
+ builder.Services.AddSingleton<IEmbeddingGenerator<string, Embedding<float>>>(sp =>
88
+ {
89
+ var cfg = sp.GetRequiredService<IConfiguration>();
90
+ return new OpenAIClient(cfg["OpenAI:ApiKey"]!)
91
+ .GetEmbeddingClient("text-embedding-3-small")
92
+ .AsIEmbeddingGenerator();
93
+ });
94
+ builder.Services.AddScoped<EmbeddingService>();
95
+ ```
96
+
97
+ ---
98
+
99
+ ## A tabela pgvector
100
+
101
+ Reference template: `templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template`
102
+
103
+ ```sql
104
+ CREATE TABLE kb_embeddings (
105
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
106
+ tenant_id uuid NOT NULL,
107
+ source text NOT NULL, -- document identifier / URL
108
+ chunk_index int NOT NULL, -- position within source document
109
+ content text NOT NULL, -- chunk text returned to the agent
110
+ embedding vector(1536) NOT NULL, -- matches embedding model dimensions
111
+ metadata jsonb NOT NULL DEFAULT '{}',
112
+ created_at timestamptz NOT NULL DEFAULT now()
113
+ );
114
+
115
+ -- HNSW index — cosine distance, production-grade recall
116
+ CREATE INDEX ix_kb_embeddings_hnsw
117
+ ON kb_embeddings
118
+ USING hnsw (embedding vector_cosine_ops);
119
+
120
+ -- Index for tenant filter (applied in every query)
121
+ CREATE INDEX ix_kb_embeddings_tenant
122
+ ON kb_embeddings (tenant_id);
123
+ ```
124
+
125
+ For pgvector extension setup (`CREATE EXTENSION IF NOT EXISTS vector;`, `UseVector()` on `NpgsqlDataSourceBuilder`, HNSW parameter tuning), see `infrastructure-neon-pgvector`.
126
+
127
+ > **Dimension match:** `vector(1536)` matches `text-embedding-3-small` (default 1536 dimensions). If the project switches models, re-create the column with the new dimension. Never mix models on a single column.
128
+
129
+ ---
130
+
131
+ ## VectorSearchTool (tool MAF)
132
+
133
+ Reference template: `templates/code/dotnet/ai-agents/VectorSearchTool.cs.template`
134
+
135
+ A sealed class with a `[Description]`-decorated method the agent calls when it needs context from the knowledge base.
136
+
137
+ ```csharp
138
+ // Registration — in the agent setup code
139
+ var tool = sp.GetRequiredService<VectorSearchTool>();
140
+ var agent = _modelRegistry.GetChatClient("text-default")
141
+ .AsAIAgent(
142
+ instructions: systemPrompt,
143
+ tools: [AIFunctionFactory.Create(tool.SearchAsync)]);
144
+ ```
145
+
146
+ The tool:
147
+ 1. Takes a natural-language `query` string (the only parameter the LLM sees).
148
+ 2. Calls `EmbeddingService.EmbedAsync(query)` to get a query vector.
149
+ 3. Runs a parameterized SQL `SELECT content FROM kb_embeddings WHERE tenant_id = @tenant ORDER BY embedding <=> @queryVec LIMIT 5`.
150
+ 4. Concatenates chunk contents and returns them as a single string — the agent uses this as grounding context.
151
+
152
+ The `tenant_id` filter is **never a tool parameter** (that would allow the LLM to cross tenant boundaries). It is injected via a constructor dependency — e.g., a scoped `ITenantContext` or a `Guid currentTenantId` resolved from the authenticated request. See §Multi-tenant below.
153
+
154
+ Note on raw Npgsql vs EF Core: this template uses `NpgsqlDataSource` (raw SQL) for the cosine-distance query, since pgvector operators (`<=>`) are easier to express and verify in raw SQL. If the project already wires pgvector through EF Core (`UseVector()`, `HasMethod("hnsw")`), the equivalent `db.Database.SqlQuery<>` form per `infrastructure-neon-pgvector` is equally valid — choose whichever is already in use.
155
+
156
+ ---
157
+
158
+ ## Exemplo de uso
159
+
160
+ ```csharp
161
+ // In an agent feature handler (the project's code)
162
+ using Microsoft.Agents.AI;
163
+ using Microsoft.Extensions.AI;
164
+
165
+ public sealed class KnowledgeAgentHandler(
166
+ ModelRegistry modelRegistry,
167
+ VectorSearchTool searchTool,
168
+ IPromptRepository prompts)
169
+ {
170
+ public async Task<string> AnswerAsync(string question, CancellationToken ct = default)
171
+ {
172
+ var systemPrompt = await prompts.GetActivePromptAsync("knowledge-agent", ct);
173
+
174
+ var agent = modelRegistry.GetChatClient("text-default")
175
+ .AsAIAgent(
176
+ instructions: systemPrompt,
177
+ tools: [AIFunctionFactory.Create(searchTool.SearchAsync)]);
178
+
179
+ var response = await agent.RunAsync(question, ct);
180
+ return response.Text;
181
+ }
182
+ }
183
+ ```
184
+
185
+ The agent decides autonomously when to call `VectorSearchTool.SearchAsync`. It receives the top-k chunks as a string and incorporates them into its response. The handler code stays clean — no manual retrieval loop.
186
+
187
+ ---
188
+
189
+ ## Multi-tenant
190
+
191
+ Each tenant's knowledge is partitioned by `tenant_id`:
192
+
193
+ - Every row in `kb_embeddings` has a `tenant_id UUID NOT NULL`.
194
+ - Every query filters on `tenant_id = @tenant` before any cosine-distance ordering.
195
+ - `VectorSearchTool` resolves `tenant_id` from the authenticated request context (injected at construction time) — **never from a tool parameter the LLM supplies**.
196
+ - For database-level enforcement, enable Row-Level Security:
197
+
198
+ ```sql
199
+ ALTER TABLE kb_embeddings ENABLE ROW LEVEL SECURITY;
200
+ -- See the project's RLS policy template (templates/code/sql/postgresql-rls-policy.sql)
201
+ -- for the policy pattern that binds app.current_tenant_id to the session.
202
+ ```
203
+
204
+ > **Rule:** never query across tenants without the filter. One missing `WHERE tenant_id = @tenant` leaks every tenant's data to the requester.
205
+
206
+ ---
207
+
208
+ ## Anti-patterns
209
+
210
+ | Anti-pattern | Why it's wrong | Right way |
211
+ |--------------|----------------|-----------|
212
+ | Hosted RAG (upload PDFs to a service) | Loses control of chunking, embedding, and storage; vendor lock-in | Custom RAG: own all three layers |
213
+ | Embeddings column without an index | Full table scan — 100 ms becomes 30 s at 100k rows | HNSW index on the embedding column always |
214
+ | One fixed chunk size for all document types | Contracts need dense sentences; catalogs need short snippets; mixed chunks degrade recall | Choose strategy per document type (see `data-vector-search-rag-chunking`) |
215
+ | `tenant_id` as a tool parameter the LLM passes | LLM can be prompted to pass any GUID — cross-tenant data leak | Inject `tenant_id` from auth context at construction; never expose as a model-facing parameter |
216
+ | Embedding with model A, querying with model B | Vectors live in different spaces — cosine distance is meaningless | One embedding model per column; re-index when switching models |
217
+ | `ORDER BY similarity DESC` in SQL | `<=>` returns distance (0 = identical), not similarity | `ORDER BY embedding <=> @queryVec` (ascending distance = most similar first) |
218
+
219
+ ---
220
+
221
+ ## Checklist (verifiable by morph-eval)
222
+
223
+ - [ ] EmbeddingService (concrete class) registered in DI; it injects IEmbeddingGenerator<string, Embedding<float>> for the project's chosen embedding model.
224
+ - [ ] Embedding model declared in `model-registry.json` under the `embeddings` alias.
225
+ - [ ] `kb_embeddings` table has `embedding vector(1536)` and HNSW index with `vector_cosine_ops`.
226
+ - [ ] `tenant_id` column present and filtered in every query.
227
+ - [ ] `VectorSearchTool.SearchAsync` takes only `query` + `ct` — no tenant param visible to the LLM.
228
+ - [ ] `VectorSearchTool` registered on the agent via `AIFunctionFactory.Create(tool.SearchAsync)`.
229
+ - [ ] No `Microsoft.SemanticKernel` references in the project.
230
+ - [ ] Vector dimensions consistent between embedding model and `vector(N)` column definition.
231
+ - [ ] RLS enabled on `kb_embeddings` (or equivalent app-level guard documented in `decisions.md`).
232
+ - [ ] Re-indexing strategy documented when document content changes (content hash check).
233
+
234
+ ---
235
+
236
+ ## References
237
+
238
+ - `ai-agents-setup` — MAF packages, `AsAIAgent`, `AIFunctionFactory.Create`
239
+ - `ai-agents-providers-model-registry` — `embeddings` alias, `IEmbeddingGenerator` wiring
240
+ - `data-vector-search-rag-chunking` — chunk strategies per document type
241
+ - `infrastructure-neon-pgvector` — extension setup, `UseVector()`, HNSW parameter tuning
242
+ - `ai-agents-vector-data-extensions` — the official `Microsoft.Extensions.VectorData` abstraction, and when it's worth adopting instead of this custom implementation
243
+
244
+ ---
245
+
246
+ *MORPH-SPEC by Polymorphism Tech — ai-agents/rag-custom-pgvector.md v1.0 (2026-05-19)*
@@ -76,6 +76,8 @@ Está adicionando um agente AI a um projeto .NET?
76
76
  - `Microsoft.Extensions.AI.OpenAI` 9.* — replaced by `Microsoft.Agents.AI.OpenAI` in GA.
77
77
  - Hosted agent services (Foundry Agent Service, etc.) — use direct models.
78
78
 
79
+ > **Exceção — conectores de Vector Store:** the ban above targets the SK **orchestration** surface (`Kernel`, `KernelFunction`, `ChatHistory`, `Kernel.InvokePromptAsync()`). It does **not** apply to `Microsoft.SemanticKernel.Connectors.*` packages (PgVector, Qdrant, SqlServer, CosmosNoSQL, InMemory) — those are `Microsoft.Extensions.VectorData` backend connectors with zero SK orchestration code, despite the package name. See `ai-agents-vector-data-extensions` before treating one of these as forbidden.
80
+
79
81
  ---
80
82
 
81
83
  ## 2. Quick Start (canonical minimal agent)
@@ -250,6 +252,7 @@ var instrumented = agent.WithOpenTelemetry();
250
252
  - `ai-agents-providers-model-registry` — provider-agnostic pattern
251
253
  - `ai-agents-structured-output` (Onda 2) — typed responses
252
254
  - `ai-agents-middleware-patterns` (Onda 2) — telemetry, retry, anti-hallucination, streaming agent responses
255
+ - `ai-agents-vector-data-extensions` — the `Microsoft.SemanticKernel.Connectors.*` exception to the Forbidden list above
253
256
  - Microsoft.Agents.AI docs: https://learn.microsoft.com/agent-framework/
254
257
  - Verified packages list (context7 /microsoft/agent-framework): retrieved 2026-05-19
255
258