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
2 changes: 1 addition & 1 deletion .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run live roundtrip against receipts.liminate.dev
- name: Run live roundtrip against liminate.dev
run: python3 benchmarks/bench_list_seeding.py
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ When the user corrects the model's approach — "don't defer," "check the actual

At the end of the session, the accumulated `.limn` file is yours. Save it, diff it, hand it to another agent, run it through the interpreter.

The contracts this skill produces can be scanned at [receipts.liminate.dev](https://receipts.liminate.dev) — paste a `.limn` contract (or open a session-end permalink) and Receipts runs it through the interpreter, rendering reasoning state, citation checks, tracked decisions, open questions, and an annotated source view. That's the one-click path from a working session to a rendered inspection.
The contracts this skill produces can be scanned at [liminate.dev/receipts](https://liminate.dev/receipts) — paste a `.limn` contract (or open a session-end permalink) and Receipts runs it through the interpreter, rendering reasoning state, citation checks, tracked decisions, open questions, and an annotated source view. That's the one-click path from a working session to a rendered inspection.

### The session pack

Expand Down
8 changes: 4 additions & 4 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,13 @@ Supporting reference material:

## Receipts — inspection surface

Receipts (`https://receipts.liminate.dev`) is the hosted inspection surface for session contracts. It runs the contract through the Liminate interpreter with the session pack loaded and renders the result as a seven-section inspection view: reasoning state, warnings, session corrections, tracked decisions, open questions, citation checks, and annotated source.
Receipts (`https://liminate.dev/receipts`) is the hosted inspection surface for session contracts. It runs the contract through the Liminate interpreter with the session pack loaded and renders the result as a seven-section inspection view: reasoning state, warnings, session corrections, tracked decisions, open questions, citation checks, and annotated source.

Three ways to use it:

1. **Click the session-end permalink.** The agent saves the contract to Receipts via `POST /save` and presents a short permalink (e.g., `receipts.liminate.dev/c/a7x9k2Bf`). At Tier 1 (no tools), or if a host classifier blocks the agent's call, the agent provides a self-contained `save_receipt.py` for the user to run instead (not a paste-ready curl — pasting a multi-line curl out of chat corrupts the JSON body; see [`references/save-procedure.md`](references/save-procedure.md)). The request uses `$RECEIPTS_API_KEY` to authenticate. If the user hasn't set this up, direct them to receipts.liminate.dev/keys.
2. **Paste manually.** Go to `receipts.liminate.dev`, paste the `.limn` contract, click Run.
3. **Save for later.** After running a contract, click Save to get a short permalink (e.g., `receipts.liminate.dev/c/a7x9k2Bf`) that loads the contract from storage.
1. **Click the session-end permalink.** The agent saves the contract to Receipts via `POST /save` and presents a short permalink (e.g., `liminate.dev/c/a7x9k2Bf`). At Tier 1 (no tools), or if a host classifier blocks the agent's call, the agent provides a self-contained `save_receipt.py` for the user to run instead (not a paste-ready curl — pasting a multi-line curl out of chat corrupts the JSON body; see [`references/save-procedure.md`](references/save-procedure.md)). The request uses `$RECEIPTS_API_KEY` to authenticate. If the user hasn't set this up, direct them to liminate.dev/keys.
2. **Paste manually.** Go to `liminate.dev/receipts`, paste the `.limn` contract, click Run.
3. **Save for later.** After running a contract, click Save to get a short permalink (e.g., `liminate.dev/c/a7x9k2Bf`) that loads the contract from storage.

The inspection surface checks `cite` statements by running them through the Liminate interpreter's `substring_check` execution type. The interpreter checks — not the model. A failing `cite` shows as a red ✗ with the interpreter's error message.

Expand Down
2 changes: 1 addition & 1 deletion benchmarks/bench_list_seeding.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import urllib.request
from typing import Any

DEFAULT_ENDPOINT = "https://receipts.liminate.dev/save"
DEFAULT_ENDPOINT = "https://liminate.dev/save"

ADD_RE = re.compile(r'^\s*add\s+"[^"]*"\s+to\s+([a-zA-Z][\w-]*)', re.MULTILINE)
REMEMBER_LIST_RE = re.compile(
Expand Down
2 changes: 1 addition & 1 deletion docs/LOCAL-ONLY.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ No server involved. The chain lives in your filesystem or your repository.

## When to add Receipts

Receipts (`receipts.liminate.dev`) adds three things that local mode does not provide:
Receipts (`liminate.dev/receipts`) adds three things that local mode does not provide:

- **Permalinks.** A short URL that loads the contract in a rendered inspection view.
- **Lineage.** Parent/child relationships between contracts, queryable via API.
Expand Down
8 changes: 4 additions & 4 deletions docs/TRUST-BOUNDARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Nothing leaves the machine. No network calls. No telemetry. No phone-home. The i
```
┌─────────────┐ ┌──────────────────────────┐ ┌───────────────┐
│ .limn file │ ───▶ │ POST /save │ ───▶ │ SQLite on │
│ (your disk) │ │ receipts.liminate.dev │ │ Railway vol │
│ (your disk) │ │ liminate.dev │ │ Railway vol │
└─────────────┘ │ + Bearer token │ └───────────────┘
└──────────────────────────┘
Expand All @@ -49,7 +49,7 @@ Nothing leaves the machine. No network calls. No telemetry. No phone-home. The i
(e.g. /c/a7x9k2Bf)
```

The agent or user sends the full contract source text to `receipts.liminate.dev/save` with a Bearer token (API key) or session cookie (GitHub OAuth). The server runs the contract through its bundled copy of the interpreter, stores the source and results in SQLite, and returns a permalink.
The agent or user sends the full contract source text to `liminate.dev/save` with a Bearer token (API key) or session cookie (GitHub OAuth). The server runs the contract through its bundled copy of the interpreter, stores the source and results in SQLite, and returns a permalink.

**What the server receives and stores:**

Expand Down Expand Up @@ -133,7 +133,7 @@ Session contracts are agent-facing artifacts. When an agent reads a contract —

## Backend security posture

This section states exactly what the Receipts backend (`receipts.liminate.dev`) does and does not provide as of the date at the bottom of this document. It is written for a security reviewer evaluating the hosted service.
This section states exactly what the Receipts backend (`liminate.dev`) does and does not provide as of the date at the bottom of this document. It is written for a security reviewer evaluating the hosted service.

**Tenant isolation.** Per-user, not per-organization. Each contract has an `owner_id`. Private contracts are visible only to their owner. There is no team, organization, or workspace model. The `team_id` column exists in the database schema but is unused — it is reserved for future multi-tenant features.

Expand Down Expand Up @@ -181,4 +181,4 @@ The Receipts server records `liminate_version` and `pack_version` on every saved

---

*This document describes the system as deployed at `receipts.liminate.dev` on May 23, 2026. Interpreter version: 0.10.0. Session pack version: 0.3.0. Receipts API version: 0.3.0.*
*This document describes the system as deployed on May 23, 2026 (then served at `receipts.liminate.dev`, since consolidated to `liminate.dev`). Interpreter version: 0.10.0. Session pack version: 0.3.0. Receipts API version: 0.3.0.*
2 changes: 1 addition & 1 deletion helper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ actually happened.
- No `--session-id` → one is generated, recorded in the contract, and printed.
- No consent signal → unattended → local-only.
- No `$RECEIPTS_API_KEY` → local persistence still succeeds; only the upload
path reports the key is unset (see `receipts.liminate.dev/keys`).
path reports the key is unset (see `liminate.dev/keys`).
- `liminate` not importable → `init` validation degrades to a parse check; the
contract is still written.
6 changes: 3 additions & 3 deletions helper/contract_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
# Constants
# --------------------------------------------------------------------------

SAVE_URL = "https://receipts.liminate.dev/save"
RECEIPTS_BASE = "https://receipts.liminate.dev"
SAVE_URL = "https://liminate.dev/save"
RECEIPTS_BASE = "https://liminate.dev"

# Distinct exit code: an attended save reached the consent gate but no
# explicit `--consent upload` was given. The caller (the model, in prose)
Expand Down Expand Up @@ -452,7 +452,7 @@ def _cmd_save(args) -> int:
return 0
if result["decision"] == UploadDecision.LOCAL_ONLY_NO_KEY.value:
print("upload skipped: RECEIPTS_API_KEY not set — local-only. "
"Generate a key at receipts.liminate.dev/keys")
"Generate a key at liminate.dev/keys")
return 0
if result["needs_confirmation"]:
extra = " (sensitive content detected)" if result["sensitive"] else ""
Expand Down
22 changes: 11 additions & 11 deletions references/save-procedure.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ the lineage chain.
**Tier 2+ (bash/file tools available):** the helper's `save --attended true --consent upload` makes exactly this call for you. Shown here as the contract it fulfils and the manual fallback if the helper is unavailable:

```bash
curl -s -X POST https://receipts.liminate.dev/save \
curl -s -X POST https://liminate.dev/save \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $RECEIPTS_API_KEY" \
-d '{"source": "<full contract text, JSON-escaped>", "label": "<session label>", "agent_id": "<model identifier>", "session_id": "<session identifier>", "parent_id": "<prior contract Receipts ID, or omit>"}' \
| python3 -c "import sys,json; print('https://receipts.liminate.dev' + json.load(sys.stdin)['contract']['permalink'])"
| python3 -c "import sys,json; print('https://liminate.dev/receipts' + json.load(sys.stdin)['contract']['permalink'])"
```

**Save payload fields:**
Expand All @@ -69,9 +69,9 @@ curl -s -X POST https://receipts.liminate.dev/save \

All three identity fields are nullable. Omitting them is safe — the contract saves without them. But including them is what makes the receipt answer "who generated this" (agent_id), "in which session" (session_id), and "inheriting from what" (parent_id). A contract without these fields is an orphan — inspectable but not traceable.

`$RECEIPTS_API_KEY` is an environment variable the user sets up once. If the variable is not set, tell the user: "To save contracts to your account, generate an API key at receipts.liminate.dev/keys and run the setup command shown there."
`$RECEIPTS_API_KEY` is an environment variable the user sets up once. If the variable is not set, tell the user: "To save contracts to your account, generate an API key at liminate.dev/keys and run the setup command shown there."

Present the resulting permalink (e.g., `https://receipts.liminate.dev/c/a7x9k2Bf`).
Present the resulting permalink (e.g., `https://liminate.dev/c/a7x9k2Bf`).

**Classifier / permission note (Tier 2+).** Whether this call goes through depends on the host's permission mode. The behavior is mode-specific, not universal — do not assume every user must edit settings:

Expand All @@ -81,7 +81,7 @@ Present the resulting permalink (e.g., `https://receipts.liminate.dev/c/a7x9k2Bf
If you hit the auto-mode denial, do **not** try to tunnel around it (wrapping the curl in a python script, a heredoc, or a test runner — the classifier flags that as an Auto-Mode Bypass and the user loses trust). Instead, tell the user to add a one-line allow rule scoped to this exact endpoint:

```json
"Bash(curl -s * https://receipts.liminate.dev/* *)"
"Bash(curl -s * https://liminate.dev/* *)"
```

This single rule covers both the `POST /save` call and the
Expand Down Expand Up @@ -116,14 +116,14 @@ via `/permissions` or in their `settings.local.json` `allow` array, then restart
if session_id: payload["session_id"] = session_id
if parent_id: payload["parent_id"] = parent_id
body = json.dumps(payload).encode()
req = urllib.request.Request("https://receipts.liminate.dev/save", data=body, method="POST")
req = urllib.request.Request("https://liminate.dev/save", data=body, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer " + (key or ""))
try:
with urllib.request.urlopen(req, timeout=30) as r:
print("HTTP", r.status)
data = json.loads(r.read().decode())
print("PERMALINK:", "https://receipts.liminate.dev" + data["contract"]["permalink"])
print("PERMALINK:", "https://liminate.dev/receipts" + data["contract"]["permalink"])
except urllib.error.HTTPError as e:
print("HTTP", e.code); print(e.read().decode())
```
Expand All @@ -132,7 +132,7 @@ via `/permissions` or in their `settings.local.json` `allow` array, then restart

Avoid heredocs (`python3 - <<'PY' … PY`) for the user-run path — a paste that drops the closing delimiter leaves the shell hanging at a `heredoc>` prompt. A file the user runs by path is the robust path.

`$RECEIPTS_API_KEY` is an environment variable the user sets up once. If the variable is not set, tell the user: "To save contracts to your account, generate an API key at receipts.liminate.dev/keys and run the setup command shown there."
`$RECEIPTS_API_KEY` is an environment variable the user sets up once. If the variable is not set, tell the user: "To save contracts to your account, generate an API key at liminate.dev/keys and run the setup command shown there."

**Do NOT generate fragment-encoded URLs (`#contract=<base64>`) for contracts longer than 5 lines.** The encoding is token-expensive, produces unwieldy URLs, and takes minutes to generate. Fragment URLs are acceptable only for very short demo contracts. For any real session contract, use `POST /save`.

Expand Down Expand Up @@ -163,7 +163,7 @@ is included, and the agent is the only one who can perform the lookup.
2. Query the user's contract history:

```bash
curl -s https://receipts.liminate.dev/api/v1/export \
curl -s https://liminate.dev/api/v1/export \
-H "Authorization: Bearer $RECEIPTS_API_KEY" \
| python3 -c "
import sys, json
Expand All @@ -182,7 +182,7 @@ is included, and the agent is the only one who can perform the lookup.
prior contract first if its source is available:

```bash
curl -s -X POST https://receipts.liminate.dev/save \
curl -s -X POST https://liminate.dev/save \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $RECEIPTS_API_KEY" \
-d '{"source": "<prior contract text, JSON-escaped>", "label": "<prior session label>"}' \
Expand All @@ -193,7 +193,7 @@ is included, and the agent is the only one who can perform the lookup.

**Tier 1 (conversation only):** If a permalink was generated in a
prior session, extract the ID from the URL (e.g.,
`receipts.liminate.dev/c/HW496KG7` → `HW496KG7`). If no permalink
`liminate.dev/c/HW496KG7` → `HW496KG7`). If no permalink
exists and the user cannot run the export query, the lineage link
cannot be established — omit `parent_id`.

Expand Down
15 changes: 13 additions & 2 deletions tests/test_contract_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,15 @@ def test_save_only_uploads_on_attended_plus_consent(mod, tmp_path, monkeypatch):
posted = []
monkeypatch.setattr(mod, "_upload",
lambda src, key, **k: posted.append((src, key)) or
"https://receipts.liminate.dev/c/TESTID")
"https://liminate.dev/c/TESTID")
env = {"HOME": str(tmp_path), "XDG_DATA_HOME": "", "LIMINATE_CONTRACTS_DIR": "",
"RECEIPTS_API_KEY": "secret-key-present"}
contract = 'remember a string called source-state with "verified"\n'
result = mod.do_save(session_id="save-up", env=env, attended=True,
consent_upload=True, contract_src=contract, isatty=False)
assert len(posted) == 1
assert result["uploaded"] is True
assert result["permalink"] == "https://receipts.liminate.dev/c/TESTID"
assert result["permalink"] == "https://liminate.dev/c/TESTID"


def test_save_no_key_never_blocks_local_persist(mod, tmp_path, monkeypatch):
Expand Down Expand Up @@ -315,6 +315,17 @@ def test_sensitivity_scan_clears_benign_content(mod):
# Hygiene: no coupling to any non-public tool, stdlib-only
# --------------------------------------------------------------------------

def test_helper_targets_the_current_receipts_host(mod):
# Receipts moved from the receipts.liminate.dev subdomain to the apex
# liminate.dev (API paths unchanged, UI now at /receipts).
assert mod.RECEIPTS_BASE == "https://liminate.dev"
assert mod.SAVE_URL == "https://liminate.dev/save"


def test_helper_has_no_dead_receipts_subdomain():
assert "receipts.liminate.dev" not in HELPER_PATH.read_text()


def test_helper_does_not_reference_domain_loader():
text = HELPER_PATH.read_text().lower()
assert "domain-loader" not in text
Expand Down
Loading