Assistant identity and credential authority / Field essay 47

Let an agent ask for a credential without ever seeing it.

Separate the agent’s request from the secret value. Bind operator input to the live run, requesting step, target and logical credential; resume the waiting work without returning the value to the transcript.

Published

ASSISTANT CREDENTIAL REQUEST AND SCOPED SECRET DELIVERY
FIELD ESSAY 47 / ASSISTANT IDENTITY AND CREDENTIAL AUTHORITY

Let an agent ask for a credential without ever seeing it

An agent can reach a point where it knows what to do but lacks one credential. The dangerous shortcut is pasting the secret into chat and hoping the transcript, logs, retries and delegated steps all keep it private. I built a separate request path so a model can ask for a named credential while a person supplies the value outside its view.

A request is state, not a chat message

The boundary is between the agent’s description of what it needs and the value that grants access. The agent can name a logical credential and explain why a run is waiting. It cannot read back what a person typed or turn the value into a prompt. The request becomes durable state attached to the work that needs it.

That state needs enough context to prevent accidental reuse: the destination, requesting step, active run and credential’s logical name. A generic “secret needed” flag loses the binding. A request tied only to a run may be broader than the step that needs it. I deliver the credential only to the declared step, for the declared target, while that run is active.

The person enters the value in a dedicated password field. The server stores it encrypted and does not return it in a page, response, error, runner reply or model transcript. Even a masked suffix needs a length rule: “last four” can reveal a short secret in full. Treat display as a disclosure decision, not a styling detail.

Do not let an agent rename a managed secret

Our independent review found a subtler failure than a transcript leak. An AI step could request a name already managed by the system. If a person filled that request, the new value could replace the credential stored under that name. The model did not need to read the old secret to influence which secret might be overwritten.

I now reject managed names before presenting a request and canonicalize names consistently. Case changes and whitespace cannot create a second spelling of the same managed key. The check belongs at the service boundary, not just in the UI: a worker may have been idle since validation. A model may request a capability, but it should not choose a storage identity that shadows one an operator already controls.

Resume the same request, do not ask twice

When a run waits for a credential, saving the value should resume the waiting work. The resumed step must consume the pending request rather than call the model again and generate a second prompt. Otherwise a routine resume can duplicate asks, confuse the operator or let a second request drift to a different name.

I represent the ask explicitly and make satisfaction idempotent: one request can be satisfied once; concurrent submissions converge on that state; an already-satisfied request does not create another grant. If the person cannot provide it, that is a deliberate terminal choice with a reason, not an indefinite spinner. Waiting runs end with a useful explanation instead of appearing active forever.

The state machine also needs lifecycle rules. Which run owns the request? How long is it valid? What happens if the run is cancelled, the step changes or the underlying credential rotates? Those answers belong in the service and storage contract, not in the model’s memory.

Migrations are part of the security boundary

The feature crossed the database’s allowed status transitions. A separate workflow changed the same rule, so two migrations each rebuilt it from an older picture. Whichever ran second could erase the other change. That is not merely housekeeping: a lost transition can strand a waiting run or make an unsafe state reachable.

I resolved the overlap against the current schema and tested the combined migration path. When multiple workflows share status rules, treat those rules as shared infrastructure: make changes additive where possible, inspect the current schema and test the actual migration order. A migration file existing in the repository does not prove production will preserve both branches’ intent.

Keep the secret out of every observation surface

“Not in the prompt” is only the first check. Review request creation, the human form, save response, database representation, background runner, retries, errors, refresh and concurrent submissions. Use a synthetic canary and search every output surface that could expose it. Test reserved names, short values, padded and differently cased names, expired requests, duplicate submissions and a run resumed after a successful save.

The architecture is simple to state: the model asks for a capability by name; a person supplies its value in a separate trusted interface; the server stores it without echoing it; and the broker injects it only into the exact step and target that declared it. Every transition is explicit, bounded and auditable. The secret never becomes conversational context.

When an agent says “I need an API key,” ask whether it needs to know the key or only to invoke a narrowly authorized operation with it. What credential boundary in your system still depends on a model never repeating a value?

Make credential requests expire like capabilities

A pending ask should carry an expiry, owner and terminal states. If a run is cancelled, the request should stop being actionable. If the underlying credential rotates, the old pending request should not silently bind to the new value unless a human confirms that intent. If the agent changes its requested destination or step, create a new request and require a new human decision rather than widening the existing one.

The delivery service should check the binding again at the moment of use. Validation when the card is first displayed can become stale while a worker waits. The broker can verify the target, step identity, active run, request status and managed-name policy immediately before injecting the value. It should return only whether the operation was authorized and whether the secret was available; the raw value never needs to travel back through the conversational layer.

For a practical test, create a synthetic credential, submit it, resume the run and inspect every output surface. Then cancel the run, replay the old request, change the step identity, submit twice and try a reserved name with different casing and whitespace. Each case should produce a typed result and no secret echo. Which capability request in your system remains valid after the person who approved it has changed their mind?

Keep the credential request narrower than the run

The secret handoff is a capability grant, so I bind it to the smallest useful scope. The requesting step names the destination and logical credential; the service checks that the step still belongs to the active run and that the requested name is not already managed elsewhere. A broad run-level grant creates ambiguity about which later step can receive the value.

The operator view should explain the requested destination and purpose without showing the value again after submission. Logs record that a credential was supplied and which request was resumed, but never the secret itself. That includes errors, debug traces and retry payloads. I use a synthetic canary when testing: attempt to find it in rendered pages, API responses, worker output and logs. The test should fail if any surface echoes it.

I also separate delivery from storage. Encrypting the value at rest does not prove that a worker can receive it safely, and hiding it in the user interface does not prove that an API response omitted it. Each boundary gets its own assertion. Finally, the request should expire when its run or step is no longer active. A stale approval should not become a standing credential grant that a later job can inherit.

How this was verified

I checked the shipped request, storage and delivery paths and pinned fix/review receipts in the source packet. Reviewer counts and timing claims without committed receipts are omitted.

CONNECTED READINGForward and back
Keep reading

The answer deserves
another experiment.

Back to the journal