Skip to content

Overview

Find out what happened to one customer record as it crossed your services, and where its data changed.

Free, self-hosted, and small enough to run on a laptop. No account, no hosted service, and nothing captured is sent anywhere you did not configure.

Live demo: demo.wayscribe.dev, read-only with sample data and no sign-in.

Wayscribe was called Flight Recorder until September 2026.

A customer’s journey across two services, with the transformation step open
and a field-level diff showing Phone going in with a value and phone coming out
null

One record, every service that touched it, and the step where the value was lost. Regenerate with pnpm screenshots.

Watch the 61-second demo (no voiceover, captions burned in, quiet music), view the captioned transformation excerpt, or record the complete walkthrough from a running demo stack.

Development happens on GitLab. Issues and merge requests go there. Any GitHub repository is a read-only mirror; see docs/MIRRORING.md.

If you only read one thing, read the decision log. Every architecture decision, with the reasoning kept in, including why redaction matches key names at any depth after a live credential leak was found in it, and why replay connects to a resolved address rather than a hostname. Several record decisions that were wrong the first time and say so.

What running it found is the companion: the defects that were live in main with a green test suite, why the tests missed them, and what changed in how this is tested as a result.


A customer calls. Their phone number is missing in your CRM.

That record arrived through a webhook, went through a transformation, landed in PostgreSQL, went onto a queue, was picked up by a worker, and was pushed to a third-party API. Six services. Somewhere in there the phone number became null.

You have logs. They are in six places, keyed by request ID, and none of them knows that customer by name. You may have traces, including record attributes your instrumentation adds, but no paired view of what each step received and produced or replay tied to that evidence.

So you start grepping.

Wayscribe is built for that exact half hour. Search the customer. Read the timeline. Look at the step where the value changed.


Search 0018Z00002ABC and you get one record’s history, in order, across every service that touched it:

received receive-salesforce-webhook demo-integration
transformed transform-salesforce-account demo-integration
persisted persist-customer demo-integration
identified identify demo-integration
published publish-customer-updated demo-integration
consumed consume-customer-updated demo-worker
delivered deliver-customer-to-target demo-worker 422
retried retry-customer-delivery demo-worker 422
retried retry-customer-delivery demo-worker 422
failed move-message-to-dead-letter demo-worker

Open the transformation and you get a field-level diff of what that step received against what it produced:

Field Before After
Id "0018Z00002ABC" (absent)
Name "Jorge Polanco" (absent)
Phone "+1 919 555 1234" (absent)
Status__c "Active" (absent)
externalId (absent) "0018Z00002ABC"
name (absent) "Jorge Polanco"
phone (absent) null
status (absent) "active"

There it is. Phone went in carrying a value and phone came out null, while every other field arrived intact. That pair is the bug: a mapping reading Phone__c from a payload that carries Phone.

Then replay the original input against your corrected code and compare:

Field Before After
phone null "+1 919 555 1234"

One changed field. The fix works, tested against the input that actually failed.

The replay page for transform-salesforce-account: the recorded Salesforce
payload about to be sent to a development destination, the corrected handler’s
200 response carrying the phone number, and an Original versus replay table
where phone goes from null to “+1 919 555 1234”

Nothing is sent until you have read what will be sent, and the destination has to be on an allowlist. Replay to production is not supported.

That is the whole loop: find where the value was lost, then prove the fix.

No identifier yet, only an alert that deliveries are failing? The Journeys page lists what happened in the last hour, day, week or month, or in a range you choose, narrowed by status, entity type, environment, service, or part of a journey’s label or displayable alias. Its Failures shortcut shows only what failed. Recorded journey and step duration thresholds narrow slow work; the active-inactivity threshold is a debugging clue within the selected activity window, not proof a job is stuck. Each row shows the recorded first-to-last event span and opens the same timeline.

The timeline separates recorded gaps from broker-measured queue waits, keeps missing duration and clock uncertainty visible, and groups attempts only when the recorder supplied an explicit retry identity. Requested Retry-After and observed delay between attempts remain separate evidence.

