Short-lived browser collaboration slots for LLM-owned work.
https://parking-lot.tini.works
updates: memory
default TTL: 10m0s
max TTL: 30m0s
An agent creates a temporary slot, uploads an index.html plus any relative-path
assets, sends the public slot URL to a human, and reads back managed feedback as latest state.
The public slot behaves like a small short-lived web root: / resolves to
index.html, and files can reference each other with relative paths.
Slots are intentionally small and temporary. They are for quick review, comments, annotations, forms, and edit boxes during an agent/human interaction.
| Token | Who uses it | What it can do |
|---|---|---|
create token | Internal agent/server only | Create new slots. |
owner_token | The requesting LLM/agent | Upload files, change visitor policy, read latest state, connect as owner. |
share_url | Human browser link | Short friendly link that opens the slot and stores visitor capability in an HttpOnly cookie. |
visitor_token | Low-level browser/client code | Explicit bearer token for managed visitor writes when not using share_url. |
Do not put API tokens in query strings. Send API tokens with
Authorization: Bearer .... For humans, prefer share_url; it uses
a short #pl=... fragment, resolves through a POST body, and then opens the slot
with cookie-backed visitor auth.
Set the base URL and internal create token in the agent environment:
BASE="https://parking-lot.tini.works"
CREATE_TOKEN="internal-create-token"
curl -sS -X POST "$BASE/api/slots" \
-H "Authorization: Bearer $CREATE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ttl_seconds": 600,
"enabled_modes": ["comment", "form", "annotation", "edit_box"]
}'
The response includes slot_id, owner_token,
share_url, visitor_token, public_url, and
owner_ws_url. Share share_url with humans.
SLOT_ID="slot_..."
OWNER_TOKEN="..."
curl -sS -X PUT "$BASE/api/slots/$SLOT_ID/files/index.html" \
-H "Authorization: Bearer $OWNER_TOKEN" \
-H "Content-Type: text/html" \
--data-binary @index.html
curl -sS -X PUT "$BASE/api/slots/$SLOT_ID/files/assets/app.css" \
-H "Authorization: Bearer $OWNER_TOKEN" \
-H "Content-Type: text/css" \
--data-binary @assets/app.css
The public slot URL serves index.html at the slot root and other files by relative path.
echo "$SHARE_URL"
# Example: https://parking-lot.tini.works/#pl=amber-bridge-9rm6kp
Keep public_url only for low-level clients that need the explicit
/s/slot_id/#v=visitor_token form.
Visitors can write only through modes enabled by the owner policy. Pages opened from
share_url can rely on the visitor cookie and omit the authorization header:
await fetch("/api/slots/" + slotId + "/visitor-updates?mode=comment&target=review", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({"text": "This part is unclear.", "author": "human"})
})
Low-level clients can still use the explicit visitor token:
VISITOR_TOKEN="..."
curl -sS -X POST "$BASE/api/slots/$SLOT_ID/visitor-updates?mode=comment&target=review" \
-H "Authorization: Bearer $VISITOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text":"This part is unclear.","author":"human"}'
curl -sS "$BASE/api/slots/$SLOT_ID/latest" \
-H "Authorization: Bearer $OWNER_TOKEN"
To steer from human feedback, the owner agent should connect to owner_ws_url
before or immediately after sharing the slot. The first message is hello
with current latest and policy. Every accepted visitor or owner
change is broadcast as an update message.
OWNER_WS_URL="wss://.../api/slots/$SLOT_ID/ws?role=owner"
websocat -H "Authorization: Bearer $OWNER_TOKEN" "$OWNER_WS_URL"
Owner message shapes:
{"type":"hello","latest":{...},"policy":{...}}
{"type":"update","event":{"id":1,"actor":"visitor","mode":"comment","target":"review","payload":{...}},"latest":{...}}
{"type":"policy_update","policy":{...}}
The steering loop is:
hello, seed local state from latest.update, inspect event.actor, mode, target, payload, and latest.GET /api/slots/$SLOT_ID/latest and track the highest events[].id already handled.Owners can send live state back to the browser on the same socket:
{"type":"owner_update","target":"status","payload":{"message":"Updated from feedback"}}
Uploaded pages can include the built-in helper instead of hand-writing visitor WebSocket code.
It connects as the visitor, sends managed updates, dispatches DOM events, and renders owner
updates into elements marked with data-pl-owner-target.
<script src="/parking-lot-client.js"></script>
<div data-pl-owner-target="status">Waiting for owner...</div>
<button id="send">Send note</button>
<script>
ParkingLot.on("update", ({event, latest}) => {
console.log("slot update", event, latest)
})
document.querySelector("#send").onclick = () => {
ParkingLot.sendVisitorUpdate("comment", "review", {
text: "Please revise this part",
author: "human"
})
}
</script>
| Mode | Payload | Latest-state behavior |
|---|---|---|
comment | {"text":"...", "author":"..."} | Appends a comment. |
form | {"fields":{"name":"value"}} | Replaces form state for the target. |
annotation | {"anchor":"...", "data":{...}} | Appends an annotation. |
edit_box | {"text":"..."} | Replaces edit-box state for the target. |
curl -sS -X PATCH "$BASE/api/slots/$SLOT_ID/policy" \
-H "Authorization: Bearer $OWNER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled_modes":["comment","annotation"]}'
Future visitor writes are checked against the latest policy.
Owners connect with an authorization header:
GET /api/slots/$SLOT_ID/ws?role=owner
Authorization: Bearer $OWNER_TOKEN
Visitors connect to /api/slots/$SLOT_ID/ws?role=visitor, then send the first message:
{"type":"auth","token":"visitor-token"}
After auth, visitors send:
{"type":"visitor_update","mode":"comment","target":"review","payload":{"text":"Looks good"}}
Owners can send live owner state:
{"type":"owner_update","target":"status","payload":{"message":"Updated the diagram"}}
10m0s.30m0s.5242880.1048576.410 Gone.GET /healthz returns service health and update backend./insert/opentelemetry/v1/logs.