wardx 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,41 @@ The SDK records logs, events, and metrics. The SDK also gets Remote Config and a
6
6
 
7
7
  A measure call changes local memory only. The SDK sends frames on a timer. The SDK uses HTTP `POST /v1/sync` with JSON and gzip.
8
8
 
9
+ ```text
10
+ AGENT
11
+ arisa.sh / Codex / Claude
12
+ │
13
+ MCP stdio
14
+ tools + wardx://project/{name}
15
+ ▼
16
+ ┌───────────────────────────────────────────────────┐
17
+ │ wardx-server (one process) │
18
+ │ N isolated projects │
19
+ │ │
20
+ │ MCP ──► ControlService │
21
+ │ ├── Remote Config snapshot │
22
+ │ ├── Experiment definitions │
23
+ │ ├── Aggregates │
24
+ │ ├── Recent logs │
25
+ │ └── Catalog │
26
+ │ │
27
+ │ HTTP POST /v1/sync │
28
+ │ ├── envelope store (config.sink) │
29
+ │ │ null | memory | ndjson │
30
+ │ └── per-project ingest │
31
+ │ aggregator, recent logs, clients │
32
+ │ config reply filtered by client.role │
33
+ └─────────────────────────▲─────────────────────────┘
34
+ │
35
+ frames up / that role's config down
36
+ ┌───────────────┴───────────────┐
37
+ ▼ ▼
38
+ Node SDK C# / Unity SDK
39
+ wardx / @wardx/core clients/csharp
40
+ role: game-server role: mobile
41
+ metrics / config.get same /v1/sync
42
+ ```
43
+
9
44
  ## Install
10
45
 