The Journeys page: a filter bar above a table of journeys with their last
activity, recorded span, status, entity type, what each is shown as, their step
(the step a failed journey failed at, in red, else its last step) and event
count


You need Docker with Compose. Nothing else: no Node, no database, no account.

Terminal window
git clone https://gitlab.com/jojithedev/wayscribe.git && cd wayscribe
Terminal window
docker compose -f infrastructure/compose.yaml \
-f infrastructure/compose.demo.yaml up --build

That builds the images and boots the API, the interface, PostgreSQL, a queue, and four demo services that bring their own broken integration to investigate. Measured on 2026-09-14 from a fresh clone on a laptop with no Docker layer cache, the build took 38 seconds and the boot 12; the first run also downloads the base images.

Then start a journey:

Terminal window
curl -X POST http://localhost:3100/trigger

Four demo services move a Salesforce account through a webhook, a transformation, PostgreSQL, a queue, a worker, and a third-party API. The transformation contains a real defect, the queue really retries, and the target really rejects the result with a 422. About ten seconds after the trigger the journey has reached its dead-letter state: the demo queue redelivers after 3 seconds and dead-letters on the third receive (elasticmq.conf).

Open http://localhost:3000 and sign in with the admin token, which is replace-for-local-development-0000 until you set your own (below). Search 0018Z00002ABC. That is the journey above. (pnpm demo:trigger does the same as the curl and prints the direct link, if you have Node 24 and pnpm.)

The API binds 127.0.0.1:8080 and the interface 127.0.0.1:3000. If either port is taken on your machine, move it:

Terminal window
API_PORT=8081 WEB_PORT=3001 docker compose -f infrastructure/compose.yaml \
-f infrastructure/compose.demo.yaml up --build

Set your own ADMIN_TOKEN and ENCRYPTION_KEY before this holds anything real:

Terminal window
cp .env.example .env && printf 'ENCRYPTION_KEY=%s\nADMIN_TOKEN=%s\n' "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" >> .env

Recreate the stack with the same up command for them to take effect. What the demo already recorded stays under the default key, which the new one cannot read; start from an empty database with down -v first if that matters. The API logs a warning at every boot while the published development defaults are still in use.

Each row says what is supported and what CI actually runs, which are not always the same thing. tests/supported-versions.test.ts fails when this table disagrees with .gitlab-ci.yml or with the engines fields.

Component Supported What CI tests
Node.js for the SDK and the CLI 22.12 or later 22.12.0 and 24: the SDK’s build and unit tests, the CLI’s unit tests, and import and require() of the packed SDK tarball in fresh projects (sdk-node)
Node.js for running from a clone 24 24: every other job runs on node:24 or node:24-alpine
PostgreSQL 15 or later 15, 17 and 18: the whole integration suite, migrations included (database). 16 is not run; it lies between two releases that are
Docker Compose 2.24 or later not pinned: the manual demo job and the release gate use Alpine’s current docker-cli-compose. 2.24 is the oldest Compose that reads env_file with required: false, which the Compose files use
Container images linux/amd64, linux/arm64 the runner’s own architecture only: container-scan builds and scans both images on default-branch pipelines. Releases build both platforms; the arm64 images are not run in CI

doctor fails on PostgreSQL older than 15 and warns on a release newer than the newest one CI tests.


For a common stack, start from a recipe.

Install the released SDK by exact version:

Terminal window
npm install @wayscribe/node@0.2.6

To use source changes that have not been released, pack the SDK from a clone, commit the tarball to your application, and depend on it by path:

Terminal window
pnpm install
pnpm --silent --filter @wayscribe/node run pack:release /path/to/your-app/vendor/
cd /path/to/your-app
npm install ./vendor/wayscribe-node-0.2.6.tgz # records "file:vendor/…tgz"

A tarball is a built copy that travels with your application. A path into the clone instead links to a directory whose build output is not in git, which breaks on the next git clean, branch switch, or machine without the clone, and nothing rebuilds it for a job that has no build step of its own. After pulling a change that adds or updates the tarball, run npm ci before the job runs again; a deploy that skipped it hung. The SDK README has the details.

