Guest Stay Agent · System Design

A code-faithful map of the proof of concept: model proposes, deterministic policy decides, validated stay memory persists.

Cloudflare Worker + static assetsOpenAI-compatible model APIAdapter · not a shipped product← Return to demoSource repository ↗
HTTPS propose chat/completions + tool loop read tools proposal memory candidate allowlisted record read current memory proposal + verdict + trace fallback: local veto + volatile memory load UI server-owned fixture Guest / Reviewer browser Browser fallback policy.js + memory no durable side effect Cloudflare edge · custom domain Worker router /api/status /api/propose /api/act + memory HTTPS redirect · no-store JSON Static Assets HTML · JS · CSS scenario JSON Model proposer tools · JSON proposal max 6 model turns Deterministic policy act · escalate · stop model cannot self-override Memory validator exact-shape allowlist reject unknown fields Stay Memory Workers KV key: booking_id Trusted fixtures stay + scenario files server reloads; client not trusted Model provider OpenAI-compatible secret stays server-side Legend Frontend Backend Data Assets Control boundary degraded fallback

Core invariant

  • • The model proposes one fixture option.
  • • Policy independently binds act, escalate, or stop.
  • • Model text cannot grant money, access, or override authority.

Primary Play path

  • • Worker reloads trusted fixture and stay data.
  • • Policy and memory validation run server-side.
  • • Only the validated record can reach Workers KV.

Degraded path

  • • Browser calls the proposal endpoint or stand-in.
  • • Browser policy still blocks the demonstration.
  • • Memory is volatile; no durable side effect occurs.

Server-gated request lifecycle

1 · RequestBrowser sends a fixture filename and guest message to /api/act.
2 · GroundWorker reloads its own fixture and stay JSON, then reads KV memory by booking ID.
3 · ProposeModel may call in-process tools and must return one JSON option.
4 · DecideDeterministic policy ignores claimed authority and evaluates the selected option.
5 · PersistPolicy applies the verdict to memory; the validator rejects any unknown shape.
6 · RespondWorker returns proposal, trace, verdict, and current validated memory.
ACTAllowed option. Reply may pass through and the fixture’s memory spec can be persisted.
ESCALATEMoney, access, safety, ambiguity, or forced escalation. No side-effect ticket is issued.
STOPInvalid option or withdrawn state. No confirmation or door code reaches the guest.

Trust boundaries and controls

BoundaryCurrent controlConsequence
Browser → Worker/api/act requires X-Act-Key. The UI key is intentionally public and time-boxed; the operator key is a Worker secret.The UI key bounds the demo write window. It is not user authentication.
Client fixture → policyThe server accepts only a restricted fixture filename and reloads JSON through the Worker asset binding.A caller cannot forge agent_may, legality, or money flags in the server-gated path.
Model → actionThe proposer returns an option and text. veto() derives the call and ticket independently.A model-provided call: "act" or call: "override" cannot bypass policy.
Policy → KVvalidateMemoryRecord() accepts an exact top-level and escalation-row shape.Raw reasoning, tool traces, and unknown guest data cannot be written through this path.
Worker → model providerProvider key, base URL, and model resolve from Worker secrets and variables.The API credential never ships in the static page.

State and memory model

StateOwnerLifetime and use
Scenario fixturesRepository static assetsFictional stay facts, options, expected calls, UI story, and per-verdict memory specs.
Browser sessionpublic/app.jsCurrent playlist, thread, proposal, verdict, trace, and fallback memory. Reset or reload discards it.
Stay memoryWorkers KVOne JSON record per booking ID: version, commitments, offers, escalations, thread, and language.
Decision traceResponse onlyTool names, arguments, and results return to the UI. The demo does not persist a durable trace ledger.
Model reasoningNot stored in KVThe response includes a short model rationale for demonstration. Raw private chain-of-thought is outside the allowed memory shape.

Failure behavior

FailureObserved behaviorSafety property
Missing provider keyProposal endpoints return 503. The UI can use the deterministic stand-in.No external model call and no server-side memory write.
Provider error or invalid JSONWorker returns a fallback error. The browser uses the stand-in path.Fallback remains subject to policy, but only in browser memory.
Expired or missing act key/api/act returns 401; Play falls back to the client path.The failed call cannot mutate KV.
Unknown fixtureWorker rejects filenames outside the allowlisted pattern or missing asset set.Untrusted option flags never reach server policy.
Withdrawn inventoryPolicy returns stop, no guest confirmation, and a re-query/page-host workflow.Time-of-check/time-of-use failure cannot become a fake confirmation.
Invalid memory shapeValidator returns null; Worker skips the KV write.Schema drift fails closed for persistence.

Known limits

Tests and delivery

Offline tests exercise act, escalate, stop, host override, memory application, memory validation, model proposal parsing, tool calls, and the judgment fixture. CI runs the policy and proposer suites plus a Wrangler dry run. A push to main deploys the Worker when the repository has a Cloudflare API token.

The local Node server serves the same static assets and /api/propose. It does not implement the Worker’s server-gated /api/act or durable KV path.

Implementation map

public/index.html

Demo shell and visible guest/operator panels.

public/app.js

Browser story runner, API calls, fallback, rendering, and host-override demonstration.

public/policy.js

Stand-in proposer, deterministic veto, host override, and pure memory application.

src/worker.js

Cloudflare routes, act-key check, server-gated loop, KV read/write, and asset fallback.

scripts/lib/guest-propose.mjs

Provider resolution, system prompt, tool loop, JSON parsing, and proposal trace.

src/lib/trusted-fixtures.mjs

Server-owned fixture and stay loading through the asset binding.

src/lib/memory-record.mjs

Exact-shape persistence allowlist.

public/fixtures/*.json

Fictional stays, journeys, options, expected calls, and memory specifications.

.github/workflows/ci.yml

Node 22 tests, required-file checks, dry run, and conditional deployment.

scripts/guest-demo-server.mjs

Loopback static server and local proposal endpoint.