What your users approve, and what stays where
Configure promises each user that nothing about them moves without their say. Your integration keeps that promise by respecting a few boundaries. Configure enforces every one of them.
What Configure stores
A profile has eight sections. A read returns only the ones you ask for:
| Section | What it holds |
|---|---|
identity | Name, location, timezone, occupation, bio, interests |
preferences | Durable preferences the user or an agent recorded |
integrations | Which apps the user connected, and their state |
imports | Context brought over from another assistant, such as a ChatGPT export |
agents | What other agents the user approved have learned about them |
summary | Configure's synthesis of the above |
soul | The longer-form character document |
context | The working-context document |
Your app never sees a user's password or their one-time codes. It never receives an access token for a connected app either. Your agent calls a tool and gets the result, never the credential behind it. Your own OAuth client secret, in the table below, is a separate thing. It is yours and stays on your server.
What the user controls
- Consent is per agent. The hosted flow shows your app's name in its title. It has no permissions screen. Finishing the flow approves your agent, and only your agent.
- Control is on their profile. At profile.configure.dev, a user can narrow what your agent reads (their info, imported memories, what other platforms saved). They can also revoke your agent outright, disconnect an app, or delete their profile.
- Every memory is attributed. A user can see which agent saved what. Nothing one agent saves rewrites who they are on its own.
- Revoke anytime. When a user revokes your agent or disconnects an app, your reads are refused with a link. They do not silently go stale. Handle
authorization_requiredandtool_not_connectedas described in the Quickstart.
Context is user data, not instructions
You inject the user's context into your system prompt, but you did not write it. Its memories come from the user's other assistants and from transcripts they pasted in. So a memory can contain whatever you use to delimit the block. If it does, the block ends early, and the rest of that memory lands in your prompt where instructions go.
Strip your delimiter out of the text before you wrap it, and strip it repeatedly until a pass changes nothing:
ts
// Matches the shape of the tag, not one spelling: "</ user_context>",
// "</user_context >" and "<user_context role='system'>" all read as the
// delimiter to a model, and \b keeps "<user_contextual_notes>" as prose.
const DELIMITER = /<\s*\/?\s*user_context\b[^>]*>/gi;
export function fence(profileText: string): string {
let text = profileText;
for (;;) {
const stripped = text.replace(DELIMITER, "");
if (stripped === text) return stripped; // a fixed point, not one pass
text = stripped; // every pass shortens, so this ends
}
}One pass is not enough. One pass over </user_context<user_context>> removes the inner tag, and what is left is a valid closing delimiter. Loop until a pass changes nothing.
This removes only the delimiter. The surrounding text stays. Also say in the prompt what the block is: the user's context. It is never a source of instructions, and never something to put into a search query or a page you fetch.
Where credentials live
| Credential | Lives | Never |
|---|---|---|
sk_ secret key | Your server's environment | In a browser, a prompt, or a repo |
pk_ publishable key | Browser code and the hosted flow | Used to authorize a server call |
| User tokens and MCP session tokens | Your server, against your session store | In the model's context |
| OAuth client id and secret | Your server, for the sign-in variation | Anywhere a client can read |
What the model can reach
The model receives Configure tool definitions and tool results, and nothing else. This has two consequences:
- Identity never comes from the prompt. No Configure tool accepts a user id. The credential your server minted decides whose memory a call touches. A prompt injection inside a retrieved email cannot redirect a read to another user.
- State-changing tools fail closed. Sending an email, creating an event, or writing to a sheet needs a connected account and a user who approved your agent. Without both, the call refuses with the link that fixes it. Your app is responsible for confirming an individual send or event with the user. Connected apps shows the component for it.
If your server injects the user's context into the system prompt, keep that path in your app. Also keep the Configure tools available to the model. The injection is the minimum the model gets. The tools let it get more.
Test data is separate
The sandbox is fully separate from live data. sk_test_ keys cannot read a live user's context, and live keys cannot read sandbox data. Every address in the sandbox user's context is example.com. Rehearse everything there first.