import { createRecorder } from "@wayscribe/node";
const recorder = createRecorder({
endpoint: "http://localhost:8080",
apiKey: process.env.WAYSCRIBE_API_KEY,
serviceName: "billing-api",
// Must match the environment the API key was issued for.
environment: "development",
// Prints `delivered_first` once events are stored, or why they are not.
// Turn it off once the service is known to send.
logDiagnostics: true
});
// A journey is one record's history. The entity is what you will search for.
const journey = recorder.startJourney({
entity: { type: "customer", id: account.Id }
});
// Each wrapper runs your code, returns its value unchanged, and records what
// went in and what came out. The difference between those two is the point.
const customer = await journey.transform("map-account", account, () =>
toCustomer(account)
);
const id = await journey.persist("save-customer", customer, () =>
db.customers.insert(customer)
);
// Aliases are the other identifiers this record answers to. Now a colleague who
// only has the internal ID can still find this journey.
journey.identify({ internalCustomerId: String(id) });
// Sends what is still buffered. A service calls this on shutdown; a script that
// exits without it stores nothing.
await recorder.shutdown();

Then search for account.Id. Or the internal ID. Or any other identifier you attached.

examples/instrument-a-service is a standalone project that does this end to end, including the part where a value goes missing; the instrumentation in it is about thirty lines.

An observability library that takes down the service it observes is worse than no library. This one is built so that cannot happen:

  • No runtime dependencies. It brings nothing with it.
  • Every entry point is wrapped. A recorder failure increments a counter and returns; it never reaches your code.
  • Wrappers rethrow your exact error object, so instanceof checks and custom properties on your errors keep working.
  • The event queue is bounded, and drops oldest under backpressure rather than growing without limit.
  • The transport retries behind a circuit breaker and gives up rather than piling up.
  • shutdown() races the final flush against a timeout and never hangs.
  • Nothing is written to your console unless you ask for it, apart from six warnings printed once per process: a required setting that is missing or empty, an optional setting the recorder could not use, a setting under its old name, a journeyIdSecret that cannot be used, a field whose name looks like a secret that was sent in plain text, and a journey label or displayable alias that looks like personal data (SDK README).

Tracing answers “which call was slow, and did it succeed?” Wayscribe answers “what happened to this record, and where did its data change?” Both are useful. They are not the same question.

Wayscribe APM / tracing
You search by a customer, order, or invoice ID, with first-class aliases a trace ID, a service, or attributes you add
The unit is one record’s journey one request’s spans
It shows you paired step input and output, with a field diff latency, status, structure, and attributes you add
Correlation is deterministic, by entity and alias by propagated trace context
It answers “where did this value change?” “what happened in this trace?”

Wayscribe makes five behaviors work together as one record-debugging flow:

  1. Record-first navigation. A customer, order, invoice, or alias is the primary object rather than an attribute attached separately to spans.
  2. Identity mapping. One record is a Salesforce ID here, an internal ID there, a queue message ID in between. Search any of them and get the same journey.
  3. Field-level transformation diffs. Structural rather than textual ({path, kind, before, after}), because input and output routinely use different field names, and a unified −/+ view would have to pick one and mislead about the other.
  4. Works with the architecture you already have. Webhooks, PostgreSQL, queues, workers, third-party APIs. No rewrite, no service mesh, no agent.
  5. Journey-linked safe replay. Rerun the exact recorded input against a development destination and diff the result.

It is not a replacement for OpenTelemetry. When OTel is present, the SDK reads the active trace and span IDs onto each event so you can pivot between the two. It does not write traceparent; OTel owns that header.


As of October 2026, I have not found an open-source tool that does all four for services you already run: (1) follow one record by its business id and aliases across services; (2) capture what each step received and produced and show the field that changed; (3) replay a recorded input against development; (4) with no platform to move onto.

Several open-source tools do part of it:

