npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@agenttool/data-sync

v0.1.1

Published

Explicit encrypted pull replication for agent-data/v1 nodes

Readme

@agenttool/data-sync

agent-data-sync/v1 is a small, explicit pull protocol for copying an agent-data/v1 collection between two agent-owned nodes. The destination asks one operator-configured peer for a bounded page, decrypts the page's ADDS objects locally, verifies the immutable record identity and content hash, then indexes the authorised plaintext for offline query.

This first slice implements resumable two-node pull. It does not discover peers, accept caller-supplied peer URLs or credentials, push changes, resolve conflicts, provide multi-master consensus, prove physical durability, or claim that an HTTP request is a peer-to-peer transport. CAR is deliberately not a v1 wire dependency; the inline JSON bundle profile is bounded and can later gain a bulk CAR profile without changing record identity.

Install

The package requires Bun because its optional durable checkpoint store uses bun:sqlite. Install the two required runtime peers and the bridge from their exact LOVE Package artifacts:

bun add https://docs.agenttool.dev/packages/v1/@agenttool/adds/0.2.1/agenttool-adds-0.2.1.tgz
bun add https://docs.agenttool.dev/packages/v1/@agenttool/data/0.3.1/agenttool-data-0.3.1.tgz
bun add https://docs.agenttool.dev/packages/v1/@agenttool/data-sync/0.1.1/agenttool-data-sync-0.1.1.tgz

The bridge requires @agenttool/adds at ^0.2.1 and @agenttool/data at ^0.3.1. The repository uses root-only development overrides to link the adjacent packages. On a clean source checkout, build the peers once before the bridge gate:

(cd ../data && bun install --frozen-lockfile && bun run build)
(cd ../data-protocol && bun install --frozen-lockfile && bun run build)
bun install
bun run ci
bun run build

Those development commands do not publish or upload a package.

Two nodes

The source and destination each need an ADDS identity. Persist those private keys through a scoped secret store in a real node; generateIdentity() is used below only to keep the local example short.

import { generateIdentity } from "@agenttool/adds";
import { DataNode } from "@agenttool/data";
import {
  DataSyncService,
  serveDataSyncNode,
} from "@agenttool/data-sync";

const sourceNode = await DataNode.open({
  root: ".agent-data-source",
  node_id: "node_source",
  collections: [{ id: "research", schema: { version: "1" } }],
});
const sourceIdentity = generateIdentity("did:example:source-sync");
const sourceSync = new DataSyncService({
  node: sourceNode,
  identity: sourceIdentity,
});

const destinationNode = await DataNode.open({
  root: ".agent-data-destination",
  node_id: "node_destination",
});
const destinationIdentity = generateIdentity("did:example:destination-sync");
const destinationSync = new DataSyncService({
  node: destinationNode,
  identity: destinationIdentity,
  checkpoint_path: ".agent-data-destination/sync.sqlite",
  peers: [{
    peer_id: "source",
    expected_node_id: "node_source",
    expected_publisher: sourceSync.publisher,
    base_url: "http://127.0.0.1:7742",
    bearer: "destination-page-only-token",
  }],
});
serveDataSyncNode(sourceSync, {
  hostname: "127.0.0.1",
  port: 7742,
  node_bearer: "source-local-admin-token",
  page_authorities: [{
    peer_id: "destination",
    bearer: "destination-page-only-token",
    collection_ids: ["research"],
    recipient: destinationSync.recipient,
  }],
});

const result = await destinationSync.pull({
  protocol: "agent-data-sync/v1",
  peer_id: "source",
  collection_id: "research",
  max_pages: 10,
});

const offline = destinationNode.query({
  collections: ["research"],
  text: "local-first",
  consistency: "local",
});

Non-loopback peers require HTTPS. base_url must be an exact origin without a path, URL credentials, query, or fragment; redirects fail closed. This is an operator-trust configuration boundary, not the API service's DNS-pinned safe-net transport or a universal network sandbox. Use egress policy when a configured hostname or its DNS is not fully trusted.

Agent SDK

The TypeScript and Python SDKs talk only to the local node. They accept a stable peer_id, never a peer URL, peer bearer, ADDS Grant, recipient key, or cursor:

const pulled = await at.data.sync.pull({
  peer_id: "source",
  collection_id: "research",
  max_pages: 10,
});
const status = await at.data.sync.status({
  peer_id: "source",
  collection_id: "research",
});

status.cursor_present says whether resumable state exists. The raw cursor is stored only in the checkpoint store and is intentionally absent from pull and status results. Sync-specific SDK failures also discard server prose/details so a drifted peer error cannot echo a cursor or credential through the SDK.

Wire and custody boundary

All three portable payload kinds are encrypted as separate ADDS objects:

  • collection definition;
  • immutable record envelope plus content bytes;
  • tombstone, including its optional reason.

The visible routing envelope contains the protocol, source node id, collection/record ids, change type/order/time, opaque resume cursors, and has_more. ADDS control metadata is also visible: CIDs, publisher/audience ids and public keys, Grant times, chunk counts, encrypted/plaintext sizes, and the fixed sync schema/media type. Collection definitions, record envelopes and content, tombstone reasons, and the page-control body remain ciphertext. The encrypted page-control object binds the visible origin, physical feed incarnation, collection, both cursors, has_more, ordered change headers, and every encrypted object root. The destination therefore detects a storage or relay trying to skip, reorder, or redirect signed page content before it applies anything.

Each inline bundle contains a signed ADDS Manifest and its exact content-addressed ciphertext Blocks. Its direct, finite Grant is separate from the keyless bundle and addressed to the destination's advertised X25519 key. The wire never carries the destination private key, object DEK, node bearer, or record plaintext.

ADDS supplies content integrity, publisher signatures, encryption, and a recipient-bound direct Grant. Each destination config pins the source publisher id and Ed25519 public key; it is not learned from the response. The protocol still does not resolve or externally attest that identifier. The configured HTTPS origin and page-only bearer authenticate and authorise the live request. Source and destination necessarily see authorised plaintext. Once imported, the destination intentionally stores that plaintext in its local agent-data/v1 blob store and FTS index.

An apply is checkpoint-atomic, not one cross-database transaction: the cursor advances only after every object in a page is verified and applied. A crash can therefore leave an imported prefix with the old cursor. Retrying the same page is safe because collections, records, blobs, and tombstones are immutable and idempotent; a semantically different envelope under an existing record id fails closed. Checkpoints bind the configured exact origin, expected node id, pinned publisher, and the source's persisted random feed incarnation; a repointed alias or recreated feed cannot silently reuse an old cursor. When checkpoint_path selects SQLite, its database and WAL/SHM files are set to mode 0600, subject to the host filesystem honouring those permissions. The default in-memory checkpoint does not survive process restart.

A node/feed or publisher mismatch deliberately blocks resume. After separately verifying an intentional replacement, stop competing pullers and call destinationSync.resetCheckpoint(peer_id, collection_id); it removes only that private continuation record. Imported immutable data remains, and the next pull replays the new feed from its beginning idempotently. The active-pull guard is process-local, not a distributed lease; one operator should own each checkpoint database.

HTTP surface

The sync wrapper preserves every @agenttool/data route and augments both manifest routes with capabilities.peer_sync: true plus a sync descriptor. Pull/status keep the local/admin node bearer. Page reads require a distinct page-only bearer pinned to an explicit collection allow-list and recipient:

| Method | Path | Authority | Purpose | |---|---|---|---| | POST | /v1/data/sync/page | scoped page bearer | Source builds one bounded encrypted page for its pinned recipient. | | POST | /v1/data/sync/pull | local/admin node bearer | Destination pulls from one configured peer_id and applies pages. | | GET | /v1/data/sync/status | local/admin node bearer | Sanitised checkpoint counters; no raw cursor. |

The source reads changes one at a time so max_plaintext_bytes can stop before advancing over an oversized next record. JSON request bodies, page change counts, decrypted content, bundle blocks, encoded responses, total pages, and peer request time are independently bounded. These are per-operation bounds, not global rate limiting or fairness guarantees.

License

Apache-2.0. See LICENSE and NOTICE.