@senad-d/observme 0.1.1 → 0.1.3
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 +7 -0
- package/README.md +105 -13
- package/SECURITY.md +1 -1
- package/dashboards/observme-slos.yaml +1 -1
- package/docs/README.md +54 -0
- package/docs/agent-subagent-observability-requirements.md +34 -38
- package/docs/configuration.md +9 -0
- package/docs/extension-integration.md +234 -0
- package/docs/reference/00-README.md +81 -0
- package/{ObservMe-Production-Docs → docs/reference}/05-otel-pipeline-and-collector.md +5 -0
- package/{ObservMe-Production-Docs → docs/reference}/07-extension-implementation-blueprint.md +18 -7
- package/{ObservMe-Production-Docs → docs/reference}/pi-session-format.md +1 -1
- package/docs/review-validation.md +1 -1
- package/docs/validation-flow.md +8 -1
- package/examples/README.md +52 -0
- package/examples/collector.yaml +5 -0
- package/examples/integrations/subagent-runner.ts +262 -0
- package/examples/observme.yaml +4 -0
- package/package.json +11 -2
- package/skills/observme-docs/SKILL.md +65 -0
- package/src/integration.ts +176 -0
- package/src/pi/handlers.ts +11 -0
- package/src/pi/integration-api.ts +395 -0
- package/src/pi/subagent-spawn.ts +1 -1
- package/tsconfig.json +1 -1
- package/ObservMe-Production-Docs/00-README.md +0 -79
- /package/{ObservMe-Production-Docs → docs/reference}/01-requirements-and-scope.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/02-reference-architecture.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/03-pi-event-and-session-model.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/04-telemetry-semantic-conventions.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/06-security-privacy-redaction.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/08-query-grafana-integration.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/09-dashboards-alerts-slos.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/10-testing-release-operations.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/11-deployment-runbooks.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/12-configuration-reference.md +0 -0
- /package/{ObservMe-Production-Docs → docs/reference}/13-source-notes.md +0 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OBSERVME_INTEGRATION_CHANNEL,
|
|
3
|
+
OBSERVME_INTEGRATION_VERSION,
|
|
4
|
+
type ObservMeCompleteSubagentOptions,
|
|
5
|
+
type ObservMeFailSubagentOptions,
|
|
6
|
+
type ObservMeIntegrationApi,
|
|
7
|
+
type ObservMeIntegrationContextSuccess,
|
|
8
|
+
type ObservMeIntegrationFailure,
|
|
9
|
+
type ObservMeIntegrationRequest,
|
|
10
|
+
type ObservMeIntegrationSuccess,
|
|
11
|
+
type ObservMeStartedSubagent,
|
|
12
|
+
type ObservMeStartedWaitJoin,
|
|
13
|
+
type ObservMeStartSubagentOptions,
|
|
14
|
+
type ObservMeWaitJoinOptions,
|
|
15
|
+
} from "../integration.ts";
|
|
16
|
+
import { SESSION_ATTRIBUTES } from "../semconv/attributes.ts";
|
|
17
|
+
import {
|
|
18
|
+
completeSubagentSpawn,
|
|
19
|
+
endAgentJoin,
|
|
20
|
+
endAgentWait,
|
|
21
|
+
failSubagentSpawn,
|
|
22
|
+
startAgentJoin,
|
|
23
|
+
startAgentWait,
|
|
24
|
+
startSubagentSpawn,
|
|
25
|
+
} from "./subagent-spawn.ts";
|
|
26
|
+
import type { HandlerSessionState, ObservMeTelemetrySession } from "./handler-types.ts";
|
|
27
|
+
|
|
28
|
+
interface IntegrationEventBus {
|
|
29
|
+
on(channel: string, handler: (data: unknown) => void): () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface IntegrationPiApi {
|
|
33
|
+
readonly events?: IntegrationEventBus;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const integrationIdentifierPattern = /^[A-Za-z0-9._:-]{1,128}$/u;
|
|
37
|
+
const maximumIntegrationCommandLength = 4096;
|
|
38
|
+
const maximumIntegrationArgumentCount = 256;
|
|
39
|
+
const maximumIntegrationArgumentLength = 4096;
|
|
40
|
+
const maximumIntegrationEnvironmentEntries = 4096;
|
|
41
|
+
|
|
42
|
+
export function registerObservMeIntegration(pi: unknown, state: HandlerSessionState): (() => void) | undefined {
|
|
43
|
+
const events = resolveIntegrationEventBus(pi);
|
|
44
|
+
if (!events) return undefined;
|
|
45
|
+
|
|
46
|
+
const api = new SessionBackedObservMeIntegrationApi(state);
|
|
47
|
+
try {
|
|
48
|
+
return events.on(OBSERVME_INTEGRATION_CHANNEL, api.handleRequest.bind(api));
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationApi {
|
|
55
|
+
readonly version = OBSERVME_INTEGRATION_VERSION;
|
|
56
|
+
readonly #state: HandlerSessionState;
|
|
57
|
+
|
|
58
|
+
constructor(state: HandlerSessionState) {
|
|
59
|
+
this.#state = state;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
handleRequest(value: unknown): void {
|
|
63
|
+
try {
|
|
64
|
+
if (!isCompatibleIntegrationRequest(value)) return;
|
|
65
|
+
value.respond(this);
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getContext(): ObservMeIntegrationContextSuccess | ObservMeIntegrationFailure {
|
|
72
|
+
const session = this.#state.session;
|
|
73
|
+
if (!session) return integrationFailure("session_unavailable");
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
ok: true,
|
|
77
|
+
context: {
|
|
78
|
+
workflowId: session.lineage.workflowId,
|
|
79
|
+
workflowRootAgentId: session.lineage.workflowRootAgentId,
|
|
80
|
+
agentId: session.lineage.agentId,
|
|
81
|
+
parentAgentId: session.lineage.parentAgentId,
|
|
82
|
+
rootAgentId: session.lineage.rootAgentId,
|
|
83
|
+
depth: session.lineage.depth,
|
|
84
|
+
role: session.lineage.role,
|
|
85
|
+
capability: session.lineage.capability,
|
|
86
|
+
sessionId: readSessionId(session),
|
|
87
|
+
traceId: readSessionTraceId(session),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
startSubagent(options: ObservMeStartSubagentOptions = {}): ObservMeStartedSubagent | ObservMeIntegrationFailure {
|
|
93
|
+
const session = this.#state.session;
|
|
94
|
+
if (!session) return integrationFailure("session_unavailable");
|
|
95
|
+
try {
|
|
96
|
+
if (!isValidStartSubagentOptions(options)) return integrationFailure("invalid_request");
|
|
97
|
+
if (options.spawnId && session.spans.activeSubagentSpawns.has(options.spawnId)) {
|
|
98
|
+
return integrationFailure("spawn_already_exists");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const started = startSubagentSpawn(session, options);
|
|
102
|
+
return {
|
|
103
|
+
ok: true,
|
|
104
|
+
spawnId: started.spawnId,
|
|
105
|
+
childAgentId: started.childAgentId,
|
|
106
|
+
env: started.env,
|
|
107
|
+
traceContextPropagated: started.traceContextPropagated,
|
|
108
|
+
};
|
|
109
|
+
} catch {
|
|
110
|
+
return integrationFailure("operation_failed");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
completeSubagent(
|
|
115
|
+
spawnId: string,
|
|
116
|
+
options: ObservMeCompleteSubagentOptions = {},
|
|
117
|
+
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
118
|
+
const session = this.#state.session;
|
|
119
|
+
if (!session) return integrationFailure("session_unavailable");
|
|
120
|
+
try {
|
|
121
|
+
if (!isValidIntegrationIdentifier(spawnId) || !isValidCompleteSubagentOptions(options)) {
|
|
122
|
+
return integrationFailure("invalid_request");
|
|
123
|
+
}
|
|
124
|
+
if (!session.spans.activeSubagentSpawns.has(spawnId)) return integrationFailure("spawn_not_found");
|
|
125
|
+
|
|
126
|
+
completeSubagentSpawn(session, spawnId, options);
|
|
127
|
+
return integrationSuccess();
|
|
128
|
+
} catch {
|
|
129
|
+
return integrationFailure("operation_failed");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
failSubagent(
|
|
134
|
+
spawnId: string,
|
|
135
|
+
options: ObservMeFailSubagentOptions = {},
|
|
136
|
+
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
137
|
+
const session = this.#state.session;
|
|
138
|
+
if (!session) return integrationFailure("session_unavailable");
|
|
139
|
+
try {
|
|
140
|
+
if (!isValidIntegrationIdentifier(spawnId) || !isValidFailSubagentOptions(options)) {
|
|
141
|
+
return integrationFailure("invalid_request");
|
|
142
|
+
}
|
|
143
|
+
if (!session.spans.activeSubagentSpawns.has(spawnId)) return integrationFailure("spawn_not_found");
|
|
144
|
+
|
|
145
|
+
failSubagentSpawn(session, spawnId, options);
|
|
146
|
+
return integrationSuccess();
|
|
147
|
+
} catch {
|
|
148
|
+
return integrationFailure("operation_failed");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
startWait(options: ObservMeWaitJoinOptions = {}): ObservMeStartedWaitJoin | ObservMeIntegrationFailure {
|
|
153
|
+
return this.startWaitJoin(options, "wait");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
endWait(
|
|
157
|
+
waitId: string,
|
|
158
|
+
options: ObservMeWaitJoinOptions = {},
|
|
159
|
+
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
160
|
+
return this.endWaitJoin(waitId, options, "wait");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
startJoin(options: ObservMeWaitJoinOptions = {}): ObservMeStartedWaitJoin | ObservMeIntegrationFailure {
|
|
164
|
+
return this.startWaitJoin(options, "join");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
endJoin(
|
|
168
|
+
joinId: string,
|
|
169
|
+
options: ObservMeWaitJoinOptions = {},
|
|
170
|
+
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
171
|
+
return this.endWaitJoin(joinId, options, "join");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private startWaitJoin(
|
|
175
|
+
options: ObservMeWaitJoinOptions,
|
|
176
|
+
kind: "wait" | "join",
|
|
177
|
+
): ObservMeStartedWaitJoin | ObservMeIntegrationFailure {
|
|
178
|
+
const session = this.#state.session;
|
|
179
|
+
if (!session) return integrationFailure("session_unavailable");
|
|
180
|
+
try {
|
|
181
|
+
if (!isValidWaitJoinOptions(options)) return integrationFailure("invalid_request");
|
|
182
|
+
|
|
183
|
+
const requestedId = resolveRequestedWaitJoinId(options, kind);
|
|
184
|
+
const registry = kind === "wait" ? session.spans.activeAgentWaits : session.spans.activeAgentJoins;
|
|
185
|
+
if (requestedId && registry.has(requestedId)) {
|
|
186
|
+
return integrationFailure(kind === "wait" ? "wait_already_exists" : "join_already_exists");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const started = kind === "wait" ? startAgentWait(session, options) : startAgentJoin(session, options);
|
|
190
|
+
return { ok: true, id: started.id };
|
|
191
|
+
} catch {
|
|
192
|
+
return integrationFailure("operation_failed");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private endWaitJoin(
|
|
197
|
+
id: string,
|
|
198
|
+
options: ObservMeWaitJoinOptions,
|
|
199
|
+
kind: "wait" | "join",
|
|
200
|
+
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
201
|
+
const session = this.#state.session;
|
|
202
|
+
if (!session) return integrationFailure("session_unavailable");
|
|
203
|
+
try {
|
|
204
|
+
if (!isValidIntegrationIdentifier(id) || !isValidWaitJoinOptions(options)) {
|
|
205
|
+
return integrationFailure("invalid_request");
|
|
206
|
+
}
|
|
207
|
+
const registry = kind === "wait" ? session.spans.activeAgentWaits : session.spans.activeAgentJoins;
|
|
208
|
+
if (!registry.has(id)) return integrationFailure(kind === "wait" ? "wait_not_found" : "join_not_found");
|
|
209
|
+
|
|
210
|
+
if (kind === "wait") endAgentWait(session, id, options);
|
|
211
|
+
else endAgentJoin(session, id, options);
|
|
212
|
+
return integrationSuccess();
|
|
213
|
+
} catch {
|
|
214
|
+
return integrationFailure("operation_failed");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function resolveIntegrationEventBus(pi: unknown): IntegrationEventBus | undefined {
|
|
220
|
+
if (!pi || typeof pi !== "object") return undefined;
|
|
221
|
+
try {
|
|
222
|
+
const events = (pi as IntegrationPiApi).events;
|
|
223
|
+
return events && typeof events.on === "function" ? events : undefined;
|
|
224
|
+
} catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isCompatibleIntegrationRequest(value: unknown): value is ObservMeIntegrationRequest {
|
|
230
|
+
if (!value || typeof value !== "object") return false;
|
|
231
|
+
const request = value as Partial<ObservMeIntegrationRequest>;
|
|
232
|
+
return (
|
|
233
|
+
Array.isArray(request.supportedVersions) &&
|
|
234
|
+
request.supportedVersions.includes(OBSERVME_INTEGRATION_VERSION) &&
|
|
235
|
+
typeof request.respond === "function"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isValidStartSubagentOptions(value: unknown): value is ObservMeStartSubagentOptions {
|
|
240
|
+
if (!isIntegrationRecord(value)) return false;
|
|
241
|
+
const options = value as Partial<ObservMeStartSubagentOptions>;
|
|
242
|
+
return (
|
|
243
|
+
isOptionalIntegrationIdentifier(options.spawnId) &&
|
|
244
|
+
isOptionalIntegrationIdentifier(options.childAgentId) &&
|
|
245
|
+
isOptionalBoundedString(options.command, maximumIntegrationCommandLength) &&
|
|
246
|
+
isValidIntegrationArguments(options.args) &&
|
|
247
|
+
isOptionalSpawnType(options.spawnType) &&
|
|
248
|
+
isOptionalSpawnReason(options.spawnReason) &&
|
|
249
|
+
isOptionalIntegrationIdentifier(options.toolCallId) &&
|
|
250
|
+
isValidIntegrationEnvironment(options.env)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isValidCompleteSubagentOptions(value: unknown): value is ObservMeCompleteSubagentOptions {
|
|
255
|
+
if (!isIntegrationRecord(value)) return false;
|
|
256
|
+
const options = value as Partial<ObservMeCompleteSubagentOptions>;
|
|
257
|
+
return (
|
|
258
|
+
isOptionalIntegrationIdentifier(options.childAgentId) &&
|
|
259
|
+
isOptionalChildStatus(options.childStatus) &&
|
|
260
|
+
(options.outcome === undefined || options.outcome === "completed" || options.outcome === "failed" || options.outcome === "cancelled")
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isValidFailSubagentOptions(value: unknown): value is ObservMeFailSubagentOptions {
|
|
265
|
+
if (!isIntegrationRecord(value)) return false;
|
|
266
|
+
const options = value as Partial<ObservMeFailSubagentOptions>;
|
|
267
|
+
return isOptionalIntegrationIdentifier(options.childAgentId) && isOptionalBoundedString(options.errorClass, 256);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isValidWaitJoinOptions(value: unknown): value is ObservMeWaitJoinOptions {
|
|
271
|
+
if (!isIntegrationRecord(value)) return false;
|
|
272
|
+
const options = value as Partial<ObservMeWaitJoinOptions>;
|
|
273
|
+
return (
|
|
274
|
+
isOptionalIntegrationIdentifier(options.id) &&
|
|
275
|
+
isOptionalIntegrationIdentifier(options.spawnId) &&
|
|
276
|
+
isOptionalIntegrationIdentifier(options.childAgentId) &&
|
|
277
|
+
isOptionalChildStatus(options.childStatus) &&
|
|
278
|
+
isOptionalJoinStatus(options.joinStatus) &&
|
|
279
|
+
isOptionalWaitReason(options.reason) &&
|
|
280
|
+
(options.failurePropagated === undefined || typeof options.failurePropagated === "boolean") &&
|
|
281
|
+
(options.durationMs === undefined || isValidDuration(options.durationMs))
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isIntegrationRecord(value: unknown): value is Record<string, unknown> {
|
|
286
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isValidIntegrationIdentifier(value: unknown): value is string {
|
|
290
|
+
return typeof value === "string" && integrationIdentifierPattern.test(value);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function isOptionalIntegrationIdentifier(value: unknown): value is string | undefined {
|
|
294
|
+
return value === undefined || isValidIntegrationIdentifier(value);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isOptionalBoundedString(value: unknown, maximumLength: number): value is string | undefined {
|
|
298
|
+
return value === undefined || (typeof value === "string" && value.length <= maximumLength);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isValidIntegrationArguments(value: unknown): value is readonly string[] | undefined {
|
|
302
|
+
return (
|
|
303
|
+
value === undefined ||
|
|
304
|
+
(Array.isArray(value) &&
|
|
305
|
+
value.length <= maximumIntegrationArgumentCount &&
|
|
306
|
+
value.every(isValidIntegrationArgument))
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function isValidIntegrationArgument(value: unknown): value is string {
|
|
311
|
+
return typeof value === "string" && value.length <= maximumIntegrationArgumentLength;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function isValidIntegrationEnvironment(value: unknown): boolean {
|
|
315
|
+
if (value === undefined) return true;
|
|
316
|
+
if (!isIntegrationRecord(value)) return false;
|
|
317
|
+
const entries = Object.entries(value);
|
|
318
|
+
return entries.length <= maximumIntegrationEnvironmentEntries && entries.every(isValidIntegrationEnvironmentEntry);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function isValidIntegrationEnvironmentEntry(entry: [string, unknown]): boolean {
|
|
322
|
+
return entry[0].length > 0 && (typeof entry[1] === "string" || entry[1] === undefined);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isOptionalSpawnType(value: unknown): boolean {
|
|
326
|
+
return value === undefined || value === "command" || value === "tool" || value === "extension" || value === "unknown";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function isOptionalSpawnReason(value: unknown): boolean {
|
|
330
|
+
return (
|
|
331
|
+
value === undefined ||
|
|
332
|
+
value === "delegated_task" ||
|
|
333
|
+
value === "parallel_search" ||
|
|
334
|
+
value === "review" ||
|
|
335
|
+
value === "tool_wrapper" ||
|
|
336
|
+
value === "unknown"
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function isOptionalChildStatus(value: unknown): boolean {
|
|
341
|
+
return (
|
|
342
|
+
value === undefined ||
|
|
343
|
+
value === "starting" ||
|
|
344
|
+
value === "active" ||
|
|
345
|
+
value === "completed" ||
|
|
346
|
+
value === "failed" ||
|
|
347
|
+
value === "cancelled" ||
|
|
348
|
+
value === "orphaned"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function isOptionalJoinStatus(value: unknown): boolean {
|
|
353
|
+
return (
|
|
354
|
+
value === undefined ||
|
|
355
|
+
value === "completed" ||
|
|
356
|
+
value === "failed" ||
|
|
357
|
+
value === "cancelled" ||
|
|
358
|
+
value === "timeout" ||
|
|
359
|
+
value === "unknown" ||
|
|
360
|
+
value === "waiting"
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function isOptionalWaitReason(value: unknown): boolean {
|
|
365
|
+
return value === undefined || value === "dependency" || value === "rate_limit" || value === "child_running" || value === "unknown";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isValidDuration(value: unknown): value is number {
|
|
369
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function resolveRequestedWaitJoinId(options: ObservMeWaitJoinOptions, kind: "wait" | "join"): string | undefined {
|
|
373
|
+
return options.id ?? (options.spawnId ? `${kind}-${options.spawnId}` : undefined);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function readSessionId(session: ObservMeTelemetrySession): string | undefined {
|
|
377
|
+
const value = session.sessionAttributes?.[SESSION_ATTRIBUTES.PI_SESSION_ID];
|
|
378
|
+
return typeof value === "string" ? value : undefined;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function readSessionTraceId(session: ObservMeTelemetrySession): string | undefined {
|
|
382
|
+
try {
|
|
383
|
+
return session.sessionSpan?.spanContext().traceId;
|
|
384
|
+
} catch {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function integrationSuccess(): ObservMeIntegrationSuccess {
|
|
390
|
+
return { ok: true };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function integrationFailure(reason: ObservMeIntegrationFailure["reason"]): ObservMeIntegrationFailure {
|
|
394
|
+
return { ok: false, reason };
|
|
395
|
+
}
|
package/src/pi/subagent-spawn.ts
CHANGED
|
@@ -44,7 +44,7 @@ export type {
|
|
|
44
44
|
|
|
45
45
|
export type SubagentSpawnType = "command" | "tool" | "extension" | "unknown";
|
|
46
46
|
export type AgentJoinStatus = "completed" | "failed" | "cancelled" | "timeout" | "unknown" | "waiting";
|
|
47
|
-
export type AttributePrimitive = boolean | number | string;
|
|
47
|
+
export type AttributePrimitive = boolean | number | string | string[];
|
|
48
48
|
export type AttributeMap = Record<string, AttributePrimitive>;
|
|
49
49
|
|
|
50
50
|
export interface SubagentSpanRegistry {
|
package/tsconfig.json
CHANGED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# ObservMe Production Documentation
|
|
2
|
-
|
|
3
|
-
**ObservMe** is a production-grade Pi extension for observability. It captures Pi session, turn, tool, LLM, branch, compaction, and error events, translates them into OpenTelemetry telemetry, and exports them to a durable external observability stack such as OpenTelemetry Collector, Grafana Tempo, Grafana Loki, Prometheus, Mimir, or Grafana Cloud.
|
|
4
|
-
|
|
5
|
-
ObservMe is intentionally **not** a local analytics database. Pi agents are ephemeral. Observability data must leave the Pi agent process quickly, safely, and reliably. When multiple Pi agents or subagents are running, ObservMe must also preserve workflow and agent-tree lineage so operators can see which parent agent spawned each child agent and how the full orchestrated workload behaved.
|
|
6
|
-
|
|
7
|
-
## Core Design
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
Pi Agent
|
|
11
|
-
└── ObservMe Pi Extension
|
|
12
|
-
├── OTEL Traces ──► OTEL Collector ──► Tempo
|
|
13
|
-
├── OTEL Metrics ──► OTEL Collector ──► Prometheus / Mimir
|
|
14
|
-
└── OTEL Logs ──► OTEL Collector ──► Loki
|
|
15
|
-
|
|
16
|
-
Grafana
|
|
17
|
-
├── Tempo datasource
|
|
18
|
-
├── Loki datasource
|
|
19
|
-
└── Prometheus/Mimir datasource
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Document Set
|
|
23
|
-
|
|
24
|
-
| File | Purpose |
|
|
25
|
-
|---|---|
|
|
26
|
-
| `01-requirements-and-scope.md` | Product goals, non-goals, personas, requirements, rollout phases |
|
|
27
|
-
| `02-reference-architecture.md` | Full system architecture and component responsibilities |
|
|
28
|
-
| `03-pi-event-and-session-model.md` | Pi session/event sources and how ObservMe interprets them |
|
|
29
|
-
| `04-telemetry-semantic-conventions.md` | ObservMe attribute, metric, log, and span naming specification |
|
|
30
|
-
| `05-otel-pipeline-and-collector.md` | OTLP exporter strategy and production Collector configurations |
|
|
31
|
-
| `06-security-privacy-redaction.md` | Prompt safety, secret redaction, PII handling, tenant isolation |
|
|
32
|
-
| `07-extension-implementation-blueprint.md` | TypeScript implementation architecture, modules, lifecycle, code patterns |
|
|
33
|
-
| `08-query-grafana-integration.md` | `/obs` commands, Grafana/Tempo/Loki/Prometheus query integration |
|
|
34
|
-
| `09-dashboards-alerts-slos.md` | Dashboard pack, PromQL, LogQL, TraceQL, alerts, SLOs |
|
|
35
|
-
| `10-testing-release-operations.md` | Test strategy, chaos testing, performance validation, release process |
|
|
36
|
-
| `11-deployment-runbooks.md` | Local, Docker Compose, Kubernetes, and production runbooks |
|
|
37
|
-
| `12-configuration-reference.md` | Complete ObservMe configuration schema and examples |
|
|
38
|
-
| `13-source-notes.md` | Official documentation cross-check notes and external assumptions |
|
|
39
|
-
| `pi-session-format.md` | Pi session JSONL reference copied and adapted from Pi's official session-format documentation |
|
|
40
|
-
|
|
41
|
-
## Production Principles
|
|
42
|
-
|
|
43
|
-
1. **OTLP-first.** ObservMe emits OpenTelemetry traces, metrics, and logs.
|
|
44
|
-
2. **No durable local telemetry storage.** Only bounded in-memory queues are allowed.
|
|
45
|
-
3. **Fail open.** If observability fails, Pi must continue operating.
|
|
46
|
-
4. **Privacy by default.** Prompt and response content capture is disabled by default.
|
|
47
|
-
5. **Pi-native and OTEL-compatible semantics.** ObservMe uses `observme.*` and `pi.*` for Pi-specific concepts and official OpenTelemetry `gen_ai.*` attributes where they fit; it does not depend on OpenInference.
|
|
48
|
-
6. **Agent lineage by design.** Agent and subagent telemetry carries `pi.agent.*` lineage attributes and, where possible, W3C trace context so parent/child relationships survive process boundaries.
|
|
49
|
-
7. **Workflow/tree observability.** Orchestrator workloads are modeled as agent trees with `pi.workflow.*` correlation, depth, fan-out, wait/join, orphan, and critical-path signals so operators can understand both single-agent and multi-agent execution.
|
|
50
|
-
8. **Collector recommended.** Direct-to-backend export is allowed only for development or constrained environments.
|
|
51
|
-
9. **Backend-neutral.** Tempo/Loki/Prometheus are reference backends, not hard dependencies.
|
|
52
|
-
|
|
53
|
-
## Minimum Viable Production Release
|
|
54
|
-
|
|
55
|
-
A production-ready `1.0.0` release must include:
|
|
56
|
-
|
|
57
|
-
- Extension load and health checks
|
|
58
|
-
- Session, workflow, agent-run, turn, LLM, tool, subagent-spawn, agent wait/join, compaction, branch, model-change telemetry
|
|
59
|
-
- Multi-agent tree metrics for active agents, depth, fan-out, orphan agents, trace-context propagation failures, child failures, and workflow duration
|
|
60
|
-
- OTLP trace exporter
|
|
61
|
-
- OTLP metric exporter
|
|
62
|
-
- OTLP log exporter
|
|
63
|
-
- Configurable redaction and capture controls
|
|
64
|
-
- Bounded queues and exporter timeouts
|
|
65
|
-
- Grafana dashboard JSON
|
|
66
|
-
- Collector reference configs
|
|
67
|
-
- Tests for event mapping, redaction, exporter failure, and cardinality
|
|
68
|
-
- `/obs status`, `/obs health`, `/obs session`, `/obs cost`, `/obs trace`, `/obs agents` commands
|
|
69
|
-
|
|
70
|
-
## Source References
|
|
71
|
-
|
|
72
|
-
This document set assumes:
|
|
73
|
-
|
|
74
|
-
- Pi extensions provide documented lifecycle hooks, commands, tools, custom entries, session events, and queryable session state.
|
|
75
|
-
- Pi can run multiple extension instances or spawn additional Pi processes through tools/subagent patterns; cross-process workflow and parent/child lineage requires explicit ObservMe correlation and trace-context propagation.
|
|
76
|
-
- Pi sessions are JSONL files with typed entries such as `message`, `compaction`, `branch_summary`, `model_change`, `thinking_level_change`, `custom`, `custom_message`, `label`, and `session_info`.
|
|
77
|
-
- OpenTelemetry Collector receives, processes, and exports telemetry via pipelines.
|
|
78
|
-
- OpenTelemetry GenAI semantic conventions are the official OTEL namespace for GenAI client/agent spans, attributes, and metrics, but many GenAI fields are still experimental/evolving in SDK packages; ObservMe extends them for Pi-specific workflow concepts.
|
|
79
|
-
- Grafana Tempo, Loki, and Prometheus/Mimir are reference storage systems for traces, logs, and metrics.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|