Tool What it does What it lacks for this job
Apache NiFi provenance Finds a piece of data, shows it at each step, replays it Only data moving through a NiFi dataflow
Workflow engines such as Temporal Keep each execution’s inputs and results, searchable Only code written as their workflows
Tracing backends such as Jaeger or Grafana Tempo Find traces by span attribute, such as an order id No documented capture of step payloads, field diff or replay
Webhook servers such as Svix Deliver webhooks with retries Only the webhook, not the steps around it
Model-history libraries such as PaperTrail or django-simple-history Record before and after values of a model’s fields One application’s database models only
Traffic capture and replay such as Keploy and Kubeshark Record traffic; Keploy replays it as tests No record history by business id across services
Lineage standards such as OpenLineage Lineage of jobs, runs and datasets No single record in the model

Convoy, Bemi and n8n come close to parts of this and are source-available rather than OSI open source. Commercial tools such as Nodinite, Turbo360, Particular ServicePulse, Dynatrace Business Flow and Honeycomb cover parts of this job on their own platforms. Honeycomb also documents managed and self-managed Private Cloud deployments in a customer’s AWS account.

docs/ALTERNATIVES.md has the licence, the overlap and the gap for each, with a source and the date it was checked. If a tool does all four, this claim is wrong: please open an issue with a link to it.


your services ──SDK──▶ API ──▶ PostgreSQL
│
└──▶ web interface

That is the entire architecture. PostgreSQL is the only required backing service: no Kafka, no Elasticsearch, no object store, no sidecar, no agent.

  • Events are captured synchronously, so the recorded payload is what the step actually saw, then batched and sent in the background.
  • Payloads are redacted inside your process, before they leave it, against a built-in list of secret-looking paths. Paths you add are appended to that list rather than replacing it, so adding one cannot silently disable the rest.
  • Entity identifiers and alias values are encrypted at rest. Payloads are not: they are stored as jsonb, which is exactly why redaction runs before they leave your process and again before they are written. Two things the instrumenting code declares public are also kept in plain text so they can be found by partial text: a journey’s label, and a copy of each alias value it marked displayable (SECURITY.md section 6).
  • Search uses HMAC tokens, so an identifier is findable without being stored in the clear.
  • Every encrypted value names the key that wrote it, so ENCRYPTION_KEY rotates through a grace period instead of costing the data already stored: the old key stays readable while rotate:reencrypt moves everything across.
  • Cross-project isolation is structural: composite primary and foreign keys make one project’s key reaching another project’s data unrepresentable, rather than something every query has to remember to check.
  • Replay resolves a hostname once and connects to that address, so a name that passes the allowlist cannot answer differently a moment later.
  • Retention sweeps per environment, on an interval, inside the API process.

Every non-obvious decision is written down with its reasoning in the decision log: 72 ADRs, including the several that were wrong the first time and say so.


The 0.2.6 preview is published.

Ingestion, search, journey timelines, field-level diffs, the Node SDK, cross-process propagation, retention, the demo, development replay, and a read-only CLI were released in 0.1.0. 0.2.0 added the native Python and Go SDKs, optional OTLP log ingestion (off by default), the CLI ingestion check and redaction preview, and database backup helpers. 0.2.1 redacts card-number names by default and warns OTLP senders about secret-looking names stored as sent. 0.2.2 updated the API’s dependencies for security advisories and corrected the SDK documentation. 0.2.3 added an anonymous read-only mode for public demos and the deployment tooling for one. 0.2.4 made the journey page and What changed easier to read and usable on phones, and gave the public demo varied failures. 0.2.5 fixed link previews of the public demo. 0.2.6 names a failed step’s likely cause: the earlier step that changed the field the error rejected. @wayscribe/node@0.2.6, the api:v0.2.6 and web:v0.2.6 images, the Go module wayscribe.dev/go v0.2.6 and the Python package wayscribe 0.2.6 are public. The 0.2.0 images were never published because of a registry incompatibility, so the first images after 0.1.0 are v0.2.1. CHANGELOG.md lists what changed.

