Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ CLI commands:
- `notch brief`, `notch brief create`, `notch brief list`, `notch brief show <id>`
- `notch packet create`, `notch packet import <file>`, `notch packet import -`, `notch packet list`, `notch packet show <id>`, `notch packet preview <id>`
- `notch packet pack <id>`, `notch packet unpack <archive>`
- `notch inbox init`, `notch inbox list`, `notch inbox pull <delivery-id>`, `notch inbox ack <delivery-id>`
- `notch send <archive> --to <local-address>`
- `notch seed from <repo-or-store-path>`
- `notch prompt --client <client>`
- `notch scan <file-or-stdin>`
Expand All @@ -51,13 +53,13 @@ MCP tools:
- `create_seed_packet`, `import_seed_packet`
- `create_mark`, `create_reply`, `check_store`
- `get_status`, `run_doctor`
- `inbox_init`, `send_packet`, `list_inbox`, `pull_inbox_packet`, `ack_inbox_delivery`

## Deferred (do not implement)

These are deliberately out of scope. Same-repo same-tool continuity is solved by CLAUDE.md, native tool memory, and `git commit`. 3Notch's wedge is cross-boundary transport.

- `notch pass`, MCP `create_pass`/`get_latest_pass`/`get_recent_passes`
- `notch send` (sugar for create + import; the two-step is fine)
- `notch decision *`, MCP `record_decision`/`get_decisions`
- `notch question *`, MCP `add_open_question`/`get_open_questions`
- `notch conflict *`, MCP `create_conflict`/`list_conflicts`/`resolve_conflict`
Expand All @@ -70,6 +72,7 @@ A regression-guard test (`tests/unit/no-deferred-commands.test.ts`) prevents acc
- When the user presents a product idea or asks "is this the right path?", discuss and pressure-test it before changing specs, plans, or repo files.
- Treat cross-repo packets, cross-tool handoff, and private context seeding as the core product loops.
- No telemetry, cloud sync, hosted service, dashboard, vector database, or SQLite/native DB.
- Durable inbox remains a dependency-free local filesystem spool. Do not turn it into chat, a daemon, hosted identity, background sync, or model invocation.
- Do not build hidden chat or project scraping. MCP tools write selected or summarized context the user or agent explicitly supplies during a session.
- Preserve human-readable `.notch/` source files as the source of truth.
- Use `.notch/outbox/` for created packets, `.notch/inbox/` for imported packets, and ignored `.notch/private/` for user preferences, workflow conventions, and seed packets.
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

## [Unreleased]

### Fixed
- Receiver size-policy failures remain retryable after limits change, legacy size rejections can recover, and rejected deliveries no longer produce false-success resend results.
- Durable inbox listing ignores unmanaged sync-folder clutter while continuing to reject malformed entries in the managed delivery-ID namespace.

## [0.6.0] - 2026-08-04

### Added
- Explicit local durable inbox commands: `notch inbox init/list/pull/ack` and `notch send` for asynchronous packed-packet delivery between separately configured stores.
- Matching vendor-neutral MCP tools for Claude, Codex, Grok, Cursor, and other stdio MCP clients, with structured output schemas and safety annotations.
- Retained delivery state, deterministic retry identity, redacted lifecycle audit events, and an end-to-end request/reply test across two clean stores.

### Security
- Durable delivery fails closed on unregistered recipients, traversal-shaped addresses, symlinks, malformed metadata, conflicting retries, changed hashes, private/seed packets, secret-bearing content, and configured size limits.
- Archive reads now cap compressed bytes, decompressed bytes, and entry count, and reject invalid tar checksums, numeric fields, padding, duplicate paths, and trailing data.
- Refreshed dependencies and pinned `tsup` to a patched esbuild line; `npm audit` reports zero known vulnerabilities.

### Changed
- Packed packet import remains the authority for received content; durable inbox adds delivery around the existing packet, artifact, secret-scan, relationship, and review model without a daemon, database, account, or hosted relay.

## [0.5.0] - 2026-07-19

### Added
Expand Down
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ notch packet pack <packet-id>
notch packet unpack <packet-id>.notchpkt
```

### Async agents or repos — use durable inbox

When two agents cannot rely on one live chat or shared store, point both at one explicit local mailbox root:

```bash
# Run once in each store with a unique name and the same root
notch inbox init --name review-agent --root /shared/3notch-mailbox

# Pack and send from the source
notch packet pack <packet-id>
notch send <packet-id>.notchpkt --to local:review-agent

