@retinue/agentkit 0.1.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/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/adapters/bullmq/consumer.d.ts +33 -0
- package/dist/adapters/bullmq/consumer.js +41 -0
- package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
- package/dist/adapters/bullmq/dispatcher.js +160 -0
- package/dist/adapters/bullmq/export.d.ts +31 -0
- package/dist/adapters/bullmq/export.js +53 -0
- package/dist/adapters/bullmq/extraction.d.ts +42 -0
- package/dist/adapters/bullmq/extraction.js +63 -0
- package/dist/adapters/bullmq/index.d.ts +13 -0
- package/dist/adapters/bullmq/index.js +13 -0
- package/dist/adapters/bullmq/lock.d.ts +77 -0
- package/dist/adapters/bullmq/lock.js +126 -0
- package/dist/adapters/bullmq/queue.d.ts +50 -0
- package/dist/adapters/bullmq/queue.js +81 -0
- package/dist/adapters/memory/artifact-exports.d.ts +11 -0
- package/dist/adapters/memory/artifact-exports.js +102 -0
- package/dist/adapters/memory/artifacts.d.ts +15 -0
- package/dist/adapters/memory/artifacts.js +134 -0
- package/dist/adapters/memory/blobs.d.ts +7 -0
- package/dist/adapters/memory/blobs.js +27 -0
- package/dist/adapters/memory/evaluation.d.ts +18 -0
- package/dist/adapters/memory/evaluation.js +148 -0
- package/dist/adapters/memory/files.d.ts +27 -0
- package/dist/adapters/memory/files.js +0 -0
- package/dist/adapters/memory/flows.d.ts +16 -0
- package/dist/adapters/memory/flows.js +117 -0
- package/dist/adapters/memory/hitl.d.ts +9 -0
- package/dist/adapters/memory/hitl.js +130 -0
- package/dist/adapters/memory/idempotency.d.ts +13 -0
- package/dist/adapters/memory/idempotency.js +32 -0
- package/dist/adapters/memory/index.d.ts +39 -0
- package/dist/adapters/memory/index.js +107 -0
- package/dist/adapters/memory/knowledge.d.ts +43 -0
- package/dist/adapters/memory/knowledge.js +248 -0
- package/dist/adapters/memory/mcp.d.ts +9 -0
- package/dist/adapters/memory/mcp.js +37 -0
- package/dist/adapters/memory/message-store.d.ts +17 -0
- package/dist/adapters/memory/message-store.js +70 -0
- package/dist/adapters/memory/principal-memory.d.ts +7 -0
- package/dist/adapters/memory/principal-memory.js +83 -0
- package/dist/adapters/memory/runtime.d.ts +29 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/memory/sessions.d.ts +29 -0
- package/dist/adapters/memory/sessions.js +0 -0
- package/dist/adapters/memory/skills.d.ts +10 -0
- package/dist/adapters/memory/skills.js +41 -0
- package/dist/adapters/memory/thread-summary.d.ts +7 -0
- package/dist/adapters/memory/thread-summary.js +29 -0
- package/dist/adapters/memory/usage-limits.d.ts +13 -0
- package/dist/adapters/memory/usage-limits.js +72 -0
- package/dist/adapters/memory/usage.d.ts +16 -0
- package/dist/adapters/memory/usage.js +279 -0
- package/dist/adapters/otel/index.d.ts +111 -0
- package/dist/adapters/otel/index.js +133 -0
- package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
- package/dist/adapters/postgres/artifact-exports.js +117 -0
- package/dist/adapters/postgres/artifacts.d.ts +16 -0
- package/dist/adapters/postgres/artifacts.js +172 -0
- package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
- package/dist/adapters/postgres/checkpoint-store.js +34 -0
- package/dist/adapters/postgres/config.d.ts +15 -0
- package/dist/adapters/postgres/config.js +187 -0
- package/dist/adapters/postgres/conversation-store.d.ts +4 -0
- package/dist/adapters/postgres/conversation-store.js +82 -0
- package/dist/adapters/postgres/evaluation.d.ts +17 -0
- package/dist/adapters/postgres/evaluation.js +193 -0
- package/dist/adapters/postgres/file-content.d.ts +30 -0
- package/dist/adapters/postgres/file-content.js +111 -0
- package/dist/adapters/postgres/files.d.ts +19 -0
- package/dist/adapters/postgres/files.js +209 -0
- package/dist/adapters/postgres/flows.d.ts +20 -0
- package/dist/adapters/postgres/flows.js +206 -0
- package/dist/adapters/postgres/hitl.d.ts +5 -0
- package/dist/adapters/postgres/hitl.js +247 -0
- package/dist/adapters/postgres/index.d.ts +35 -0
- package/dist/adapters/postgres/index.js +35 -0
- package/dist/adapters/postgres/knowledge.d.ts +48 -0
- package/dist/adapters/postgres/knowledge.js +255 -0
- package/dist/adapters/postgres/memory.d.ts +14 -0
- package/dist/adapters/postgres/memory.js +194 -0
- package/dist/adapters/postgres/message-store.d.ts +11 -0
- package/dist/adapters/postgres/message-store.js +145 -0
- package/dist/adapters/postgres/migrations.d.ts +69 -0
- package/dist/adapters/postgres/migrations.js +1594 -0
- package/dist/adapters/postgres/pg-executor.d.ts +19 -0
- package/dist/adapters/postgres/pg-executor.js +32 -0
- package/dist/adapters/postgres/retention.d.ts +26 -0
- package/dist/adapters/postgres/retention.js +59 -0
- package/dist/adapters/postgres/rollups.d.ts +17 -0
- package/dist/adapters/postgres/rollups.js +267 -0
- package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
- package/dist/adapters/postgres/run-coordinator.js +98 -0
- package/dist/adapters/postgres/run-event-log.d.ts +26 -0
- package/dist/adapters/postgres/run-event-log.js +30 -0
- package/dist/adapters/postgres/run-store.d.ts +4 -0
- package/dist/adapters/postgres/run-store.js +199 -0
- package/dist/adapters/postgres/schema.d.ts +39 -0
- package/dist/adapters/postgres/schema.js +70 -0
- package/dist/adapters/postgres/session-state.d.ts +7 -0
- package/dist/adapters/postgres/session-state.js +99 -0
- package/dist/adapters/postgres/sql.d.ts +8 -0
- package/dist/adapters/postgres/sql.js +2 -0
- package/dist/adapters/postgres/transaction.d.ts +37 -0
- package/dist/adapters/postgres/transaction.js +93 -0
- package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
- package/dist/adapters/postgres/unit-of-work.js +8 -0
- package/dist/adapters/postgres/usage-limits.d.ts +15 -0
- package/dist/adapters/postgres/usage-limits.js +136 -0
- package/dist/adapters/postgres/usage.d.ts +15 -0
- package/dist/adapters/postgres/usage.js +226 -0
- package/dist/adapters/redis/index.d.ts +9 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/realtime.d.ts +74 -0
- package/dist/adapters/redis/realtime.js +112 -0
- package/dist/adapters/supabase/index.d.ts +88 -0
- package/dist/adapters/supabase/index.js +84 -0
- package/dist/adapters/supabase/realtime.d.ts +30 -0
- package/dist/adapters/supabase/realtime.js +53 -0
- package/dist/adapters/supabase/rls.d.ts +99 -0
- package/dist/adapters/supabase/rls.js +216 -0
- package/dist/adapters/supabase/storage.d.ts +50 -0
- package/dist/adapters/supabase/storage.js +207 -0
- package/dist/agents/agent.d.ts +66 -0
- package/dist/agents/agent.js +209 -0
- package/dist/agents/define.d.ts +21 -0
- package/dist/agents/define.js +63 -0
- package/dist/agents/engine.d.ts +98 -0
- package/dist/agents/engine.js +462 -0
- package/dist/agents/index.d.ts +50 -0
- package/dist/agents/index.js +17 -0
- package/dist/artifacts/index.d.ts +114 -0
- package/dist/artifacts/index.js +219 -0
- package/dist/authorization/index.d.ts +76 -0
- package/dist/authorization/index.js +76 -0
- package/dist/capabilities/index.d.ts +120 -0
- package/dist/capabilities/index.js +167 -0
- package/dist/capabilities/runtime.d.ts +89 -0
- package/dist/capabilities/runtime.js +84 -0
- package/dist/citations/index.d.ts +161 -0
- package/dist/citations/index.js +182 -0
- package/dist/context/assembler.d.ts +82 -0
- package/dist/context/assembler.js +129 -0
- package/dist/context/compaction.d.ts +45 -0
- package/dist/context/compaction.js +55 -0
- package/dist/context/index.d.ts +75 -0
- package/dist/context/index.js +17 -0
- package/dist/core/content-parts.d.ts +194 -0
- package/dist/core/content-parts.js +23 -0
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.js +9 -0
- package/dist/core/env.d.ts +25 -0
- package/dist/core/env.js +41 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +65 -0
- package/dist/core/events.d.ts +139 -0
- package/dist/core/events.js +99 -0
- package/dist/core/ids.d.ts +52 -0
- package/dist/core/ids.js +13 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/tokens.d.ts +22 -0
- package/dist/core/tokens.js +22 -0
- package/dist/core/validation.d.ts +34 -0
- package/dist/core/validation.js +176 -0
- package/dist/documents/extraction.d.ts +121 -0
- package/dist/documents/extraction.js +293 -0
- package/dist/documents/index.d.ts +199 -0
- package/dist/documents/index.js +65 -0
- package/dist/documents/parsers/pdf.d.ts +47 -0
- package/dist/documents/parsers/pdf.js +508 -0
- package/dist/documents/parsers/text.d.ts +59 -0
- package/dist/documents/parsers/text.js +325 -0
- package/dist/documents/read-tool.d.ts +52 -0
- package/dist/documents/read-tool.js +109 -0
- package/dist/documents/render.d.ts +29 -0
- package/dist/documents/render.js +59 -0
- package/dist/documents/vision.d.ts +159 -0
- package/dist/documents/vision.js +214 -0
- package/dist/entries/adapters-bullmq.d.ts +8 -0
- package/dist/entries/adapters-bullmq.js +8 -0
- package/dist/entries/adapters-otel.d.ts +13 -0
- package/dist/entries/adapters-otel.js +13 -0
- package/dist/entries/adapters-postgres.d.ts +10 -0
- package/dist/entries/adapters-postgres.js +10 -0
- package/dist/entries/adapters-redis.d.ts +3 -0
- package/dist/entries/adapters-redis.js +3 -0
- package/dist/entries/context.d.ts +20 -0
- package/dist/entries/context.js +20 -0
- package/dist/entries/flows.d.ts +15 -0
- package/dist/entries/flows.js +15 -0
- package/dist/entries/hitl.d.ts +10 -0
- package/dist/entries/hitl.js +10 -0
- package/dist/entries/knowledge.d.ts +18 -0
- package/dist/entries/knowledge.js +19 -0
- package/dist/entries/mcp.d.ts +10 -0
- package/dist/entries/mcp.js +10 -0
- package/dist/entries/observability.d.ts +14 -0
- package/dist/entries/observability.js +16 -0
- package/dist/entries/persistence.d.ts +11 -0
- package/dist/entries/persistence.js +11 -0
- package/dist/entries/providers.d.ts +14 -0
- package/dist/entries/providers.js +14 -0
- package/dist/entries/runtime.d.ts +13 -0
- package/dist/entries/runtime.js +13 -0
- package/dist/entries/server.d.ts +24 -0
- package/dist/entries/server.js +24 -0
- package/dist/entries/tools.d.ts +21 -0
- package/dist/entries/tools.js +21 -0
- package/dist/entries/usage.d.ts +10 -0
- package/dist/entries/usage.js +10 -0
- package/dist/evaluation/gate.d.ts +168 -0
- package/dist/evaluation/gate.js +180 -0
- package/dist/evaluation/graders.d.ts +125 -0
- package/dist/evaluation/graders.js +203 -0
- package/dist/evaluation/index.d.ts +120 -0
- package/dist/evaluation/index.js +183 -0
- package/dist/evaluation/judge.d.ts +75 -0
- package/dist/evaluation/judge.js +111 -0
- package/dist/export/index.d.ts +162 -0
- package/dist/export/index.js +363 -0
- package/dist/export/markdown.d.ts +19 -0
- package/dist/export/markdown.js +29 -0
- package/dist/export/pdf.d.ts +73 -0
- package/dist/export/pdf.js +407 -0
- package/dist/files/context.d.ts +97 -0
- package/dist/files/context.js +185 -0
- package/dist/files/index.d.ts +210 -0
- package/dist/files/index.js +338 -0
- package/dist/files/read-tool.d.ts +81 -0
- package/dist/files/read-tool.js +163 -0
- package/dist/files/turn-parts.d.ts +96 -0
- package/dist/files/turn-parts.js +171 -0
- package/dist/flows/index.d.ts +270 -0
- package/dist/flows/index.js +62 -0
- package/dist/flows/interpreter.d.ts +146 -0
- package/dist/flows/interpreter.js +426 -0
- package/dist/flows/runner.d.ts +145 -0
- package/dist/flows/runner.js +270 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/resolvers.d.ts +237 -0
- package/dist/graphql/resolvers.js +253 -0
- package/dist/graphql/schema.d.ts +11 -0
- package/dist/graphql/schema.js +258 -0
- package/dist/graphql/sse.d.ts +77 -0
- package/dist/graphql/sse.js +100 -0
- package/dist/hitl/approved-execution.d.ts +127 -0
- package/dist/hitl/approved-execution.js +177 -0
- package/dist/hitl/index.d.ts +79 -0
- package/dist/hitl/index.js +12 -0
- package/dist/hitl/service.d.ts +221 -0
- package/dist/hitl/service.js +268 -0
- package/dist/idempotency/index.d.ts +70 -0
- package/dist/idempotency/index.js +59 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +60 -0
- package/dist/knowledge/chunking.d.ts +57 -0
- package/dist/knowledge/chunking.js +158 -0
- package/dist/knowledge/index.d.ts +119 -0
- package/dist/knowledge/index.js +166 -0
- package/dist/knowledge/retrieval.d.ts +146 -0
- package/dist/knowledge/retrieval.js +170 -0
- package/dist/loadtest/harness.d.ts +168 -0
- package/dist/loadtest/harness.js +507 -0
- package/dist/loadtest/index.d.ts +13 -0
- package/dist/loadtest/index.js +13 -0
- package/dist/loadtest/injection.d.ts +89 -0
- package/dist/loadtest/injection.js +147 -0
- package/dist/loadtest/metrics.d.ts +197 -0
- package/dist/loadtest/metrics.js +160 -0
- package/dist/loadtest/runbooks.d.ts +28 -0
- package/dist/loadtest/runbooks.js +159 -0
- package/dist/loadtest/scenario.d.ts +104 -0
- package/dist/loadtest/scenario.js +208 -0
- package/dist/mcp/egress.d.ts +53 -0
- package/dist/mcp/egress.js +115 -0
- package/dist/mcp/index.d.ts +93 -0
- package/dist/mcp/index.js +33 -0
- package/dist/mcp/provider.d.ts +62 -0
- package/dist/mcp/provider.js +0 -0
- package/dist/models/index.d.ts +98 -0
- package/dist/models/index.js +74 -0
- package/dist/models/pricing.d.ts +24 -0
- package/dist/models/pricing.js +37 -0
- package/dist/models/provider-factory.d.ts +31 -0
- package/dist/models/provider-factory.js +67 -0
- package/dist/models/streaming.d.ts +145 -0
- package/dist/models/streaming.js +272 -0
- package/dist/models/vision.d.ts +38 -0
- package/dist/models/vision.js +62 -0
- package/dist/persistence/index.d.ts +1654 -0
- package/dist/persistence/index.js +226 -0
- package/dist/principal-memory/index.d.ts +106 -0
- package/dist/principal-memory/index.js +89 -0
- package/dist/retention/index.d.ts +89 -0
- package/dist/retention/index.js +70 -0
- package/dist/runtime/checkpoint.d.ts +37 -0
- package/dist/runtime/checkpoint.js +22 -0
- package/dist/runtime/index.d.ts +118 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/retry.d.ts +95 -0
- package/dist/runtime/retry.js +126 -0
- package/dist/runtime/serialization.d.ts +85 -0
- package/dist/runtime/serialization.js +95 -0
- package/dist/runtime/streaming.d.ts +54 -0
- package/dist/runtime/streaming.js +115 -0
- package/dist/runtime/worker.d.ts +130 -0
- package/dist/runtime/worker.js +405 -0
- package/dist/security/checklist.d.ts +53 -0
- package/dist/security/checklist.js +204 -0
- package/dist/security/findings.d.ts +56 -0
- package/dist/security/findings.js +168 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.js +14 -0
- package/dist/security/prompt-safety.d.ts +100 -0
- package/dist/security/prompt-safety.js +133 -0
- package/dist/server/boot.d.ts +32 -0
- package/dist/server/boot.js +36 -0
- package/dist/server/cli-worker.d.ts +37 -0
- package/dist/server/cli-worker.js +151 -0
- package/dist/server/cli.d.ts +27 -0
- package/dist/server/cli.js +74 -0
- package/dist/server/config.d.ts +42 -0
- package/dist/server/config.js +127 -0
- package/dist/server/health.d.ts +59 -0
- package/dist/server/health.js +90 -0
- package/dist/server/host.d.ts +39 -0
- package/dist/server/host.js +124 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +15 -0
- package/dist/server/main.d.ts +16 -0
- package/dist/server/main.js +31 -0
- package/dist/server/sse-route.d.ts +21 -0
- package/dist/server/sse-route.js +282 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +31 -0
- package/dist/skills/resolver.d.ts +54 -0
- package/dist/skills/resolver.js +121 -0
- package/dist/teams/index.d.ts +93 -0
- package/dist/teams/index.js +207 -0
- package/dist/telemetry/index.d.ts +157 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/instrument.d.ts +108 -0
- package/dist/telemetry/instrument.js +232 -0
- package/dist/telemetry/log-events.d.ts +17 -0
- package/dist/telemetry/log-events.js +58 -0
- package/dist/telemetry/metrics.d.ts +123 -0
- package/dist/telemetry/metrics.js +135 -0
- package/dist/telemetry/noop.d.ts +39 -0
- package/dist/telemetry/noop.js +143 -0
- package/dist/telemetry/redaction.d.ts +64 -0
- package/dist/telemetry/redaction.js +153 -0
- package/dist/telemetry/spans.d.ts +56 -0
- package/dist/telemetry/spans.js +78 -0
- package/dist/telemetry/trace-context.d.ts +55 -0
- package/dist/telemetry/trace-context.js +60 -0
- package/dist/toolkit/compute.d.ts +53 -0
- package/dist/toolkit/compute.js +152 -0
- package/dist/toolkit/data.d.ts +98 -0
- package/dist/toolkit/data.js +235 -0
- package/dist/toolkit/http.d.ts +113 -0
- package/dist/toolkit/http.js +205 -0
- package/dist/toolkit/index.d.ts +21 -0
- package/dist/toolkit/index.js +17 -0
- package/dist/toolkit/web.d.ts +107 -0
- package/dist/toolkit/web.js +147 -0
- package/dist/tools/define.d.ts +25 -0
- package/dist/tools/define.js +45 -0
- package/dist/tools/delegating.d.ts +132 -0
- package/dist/tools/delegating.js +211 -0
- package/dist/tools/index.d.ts +129 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/library/compute.d.ts +11 -0
- package/dist/tools/library/compute.js +46 -0
- package/dist/tools/library/data.d.ts +16 -0
- package/dist/tools/library/data.js +92 -0
- package/dist/tools/library/http.d.ts +28 -0
- package/dist/tools/library/http.js +71 -0
- package/dist/tools/library/index.d.ts +97 -0
- package/dist/tools/library/index.js +134 -0
- package/dist/tools/library/knowledge.d.ts +39 -0
- package/dist/tools/library/knowledge.js +58 -0
- package/dist/tools/library/web.d.ts +19 -0
- package/dist/tools/library/web.js +65 -0
- package/dist/tools/meta-tools.d.ts +19 -0
- package/dist/tools/meta-tools.js +36 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +291 -0
- package/dist/usage/index.d.ts +105 -0
- package/dist/usage/index.js +20 -0
- package/dist/usage/quota.d.ts +258 -0
- package/dist/usage/quota.js +510 -0
- package/dist/usage/recorder.d.ts +29 -0
- package/dist/usage/recorder.js +96 -0
- package/dist/usage/rollups.d.ts +121 -0
- package/dist/usage/rollups.js +157 -0
- package/dist/worker/export.d.ts +57 -0
- package/dist/worker/export.js +81 -0
- package/dist/worker/extraction.d.ts +57 -0
- package/dist/worker/extraction.js +84 -0
- package/dist/worker/main.d.ts +103 -0
- package/dist/worker/main.js +159 -0
- package/package.json +187 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid retrieval (#136).
|
|
3
|
+
*
|
|
4
|
+
* Semantic search misses what it was never trained on — a product code, an error number, a campaign
|
|
5
|
+
* identifier. An embedding of `ERR-4021` is an embedding of a string that looks like other strings. Keyword
|
|
6
|
+
* search misses everything phrased differently from the document. Neither is sufficient, which is why docs/05
|
|
7
|
+
* specifies both.
|
|
8
|
+
*
|
|
9
|
+
* **Fusion is reciprocal rank fusion, not weighted score addition**, and the reason is that the two scores are
|
|
10
|
+
* not comparable. A cosine similarity is bounded and roughly linear in relevance; `ts_rank_cd` is unbounded and
|
|
11
|
+
* corpus-dependent. Adding them with weights means choosing a constant that is wrong for some corpus, and the
|
|
12
|
+
* failure is silent — one signal quietly dominates and the hybrid is the worse of the two. RRF uses only the
|
|
13
|
+
* *rank*, so it needs no calibration and cannot be dominated:
|
|
14
|
+
*
|
|
15
|
+
* score(d) = Σ over signals of 1 / (K + rank(d))
|
|
16
|
+
*
|
|
17
|
+
* A document ranked first by one signal and absent from the other still beats one ranked fifth by both, which
|
|
18
|
+
* is the behaviour that makes hybrid better than either — the exact-term hit surfaces even though the semantic
|
|
19
|
+
* signal never saw it.
|
|
20
|
+
*
|
|
21
|
+
* **`K = 60`** is the value from Cormack, Clarke and Buettcher's original TREC work and the one every
|
|
22
|
+
* implementation since has used. It is large relative to the ranks that matter, which flattens the difference
|
|
23
|
+
* between rank 1 and rank 2 and lets *agreement between signals* outweigh a single signal's confidence.
|
|
24
|
+
*
|
|
25
|
+
* **The empty answer is a distinct shape.** `RetrievalOutcome` is a union, so "found nothing" has no success
|
|
26
|
+
* shape to hide in: a caller cannot accidentally treat an empty hit list as a weak answer, and the model is
|
|
27
|
+
* told there is nothing rather than handed the least-bad match — which it would cite. AC-4 is a type here, not
|
|
28
|
+
* a convention.
|
|
29
|
+
*/
|
|
30
|
+
import type { TenantId } from "../core/ids.js";
|
|
31
|
+
import type { KeywordIndex, KnowledgeChunk, KnowledgeSourceType, VectorIndex } from "../persistence/index.js";
|
|
32
|
+
import type { EmbeddingProvider } from "./index.js";
|
|
33
|
+
/** The rank-fusion constant. See the note above on why 60 and why rank rather than score. */
|
|
34
|
+
export declare const RRF_K = 60;
|
|
35
|
+
/** How many candidates each signal contributes before fusion. */
|
|
36
|
+
export declare const DEFAULT_CANDIDATES = 20;
|
|
37
|
+
/**
|
|
38
|
+
* Below this fused score, relative to the best result, nothing is returned.
|
|
39
|
+
*
|
|
40
|
+
* The floor that makes AC-4 real. Without one, every query returns *something* — the least-bad match — and a
|
|
41
|
+
* model handed the least-bad match cites it. 0.4 of the best score is loose enough not to discard a genuine
|
|
42
|
+
* second-best answer and tight enough to reject a corpus that simply has nothing to say.
|
|
43
|
+
*/
|
|
44
|
+
export declare const DEFAULT_RELEVANCE_FLOOR = 0.4;
|
|
45
|
+
export type RetrievalMode = "semantic" | "keyword" | "hybrid";
|
|
46
|
+
/** What a citation needs, derived from a hit so there is one shape rather than each caller's own (AC-6). */
|
|
47
|
+
export type SourceReference = {
|
|
48
|
+
readonly sourceType: KnowledgeSourceType;
|
|
49
|
+
readonly sourceId: string;
|
|
50
|
+
readonly chunkIndex: number;
|
|
51
|
+
readonly chunkId: string;
|
|
52
|
+
/** The heading path, when the chunker found one. What makes a citation point at a place, not a document. */
|
|
53
|
+
readonly locator?: string;
|
|
54
|
+
};
|
|
55
|
+
export type RetrievalHit = {
|
|
56
|
+
readonly chunk: KnowledgeChunk;
|
|
57
|
+
/** The fused score, 0–1 relative to the best hit. Not comparable across queries, only within one. */
|
|
58
|
+
readonly score: number;
|
|
59
|
+
/** Which signals found it, for explaining a result and for measuring the fusion. */
|
|
60
|
+
readonly signals: readonly RetrievalMode[];
|
|
61
|
+
readonly reference: SourceReference;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Why retrieval returned nothing.
|
|
65
|
+
*
|
|
66
|
+
* Separate values because the assistant's sentence differs: "nothing in your documents mentions that" is a
|
|
67
|
+
* different answer from "you have no indexed documents", and telling a user the first when the second is true
|
|
68
|
+
* sends them looking for content they never uploaded.
|
|
69
|
+
*/
|
|
70
|
+
export declare const NO_RESULT_REASONS: readonly ["nothing-indexed", "no-match", "below-threshold", "no-access"];
|
|
71
|
+
export type NoResultReason = (typeof NO_RESULT_REASONS)[number];
|
|
72
|
+
export type RetrievalOutcome = {
|
|
73
|
+
readonly found: true;
|
|
74
|
+
readonly hits: readonly RetrievalHit[];
|
|
75
|
+
readonly mode: RetrievalMode;
|
|
76
|
+
} | {
|
|
77
|
+
readonly found: false;
|
|
78
|
+
readonly reason: NoResultReason;
|
|
79
|
+
readonly message: string;
|
|
80
|
+
readonly mode: RetrievalMode;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Reorders a fused candidate set — AC-5.
|
|
84
|
+
*
|
|
85
|
+
* A port, and **switchable**, because a reranker's value is a claim that has to be provable. A cross-encoder is
|
|
86
|
+
* materially more expensive than the retrieval it reorders, so "we rerank" without a measured contribution is
|
|
87
|
+
* a cost nobody justified. Absent means fusion order stands, which is the honest default.
|
|
88
|
+
*/
|
|
89
|
+
export interface Reranker {
|
|
90
|
+
readonly id: string;
|
|
91
|
+
rerank(input: {
|
|
92
|
+
readonly query: string;
|
|
93
|
+
readonly candidates: readonly RetrievalHit[];
|
|
94
|
+
readonly limit: number;
|
|
95
|
+
}): Promise<readonly RetrievalHit[]>;
|
|
96
|
+
}
|
|
97
|
+
export type RetrieverDeps = {
|
|
98
|
+
readonly vector: VectorIndex;
|
|
99
|
+
readonly keyword: KeywordIndex;
|
|
100
|
+
readonly embeddings: EmbeddingProvider;
|
|
101
|
+
readonly reranker?: Reranker;
|
|
102
|
+
readonly candidates?: number;
|
|
103
|
+
/** Relative: how much worse than the best hit is still worth returning. */
|
|
104
|
+
readonly relevanceFloor?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Absolute: how similar a vector hit must be to be a candidate at all.
|
|
107
|
+
*
|
|
108
|
+
* Two floors because they answer different questions, and one cannot do both — a *relative* floor can never
|
|
109
|
+
* reject a uniformly poor result set, because something is always the best of it. This is what makes AC-4
|
|
110
|
+
* possible: without it the vector index returns every chunk it is asked for (0.5 is orthogonal, not
|
|
111
|
+
* "no match"), the best of them normalises to 1.0, and every query finds something.
|
|
112
|
+
*/
|
|
113
|
+
readonly semanticFloor?: number;
|
|
114
|
+
};
|
|
115
|
+
export type RetrieveInput = {
|
|
116
|
+
readonly query: string;
|
|
117
|
+
/**
|
|
118
|
+
* The subjects this caller may read.
|
|
119
|
+
*
|
|
120
|
+
* Required, and passed to *both* signals. A filter applied to one and not the other is a filter that does not
|
|
121
|
+
* exist — the unfiltered signal would surface the chunk and fusion would rank it.
|
|
122
|
+
*/
|
|
123
|
+
readonly authSubjects: readonly string[];
|
|
124
|
+
readonly limit: number;
|
|
125
|
+
readonly sourceTypes?: readonly KnowledgeSourceType[];
|
|
126
|
+
/** Defaults to `hybrid`. The other two exist so the hybrid claim can be measured against them. */
|
|
127
|
+
readonly mode?: RetrievalMode;
|
|
128
|
+
};
|
|
129
|
+
export declare const createRetriever: (deps: RetrieverDeps) => {
|
|
130
|
+
rerankerId: string | null;
|
|
131
|
+
retrieve(context: {
|
|
132
|
+
readonly tenantId: TenantId;
|
|
133
|
+
}, input: RetrieveInput): Promise<RetrievalOutcome>;
|
|
134
|
+
};
|
|
135
|
+
export type Retriever = ReturnType<typeof createRetriever>;
|
|
136
|
+
/**
|
|
137
|
+
* A reranker that promotes chunks matching the query's rare exact terms.
|
|
138
|
+
*
|
|
139
|
+
* Deliberately simple and deliberately *not* a model: this exists so AC-5's "switchable and measured" has
|
|
140
|
+
* something real to switch on, and so the measurement harness has a baseline that is better than nothing and
|
|
141
|
+
* obviously cheaper than a cross-encoder. It promotes a candidate containing a query term verbatim, which is
|
|
142
|
+
* the signal fusion is weakest on — a rank-based fusion cannot know that one of the two signals matched an
|
|
143
|
+
* exact identifier rather than a common word.
|
|
144
|
+
*/
|
|
145
|
+
export declare const createExactTermReranker: () => Reranker;
|
|
146
|
+
//# sourceMappingURL=retrieval.d.ts.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid retrieval (#136).
|
|
3
|
+
*
|
|
4
|
+
* Semantic search misses what it was never trained on — a product code, an error number, a campaign
|
|
5
|
+
* identifier. An embedding of `ERR-4021` is an embedding of a string that looks like other strings. Keyword
|
|
6
|
+
* search misses everything phrased differently from the document. Neither is sufficient, which is why docs/05
|
|
7
|
+
* specifies both.
|
|
8
|
+
*
|
|
9
|
+
* **Fusion is reciprocal rank fusion, not weighted score addition**, and the reason is that the two scores are
|
|
10
|
+
* not comparable. A cosine similarity is bounded and roughly linear in relevance; `ts_rank_cd` is unbounded and
|
|
11
|
+
* corpus-dependent. Adding them with weights means choosing a constant that is wrong for some corpus, and the
|
|
12
|
+
* failure is silent — one signal quietly dominates and the hybrid is the worse of the two. RRF uses only the
|
|
13
|
+
* *rank*, so it needs no calibration and cannot be dominated:
|
|
14
|
+
*
|
|
15
|
+
* score(d) = Σ over signals of 1 / (K + rank(d))
|
|
16
|
+
*
|
|
17
|
+
* A document ranked first by one signal and absent from the other still beats one ranked fifth by both, which
|
|
18
|
+
* is the behaviour that makes hybrid better than either — the exact-term hit surfaces even though the semantic
|
|
19
|
+
* signal never saw it.
|
|
20
|
+
*
|
|
21
|
+
* **`K = 60`** is the value from Cormack, Clarke and Buettcher's original TREC work and the one every
|
|
22
|
+
* implementation since has used. It is large relative to the ranks that matter, which flattens the difference
|
|
23
|
+
* between rank 1 and rank 2 and lets *agreement between signals* outweigh a single signal's confidence.
|
|
24
|
+
*
|
|
25
|
+
* **The empty answer is a distinct shape.** `RetrievalOutcome` is a union, so "found nothing" has no success
|
|
26
|
+
* shape to hide in: a caller cannot accidentally treat an empty hit list as a weak answer, and the model is
|
|
27
|
+
* told there is nothing rather than handed the least-bad match — which it would cite. AC-4 is a type here, not
|
|
28
|
+
* a convention.
|
|
29
|
+
*/
|
|
30
|
+
import { SEMANTIC_RELEVANCE_FLOOR } from "../persistence/index.js";
|
|
31
|
+
/** The rank-fusion constant. See the note above on why 60 and why rank rather than score. */
|
|
32
|
+
export const RRF_K = 60;
|
|
33
|
+
/** How many candidates each signal contributes before fusion. */
|
|
34
|
+
export const DEFAULT_CANDIDATES = 20;
|
|
35
|
+
/**
|
|
36
|
+
* Below this fused score, relative to the best result, nothing is returned.
|
|
37
|
+
*
|
|
38
|
+
* The floor that makes AC-4 real. Without one, every query returns *something* — the least-bad match — and a
|
|
39
|
+
* model handed the least-bad match cites it. 0.4 of the best score is loose enough not to discard a genuine
|
|
40
|
+
* second-best answer and tight enough to reject a corpus that simply has nothing to say.
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_RELEVANCE_FLOOR = 0.4;
|
|
43
|
+
/**
|
|
44
|
+
* Why retrieval returned nothing.
|
|
45
|
+
*
|
|
46
|
+
* Separate values because the assistant's sentence differs: "nothing in your documents mentions that" is a
|
|
47
|
+
* different answer from "you have no indexed documents", and telling a user the first when the second is true
|
|
48
|
+
* sends them looking for content they never uploaded.
|
|
49
|
+
*/
|
|
50
|
+
export const NO_RESULT_REASONS = ["nothing-indexed", "no-match", "below-threshold", "no-access"];
|
|
51
|
+
const referenceFor = (chunk) => ({
|
|
52
|
+
sourceType: chunk.sourceType,
|
|
53
|
+
sourceId: chunk.sourceId,
|
|
54
|
+
chunkIndex: chunk.chunkIndex,
|
|
55
|
+
chunkId: chunk.id,
|
|
56
|
+
...(chunk.locator === undefined ? {} : { locator: chunk.locator }),
|
|
57
|
+
});
|
|
58
|
+
const NO_RESULT_MESSAGES = {
|
|
59
|
+
"nothing-indexed": "There is no indexed material to search yet.",
|
|
60
|
+
"no-match": "Nothing in the available material matches that.",
|
|
61
|
+
"below-threshold": "Nothing in the available material is a close enough match to rely on.",
|
|
62
|
+
"no-access": "There is no material you have access to that matches that.",
|
|
63
|
+
};
|
|
64
|
+
export const createRetriever = (deps) => {
|
|
65
|
+
const candidateCount = deps.candidates ?? DEFAULT_CANDIDATES;
|
|
66
|
+
const floor = deps.relevanceFloor ?? DEFAULT_RELEVANCE_FLOOR;
|
|
67
|
+
const semanticFloor = deps.semanticFloor ?? SEMANTIC_RELEVANCE_FLOOR;
|
|
68
|
+
return {
|
|
69
|
+
rerankerId: deps.reranker?.id ?? null,
|
|
70
|
+
async retrieve(context, input) {
|
|
71
|
+
const mode = input.mode ?? "hybrid";
|
|
72
|
+
// Checked before either index is asked. An empty subject list is not a query with no results — it is a
|
|
73
|
+
// caller with no access, and the two want different sentences.
|
|
74
|
+
if (input.authSubjects.length === 0)
|
|
75
|
+
return { found: false, reason: "no-access", message: NO_RESULT_MESSAGES["no-access"], mode };
|
|
76
|
+
if (input.query.trim() === "")
|
|
77
|
+
return { found: false, reason: "no-match", message: NO_RESULT_MESSAGES["no-match"], mode };
|
|
78
|
+
const scope = {
|
|
79
|
+
tenantId: context.tenantId,
|
|
80
|
+
authSubjects: input.authSubjects,
|
|
81
|
+
limit: candidateCount,
|
|
82
|
+
...(input.sourceTypes === undefined ? {} : { sourceTypes: input.sourceTypes }),
|
|
83
|
+
};
|
|
84
|
+
const semantic = mode === "keyword"
|
|
85
|
+
? []
|
|
86
|
+
: await (async () => {
|
|
87
|
+
const [embedding] = await deps.embeddings.embed([input.query]);
|
|
88
|
+
// The absolute floor goes to the index, not applied afterwards: a chunk that shares nothing with
|
|
89
|
+
// the query must not be a candidate, because fusion would rank it first in a result set where
|
|
90
|
+
// nothing is good.
|
|
91
|
+
return deps.vector.search({ ...scope, embedding: embedding ?? [], minScore: semanticFloor });
|
|
92
|
+
})();
|
|
93
|
+
const lexical = mode === "semantic" ? [] : await deps.keyword.search({ ...scope, query: input.query });
|
|
94
|
+
if (semantic.length === 0 && lexical.length === 0)
|
|
95
|
+
return { found: false, reason: "no-match", message: NO_RESULT_MESSAGES["no-match"], mode };
|
|
96
|
+
// RRF. Rank, not score: see the note at the top on why adding two incomparable scales fails silently.
|
|
97
|
+
const fused = new Map();
|
|
98
|
+
const contribute = (hits, signal) => {
|
|
99
|
+
hits.forEach((hit, rank) => {
|
|
100
|
+
const existing = fused.get(hit.chunk.id);
|
|
101
|
+
const increment = 1 / (RRF_K + rank + 1);
|
|
102
|
+
if (existing === undefined)
|
|
103
|
+
fused.set(hit.chunk.id, { chunk: hit.chunk, score: increment, signals: new Set([signal]) });
|
|
104
|
+
else {
|
|
105
|
+
existing.score += increment;
|
|
106
|
+
existing.signals.add(signal);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
contribute(semantic, "semantic");
|
|
111
|
+
contribute(lexical, "keyword");
|
|
112
|
+
const ordered = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.chunk.id.localeCompare(b.chunk.id));
|
|
113
|
+
const best = ordered[0]?.score ?? 0;
|
|
114
|
+
const candidates = ordered.map((entry) => ({
|
|
115
|
+
chunk: entry.chunk,
|
|
116
|
+
// Normalised against the best fused score, so the floor means the same thing whatever the corpus.
|
|
117
|
+
score: best === 0 ? 0 : entry.score / best,
|
|
118
|
+
signals: [...entry.signals],
|
|
119
|
+
reference: referenceFor(entry.chunk),
|
|
120
|
+
}));
|
|
121
|
+
const relevant = candidates.filter((hit) => hit.score >= floor);
|
|
122
|
+
if (relevant.length === 0)
|
|
123
|
+
// Something was found and none of it was good enough. A distinct reason from "no match", because the
|
|
124
|
+
// useful answer differs: one says rephrase, the other says there is nothing there.
|
|
125
|
+
return { found: false, reason: "below-threshold", message: NO_RESULT_MESSAGES["below-threshold"], mode };
|
|
126
|
+
const hits = deps.reranker === undefined
|
|
127
|
+
? relevant.slice(0, input.limit)
|
|
128
|
+
: await deps.reranker.rerank({ query: input.query, candidates: relevant, limit: input.limit });
|
|
129
|
+
// A reranker that returned nothing is a reranker that broke; fusion order is a better answer than an
|
|
130
|
+
// empty one, and a silent empty result here would look like AC-4 working.
|
|
131
|
+
return hits.length === 0
|
|
132
|
+
? { found: true, hits: relevant.slice(0, input.limit), mode }
|
|
133
|
+
: { found: true, hits: hits.slice(0, input.limit), mode };
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* A reranker that promotes chunks matching the query's rare exact terms.
|
|
139
|
+
*
|
|
140
|
+
* Deliberately simple and deliberately *not* a model: this exists so AC-5's "switchable and measured" has
|
|
141
|
+
* something real to switch on, and so the measurement harness has a baseline that is better than nothing and
|
|
142
|
+
* obviously cheaper than a cross-encoder. It promotes a candidate containing a query term verbatim, which is
|
|
143
|
+
* the signal fusion is weakest on — a rank-based fusion cannot know that one of the two signals matched an
|
|
144
|
+
* exact identifier rather than a common word.
|
|
145
|
+
*/
|
|
146
|
+
export const createExactTermReranker = () => ({
|
|
147
|
+
id: "exact-term",
|
|
148
|
+
async rerank({ query, candidates, limit }) {
|
|
149
|
+
const terms = (query.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []).filter(
|
|
150
|
+
// Only terms that look like identifiers: containing a digit or a hyphen. A common word appearing
|
|
151
|
+
// verbatim is not evidence of anything, and boosting on it would just re-rank by word frequency.
|
|
152
|
+
(t) => /[0-9]/.test(t) || t.includes("-"));
|
|
153
|
+
if (terms.length === 0)
|
|
154
|
+
return candidates.slice(0, limit);
|
|
155
|
+
const boosted = candidates.map((hit) => {
|
|
156
|
+
const content = hit.chunk.content.toLowerCase();
|
|
157
|
+
const matched = terms.filter((t) => content.includes(t)).length;
|
|
158
|
+
return { hit, matched };
|
|
159
|
+
});
|
|
160
|
+
return boosted
|
|
161
|
+
.sort((a, b) => b.matched !== a.matched
|
|
162
|
+
? b.matched - a.matched
|
|
163
|
+
: // Fusion order within a tier, so reranking only ever *promotes* on evidence and never reshuffles
|
|
164
|
+
// arbitrarily.
|
|
165
|
+
b.hit.score - a.hit.score)
|
|
166
|
+
.map((b) => b.hit)
|
|
167
|
+
.slice(0, limit);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=retrieval.js.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The load and soak driver — AC-1, AC-2, AC-3, AC-4.
|
|
3
|
+
*
|
|
4
|
+
* Drives the **real durable path**: a real Postgres schema, a real BullMQ queue on a real Redis, several real
|
|
5
|
+
* `WorkerRuntime` instances competing for work, real atomic claims, real leases, real checkpoints and a real
|
|
6
|
+
* event log. The only synthetic part is the agent engine, and that is not a shortcut — see `scenario.ts`.
|
|
7
|
+
*
|
|
8
|
+
* What this is *not* is a request against a deployed HTTP endpoint. #144 asks for that, and there is no deployed
|
|
9
|
+
* instance yet; the layers this cannot reach are the GraphQL server and the process boundary between host and
|
|
10
|
+
* worker. Everything below them is exercised for real. That boundary is stated in the report the harness writes,
|
|
11
|
+
* not left for someone to infer from a green tick.
|
|
12
|
+
*
|
|
13
|
+
* Structured so the driver returns **data** and the judging happens in `metrics.ts` and `injection.ts`. A
|
|
14
|
+
* harness that both ran the load and decided whether it passed would be a harness whose verdict nobody could
|
|
15
|
+
* test, and the verdict is the part most likely to be quietly wrong.
|
|
16
|
+
*/
|
|
17
|
+
import { type JobConsumer } from "../worker/main.js";
|
|
18
|
+
import type { ConversationId, RunId, TenantId } from "../core/ids.js";
|
|
19
|
+
import type { JobDispatcher } from "../runtime/index.js";
|
|
20
|
+
import type { SqlExecutor } from "../adapters/postgres/sql.js";
|
|
21
|
+
import { type EffectLedger, type TrafficShape } from "./scenario.js";
|
|
22
|
+
import { detectGrowth, type LoadStep, type ResourceSample } from "./metrics.js";
|
|
23
|
+
export type HarnessConfig = {
|
|
24
|
+
readonly tenantId: TenantId;
|
|
25
|
+
/** Worker runtimes competing for the same queue. Several, or a claim race is never exercised. */
|
|
26
|
+
readonly workers: number;
|
|
27
|
+
readonly concurrency: number;
|
|
28
|
+
/** Short, so a killed worker's lease expires inside a test rather than inside a coffee break. */
|
|
29
|
+
readonly leaseMs: number;
|
|
30
|
+
readonly reapEveryMs: number;
|
|
31
|
+
readonly traffic: TrafficShape;
|
|
32
|
+
readonly seed: number;
|
|
33
|
+
/**
|
|
34
|
+
* The queue's bound. Past it, admission is refused.
|
|
35
|
+
*
|
|
36
|
+
* The mechanism AC-4 asks for. Without a bound, "overload" means an unbounded in-memory backlog, which is the
|
|
37
|
+
* failure mode rather than the test.
|
|
38
|
+
*/
|
|
39
|
+
readonly maxQueueDepth: number;
|
|
40
|
+
readonly sampleEveryMs: number;
|
|
41
|
+
};
|
|
42
|
+
export declare const DEFAULT_HARNESS: Omit<HarnessConfig, "tenantId">;
|
|
43
|
+
/**
|
|
44
|
+
* An in-process queue with a **bounded** depth and an explicit refusal.
|
|
45
|
+
*
|
|
46
|
+
* Used where the point is backpressure rather than Redis. Deliberately not BullMQ for the overload case: BullMQ's
|
|
47
|
+
* depth is Redis memory, so "bounded queueing" there is a Redis configuration question and the assertion would
|
|
48
|
+
* be about Redis. Here the bound is ours and the refusal is the platform's.
|
|
49
|
+
*/
|
|
50
|
+
export declare class QueueFull extends Error {
|
|
51
|
+
readonly depth: number;
|
|
52
|
+
readonly code = "resource-exhausted";
|
|
53
|
+
readonly retryable = true;
|
|
54
|
+
constructor(depth: number);
|
|
55
|
+
}
|
|
56
|
+
export type BoundedQueue = {
|
|
57
|
+
readonly dispatcher: JobDispatcher;
|
|
58
|
+
/**
|
|
59
|
+
* A **fresh consumer per worker**, not one shared object.
|
|
60
|
+
*
|
|
61
|
+
* `JobConsumer.stop()` has no handle to say *which* worker is stopping, so a shared consumer's `stop` had to
|
|
62
|
+
* clear every registration — which meant killing one worker silently stopped all three. The worker-kill
|
|
63
|
+
* injection then hung forever waiting for a drain that could never happen, produced no output at all, and it
|
|
64
|
+
* took a process listing to find. Each worker gets its own view, closing over its own slot.
|
|
65
|
+
*/
|
|
66
|
+
consumerFor(): JobConsumer;
|
|
67
|
+
depth(): number;
|
|
68
|
+
peakDepth(): number;
|
|
69
|
+
refused(): number;
|
|
70
|
+
/** Jobs handed to a worker more than once. A pass that needed retries is not a clean first-attempt pass. */
|
|
71
|
+
retried(): number;
|
|
72
|
+
drained(): Promise<void>;
|
|
73
|
+
};
|
|
74
|
+
export declare const createBoundedQueue: (maxDepth: number, perWorkerConcurrency?: number) => BoundedQueue;
|
|
75
|
+
export type WorkerHandle = {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
/** Stop without draining — the worker-kill injector. */
|
|
78
|
+
kill(): Promise<void>;
|
|
79
|
+
shutdown(): Promise<void>;
|
|
80
|
+
};
|
|
81
|
+
export type Harness = {
|
|
82
|
+
readonly effects: EffectLedger;
|
|
83
|
+
readonly queue: BoundedQueue;
|
|
84
|
+
readonly workers: readonly WorkerHandle[];
|
|
85
|
+
/** Admit and enqueue one run. Throws `QueueFull` when the queue is at its bound. */
|
|
86
|
+
admit(input: {
|
|
87
|
+
conversationId: ConversationId;
|
|
88
|
+
runId: RunId;
|
|
89
|
+
}): Promise<void>;
|
|
90
|
+
/** Runs in a terminal state, from the store — not from the harness's own bookkeeping. */
|
|
91
|
+
terminalCount(): Promise<number>;
|
|
92
|
+
/** Every run status and its count, so a paused run is never miscounted as a failure. */
|
|
93
|
+
statusCounts(): Promise<Readonly<Record<string, number>>>;
|
|
94
|
+
/**
|
|
95
|
+
* Approve everything waiting, and re-enqueue it.
|
|
96
|
+
*
|
|
97
|
+
* A load test that left approvals pending would report them as lost work, which is the opposite of the truth:
|
|
98
|
+
* a run waiting for a human is the platform holding state correctly, and it is the longest-lived state it has.
|
|
99
|
+
* Deciding them here is also the only way the resume path is exercised under load.
|
|
100
|
+
*
|
|
101
|
+
* Returns how long each run waited, so approval wait time is measured rather than assumed.
|
|
102
|
+
*/
|
|
103
|
+
approvePending(): Promise<readonly number[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Wait until nothing with this id prefix is still in flight, then report what happened.
|
|
106
|
+
*
|
|
107
|
+
* Necessary because "the queue is empty" is not "the work is done": the queue empties when the last job reaches
|
|
108
|
+
* a worker. Without settling, a step's slowest runs are still executing when it is measured, and they count as
|
|
109
|
+
* failures — my first staircase reported `mode: errors` at 20/s for exactly that reason, which would have sent
|
|
110
|
+
* an operator hunting a failure that was really my clock.
|
|
111
|
+
*
|
|
112
|
+
* A run still non-terminal when the timeout expires *does* count as stuck. That is the honest line: waiting
|
|
113
|
+
* forever would hide a genuine hang, and not waiting at all reports one that is not there.
|
|
114
|
+
*/
|
|
115
|
+
settle(input: {
|
|
116
|
+
readonly idPrefix: string;
|
|
117
|
+
readonly timeoutMs: number;
|
|
118
|
+
}): Promise<{
|
|
119
|
+
readonly completed: number;
|
|
120
|
+
readonly failed: number;
|
|
121
|
+
readonly stuck: number;
|
|
122
|
+
readonly stuckByStatus?: Readonly<Record<string, number>>;
|
|
123
|
+
readonly approvalWaitsMs: readonly number[];
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* End-to-end latency per finished run, in ms, from the store's own timestamps.
|
|
127
|
+
*
|
|
128
|
+
* The number that matters. Admission latency is a property of the enqueue and stays flat under any backlog;
|
|
129
|
+
* what a user experiences is admission to terminal, and only the store knows both ends.
|
|
130
|
+
*/
|
|
131
|
+
terminalLatencies(idPrefix: string): Promise<readonly number[]>;
|
|
132
|
+
admittedCount(): number;
|
|
133
|
+
sample(): ResourceSample;
|
|
134
|
+
stop(): Promise<void>;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Build the harness over an already-migrated schema.
|
|
138
|
+
*
|
|
139
|
+
* Takes a `SqlExecutor` rather than a URL so the caller owns the connection: the database-unavailable injector
|
|
140
|
+
* needs to stop the server underneath a live pool, and a harness that owned the pool would reconnect on its own
|
|
141
|
+
* schedule and the injector could not control the window.
|
|
142
|
+
*/
|
|
143
|
+
export declare const createHarness: (input: {
|
|
144
|
+
readonly sql: SqlExecutor;
|
|
145
|
+
readonly config: HarnessConfig;
|
|
146
|
+
readonly now?: () => number;
|
|
147
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
148
|
+
}) => Promise<Harness>;
|
|
149
|
+
/**
|
|
150
|
+
* One step of the load staircase.
|
|
151
|
+
*
|
|
152
|
+
* Offers a fixed rate for a fixed duration, then waits for the queue to drain before measuring. Measuring
|
|
153
|
+
* without draining would attribute a step's backlog to the *next* step and the staircase would show capacity
|
|
154
|
+
* that is really borrowed from earlier.
|
|
155
|
+
*/
|
|
156
|
+
export declare const runLoadStep: (input: {
|
|
157
|
+
readonly harness: Harness;
|
|
158
|
+
readonly offeredPerSecond: number;
|
|
159
|
+
readonly durationMs: number;
|
|
160
|
+
readonly tenantId: TenantId;
|
|
161
|
+
readonly startIndex: number;
|
|
162
|
+
/** How long to wait for a step's work to finish before calling the remainder stuck. */
|
|
163
|
+
readonly settleTimeoutMs?: number;
|
|
164
|
+
readonly now?: () => number;
|
|
165
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
166
|
+
}) => Promise<LoadStep>;
|
|
167
|
+
export { detectGrowth };
|
|
168
|
+
//# sourceMappingURL=harness.d.ts.map
|