The 0.2.1 release was verified from public artifacts on 2026-10-03: the npm signature and provenance, the PyPI publish attestation, the Go checksum entry, Sigstore signatures on both image indexes and all four platform manifests, and signed CycloneDX SBOMs equal to the published files. The tagged Compose files and public images passed all 12 doctor checks, and the Node, Python and Go SDKs each recorded and read back events. It ran on an existing ARM64 macOS Docker host, not a clean machine; see the 0.2.1 release verification. The 0.2.2 maintenance release was checked the same way the same day: npm registry signature and attestation, the Go checksum entry, Sigstore signatures and SBOM attestations on every image digest, doctor 12 of 12, and events recorded and read back from Node, Python and Go (0.2.2 release verification).

The 0.1.0 release was verified in detail. For 0.1.0, npm provenance points at release commit f6707c66ea2697a199871a4ef4263e52aa34c11c; both image indexes and their linux/amd64 and linux/arm64 manifests have verified Sigstore signatures and signed CycloneDX SBOM attestations. Its protected-tag release pipeline passed 23 jobs. An automated installation then used only the tagged public Compose files, public images, and public npm package on an existing ARM64 macOS Docker host. It applied migrations, passed all 12 doctor checks, recorded and read a journey through aliases, masking, a field diff and failure, and completed a development replay. The public package consumer ran on Node 24; the release pipeline separately tested the package on Node 22.12.0 and 24. The complete digests, job links, checks, and limits are in the 0.1.0 release verification.

This automated existing-host run is not a clean-machine installation, an unaided human onboarding trial, or evidence for the approximately 15-minute first-journey target. Those checks remain open.

The native Python SDK is also implemented and locally verified on Python 3.11 through 3.14. All 35 applicable SDK fixtures pass through the real dry-run API from the recorder’s captured request bytes, and a clean installed wheel records a loopback delivery outside the checkout. It is published on PyPI as wayscribe 0.2.6 (pip install wayscribe), with a publish attestation on each file; the Leadline pilot is still a separate open gate.

The native Go SDK is published as the module wayscribe.dev/go v0.2.6 (go get wayscribe.dev/go@v0.2.6, Go 1.22 or newer), with standard-library-only runtime and test dependencies. Its 35 applicable fixtures pass from public-recorder request bytes through the real dry-run API, and the external Go worker example proves a searchable completed journey through a local module replacement.

An adversarial review of the first-contact experience on 2026-08-09, run by AI agents at the maintainer’s direction, found that the demo which verified all of it was systematically narrow: ten flat plain-JSON events that never contained a Date, a shared object reference, a control character, a 101st event, or a rejected one. Thirty claims were raised, twenty-eight survived a refutation pass, and three affected the correctness of what you see: the diff turned a Date into {} and reported a shared reference as a change, a rejected event counted as sent, and one unstorable value stalled the SDK’s queue for the life of the process.

Every one of them was fixed the same day, with the twelve smaller findings. The fixed SDK was then pointed at a service this repository’s authors had not written, with a real ORM, which found four more, including a credential leak in the redaction itself. Those are fixed too. The whole account, and what changed about how this is tested because of it, is in What running it found. Nothing from that account remains open. Captured data can be deleted on demand (ADR-045), and credentials inside error messages are masked by shape, which catches the common ones and not a credential in an unfamiliar shape (ADR-046). The roadmap lists what is still known to be missing.

CHANGELOG.md lists what is done and what is known to be missing.

The source quick start above remains useful for the broken demo and for contributors. The released installation below needs no checkout.

These commands download Compose files from the immutable v0.2.6 tag and pull the released images. They need no source checkout.

infrastructure/compose.published.yaml pulls the images and migrates on first boot.

Start with the release and two secrets:

Terminal window
curl -O https://gitlab.com/jojithedev/wayscribe/-/raw/v0.2.6/infrastructure/compose.published.yaml
export COMPOSE_FILE=compose.published.yaml
export WAYSCRIBE_VERSION=v0.2.6
export ENCRYPTION_KEY=$(openssl rand -hex 32)
export ADMIN_TOKEN=$(openssl rand -hex 32)