# Verify and import at the receiver
notch inbox list
notch inbox pull <delivery-id> --import
notch inbox ack <delivery-id>
```

The local address is a routing label, not authenticated identity. Deliveries are hashed, validated, scanned, retained after ack, and traced locally. See [Durable inbox](docs/guides/durable-inbox.md).

### Browser-only chats (fallback)

Web chats that can't reach local MCP use a clipboard bridge:
Expand Down Expand Up @@ -113,7 +133,7 @@ Checkpoints and packets are plain Markdown files with structured frontmatter. Yo
3. You preview the checkpoint before another tool relies on it.
4. The next session, tool, or repo reads and resumes from it.

Targeting fields (`--to-agent`, `--to-repo`) are routing metadata, not delivery controls — your existing transport moves the bytes.
Targeting fields (`--to-agent`, `--to-repo`) remain intent metadata. Manual transports move archives directly; durable inbox uses a separate registered `local:` address as its delivery control.

## Commands

Expand All @@ -124,6 +144,8 @@ notch packet import <file-or-folder> import a packet into .notch/inbox/
notch packet preview <id> show what an agent will read
notch packet pack <id> produce a .notchpkt archive
notch packet unpack <archive> import a .notchpkt archive
notch inbox init/list/pull/ack configure and process durable deliveries
notch send <archive> --to <address> send a packed project handoff
notch packet list / show list / inspect packets
notch reply <id> typed reply to a packet
notch mark self-addressed private capture
Expand All @@ -141,8 +163,8 @@ notch mcp serve local stdio MCP server

`notch mcp serve` exposes these tools over local stdio:

- Read-only: `get_brief`, `list_briefs`, `get_targeted_brief`, `get_packet`, `list_packets`, `get_status`, `check_store`, `run_doctor`
- Write: `create_brief`, `create_packet`, `create_mark`, `create_reply`, `create_seed_packet`, `import_packet`, `import_seed_packet`
- Read-only: `get_brief`, `list_briefs`, `get_targeted_brief`, `get_packet`, `list_packets`, `list_inbox`, `get_status`, `check_store`, `run_doctor`
- Write: `create_brief`, `create_packet`, `create_mark`, `create_reply`, `create_seed_packet`, `import_packet`, `import_seed_packet`, `inbox_init`, `send_packet`, `pull_inbox_packet`, `ack_inbox_delivery`

Private records under `.notch/private/` are hidden unless the server starts with `--include-private`. See [docs/guides/mcp-setup.md](docs/guides/mcp-setup.md) for client-specific setup.

Expand All @@ -154,6 +176,7 @@ Full index: [docs/README.md](docs/README.md).
| --- | --- |
| Move context between repos | [docs/guides/cross-repo-packets.md](docs/guides/cross-repo-packets.md) |
| Move context between tools | [docs/guides/cross-tool-handoff.md](docs/guides/cross-tool-handoff.md) |
| Deliver packets asynchronously | [docs/guides/durable-inbox.md](docs/guides/durable-inbox.md) |
| Carry preferences into a new repo | [docs/guides/private-context-seeding.md](docs/guides/private-context-seeding.md) |
| Brief an agent on scoped work | [docs/guides/targeted-brief-workflow.md](docs/guides/targeted-brief-workflow.md) |
| Set up continuation checkpoints | [docs/guides/continuation-checkpoints.md](docs/guides/continuation-checkpoints.md) |
Expand All @@ -170,7 +193,7 @@ Full index: [docs/README.md](docs/README.md).
- No telemetry.
- No vector database or native database dependency.
- No arbitrary shell execution through MCP.
- Your existing tools move bytes; 3Notch validates, scans, and stores them.
- Manual transports or the explicit local durable inbox move bytes; 3Notch validates, scans, hashes, and stores them.

A regression-guard test (`tests/unit/no-deferred-commands.test.ts`) prevents accidental scope creep.

Expand All @@ -185,7 +208,8 @@ A regression-guard test (`tests/unit/no-deferred-commands.test.ts`) prevents acc
- **Claude Code → web-chat sharing** (reverse of the current bridge).

**Moving packets between machines and people**
- **Cross-machine transport recipes** for Tailscale, iCloud, Syncthing, git, scp/rsync — opinionated adapter docs.
- **Hosted authenticated transport** that preserves the durable-inbox CLI/MCP contract without putting accounts or network listeners in the local core.
- **Cross-machine transport recipes** for Tailscale, iCloud, Syncthing, git, scp/rsync — opinionated adapter docs and honest sync guarantees.
- **Cross-user / teammate workflows** — conventions for handing packets through whatever channel a team already uses.

**Distribution polish**
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Task-shaped how-tos for the main flows.

- [Cross-repo packets](guides/cross-repo-packets.md) — create, preview, import; `--file` vs `--ref`; pack/unpack.
- [Cross-tool handoff](guides/cross-tool-handoff.md) — same-store, cross-repo, cross-machine, and web-chat shapes.
- [Durable inbox](guides/durable-inbox.md) — asynchronous packet delivery through an explicit local mailbox root.
- [Private context seeding](guides/private-context-seeding.md) — carrying reviewed preferences into a new repo.
- [Targeted brief workflow](guides/targeted-brief-workflow.md) — scoped task briefs vs cross-boundary packets.
- [MCP setup](guides/mcp-setup.md) — server flags, available tools, agent instruction packs.
Expand Down
77 changes: 77 additions & 0 deletions docs/active-plans/v5/3notch-v5-durable-inbox-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 3Notch V5 — Durable Inbox

Status: implementation complete; ready for review
Branch: `agent/2026-08-04-durable-inbox`
Base: `a9006ca`

## Goal

Let two separately configured 3Notch stores exchange immutable `.notchpkt` files asynchronously through one explicit local mailbox root. Preserve the existing packet, import, reply, privacy, audit, and MCP model.

## Product Contract

```text
notch inbox init --name <name> --root <path>
notch send <packet.notchpkt> --to local:<recipient>
notch inbox list [--all]
notch inbox pull <delivery-id> [--import] [--as-reviewed]
notch inbox ack <delivery-id>
```

Matching MCP tools:

- `inbox_init`
- `send_packet`
- `list_inbox`
- `pull_inbox_packet`
- `ack_inbox_delivery`

The CLI and MCP return the same small structured result fields. Tool descriptions and server instructions explain the pack → send → list → pull/import → ack sequence.

## Simplicity Boundary

V5 is a filesystem spool around existing packets. It is not chat, sync, identity, hosting, agent invocation, or a database. It adds no daemon or dependency. The local adapter does not interpret packet content. The service layer reuses existing packet validation before delivery and import.

The CLI/MCP schemas are the stable boundary for a later authenticated Streamable HTTP adapter or plugin. That future layer can serve Claude, Codex, Grok, and other MCP clients without vendor branches in the inbox core.

## Storage

Each store keeps ignored machine-local config at `.notch/inbox-config.json`:

```json
{
"schemaVersion": "1.0.0",
"transport": "local",
"name": "review-agent",
"address": "local:review-agent",
"root": "/explicit/mailbox/root"
}
```

The root contains recipient markers and immutable delivery directories. Each delivery contains validated metadata, exact archive bytes, and a SHA-256 sidecar. Delivery state is `pending`, `pulled`, `acked`, or `rejected`; ack never deletes bytes.

Delivery IDs are deterministic from the packet ID. Same ID + same archive hash is an idempotent retry. Same ID + different bytes is a conflict.

## Security And Durability

- Require an explicit root and a registered recipient; typo addresses fail.
- Reject unsafe names, traversal, symlinks inside the managed tree, non-regular files, malformed metadata, archive bombs/oversize input, hash mismatch, private packets, seed packets, secret-bearing packets, and invalid packet/artifact schemas.
- Validate and scan before the archive enters the shared root; validate the byte hash again before pull/import.
- Stage a complete delivery beside its destination and rename once; never expose partial delivery directories.
- Serialize recipient mutations using the existing short-lived filesystem lock and clean failed temp state.
- Keep address labels honest: local labels are not authenticated identities, encryption, or authorization.
- Append local redacted audit events for init, send, pull/import, ack, rejection, and failure. Never log packet content, credentials, or payment/auth data.

## Verification

- Unit: config/address/metadata/state validation, adapter CRUD, idempotent and conflicting retries, corruption, unsafe paths/symlinks, limits, private/seed/secret rejection, cleanup, audit events, and concurrency.
- CLI: useful human output, JSON parity, missing-config guidance, full state loop.
- MCP: schemas, annotations, read-only enforcement, structured results, full tool loop.
- E2E: two clean repos exchange request and reply, preserve `replyTo`, pass `notch check`, reject corruption/conflicts, and produce valid traces.
- Package: install packed tarball into a clean temp prefix and repeat the request/reply loop with built CLI.
- Repository: lint, type-check, build, all tests, e2e, no-secret diff, dependency audit review, clean focused commit.

## Non-goals

- Hosted relay, remote MCP transport, OAuth, accounts, keys, signatures, encryption, team ACLs, retention/delete, background polling, guaranteed sync-folder semantics, or model-to-model invocation.
- Changes to packet meaning, relationship semantics, `notch check`, private MCP visibility, or Stoa commercial logic.
19 changes: 19 additions & 0 deletions docs/active-plans/v5/v5-implementation-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# V5 Implementation Log

## 2026-08-04

- Activated the durable-inbox plan on isolated branch `agent/2026-08-04-durable-inbox` from upstream `a9006ca` after confirming PRs #1-#3 are merged.
- Reviewed the proposed CLI against current packet/import/MCP/store-lock behavior and corrected the missing transport-root contract.
- Kept V5 as a dependency-free local spool: explicit root, registered local address, deterministic delivery identity, sender-side preflight, hash-before-import, retained acknowledgements, vendor-neutral MCP schemas, and redacted local lifecycle audit events.
- Baseline lint, type-check, build, and 22 e2e tests passed. The current dependency lock reports 11 npm advisories; remediation will be tested separately from inbox behavior.
- Implemented the dependency-free local adapter and service: ignored config, safe local addresses, registered recipients, deterministic delivery IDs, atomic staging, recipient serialization, strict metadata schemas, retained states, hash-before-pull, pre-send/pre-import archive/schema/artifact/secret validation, private/seed blocking, decompression and size caps, and redacted lifecycle audit events.
- Added the CLI (`send`, `inbox init/list/pull/ack`) and five matching MCP tools with shared structured results, focused descriptions, output schemas, write/read annotations, and concise cross-model workflow instructions.
- Core verification passes: lint, type-check, and 16 focused adapter/service tests including 20 simultaneous sends, 10 simultaneous import pulls, retry idempotency, conflicting bytes, corruption/rejection, unsafe addresses, symlinks, malformed stores, secret/private blocking, archive bounds, retained ack history, and audit traces.
- Hardened the archive boundary for untrusted delivery: reject oversized files before reading, bound decompression and entry count, verify tar header checksums and numeric fields, and reject malformed padding, duplicate paths, unsafe entries, missing end markers, and trailing data.
- Refreshed the lockfile without forced major upgrades. The source graph and clean installed tarball both report zero npm advisories; a scoped `tsup` override keeps esbuild on the patched `0.28.1` line.
- Final verification passed after a clean `npm ci`: lint, type-check, build, 234 tests across 78 files, and 23 e2e tests across 11 files. The packed `@3notch/cli@0.6.0` tarball installed into an empty prefix and completed a real request → import → typed reply → import → ack loop with lineage preserved and both stores passing `notch check`.

## 2026-08-07

- Fixed PR review regressions in durable-inbox recovery and enumeration: receiver size-policy failures stay retryable, legacy size rejections clear after successful revalidation, terminal rejections cannot return false-success resend results, and unmanaged sync-folder clutter no longer blocks listing while malformed managed delivery entries still fail closed.
- Regression verification passed: lint, type-check, production build, 235 tests across 78 files, 23 e2e tests across 11 files, and the built CLI help smoke check.
2 changes: 1 addition & 1 deletion docs/guides/cross-repo-packets.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ notch packet create \

The packet is written to `.notch/outbox/`. When `--file` is used, it lands as a folder containing `packet.md`, `manifest.json`, and copied bytes under `artifacts/`, preserving each file's project-relative path. Without `--file`, it lands as a single `.md` file.

Recipient fields (`--to-agent`, `--to-person`, `--to-repo`) label intent. They do not authenticate or deliver anything — local filesystems and your own transport (scp, rsync, iCloud, Tailscale, email, git) move the bytes.
Recipient fields (`--to-agent`, `--to-person`, `--to-repo`) label intent. They do not authenticate or deliver anything. Manual transports move the bytes directly; [durable inbox](durable-inbox.md) uses a separately registered `local:` address.

## File vs Reference

Expand Down
8 changes: 7 additions & 1 deletion docs/guides/cross-tool-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ notch packet unpack <packet-id>.notchpkt

`.notchpkt` is a gzipped tar with deterministic entry ordering. Hashes are re-verified at unpack against the manifest before anything lands in the inbox.

## Async Agents Through A Local Mailbox

When the source and receiver operate at different times, configure one explicit durable-inbox root and exchange registered `local:` addresses. The sender packs once; the receiver lists, verifies/imports, and acknowledges later. The same tools are available over MCP, so the two sides may use different model clients.

See [durable-inbox.md](durable-inbox.md). This flow is local filesystem delivery, not authenticated identity, hosted chat, or guaranteed network sync.

## Web-Chat (no local MCP)

For Claude.ai and other browser chats that can't reach a local MCP server, use the bridge prompt and stdin import:
Expand All @@ -74,6 +80,6 @@ See [../prompts/web-chat-to-project.md](../prompts/web-chat-to-project.md) for t

- The source must hand over the context it wants stored. 3Notch does not derive it for you.
- Source links and exclusions belong in the packet, not as out-of-band conversation.
- Same-store handoff is fastest. Cross-repo and cross-machine require an explicit import.
- Same-store handoff is fastest. Manual cross-repo/cross-machine movement and durable inbox both require an explicit validated import.
- Inbox packets are immutable. To change context, author a successor with `--supersedes` or a typed reply with `notch reply`.
- Private seed records (`.notch/private/`) are hidden from MCP unless the server is started with `--include-private`.
Loading
Loading