Skip to content

13 — Decision Log and Open Questions

NOTE

Part A records every decision this spec was required to make explicitly: the chosen v0.1 behavior, the alternatives considered, the trade-off, and whether the choice is considered settled (S) or revisitable with evidence (R). Full normative text lives at the linked sections — this table is the index, not a second source of truth. Part B lists what is genuinely open.

Part A — Decision log

#QuestionDecision (v0.1)Alternatives consideredTrade-off acceptedS/RSpec
D1How is surfaceVersion generated/updated?Per-registry monotonic counter (opaque string) + random surfaceId per page load; bumps on structural + pushed availability changes only; lazy when() drift doesn't bumpContent hash (expensive, no ordering); timestamp (collisions, clock skew); bump-on-everything (version storms)Version ≠ full freshness: when is authoritative at invoke, not in the numberS03 §versioning
D2Can a registration change definition without deregistering?No: structure frozen per registration; only enabled/availability/handlers are dynamic; structural change ⇒ re-register (new registrationId)Mutable definitions + change events (staleness tokens become meaningless); versioned definitions per registration (complexity)Occasional re-register churn in exchange for "reg_x always means the same thing"S03 §lifecycle
D3React handlers changing every render?Latest-ref pattern: handlers read at invocation time; no deps array, no re-registration, no version bumpDeps-array re-registration (churn + stale closures); forced useCallback (footgun)Handlers are impossible to memo-freeze — deliberateS04 §handler-freshness
D4How are ID collisions detected?Key (plane,type,instanceId); structural dupes throw; runtime dupes → dead handle, first-wins (opt-in "replace"); cross-plane suffix heuristic warnsThrow always (crashes prod on transition races); last-wins (silent hijack)Transient legit collisions (crossfades) need enabled:false or "replace"S03 §collisions
D5Observations on demand or embedded in snapshot?On demand only; snapshot is a pure sync catalogEager embedding (token waste, side-effect risk, forces async snapshot); opt-in embedded previewsExtra round-trip per read; revisit via OQ-8 hintsS03 §snapshot
D6How is surface size limited?Hard caps on descriptions/meta/schemas; flat snapshot + priority; opt-in budget with explicit truncated marker (Experimental)Hierarchical/query-navigable surface; automatic relevance rankingSimple + predictable now; smart filtering deferred (OQ-4)R03 §limits, §snapshot
D7Representation of partially bound inputs?Agent-facing schema = original minus locked bound fields; boundFields metadata; values never in snapshotsFull schema + docs-only convention (models fill bound fields anyway); separate "context args" channelSchema surgery must be correct (tested); top-level fields only (OQ-6)S05 §binding
D8Can the agent override bound values?Locked by default ⇒ INVALID_INPUT; explicit overridableFields opt-out (value becomes a default)Overridable by default (reintroduces model guessing of authoritative UI state)Some legit override flows need explicit opt-inS05 §binding
D9Agent-visible vs internal metadata?Two fields: meta (JsonValue, capped, serialized) vs internal (never serialized; policy/audit-only). Non-serialization is a tested invariantSingle bag + serializer allowlist (easy to leak by omission)Slight duplication when a value is needed in bothS03 §definitions, 06 §minimization
D10Which policies client-side vs re-run server-side?Client = UX + honesty (visibility, availability, confirmation UX, advisory rate/authz); server re-checks authn/authz/tenant/input/rate/approval on every domain callTrusting client evaluation for "low-risk" ops (no such thing)Double evaluation cost, by designS05 §D10 table
D11Representing temporarily disabled capabilities?In snapshot with available:false + unavailableReason; invoking ⇒ CAPABILITY_NOT_AVAILABLE {reason}Hide when disabled (agent can't plan the enabling step)Slightly larger snapshotsS01 §availability
D12Hide unauthorized capabilities or show as unavailable?Authority hides, state discloses: authz failures hidden (and CAPABILITY_NOT_FOUND on invoke); state failures visible-disabledShow-all-with-flags (leaks existence); hide-all (starves agent planning)Policy authors must pick the right decision typeS06 §hide-vs-disable
D13Concurrent invocations?Actions serialized FIFO per component instance, queue depth 2, overflow RATE_LIMITED{queue-full}; observations bounded (per-consumer 8 / global 32 / queue 8 — amended by D24; "unlimited" was a P0 bug)Per-capability serialization (allows intra-instance races); global lock (needless coupling); unlimited (unbounded load/memory)Cross-instance races possible (rare; accepted)R03 §concurrency
D14Preventing double executions?Corrected by D22: key = (consumerKey, invocationId) per registry + request fingerprint; join/cached only on fingerprint match; mismatch fails INVOCATION_CONFLICT; in-flight joining + terminal-result cache (excl. CONFIRMATION_REQUIRED, RATE_LIMITED, INVOCATION_CONFLICT); adapters reuse provider tool-call ids within their consumer namespaceBare-invocationId cache (provider ids collide across consumers/pages → cross-request replay, the P0 bug); no dedupe; handler-level idempotency onlyBounded cache = dedupe window, not a guarantee foreverS03 §identity
D15Timeouts and cancellation?Per-kind defaults (5/10/30 s), per-capability override; cooperative AbortSignal; TIMEOUT cached terminal (retry needs new id); external abort ⇒ CANCELLEDNo timeouts (hangs); hard kill (impossible in JS)Non-cooperative handlers can still leak work (logged as late-settlement)S03 §concurrency
D16Navigation unmounts component mid-execution?Amended by D23: non-navigation actions keep first-settle-wins (ok or COMPONENT_UNMOUNTED{mid-flight}, loser logged); navigation-effect actions settle on handler settlement only — unmount never overwrites a committed transitionTiming-dependent classification for navigation (the P0 bug: success reported as COMPONENT_UNMOUNTED); block unmount until settle (fights React); silent zombie handlersAgent must treat mid-flight as "verify state"S03 §concurrency, 07 §COMPONENT_UNMOUNTED
D17Event-ordering guarantees?Total mutation order; post-mutation dispatch; coalesced surface-changed; queued (non-re-entrant) nested mutations; listener exceptions isolated; started<settled pairsFully async event bus (loses ordering); sync re-entrancy (corruption risk)Listeners see slightly deferred nested mutationsS02 §guarantees
D18Input/output serialization?JsonValue only; ISO dates; undefined stripped; dev-mode round-trip probe; output size cap fails loudly (output-too-large)structuredClone semantics (not portable to wire); binary support (deferred)No Dates/Maps/binary in payloads for nowS03 §serialization
D19JSON Schema subset?Closed allowlist (2020-12 core + anyOf + internal $defs; no if/then, oneOf, allOf, patternProperties…); validated at registration; depth/size capsFull JSON Schema (LLM + validator burden); provider-specific subsets (fragmentation)Some Zod features won't convert; fails fast at registrationR03 §subset
D20Zod integration without hard dependency?AgentSchema interface; fromStandardSchema(schema,{jsonSchema}) (validation via Standard Schema, conversion supplied by caller — Zod 4's z.toJSONSchema is one line); fromJsonSchema + minimal built-in validator as floorHard Zod dep in core (violates independence); auto-detect converters (magic, version-fragile)One extra line per schema for Zod users (see the zs helper in 10)S03 §schemas
D-effDo server-query/server-mutation belong to frontend actions?No: server effects reserved to procedure references; view actions = local-state|navigation (enforced, PLANE_VIOLATION); indirect refetches remain local-stateAllow server effects on view actions (recreates shadow-RPC, kills the plane distinction)Apps must define procedures for direct server work — that's the pointS01 §effects
D21Can input-aware policy/confirmation run before validated effective input exists?No — P0 correction: two-stage policy API (onAuthorize pre-input, onInvoke post-input with effectiveInput only); confirmation decided at phase 6, evidence bound to canonical request digest {surfaceId, registrationId, capabilityId, consumerKey, effectiveInput, effect}; canonical JSON defined (sorted keys, -00, finite numbers, undefined omitted)Single onInvoke with optional raw input (the contradiction: confirmation over unvalidated/unbound input); convention-only ordering (type system must forbid it)Breaking Draft-API change; two onion chains instead of oneS18 §c1, 02 §pipeline, 06 §policy-pipeline
D22Invocation identity across consumers/adapters/pages?Key (consumerKey, invocationId) per registry; fingerprint = fnv1a64(canonical request as issued); same key+different fingerprint → INVOCATION_CONFLICT fail-closed; anonymous → embedded:anonymous; adapters own per-instance consumer idsBare invocationId (collisions replay foreign results); fingerprint over effective input (impossible at phase 1 — recorded deviation from directive sketch, effective input is confirmation's digest instead); global registry-crossing cache (page reloads must isolate)Conflict error surfaces adapter id-bugs loudlyS18 §c2, 03 §identity, 07 §INVOCATION_CONFLICT
D23What defines navigation success under owner unmount?Handler-settlement authority for effect:"navigation": unregistration never settles the invocation; resolve→ok, reject+aborted-signal→CANCELLED, reject→EXECUTION_FAILED, else timeout/cancel; handler resolves on router acceptanceInjected AgentNavigationHost seam (second place to express one contract — designated fallback if real routers can't report acceptance in-handler); first-settle-wins (timing-dependent, the P0 bug)Navigation handlers must settle on acceptance (sync routers trivially do)S18 §c3, 01 §effects, 03 §concurrency
D24Are all runtime collections bounded?Yes — new limits: observations per-consumer 8 / total 32 / queue 8 (overflow RATE_LIMITED{queue-full}, never the action queue); maxPendingConfirmations 32 (overflow fails closed, no record); defaults conservative until example-app measurementsUnlimited observations (P0: avoidable load/memory); expiring oldest pending confirmation (silently invalidates an approval being read)Hard limits may need tuning with real data (revisitable defaults, not shape)S18 §c4, 03 §concurrency, 03 §limits
D25Configurable action concurrency?Contract specified (instance|capability|key|parallel{max}), default instance; implementation deferred to Phase C until base-queue race tests are green; not model-visibleImplement now (directive forbids: hardening, not slice prerequisite); never specify (adapters invent shapes)Spec-ahead-of-code, tracked specified in conformance manifestR18 §c5, 03 §concurrency
D26Default confirmation mode?By declared topology: embedded→wait, remote→two-phase; createAgentToolset throws if neither topology nor confirmations given; WebMCP fixed two-phase; dispose deterministically settles pending waitsGlobal wait default (P0: holds remote runs open across human approvals); global two-phase (punishes the embedded majority case)One more required option at toolset creationS18 §c6, 09 §confirmation-topology, 03 §toolset

Part B — Genuinely open questions

Each: context → options → current leaning → what it blocks.

OQ-1 · orpc-agent manifest derivation. The bridge needs {path → schemas, effect, requiresApproval} (05); the sideEffect → effect mapping is settled there. Per orpc-agent.dev, two concrete sources exist: a build-time export of the registry inventory (capabilities.capabilities()), or a bootstrap fetch of runtime.describe(surface, { actor, context }) — per-user, so server visibility policies already apply to what the frontend may reference. Leaning: describe()-based fetch, hand-written manifest as escape hatch. Still open: whether orpc-agent grows a dedicated frontend/contextual exposure surface alongside aiSdk/mcp/direct, which would make this a first-class export. Blocks: @agent-surface/orpc DX polish (M9), not v0.1. Decide before M9.

OQ-2 · WebMCP drift. navigator.modelContext shape, permissions, and lifecycle are unstable. Leaning: adapter tracks whatever ships; no core concession ever. Blocks: only @agent-surface/webmcp (v0.3).

OQ-3 · Cross-tab / multi-window surfaces. One agent, several tabs: merge surfaces? namespace by window? Options: SharedWorker aggregator; BroadcastChannel federation; explicit multi-registry consumer. Leaning: explicit consumer-side federation (no magic in core). Blocks: nothing pre-1.0.

OQ-4 · Relevance ranking / budgets beyond priority. Automatic "what matters now" filtering needs real usage data. Leaning: keep manual scope/priority; measure catalogs from the example app first. Blocks: large-app ergonomics.

OQ-5 · Contextual gating for server-side agents. Frontend when/bindings influencing a server-side agent's tool availability requires a session-sync protocol (frontend context → orpc-agent). Security-sensitive: the server must treat pushed context as hints, never authority. Leaning: design doc first; nothing in 0.x. Blocks: the server-agent topology only.

OQ-6 · Deep binding paths. bind covers top-level fields; nested (filters.city) needs path syntax + schema surgery on nested objects. Leaning: wait for a real case; workaround = restructure procedure input. Blocks: some binding shapes.

OQ-7 · i18n of descriptions/reasons. Agent-facing strings are English-authored today; user-facing confirmation summaries may need the app's locale. Leaning: summaries composable by host (requireConfirmation({summary})) is enough for v0.1; full i18n unscoped.

OQ-8 · Volatile snapshot hints. A capped, non-versioned snapshotHint ("42 rows, 3 selected") would help discovery without observation round-trips, but breaks "snapshot = pure catalog" and invites state leakage. Leaning: keep out; revisit with OQ-4 evidence.

OQ-9 · Meta-tools default threshold. When should the toolset auto-switch direct → meta? Leaning: never automatically in 0.x; explicit option only; measure model performance both ways.

OQ-10 · Streaming/subscribable observations. Agents polling readState during long operations is clumsy; a subscription channel conflicts with "minimal transfer" and most tool protocols. Leaning: out of 0.x; surfaceChanged hint + re-read covers the common case.

OQ-11 · Dedicated @agent-surface/zod sugar package. One line saved per schema vs. another package to version. Leaning: skip until D19 subset friction is measured; the zs() helper in userland is 3 lines.

OQ-12 · Confirmation UX for view: actions. confirmation:"required" on a local action (clear draft) uses the same evidence protocol as domain calls — correct but possibly heavy. Leaning: keep uniform (one protocol, one audit trail); revisit if UX friction shows up in the example app.

Specification + v0.1 implementation, published on npm as 0.x. Nothing is Stable yet.