COMPOSE_FILE names the files every docker compose command in this shell reads, so the commands below always see the same stack you started. WAYSCRIBE_VERSION is required: the file has no latest fallback, because the migrate service applies the schema of whatever image it pulls, and an unpinned pull could move your database across a minor release. Export both again in a new shell, along with the two secrets.

Then choose a database. Wayscribe keeps everything in one PostgreSQL database, 15 or later. For real use, bring your own: the one your team already backs up, monitors, and holds the credentials for.

Terminal window
export DATABASE_URL=postgresql://user:password@db.internal:5432/wayscribe
docker compose up -d

To try it without standing a database up first, use the bundled overlay instead. It runs PostgreSQL alongside, publishes it on 127.0.0.1:5432 (stop a local PostgreSQL on that port first), and sets DATABASE_URL for you:

Terminal window
curl -O https://gitlab.com/jojithedev/wayscribe/-/raw/v0.2.6/infrastructure/compose.bundled.yaml
export COMPOSE_FILE=compose.published.yaml:compose.bundled.yaml
docker compose up -d

The API listens on http://localhost:8080 and the interface on http://localhost:3000, where you sign in with your ADMIN_TOKEN.

A command that leaves the overlay out reports the PostgreSQL container as an orphan and suggests --remove-orphans. Do not take that advice: it removes the database container.

A new installation has no projects. Create the one you are about to instrument, and issue it a key. The same image carries the CLI, so this still needs no checkout:

Terminal window
docker compose run --rm --entrypoint node api \
packages/database/dist/cli.js project:create acme "Acme Payments"
docker compose run --rm --entrypoint node api \
packages/database/dist/cli.js key:create acme production checkout-worker

The key is printed once. It may only send events for the environment named when it was issued, production here: the SDK’s environment setting must name the same one, or every event is refused. Before giving the key to anything, check the installation with it:

Terminal window
docker compose run --rm --entrypoint node api \
packages/database/dist/cli.js doctor --api-url http://api:8080 --api-key wsk_…

doctor prints one line per check (the database, migrations, default secrets, keys, the API) and the fix for anything that fails, and exits 1 if anything did (Operations §12). Then give the key to your service as WAYSCRIBE_API_KEY and follow Instrument your own service.

No AI features in V0. A future bring-your-own-key layer may be added as an optional, disabled-by-default module. It will never be required, and nothing will be sent anywhere by default.

What “done” means for the first release

Section titled “What “done” means for the first release”

A developer can instrument an existing Node.js integration, search for one customer, reconstruct its journey across an API, database, queue, worker, and external service, see exactly where its data changed, understand the recorded failure, and safely rerun the original input against a development endpoint.

Anything not required to make that sentence true is postponed.

The promise this is built toward is find where a record was changed, lost, duplicated, delayed, or rejected. Of those five, changed and rejected are demonstrated end to end today, and delay is visible in the timeline as recorded durations and gaps. Duplication and loss are not yet first-class.


  • an application performance monitoring platform
  • a log aggregator
  • a workflow engine
  • an integration builder
  • a replacement for OpenTelemetry
  • a data warehouse lineage platform
  • a production event replay system
  • an AI incident agent

It observes workflows that already exist.

One caution worth reading. Wayscribe records the contents of your integration payloads. Treat its database as holding whatever your workflows carry. If that includes regulated data, review captureMode first: metadata-only records the shape of a journey without storing payloads at all.


Apache-2.0. The self-hosted core is free and always will be: event ingestion, entity and alias search, journey timelines, transformation diffs, error and retry inspection, development replay, and retention controls require no payment and no hosted account.

Private by default. No captured data is sent to an external service. The running services send no telemetry and no analytics, and make no outbound connection other than the ones your own configuration creates. Building the images is not offline: it downloads base images, Alpine packages and npm packages. Next.js’s build telemetry is switched off (NEXT_TELEMETRY_DISABLED=1) in the web image and in the web package’s scripts.

The reasoning is recorded in product principles and in ADR-011 and ADR-014 of the decision log.


