@johpaz/hive-sdk 0.4.6 → 0.4.7
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/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +2 -2
- package/docs/UPGRADING.md +20 -0
- package/package.json +1 -1
- package/packages/core/src/agent/agent-loop.ts +3 -1
- package/packages/core/src/agent/context-compiler.ts +6 -2
- package/packages/core/src/agent/reflector.ts +23 -10
- package/packages/core/src/storage/causal-events.ts +50 -19
- package/packages/core/src/storage/index.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -17,9 +17,26 @@
|
|
|
17
17
|
procedencia, hash y una prueba funcional del OOXML generado.
|
|
18
18
|
- Añadidas las guías `docs/UPGRADING.md` y
|
|
19
19
|
`docs/SECURITY-GUARDRAILS.md` para operación y auditoría.
|
|
20
|
+
- **Requiere `@johpaz/hive-db` ^0.5.1** (antes ^0.4.0). Trae las lecturas del
|
|
21
|
+
log causal acotadas por agente (`agents` en `causalThread`, `toolStats` y
|
|
22
|
+
`buildAgentContext`), de las que depende el aislamiento entre inquilinos del
|
|
23
|
+
log causal descrito en *Corregido*.
|
|
20
24
|
|
|
21
25
|
### Corregido
|
|
22
26
|
|
|
27
|
+
- **Con un tenant activo, el log causal se apagaba en lugar de acotarse.**
|
|
28
|
+
`causalThread`, `toolStats` y `buildAgentContext` recorrían todos los shards
|
|
29
|
+
de la base, así que con un tenant en scope `causalReadsEnabled()` las apagaba:
|
|
30
|
+
en un host multi-inquilino el reflector G9 y el contexto causal del
|
|
31
|
+
compilador no corrían nunca. Ahora las tres lecturas van siempre acotadas a
|
|
32
|
+
los agentes que corresponden —el agente del turno, o los del lote de trazas— y
|
|
33
|
+
el apagado desaparece. Además el shard de cada evento pasa a ser
|
|
34
|
+
`causalAgentKey(agentId)`: con tenant lleva el tenant delante (`t_…:agente`,
|
|
35
|
+
la misma forma que los ids del índice BM25), así que dos inquilinos con un
|
|
36
|
+
agente del mismo id ya no comparten shard. Una lista de agentes vacía se salta
|
|
37
|
+
la lectura en vez de pasarse al motor, que la trataría como "todos los
|
|
38
|
+
shards". Cubierto por `test/causal-tenant-scope.test.ts`.
|
|
39
|
+
|
|
23
40
|
- **`browser_scrape` extraía con una tool que no ve lo que el navegador
|
|
24
41
|
renderizó.** La skill existe para sitios dinámicos, y su paso de extracción
|
|
25
42
|
usaba `web_fetch`, que vuelve a pedir la URL al servidor y recibe el HTML sin
|
|
@@ -194,6 +211,11 @@
|
|
|
194
211
|
|
|
195
212
|
### Cambiado
|
|
196
213
|
|
|
214
|
+
- **El `toolStats` del reflector se acota a los agentes del lote**, también sin
|
|
215
|
+
tenant. Antes sumaba el historial de la tool de todos los agentes de la base;
|
|
216
|
+
ahora el de los agentes cuyas trazas se están analizando. Es la misma
|
|
217
|
+
semántica con y sin tenant, y no recorre el log entero.
|
|
218
|
+
|
|
197
219
|
- **Los tests que manejan un navegador real son opt-in (`BROWSER_TESTS=1`).**
|
|
198
220
|
Su guarda era `isWebViewSupported()`, que sólo comprueba que exista un binario
|
|
199
221
|
de Chromium — no que arranque. En un runner de CI (contenedor, a menudo root)
|
package/README.md
CHANGED
|
@@ -444,8 +444,8 @@ unsubscribeCanvas(handler);
|
|
|
444
444
|
|
|
445
445
|
## Storage
|
|
446
446
|
|
|
447
|
-
HiveDB (`@johpaz/hive-db`), un motor embebido con colecciones
|
|
448
|
-
índice BM25. Reemplazó a SQLite + FTS5 en 0.1.5.
|
|
447
|
+
HiveDB (`@johpaz/hive-db` 0.5.1 o posterior), un motor embebido con colecciones
|
|
448
|
+
de documentos e índice BM25. Reemplazó a SQLite + FTS5 en 0.1.5.
|
|
449
449
|
|
|
450
450
|
```typescript
|
|
451
451
|
import { ensureHiveDb, col } from "@johpaz/hive-sdk";
|
package/docs/UPGRADING.md
CHANGED
|
@@ -54,6 +54,26 @@ Estos adaptadores están en la frontera con el runtime. No deben reemplazarse po
|
|
|
54
54
|
`any`, `@ts-ignore` o `@ts-expect-error`: hacerlo convertiría una incompatibilidad
|
|
55
55
|
real de plataforma en un falso resultado verde.
|
|
56
56
|
|
|
57
|
+
## hive-db 0.5.1 y log causal por tenant
|
|
58
|
+
|
|
59
|
+
Hive SDK requiere **`@johpaz/hive-db` 0.5.1 o posterior**. Llega como
|
|
60
|
+
dependencia del SDK, así que una aplicación consumidora no la declara. Lo que
|
|
61
|
+
sigue sólo importa con el log causal encendido (`HIVE_CAUSAL_LOG=true` o
|
|
62
|
+
`causalLog.enabled`):
|
|
63
|
+
|
|
64
|
+
- Con un tenant activo (`runInTenant`) el reflector y el contexto causal del
|
|
65
|
+
compilador vuelven a funcionar. Antes se apagaban; ahora leen acotado a los
|
|
66
|
+
agentes del turno o del lote de trazas.
|
|
67
|
+
- La clave de shard de cada evento es `causalAgentKey(agentId)`: sin tenant, el
|
|
68
|
+
id del agente tal cual; con tenant, `t_…:agentId`. Los eventos que un host
|
|
69
|
+
haya escrito con tenant antes de esta versión quedaron con el id crudo y las
|
|
70
|
+
lecturas acotadas ya no los ven. Sin tenant no cambia nada.
|
|
71
|
+
- El `toolStats` del reflector cuenta el historial de los agentes del lote, no
|
|
72
|
+
el de toda la base, con y sin tenant.
|
|
73
|
+
- `watchCausalEvents` con tenant sigue exigiendo `agentId`: se le pasa el id
|
|
74
|
+
crudo y el SDK lo califica. Los eventos que entrega traen en `agentId` la
|
|
75
|
+
clave del shard; `formatCausalEvent` la muestra sin el tenant.
|
|
76
|
+
|
|
57
77
|
## Compatibilidad y CI
|
|
58
78
|
|
|
59
79
|
Los workflows fijan Bun 1.4.2, instalan con `--frozen-lockfile`, ejecutan el
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johpaz/hive-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Hive SDK — The Agent Harness SDK. Build, deploy, and scale AI agent applications with multi-channel support, context engineering, and swarm orchestration.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { logger } from "../utils/logger.ts"
|
|
17
17
|
import { col, fromIndexable } from "../storage/hive.ts"
|
|
18
18
|
import { getHiveDb } from "../storage/hivedb.ts"
|
|
19
|
+
import { causalAgentKey } from "../storage/causal-events.ts"
|
|
19
20
|
import type { HiveDB, EventInput } from "@johpaz/hive-db"
|
|
20
21
|
import type { AgentDoc, TurnSource } from "../storage/collections.ts"
|
|
21
22
|
import { callLLM, resolveProviderConfig, getDefaultLLM, type LLMMessage, type ProviderCredentials } from "./llm-client.ts"
|
|
@@ -170,7 +171,8 @@ async function appendCausalEvent(
|
|
|
170
171
|
): Promise<number | undefined> {
|
|
171
172
|
try {
|
|
172
173
|
return await db.append({
|
|
173
|
-
|
|
174
|
+
// Shard calificado con el tenant: ver causalAgentKey en causal-events.ts.
|
|
175
|
+
agentId: causalAgentKey(input.agentId),
|
|
174
176
|
streamId: input.streamId,
|
|
175
177
|
kind: input.kind,
|
|
176
178
|
payload: JSON.stringify(input.payload),
|
|
@@ -38,7 +38,7 @@ import { getMCPManager as getSingletonMCPManager } from "../mcp/singleton.ts"
|
|
|
38
38
|
import { syncMCPToolsToDB, syncMCPToolsToIndex } from "../mcp/tool-sync.ts"
|
|
39
39
|
import { getUserDate, getUserTime } from "../utils/date.ts"
|
|
40
40
|
import { getHiveDb } from "../storage/hivedb.ts"
|
|
41
|
-
import { causalReadsEnabled } from "../storage/causal-events.ts"
|
|
41
|
+
import { causalReadsEnabled, causalScope } from "../storage/causal-events.ts"
|
|
42
42
|
import { listCatalogAgents, renderAgentRoutingCatalog } from "./catalog-selector.ts"
|
|
43
43
|
import { expandToolAllowlist } from "./delegation-runtime.ts"
|
|
44
44
|
import { MINIMAL_TOOLS } from "./minimal-loadout.ts"
|
|
@@ -588,7 +588,10 @@ export async function compileContext(opts: {
|
|
|
588
588
|
// applies this turn (a real DB round-trip, not a per-turn cost) and
|
|
589
589
|
// there's a causal stream to build it from. episodicSimilarity is omitted:
|
|
590
590
|
// it requires embeddings hive doesn't generate anywhere yet.
|
|
591
|
-
|
|
591
|
+
// Acotado al shard de este agente: el stream es de una sola invocación suya,
|
|
592
|
+
// así que el hilo es el mismo y no se recorre el log de nadie más.
|
|
593
|
+
const causalAgents = causalScope([opts.agentId])
|
|
594
|
+
if (summaryApplies && opts.causalStreamId && causalAgents && causalReadsEnabled()) {
|
|
592
595
|
try {
|
|
593
596
|
const causalDb = await getHiveDb()
|
|
594
597
|
const objectiveSource = taskContext || userMessage
|
|
@@ -605,6 +608,7 @@ export async function compileContext(opts: {
|
|
|
605
608
|
currentObjective: currentObjective.slice(0, 2000),
|
|
606
609
|
maxTokens: causalMaxTokens,
|
|
607
610
|
strategy: { causalAnchors: true, compressCompletedPhases: true },
|
|
611
|
+
agents: causalAgents,
|
|
608
612
|
})) as AgentContextShape
|
|
609
613
|
|
|
610
614
|
const causalLines = [...(causalCtx.items ?? []), ...(causalCtx.anomalies ?? [])]
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
import { logger } from "../utils/logger.ts"
|
|
15
15
|
import { col, nextId } from "../storage/hive.ts"
|
|
16
16
|
import { getHiveDb } from "../storage/hivedb.ts"
|
|
17
|
-
import { causalReadsEnabled } from "../storage/causal-events.ts"
|
|
17
|
+
import { causalReadsEnabled, causalScope } from "../storage/causal-events.ts"
|
|
18
|
+
import { unqualifyDocId } from "../storage/tenant.ts"
|
|
18
19
|
import type { HiveDB, ToolStats } from "@johpaz/hive-db"
|
|
19
20
|
import type { TraceDoc, ReflectionDoc, CursorDoc } from "../storage/collections.ts"
|
|
20
21
|
import { parseThreadId } from "./thread-id.ts"
|
|
@@ -183,10 +184,16 @@ async function analyzeCausalThreads(traces: TraceDoc[], causalDb: HiveDB | null)
|
|
|
183
184
|
|
|
184
185
|
for (const streamId of streamIds) {
|
|
185
186
|
try {
|
|
186
|
-
|
|
187
|
+
// El stream es de una sola invocación, así que sus trazas nombran a los
|
|
188
|
+
// agentes que escribieron en él. Sin ninguno no hay con qué acotar, y una
|
|
189
|
+
// lectura sin acotar recorre los shards de todos los inquilinos.
|
|
190
|
+
const subset = traces.filter((t) => t.causal_stream_id === streamId)
|
|
191
|
+
const agents = causalScope(subset.map((t) => t.agent_id))
|
|
192
|
+
if (!agents) continue
|
|
193
|
+
|
|
194
|
+
const thread = (await causalDb.causalThread(streamId, agents)) as CausalThreadShape
|
|
187
195
|
if (!thread.decisions?.length && !thread.toolCalls?.length) continue
|
|
188
196
|
|
|
189
|
-
const subset = traces.filter((t) => t.causal_stream_id === streamId)
|
|
190
197
|
const originalIntent = subset[0]?.input_summary ?? ""
|
|
191
198
|
const success = subset.every((t) => t.success)
|
|
192
199
|
|
|
@@ -204,13 +211,16 @@ async function analyzeCausalThreads(traces: TraceDoc[], causalDb: HiveDB | null)
|
|
|
204
211
|
// underlying root cause mint a brand new playbook rule instead of
|
|
205
212
|
// reinforcing one (confirmed via a local before/after canary run).
|
|
206
213
|
if (evaluation.rootCause) {
|
|
214
|
+
// El log guarda la clave del shard (t_…:agente con tenant); a la regla de
|
|
215
|
+
// playbook y a affected_agents les llega el id que el host conoce.
|
|
216
|
+
const rootAgent = unqualifyDocId(evaluation.rootCause.agent)
|
|
207
217
|
const decision = thread.decisions?.find((d) => d.seq === evaluation.rootCause!.seq)
|
|
208
218
|
insights.push({
|
|
209
219
|
type: "root_cause",
|
|
210
220
|
description: decision
|
|
211
|
-
? `Root cause: decision "${decision.description}" (agent ${
|
|
212
|
-
: `Root cause: a decision by agent ${
|
|
213
|
-
affectedAgents: [
|
|
221
|
+
? `Root cause: decision "${decision.description}" (agent ${rootAgent}) preceded a tool failure.`
|
|
222
|
+
: `Root cause: a decision by agent ${rootAgent} preceded a tool failure.`,
|
|
223
|
+
affectedAgents: [rootAgent],
|
|
214
224
|
confidence: 0.6,
|
|
215
225
|
})
|
|
216
226
|
}
|
|
@@ -248,14 +258,17 @@ async function analyzeCausalThreads(traces: TraceDoc[], causalDb: HiveDB | null)
|
|
|
248
258
|
async function analyzeTracesLocally(traces: TraceDoc[], causalDb: HiveDB | null): Promise<Insight[]> {
|
|
249
259
|
const insights: Insight[] = []
|
|
250
260
|
|
|
251
|
-
// G9:
|
|
252
|
-
//
|
|
261
|
+
// G9: historial completo, por tool, de los agentes de este lote (undefined si
|
|
262
|
+
// el log está apagado o la tool todavía no tiene eventos). Acotado a esos
|
|
263
|
+
// agentes: con tenant, sin esto se sumarían llamadas de otros inquilinos; sin
|
|
264
|
+
// tenant, es el historial de estos agentes y no el de toda la base.
|
|
253
265
|
const statsByTool = new Map<string, ToolStats>()
|
|
254
|
-
|
|
266
|
+
const agents = causalDb ? causalScope(traces.map((t) => t.agent_id)) : null
|
|
267
|
+
if (causalDb && agents) {
|
|
255
268
|
const distinctTools = new Set(traces.map((t) => t.tool_used).filter((t): t is string => !!t))
|
|
256
269
|
for (const tool of distinctTools) {
|
|
257
270
|
try {
|
|
258
|
-
const stats = await causalDb.toolStats(tool)
|
|
271
|
+
const stats = await causalDb.toolStats(tool, agents)
|
|
259
272
|
if (stats) statsByTool.set(tool, stats)
|
|
260
273
|
} catch (err) {
|
|
261
274
|
log.warn(`[reflector] toolStats(${tool}) failed: ${(err as Error).message}`)
|
|
@@ -3,32 +3,63 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Read-side, separate from agent-loop.ts's write-side appendCausalEvent()
|
|
5
5
|
* (module-private there, write-only). This is the read/watch counterpart,
|
|
6
|
-
* co-located with the DB singleton accessor.
|
|
6
|
+
* co-located with the DB singleton accessor. It also owns the shard key both
|
|
7
|
+
* sides use (causalAgentKey) and the agent scope of every aggregated read
|
|
8
|
+
* (causalScope).
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import { getHiveDb } from "./hivedb.ts"
|
|
10
|
-
import { currentTenant } from "./tenant.ts"
|
|
12
|
+
import { currentTenant, qualifyDocId, unqualifyDocId } from "./tenant.ts"
|
|
11
13
|
import { loadConfig } from "../config/loader.ts"
|
|
12
14
|
import type { Event, EventPattern } from "@johpaz/hive-db"
|
|
13
15
|
|
|
14
16
|
export type { Event as CausalEvent, EventPattern as CausalEventPattern }
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
19
|
+
* Clave de shard de un agente en el log causal.
|
|
18
20
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* datos entre agencias no.
|
|
21
|
+
* El log no tiene colecciones que prefijar: cada evento va al shard de su
|
|
22
|
+
* `agentId`, y ahí está todo el aislamiento. Con un tenant activo la clave
|
|
23
|
+
* lleva el tenant delante (`t_…:agentId`, la misma forma que usa el índice
|
|
24
|
+
* BM25 vía `qualifyDocId`), así que dos inquilinos con un agente del mismo id
|
|
25
|
+
* no comparten shard. Sin tenant es la identidad y un log de un solo dueño no
|
|
26
|
+
* cambia.
|
|
26
27
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* Escritura y lecturas tienen que pasar por aquí: un evento escrito con una
|
|
29
|
+
* clave y leído con otra simplemente no aparece.
|
|
30
|
+
*/
|
|
31
|
+
export function causalAgentKey(agentId: string): string {
|
|
32
|
+
return qualifyDocId(agentId)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lista `agents` para `causalThread`, `toolStats` y `buildAgentContext`.
|
|
37
|
+
*
|
|
38
|
+
* Devuelve `null` si no queda ningún agente, y quien llama se salta la
|
|
39
|
+
* lectura: hive-db trata una lista vacía igual que la ausencia de filtro y
|
|
40
|
+
* recorre TODOS los shards, que sobre una base compartida es leer los eventos
|
|
41
|
+
* de otros inquilinos.
|
|
42
|
+
*/
|
|
43
|
+
export function causalScope(agentIds: Iterable<string | null | undefined>): string[] | null {
|
|
44
|
+
const keys = new Set<string>()
|
|
45
|
+
for (const id of agentIds) {
|
|
46
|
+
if (id) keys.add(causalAgentKey(id))
|
|
47
|
+
}
|
|
48
|
+
return keys.size > 0 ? [...keys] : null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* ¿Se lee el log causal (reflector, contexto causal del compilador)?
|
|
53
|
+
*
|
|
54
|
+
* Con hive-db 0.4, `causalThread`, `buildAgentContext` y `toolStats` recorrían
|
|
55
|
+
* todos los shards y mezclaban sus resultados, así que con un tenant activo
|
|
56
|
+
* había que apagarlas para no devolver hilos y estadísticas de otros
|
|
57
|
+
* inquilinos. hive-db 0.5.1 —la mínima que pide el SDK— acepta `agents` en las
|
|
58
|
+
* tres, y el SDK las llama siempre acotadas con {@link causalScope}: basta con
|
|
59
|
+
* que el log esté encendido.
|
|
29
60
|
*/
|
|
30
61
|
export function causalReadsEnabled(): boolean {
|
|
31
|
-
return !!loadConfig().causalLog?.enabled
|
|
62
|
+
return !!loadConfig().causalLog?.enabled
|
|
32
63
|
}
|
|
33
64
|
|
|
34
65
|
/**
|
|
@@ -52,10 +83,10 @@ export function causalReadsEnabled(): boolean {
|
|
|
52
83
|
export async function watchCausalEvents(
|
|
53
84
|
pattern: EventPattern
|
|
54
85
|
): Promise<AsyncIterable<Event> & { close(): void }> {
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
86
|
+
// Un patrón sin `agentId` sobre una base compartida entregaría los eventos de
|
|
87
|
+
// todos los enjambres, así que con un tenant activo se exige explícitamente en
|
|
88
|
+
// vez de filtrar a medias. Se recibe el id crudo del agente y su shard se
|
|
89
|
+
// busca por la clave calificada.
|
|
59
90
|
if (currentTenant() && !pattern.agentId) {
|
|
60
91
|
throw new Error(
|
|
61
92
|
"watchCausalEvents: con un tenant activo el patrón debe fijar agentId; " +
|
|
@@ -63,7 +94,7 @@ export async function watchCausalEvents(
|
|
|
63
94
|
)
|
|
64
95
|
}
|
|
65
96
|
const db = await getHiveDb()
|
|
66
|
-
return db.events(pattern)
|
|
97
|
+
return db.events(pattern.agentId ? { ...pattern, agentId: causalAgentKey(pattern.agentId) } : pattern)
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
/** One-line human-readable summary of a causal event, keyed by its kindTag. */
|
|
@@ -76,7 +107,7 @@ export function formatCausalEvent(event: Event): string {
|
|
|
76
107
|
}
|
|
77
108
|
|
|
78
109
|
const streamShort = event.streamId.slice(0, 8)
|
|
79
|
-
const header = `[${event.seq}] ${kindIcon(event.kindTag)} ${event.kindTag.padEnd(16)} agent=${event.agentId} stream=${streamShort}…`
|
|
110
|
+
const header = `[${event.seq}] ${kindIcon(event.kindTag)} ${event.kindTag.padEnd(16)} agent=${unqualifyDocId(event.agentId)} stream=${streamShort}…`
|
|
80
111
|
|
|
81
112
|
switch (event.kindTag) {
|
|
82
113
|
case "IntentLogged":
|
|
@@ -128,4 +128,4 @@ export { reconcileOnBoot } from "./reconcile.ts";
|
|
|
128
128
|
|
|
129
129
|
// ─── Log causal (G9) ─────────────────────────────────────────────────────────
|
|
130
130
|
export type { CausalEvent, CausalEventPattern } from "./causal-events.ts";
|
|
131
|
-
export { watchCausalEvents, formatCausalEvent } from "./causal-events.ts";
|
|
131
|
+
export { watchCausalEvents, formatCausalEvent, causalAgentKey, causalScope } from "./causal-events.ts";
|