@full-self-browsing/concierge
v0.4.0
Published
Typed, consent-gated actions that let an AI agent operate your web app
Maintainers
Readme
@full-self-browsing/concierge
Framework-neutral action and control runtime for letting an AI agent operate a web application. Concierge owns action admission, validation, consent, deduplication, lifecycle, workflows, and terminal execution. It does not own the model, chat UI, speech, overlay, or planning loop.
Version 0.4 is a public preview of contract 4. It requires Node 22.12 or newer; Edge runtimes are not supported in the 0.4 line. Existing actions without structured data and existing stage-level bridges remain supported. Consent binds the payload a review handler proposes.
Install
pnpm add @full-self-browsing/conciergeReact and Svelte lifecycle bindings are published separately. Optional AI SDK
6/7 tool definitions and the signed browser bridge are available from
@full-self-browsing/concierge/ai-sdk, /ai-sdk/server, and /ai-sdk/browser.
The app-owned OpenAI Realtime codec is available from
@full-self-browsing/concierge/openai-realtime. Test helpers live at
@full-self-browsing/concierge/testing.
Anonymous browser usage reporting is isolated in the optional
@full-self-browsing/concierge/telemetry subpath; importing this package root
continues to perform no browser storage, timer, DOM, or network work.
React and Svelte mount telemetry by default. Vanilla browser integrations can mount it explicitly and offer an origin-wide opt-out:
import {
mountConciergeTelemetry,
setConciergeTelemetryEnabled,
} from "@full-self-browsing/concierge/telemetry";
const unmountTelemetry = mountConciergeTelemetry(concierge);
await setConciergeTelemetryEnabled(false);See the repository's telemetry privacy contract for the exact payload and retention behavior.
Atomic catalog admission
Resolve the current stage, state-derived action availability, tool definitions, and opaque local revision together:
const resolved = concierge.resolveCatalog({
pathname: location.pathname,
canEdit: currentUser.canEdit,
});
// Give resolved.tools to the model and retain resolved.revision locally.An action may declare availableWhen(ctx). Only the literal value true
admits it; false, a thrown exception, or any non-boolean value fails closed.
Unavailable actions are absent from resolved.tools and dispatch as
unknown_action.
resolved is deeply frozen. Its branded symbol revision is memoized by the
effective stage and availability set, scoped to one Concierge instance, and
must never be serialized or used as cross-runtime authority.
Dispatch
Dispatch requires the revision that admitted the action:
const result = await concierge.dispatch(context, {
name: "openProject",
input: { projectId: "p_123" },
catalogRevision: resolved.revision,
identity: {
sessionId,
responseId,
callId,
userTurnId,
outputIndex: 0,
},
signal,
deferUntilDelivered,
});Batch dispatch accepts the same complete identity and returns an explicit
union. A completed outcome contains immutable, fully correlated
{ callId, name, outputIndex, result } rows. A terminal outcome also names
the action and lineage that entered terminal execution.
Retries deduplicate only by (sessionId, responseId, callId). An exact retry
reuses the same Promise; changing its action, input, output index, turn, or
catalog revision returns identity_conflict. A superseded local revision
returns catalog_stale. Malformed JSON in a transport batch returns
invalid_args; it is never replaced with an empty object.
Structured results and action bridges
An action may opt into schema-controlled JSON output. Existing actions need no change:
import { defineAction } from "@full-self-browsing/concierge";
import { z } from "zod";
const readResults = defineAction({
name: "readResults",
description: "Read the visible search results.",
schema: z.object({}),
redact: "drop",
output: {
schema: z.object({
kind: z.literal("results"),
ids: z.array(z.string()),
}),
redact: "drop",
},
bridge: resultsRegistry,
effects: { readOnly: true, destructive: false, idempotent: true },
handler: ({ bridge }) => ({
ok: true,
message: "Read the visible results.",
data: { kind: "results", ids: bridge?.snapshot.visibleIds() ?? [] },
}),
});Validated data is transformed by the schema, detached, recursively frozen,
and limited to 256 KiB by default. output.redact independently controls
observer exposure; it does not remove data from the agent result. An action's
bridge takes precedence over its owning stage bridge, then falls back to
null. This works for stage and cross-stage actions.
Lifecycle and workflows
concierge.onDispatch(listener) observes immutable accepted, waiting,
executing, succeeded, failed, and cancelled events. Events include
correlation, stage, terminal state, and parent/child lineage. Input appears only
through the action's redaction policy; a failed projection is reported as
dropped. Listener failures never delay or change dispatch, and an exact
dedupe hit emits no second lifecycle.
Every action handler receives serial, core-mediated workflow controls:
handler: async ({ args, workflow }) => {
workflow.cleanup(() => removeTourHighlight());
await workflow.run({
stepId: "open-settings",
name: "openSettings",
input: {},
});
await workflow.delay(300);
return { ok: true, message: "The guided tour is complete." };
}Child calls traverse normal availability, validation, consent, commit, bridge, dedupe, lifecycle, and terminal gates. They run FIFO, inherit cancellation and turn identity but not consent acknowledgements, latch the first child failure, and execute registered cleanup exactly once in LIFO order before the parent settles. Defaults are 16 nested levels and 256 steps per root workflow.
Session and transport
createSession republishes a ResolvedCatalog whenever its effective catalog
changes, including availability changes within one stage. Publishing a new
catalog aborts the prior epoch. A contract-4 transport implements
setCatalog(resolved) and one awaited onToolBatch callback returning the
batch outcome; there is no ambiguous per-call response channel.
Compatibility and stability
Documented 0.4 exports, failure reasons, wire fields, peer ranges, and contract
4 remain compatible throughout 0.4.x. Breaking changes require a synchronized
minor release and migration notes. See the
repository documentation,
security policy,
and 0.3 to 0.4 migration guide.
License
MIT © Full Self Browsing