11
46
  ```bash
@@ -18,6 +53,13 @@ import { createWardx } from 'wardx';
18
53
 
19
54
  To receive frames, run an ingest server. Install `@wardx/server` and start it with a config file.
20
55
 
56
+ > [!NOTE]
57
+ > **Agent skill.** Teach the agent this Node SDK with the [Skills CLI](https://skills.sh):
58
+ >
59
+ > ```bash
60
+ > npx skills add https://github.com/clasen/Wardx --skill wardx
61
+ > ```
62
+
21
63
  ## Design rules
22
64
 
23
65
  - A measure call does not send network data.
@@ -333,7 +375,7 @@ function grantCoins(wardx, grant) {
333
375
  2. On the grant path, add the amount to `coins.awarded` and increment `coins.grants`. The dimension is `source`, not a user id.
334
376
  3. Observe the amount in `coins.award_size` with a lookup key (`grantId`). The histogram keeps those attrs only for the window max, as `exemplar`. Histogram `max` and the upper buckets are the inconsistency signal. The exemplar is the row to open in the database.
335
377
  4. Read `economy.maxAward` from Remote Config. Emit `coins.anomaly` only when a grant exceeds that bound. That event is rare.
336
- 5. From MCP, compare `coins.awarded / coins.grants` (mean grant) and `coins.award_size` max against `economy.maxAward`. If max is high, read `exemplar.attrs.grantId`.
378
+ 5. From MCP, the overview ranks histogram outcomes by `max` and includes the exemplar. Compare that max and `coins.awarded / coins.grants` (mean grant) against `economy.maxAward`. If max is high, read `exemplar.attrs.grantId`, then `get_recent_logs` with `coins_anomaly` or that `grantId`. If the role has `path` or `git`, search that checkout for `source` / `reason`. See `@wardx/server` use case 9.
337
379
 
338
380
  Do not put `userId` on a counter or histogram dimension. The SDK and the server cap series. A unique id per player creates a series per player and then drops. An exemplar is one sample per series per window, so a lookup key there does not explode cardinality. Do not `event()` once per grant on a backend that serves many users. Use an event only for the anomaly.
339
381
 
@@ -530,8 +572,8 @@ On a process that serves many users, skip `identify()` and pass `{ subjectId }`
530
572
  1. Start a local clock when the play session starts. Do not use `sessionId`.
531
573
  2. When it ends, observe `session.duration` with minute-scale buckets. Default histogram buckets are for short durations in milliseconds.
532
574
  3. Add the same number to `session.time_ms`. Increment `session.ended`.
533
- 4. Call `experiment.goal('session.duration', { value: durationMs })` with a subject. Emit that goal once per ended session. `analyze_experiment` then has `goalSum` and `goalMean` per variant. Mean session ms is `goalSum / goals`.
534
- 5. From MCP, read `session.time_ms` in `get_aggregates` for fleet minutes. Compare variants with `analyze_experiment`, not with a counter dimension.
575
+ 4. Call `experiment.goal('session.duration', { value: durationMs })` with a subject. Emit that goal once per ended session. `analyze_experiment` then has `goalSum`, `goalMean`, and a `decision` per variant. Mean session ms is `goalSum / goals`.
576
+ 5. From MCP, read `session.time_ms` in `get_aggregates` for fleet minutes. Compare variants with `analyze_experiment`, not with a counter dimension. Ship a winner with `ship_experiment`.
535
577
 
536
578
  Do not put `userId` on the histogram. Do not emit `experiment.goal` on every heartbeat: that would count many goals for one session. The heartbeat only adds to `session.time_ms`.
537
579
 
@@ -570,7 +612,7 @@ The volume funnel `level.start` → `level.fail` / `level.complete` is the diffi
570
612
 
571
613
  Call `experiment.goal('session.duration', { value: durationMs })` when the play session ends (use case 14).
572
614
 
573
- From MCP, after onboarding: `upsert_experiment` on the existing keys (`level.3.enemyHp`, …) with a hypothesis such as "Lower HP on level 3 increases session duration", `primaryMetric: 'session.time_ms'`, and variants that only change those keys. Later `analyze_experiment`: compare `goalMean` for the duration goal. Compare the funnel counts with `get_aggregates`. See `@wardx/server` use case 7.
615
+ From MCP, after onboarding: `upsert_experiment` on the existing keys (`level.3.enemyHp`, …) with a hypothesis such as "Lower HP on level 3 increases session duration", `primaryMetric: 'session.time_ms'`, `goalKind: 'mean'`, `control`, `minExposures`, `confidence`, and variants that only change those keys. Later `analyze_experiment`: follow `decision` and compare `goalMean` for the duration goal. `ship_experiment` when status is `winner`. Compare the funnel counts with `get_aggregates`. See `@wardx/server` use case 7.
574
616
 
575
617
  ## Use case 16: Surface an error so an agent can open the source
576
618
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wardx",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Node.js SDK for Wardx telemetry, Remote Config, and experiments.",
5
5
  "keywords": [
6
6
  "wardx",
@@ -26,12 +26,17 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "exports": {
29
- ".": "./src/index.js"
29
+ ".": {
30
+ "types": "./src/index.d.ts",
31
+ "import": "./src/index.js",
32
+ "default": "./src/index.js"
33
+ }
30
34
  },
35
+ "types": "./src/index.d.ts",
31
36
  "files": [
32
37
  "src"
33
38
  ],
34
39
  "dependencies": {
35
- "@wardx/core": "0.1.3"
40
+ "@wardx/core": "0.1.5"
36
41
  }
37
- }
42
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ import type {
2
+ Attrs,
3
+ ConfigSnapshot,
4
+ ConfigValue,
5
+ CoreSettings,
6
+ CounterHandle,
7
+ CreateWardxOptions,
8
+ DimensionValue,
9
+ Dimensions,
10
+ EventTraceRecord,
11
+ Experiment,
12
+ ExperimentGoalContext,
13
+ ExperimentVariant,
14
+ FrameTraceRecord,
15
+ GaugeHandle,
16
+ HistogramHandle,
17
+ HistogramOptions,
18
+ LogApi,
19
+ LogLevel,
20
+ LogTraceRecord,
21
+ MeasureTraceRecord,
22
+ ResolvedSettings,
23
+ SdkDefaults,
24
+ StopTimer,
25
+ SubjectContext,
26
+ SyncTraceRecord,
27
+ Tracer
28
+ } from '@wardx/core';
29
+
30
+ export type {
31
+ Attrs,
32
+ ConfigSnapshot,
33
+ ConfigValue,
34
+ CoreSettings,
35
+ CounterHandle,
36
+ CreateWardxOptions,
37
+ DimensionValue,
38
+ Dimensions,
39
+ EventTraceRecord,
40
+ Experiment,
41
+ ExperimentGoalContext,
42
+ ExperimentVariant,
43
+ FrameTraceRecord,
44
+ GaugeHandle,
45
+ HistogramHandle,
46
+ HistogramOptions,
47
+ LogApi,
48
+ LogLevel,
49
+ LogTraceRecord,
50
+ MeasureTraceRecord,
51
+ ResolvedSettings,
52
+ SdkDefaults,
53
+ StopTimer,
54
+ SubjectContext,
55
+ SyncTraceRecord,
56
+ Tracer
57
+ };
58
+
59
+ export interface ConsoleTracerOptions {
60
+ stream?: { write(chunk: string): unknown };
61
+ }
62
+
63
+ export interface ConfigApi {
64
+ get<T>(key: string, fallback: T, context?: SubjectContext): T;
65
+ }
66
+
67
+ export interface ExperimentApi {
68
+ goal(name: string, context?: ExperimentGoalContext): void;
69
+ }
70
+
71
+ export class WardxNode {
72
+ settings: ResolvedSettings;
73
+ log: LogApi;
74
+ config: ConfigApi;
75
+ experiment: ExperimentApi;
76
+ constructor(settings: ResolvedSettings);
77
+ identify(subjectId: string | null | undefined): void;
78
+ counter(name: string, dims?: Dimensions | null): CounterHandle;
79
+ gauge(name: string, dims?: Dimensions | null): GaugeHandle;
80
+ histogram(name: string, a?: HistogramOptions | null, b?: HistogramOptions | null): HistogramHandle;
81
+ timer(name: string, dims?: Dimensions | null): StopTimer;
82
+ event(name: string, attrs?: Attrs | null): void;
83
+ flush(): Promise<void>;
84
+ shutdown(): Promise<void>;
85
+ }
86
+
87
+ export function createWardx(options: CreateWardxOptions): WardxNode;
88
+ export function createConsoleTracer(options?: ConsoleTracerOptions): Tracer;