Browse documentation ↓

Application behavior

The optional behavior service observes application events supplied by your server. It is separate from the request-only Nginx forest runtime.

Start with Docker

From the product checkout, after the base quickstart:

python3 scripts/setup-env.py --expansion
docker compose --env-file .env --env-file .env.expansion \
  -f compose.yaml -f compose.behavior.yaml up --build -d
curl --fail http://127.0.0.1:8090/health

Keep existing .env.expansion and secrets when restarting; setup refuses to replace them. This overlay creates an independent database for behavior data. It does not mount the credential key needed by remote operations or Assisted setup. Those features have separate setup instructions.

Run pragma-behavior on an application-side host with its own PostgreSQL database:

  • BEHAVIOR_DATABASE_URL: dedicated database; its migrations are independent of management migrations.
  • BEHAVIOR_ADMIN_TOKEN: distinct random credential of at least 32 characters.
  • BEHAVIOR_PARTITION_SECRET: stable 64-character hexadecimal HMAC key.
  • BEHAVIOR_BIND_ADDR: defaults to 127.0.0.1:8090.

Set BEHAVIOR_URL and the matching BEHAVIOR_ADMIN_TOKEN on the management API to enable Behavior. compose.behavior.yaml is an optional local development overlay with a separate behavior database; a production deployment can run that stack on another machine. Use private authenticated transport or TLS termination for cross-host SDK requests; never expose the plaintext listener publicly.

Create an application and copy its one-time ingestion token. Each token is scoped to one application. Server-side authentication supplies actor, session and cohort identity; arbitrary client headers must not supply trusted identity. Application IDs and credentials are not browser instrumentation keys.

Optional reference client sources, shown as sibling-checkout paths:

  • Node.js/TypeScript: ../pragma_change_sdks/node, package @pragmachange/sdk.
  • Python: ../pragma_change_sdks/python, package pragmachange, import pragma_change.
  • C#: ../pragma_change_sdks/csharp, package PragmaChange targeting .NET 8.
  • Rust: ../pragma_change_sdks/rust, crate pragmachange.

These are adaptable reference implementations, not finished SDK products. Packages are local sources and have not been published to registries. Each provides bounded asynchronous recording, at most five delivery attempts, retry-stable event IDs, collection counters, an explicit flush operation, and synchronous evaluation. Collection is best effort: queue overflow and terminal failures increment dropped; monitor it. Flush/close should run during application shutdown. Close drains for up to five seconds plus an in-flight request and counts any remaining events as dropped. The Rust client requires a Tokio runtime and depends on the small pragma-behavior-contract crate, not the training service. C# base URLs should end in /.

The v1 event contract requires version, event_id, application_id, actor, action, session_id, timestamp_ms, and outcome. Optional fields are resource, workflow_id, correlation_id, cohort, sequence_start, sequence_number, and scalar attributes. Action names are stable symbols such as document.export, not URLs containing resource IDs. See behavior-openapi.json.

Use a distinct workflow ID for independently concurrent journeys. Emit sequence_start: true only at a known boundary, with monotonically increasing sequence numbers when possible. Missing boundaries, backward time, and detected sequence gaps make evidence incomplete. History is bounded to 128 completed actions. Neither distributed wall clocks nor asynchronous delivery establish a global causal ordering by themselves.

Call evaluate after ordinary authorization but before the side effect. An allow does not itself authorize a resource or reserve business state. check requires application verification before proceeding; block stops the action. SDK transport failure returns check. Record the actual result afterward with a new event ID and correlation_id set to the evaluation’s event ID. The service validates its actor, workflow, action and resource against the attempt and permits one confirmed outcome. Standalone observed events may omit correlation. Retrying a delivery must preserve the event ID and content.

Middleware records response outcomes and duration. Node middleware must run after authentication; Python ASGI and C# callbacks resolve authenticated context. Rust provides an operation wrapper. Configure allowlisted attributes on both SDK and service; attributes are stripped by default. Actor/session/workflow/resource identities are HMAC-pseudonymized on ingestion. This is pseudonymization, not anonymization. No raw request bodies are automatically collected.

Each SDK also provides an explicit DatasetRecorder that writes private JSONL and a checksummed manifest locally, independent of streaming. Request recordings bind to a saved pipeline and import into Datasets; behavior recordings import into immutable snapshots through POST /admin/apps/{id}/imports. Offline uploads never alter live history. See recording contracts and examples.

Example Node integration:

const pc = new PragmaChange({
  url: process.env.PRAGMA_URL,
  token: process.env.PRAGMA_TOKEN,
  applicationId: process.env.PRAGMA_APP,
});
const attempt = pc.event({
  actor: verifiedUser.id,
  session_id: verifiedSession.id,
  workflow_id: exportWorkflow.id,
  action: "document.export",
  resource: document.id,
  outcome: "attempted",
});
const decision = await pc.evaluate(attempt);
// The application enforces check/block before executing its authorized operation.
// After a confirmed result:
pc.record({
  ...attempt,
  event_id: crypto.randomUUID(),
  correlation_id: attempt.event_id,
  outcome: "succeeded",
  timestamp_ms: Date.now(),
});

Models and evidence

Snapshots are immutable copies of accepted, non-quarantined events, with content hashes. Sequence training uses bounded order-three Markov contexts, smoothing, backoff, role/cohort priors, and personal estimates shrunk toward peers. At most 1,000 events per actor contribute to training. Vocabulary is limited to 512 action names.

Whole workflows are ordered chronologically into 60% training, 20% calibration, and 20% test groups; workflows crossing a boundary are purged. Training requires at least five workflows, 100 complete training events, and 20 events in each later split. Reported holdout flag rates are not attack accuracy. Transition timing requires at least 20 observations for a transition. The sustained alert-rate monitor requests review when more than 25 of the latest 100 evaluations have unusual transitions; it is an operational heuristic, not ADWIN or proof of an attack.

New applications start in shadow mode. Sequence anomalies recommend check; explicit prerequisite rules may recommend block. Personal and peer evidence remain visible, and numeric forest scores remain a separate endpoint capability. Activating a model resets live sequence history so surprise values from different generations are not mixed. Replay operates on isolated state. Enforce only after representative labeled evaluation and application handling of all three actions.

Raw events, decisions and live state expire according to the policy’s 1–365 day retention. Snapshots and models deliberately remain until explicit application-data deletion, retaining reproducibility. The admin purge endpoint removes snapshots, jobs, models, decisions, events and live history, and deactivates the model. Quarantining an event excludes it from new snapshots; retrain explicitly if an existing model was contaminated.