Document Purpose
Local development Setup, commands, keys, troubleshooting
Operations Backup, restore, upgrade, key rotation, retention, deleting data, doctor, metrics
Node SDK The SDK’s full surface
Python SDK The native Python recorder (pip install wayscribe), configuration and lifecycle
Go SDK The native Go recorder (wayscribe.dev/go), configuration and lifecycle
SDK specification What a recorder in any language must do, as numbered requirements with a source for each
Propagation specification The fixed HTTP, SQS/SNS, and payload-envelope carrier contract and language-neutral vectors
Demo scenario The reference journey, end to end
Architecture Components, flows, boundaries, scaling path
Event protocol The journey event contract
Ingestion contract Normative for the two ingestion routes: limits, refusals, idempotency, the dry run, and the conformance case format
API specification HTTP API contracts
Database schema Tables, indexes, constraints, retention
Replay specification Replay rules and safeguards
Security Threat model and data handling
Security policy Reporting a vulnerability, and what is in scope
Troubleshooting No journeys appear: /ready, doctor, delivered_first, diagnostic and refusal codes, key and environment mismatches
FAQ OpenTelemetry, overhead, storage, search speed, outages, erasure, PostgreSQL versions, license; measured numbers with sources
Recipes Express + BullMQ + HubSpot, Fastify + SQS + Salesforce, Next.js + Stripe webhooks; type-checked against the SDK
Security review packet One page for a pilot team’s security reviewer: data, auth, deletion, supply chain, known gaps
Decision log Every architectural decision, and why
Product principles The non-negotiables
Product specification Problem, users, requirements, V0 boundaries
Testing strategy Unit, integration, browser, acceptance
Task list Implementation checklist and current state
Roadmap Beyond the first release
Alternatives The closest open-source and commercial tools, with sources
Changelog What changed, and what does not work yet
Contributing How to help
Support Questions, bug reports, feature requests, and response expectations
Code of Conduct Community standards and private conduct reports
Mirroring How the GitHub mirror works, and why it is a CI job
Glossary Shared terminology

TypeScript · Node 24 · pnpm workspaces · Fastify · Next.js · PostgreSQL 17 · Knex · Zod · Vitest · Playwright · Docker Compose

apps/
api/ ingestion, query, and replay API
web/ developer interface
demo/ the reference journey, five entry points in one image
packages/
protocol/ event schema and version
sdk-node/ published as @wayscribe/node
sdk-python/ published as wayscribe on PyPI, plus its conformance driver
sdk-go/ Go SDK (wayscribe.dev/go) and public fixture driver
cli/ read-only CLI over the HTTP API
database/ migrations, repositories, operator CLI
payload-security/ redaction, encryption, keys, search tokens
payload-diff/ structural field-level diffs
config/ environment parsing
examples/
instrument-a-service/ standalone; the smallest real instrumentation
python-worker/ end-to-end Python worker instrumentation
go-worker/ external-module Go worker instrumentation
infrastructure/ Compose files and queue configuration
site/ wayscribe.dev: the landing page, and these docs rendered (ADR-058)

AI agents write most of the code in this repository. Jorge, the owner, makes the decisions, and each one is recorded with its reasoning in the decision log, which holds 72 ADRs. What running it found lists the defects the agents’ tests missed and running the software found, and what changed in the testing because of them.


Wayscribe is a 0.x preview, and the next thing it needs is a few outside teams running it on one real flow. It fits best if your team writes its own integrations (webhooks, transforms, queues, workers, calls to SaaS APIs), runs on Node, Python or Go without Temporal or an iPaaS in the middle, often gets “this customer’s data is wrong” tickets, and is allowed to store payloads in its own PostgreSQL.

What you get: help instrumenting that first flow, and fixes for what gets in your way. What I ask: an honest account of where it broke, what was confusing, and whether it answered the question you had.

Write to pilots@wayscribe.dev, or open a public issue with the pilot template. If you only tried the install, telling me where it stopped is just as useful.

Issues and merge requests are welcome. CONTRIBUTING.md covers the expectations; the short version is that this codebase explains why rather than what, and a change that alters a decision should update the decision log alongside the code.

Security issues go through SECURITY.md rather than a public issue.