> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pecta.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Get started with the @pecta/core Node.js SDK

> Install @pecta/core, create an engine with gates, evaluate your first agent output, and ship telemetry to the Pecta cloud — all in under 15 ms.

The `@pecta/core` SDK runs quality gates entirely inside your Node.js process. There is no network call on the hot path: gates execute in parallel, the engine returns a result object, and telemetry ships to Pecta cloud asynchronously in a background batch. This makes the SDK the right choice for latency-critical workloads such as RTB pipelines, where you need a gate decision in under 15 ms.

<Steps>
  <Step title="Install the package">
    ```bash theme={null}
    npm install @pecta/core
    ```

    The package is ESM-only and requires Node.js 22 LTS or later.
  </Step>

  <Step title="Create an engine">
    Call `createEngine` once — typically at module initialisation — and reuse the engine for every request. Engines are stateless and safe to share across async calls.

    ```typescript theme={null}
    import { createEngine, gates } from "@pecta/core";

    const engine = createEngine({
      gates: [
        gates.latency({ maxMs: 100 }),
        gates.filesystem(),
        gates.pii(),
      ],
      timeout: 50,   // total ms budget across all gates; default 50
    });
    ```

    `timeout` is the wall-clock budget shared across all gates. If the budget is exhausted the engine aborts remaining work and marks those gates as failed.
  </Step>

  <Step title="Evaluate an agent output">
    Call `engine.evaluate()` with an `EvaluationContext` after your agent returns a result. The call is async and resolves in the time it takes all gates to run (bounded by `timeout`).

    ```typescript theme={null}
    const result = await engine.evaluate({
      agent_id: "research-bot-v2",
      tool: "shell.run",
      output: { stdout: "ls -la /tmp" },
      latency_ms: 18,
    });
    ```
  </Step>

  <Step title="Check the result">
    `result.passed` is `true` only when every gate passes (or is explicitly skipped). Inspect `result.gates` to see which gates blocked the output and why.

    ```typescript theme={null}
    if (!result.passed) {
      // result.gates contains every gate's verdict and reason string
      const blocked = result.gates.filter((g) => !g.passed);
      console.error("blocked:", blocked);
      // e.g. [{ name: "filesystem", passed: false, reason: "destructive rm command detected", latency_ms: 1.2 }]
    }
    ```

    The full `EvaluationResult` shape:

    ```typescript theme={null}
    {
      evaluation_id: string;   // unique ID per call
      agent_id: string;
      tool?: string;
      passed: boolean;
      gates: Array<{
        name: string;
        passed: boolean;
        reason?: string;        // present when passed is false
        latency_ms: number;
        skipped?: boolean;      // gate chose not to run (treated as pass)
      }>;
      total_latency_ms: number;
      timestamp: string;        // ISO 8601 UTC
    }
    ```
  </Step>

  <Step title="Send telemetry">
    Instantiate `PectaTelemetry` with your publishable key and call `telemetry.track(result)` after every evaluation. The call is synchronous and non-blocking — it adds the result to an in-memory buffer and returns immediately.

    ```typescript theme={null}
    import { PectaTelemetry } from "@pecta/core/telemetry";

    const telemetry = new PectaTelemetry({
      publishableKey: process.env.PECTA_PUBLISHABLE_KEY!,
      secretKey: process.env.PECTA_SECRET_KEY,   // enables HMAC signing
      hmac: true,
      sampleRate: 0.1,   // ship 10% of evaluations to cloud
    });

    telemetry.track(result);   // never blocks, never throws

    process.on("SIGTERM", () => telemetry.shutdown());
    ```

    Telemetry flushes automatically every 5 seconds or when 100 events accumulate. Call `shutdown()` before your process exits to drain any remaining buffer.

    <Note>
      `sampleRate` controls the fraction of evaluations that are POSTed to the cloud. It does **not** affect local gate execution or which evaluations feed into reputation scoring — all evaluations are counted locally regardless of sample rate.
    </Note>
  </Step>
</Steps>

## RTB / OpenRTB example

For programmatic advertising pipelines, use the RTB gate suite. Pass the original `openRtbRequest` as `input` and the bidder response as `output`, then add `tmaxMs` and `startedAt` so the `tmaxGuard` gate can skip downstream checks when the deadline is already exhausted.

```typescript theme={null}
import { createEngine, gates } from "@pecta/core";

const engine = createEngine({
  gates: [
    gates.rtb.tmaxGuard({ bufferMs: 15 }),
    gates.rtb.impidMatch(),
    gates.rtb.adomainVerify(),
    gates.rtb.bidSanity({ maxFloorMultiple: 50 }),
    gates.rtb.audienceSafety(),
    gates.rtb.bcatCompliance(),
  ],
  timeout: 15,   // tight budget for RTB
});

const result = await engine.evaluate({
  agent_id: "dsp-bidder-prod",
  input: openRtbRequest,
  output: openRtbResponse,
  tmaxMs: openRtbRequest.tmax,
  startedAt: requestStartMs,   // performance.now() before the upstream call
});
```

## Writing a custom gate

A gate is any object with a `name` string and a `run` function. The engine provides an `AbortSignal` — check it at the start of long operations to respect fail-fast and timeout behaviour.

```typescript theme={null}
const myGate = {
  name: "my.custom",
  run: async (ctx, signal) => {
    if (signal.aborted) return { passed: false, reason: "aborted" };
    // ...your check...
    return { passed: true };
  },
};

const engine = createEngine({ gates: [myGate] });
```

`run` returns `{ passed: boolean, reason?: string, skipped?: boolean, details?: unknown }`. The engine fills in `name` and `latency_ms` automatically.
