8 min read

Should agent session resume keep its old approval?

Agent session resume must trigger a fresh authority decision when process identity, vault state, or gateway state changes.

Should agent session resume keep its old approval?

A resumed agent conversation should never inherit authority just because it inherited text. The model may still have the same plan, the same tool history, and the same confident tone. None of that tells you whether the process asking for access is the same process you approved, whether the credential vault is available, or whether the gateway still has a trustworthy record of the approval.

Treat a resume as a continuity claim that must be checked, not a right that the client gets to assert. If process identity, vault state, or gateway state changed, the old decision is over. Ask again before the next protected action.

That sounds strict until you watch how restarts happen in real work. A coding agent updates itself. An editor relaunches its helper after a crash. A user kills a stuck process and starts a new one. A laptop sleeps while the vault locks. A menu-bar app restarts after an upgrade. The conversation often reconnects so smoothly that the user sees one uninterrupted thread. Security code that treats that thread as authority will quietly join separate executions into one approval.

A resumed conversation does not carry authority

A conversation is evidence of context, while authorization is a decision about a present actor attempting a present action. They must remain separate.

Agent systems often store a long-lived conversation identifier, a transcript, and perhaps a tool-call history. Those are useful for recovering work after a disconnect. They tell the agent what it was trying to do, which files it changed, and which API request failed halfway through. They do not identify the operating-system process currently attached to the tools.

Consider a familiar sequence. An agent prepares a deployment request and receives session approval. The gateway restarts before the request is sent. The agent client reconnects, loads the old transcript, and says it is resuming the same run. If the gateway accepts that statement and restores the prior approval, the approval now covers a process that the gateway has not inspected since it came back.

The new process may be harmless. It may also be a newly installed build, a wrapper executable launched from a different directory, or a second tool client that found a cached resume token. You cannot distinguish those cases by reading the conversation.

This is why "the user already approved this task" is the wrong test. A user approves an identifiable agent run under stated conditions. The approval ends when those conditions are no longer true or can no longer be proven.

The Model Context Protocol makes this distinction easier to see. Its transport documentation says that, for stdio, the client launches the server as a subprocess and exchanges JSON-RPC messages over standard input and output. That is a live process relationship, not a durable grant stored in a transcript. The MCP changelog also removed protocol-level sessions from newer Streamable HTTP work and points stateful servers toward explicit, server-minted handles. That change does not solve authorization by itself, but it avoids pretending that a transport session identifier is an identity credential.

Keep the words straight in your design:

  • A conversation is an application record that may outlive processes.
  • A process is an operating-system instance with a finite lifetime.
  • A session approval is a grant to one identified process during that lifetime.
  • A credential use is an action that must satisfy current vault and gateway conditions.

Teams blur these terms because a happy-path demo makes all four appear to move together. Production restarts pull them apart.

Process identity has more than one part

A process identifier alone is too weak for a durable approval binding, because operating systems can reuse identifiers after a process exits. A process path alone is too weak because a file at that path can change. A code signature alone is too weak because the same signed program can run in several places at once.

Build the identity you display and bind from several facts collected at approval time. On macOS, the most useful starting point is the running process, its executable identity, and its code-signing authority. Apple documents designated requirements as the code requirement that identifies signed code, often constructed from the signing authority and embedded identifier when the app did not provide an explicit one. That gives a user something better than a raw PID to inspect.

Still, do not turn a signing authority into a magic answer. Two separate processes with the same designated requirement are still two separate processes. A signed agent may launch an unsigned helper. A locally built development binary may have an ad hoc signature. An attacker who controls the approved process after approval does not become safe because the original signature looked right.

Use a process binding with facts that mean something together:

approval_subject = {
  process_id: 48192,
  process_start_time: "2026-07-22T14:18:03Z",
  executable_file_id: "volume:.../inode:...",
  executable_hash: "sha256:...",
  signing_requirement: "anchor ... and identifier ...",
  parent_process_id: 48001,
  launch_nonce: "random-128-bit-value"
}

The exact fields vary by platform. The rule does not: the gateway needs enough evidence to reject an old approval when the old subject has gone away. The launch nonce matters because it comes from the gateway after it inspects the new process. A client cannot safely restore it from a local cache and call that continuity.

You do not need to show every field to the user. In fact, doing so usually makes the approval card worse. Show the signing authority, executable name, and a clear description of the action channel. Keep the full binding in the journal for later inspection.

There is one hard edge worth stating plainly. If your gateway cannot see or attest to the process that makes the request, it cannot grant a process-specific approval. It may still grant a smaller approval tied to another trustworthy subject, but it should not claim it has verified the process. Calling a client-provided process name "identity" is how weak designs acquire official-sounding labels.

A process restart ends a session approval

A session approval should end when its process exits, even if the replacement process has identical code, arguments, and conversation history.

This rule catches the cases that look boring until they do damage. Suppose an agent runs under an editor extension. The extension crashes, its supervisor starts a replacement, and the replacement reloads the old task state. The new process has the same project directory and likely the same user account. It does not have the authority granted to the process that crashed.

The same holds when an agent forks. A parent process may receive approval, then spawn a helper to execute shell commands or make network calls. If the approval applies to the parent only, the helper must either act through the parent under a controlled delegation or request its own approval. Passing a bearer token down the process tree makes approval portable in exactly the way you are trying to avoid.

Do not repair this with a long expiration time. A five-minute approval that any replacement process can resume is not a five-minute session approval. It is a five-minute bearer credential with a friendly label.

A better rule is simple:

if current.process_id != approved.process_id:
    deny("approval belongs to a different process")

if current.process_start_time != approved.process_start_time:
    deny("process lifetime changed")

if current.launch_nonce != approved.launch_nonce:
    deny("gateway has not bound this run")

The gateway should run this check before it decides whether a request qualifies for session approval. Do not only check it when the agent reconnects. A process can change under a client architecture that multiplexes work, and a stale approval object can survive longer than expected in memory.

A user may complain that the replacement is obviously the same agent. That is usually a request for a smoother approval card, not a reason to erase the boundary. Explain that the agent restarted and show the new signer. One deliberate click is cheaper than investigating an unintended production call that came from an uninspected process.

A vault state change removes the right to act

Vault state is not a background detail. When a vault locks, any previous decision that depended on its availability must stop granting permission for credentialed actions.

There are two failures to avoid. The first is obvious: a gateway keeps using a decrypted credential after the vault locks. The second is quieter: the gateway queues a credentialed action while locked, then executes it automatically when the vault unlocks because an old session approval still exists. The second failure turns a later human unlock into an accidental approval of an earlier request.

Apple's Keychain documentation draws the boundary clearly. Access control can require user presence when an app tries to retrieve an item, and Apple recommends choosing the most restrictive accessibility setting that fits the app. The Secure Enclave can gate cryptographic operations without exposing underlying biometric data to user-space software. Those mechanics are useful, but they do not decide your gateway semantics. Your application must decide what happens to already approved agent runs when that gate closes.

The safest answer is to maintain a vault epoch. Increment it whenever the vault locks, unlocks, resets, or loses its protected session. Attach the epoch to each approval record. A mismatch means the record cannot authorize a credentialed action.

approved_vault_epoch = 17
current_vault_epoch = 18

if approved_vault_epoch != current_vault_epoch:
    require_new_session_approval()

This does not mean every unlock must turn into an irritating ceremony. If the agent has no active session, nothing happens. If a request needs a credential after unlock, the gateway can show a new approval card that says why: the vault state changed after the earlier approval. For credentials marked for approval on every use, keep the per-call check as well. Session approval and per-call approval answer different questions.

Session approval asks whether this identified process may use this channel during its current run. Per-call approval asks whether the human wants this exact use now. Treating session approval as a substitute for a per-call decision defeats the point of marking a credential that way.

A gateway restart destroys its memory of the grant

Leave evidence for every call
Sessions and Activity journals separate agent runs from individual calls when reconnects become ambiguous.

A gateway restart should invalidate in-memory session approvals because the restarted gateway cannot prove that its old approval record still has a complete and untampered binding to the live world.

Some teams persist approval tokens and reload them after restart. The motivation is understandable: the user clicked once, the agent is still working, and a restart should not make the workflow fail. But a persistent token often becomes a reusable credential. The client sends it back after reconnecting, the gateway recognizes it, and the old grant returns without a new process inspection.

That design also makes recovery ambiguous. Did the gateway crash before it sent the action? Did it send the action and crash before it recorded the response? Did the remote service receive the request twice after retry? Restoring authorization and retrying the request as one operation mixes identity recovery with delivery recovery. They need different controls.

Give each gateway lifetime a boot identifier that exists only in its current memory. Add it to every approval record. After restart, the current boot identifier differs, so no prior session record can match.

approval = {
  gateway_boot_id: "b7f9...",
  process_binding: "...",
  vault_epoch: 17,
  approved_at: "2026-07-22T14:20:11Z"
}

if approval.gateway_boot_id != gateway.current_boot_id:
    require_new_session_approval()

The gateway can persist an audit event saying that an approval occurred. It should not reload that event as an active grant. Audit history explains what happened; it does not recreate a live authority relationship.

Sallyport follows this shape by keeping the vault in the signed menu-bar app and denying actions while the vault is locked. Its per-session authorization is tied to a new agent process, and a user can revoke a recorded run immediately. Those boundaries are useful only if a restart or lock cannot quietly reattach an old run to a new decision.

Bind approval to evidence the client cannot replay

The approval record needs server-created, current evidence. A client-provided resume token may help the gateway find a conversation or display a useful label, but it cannot be the proof that authorization remains valid.

The smallest practical pattern uses four changing values:

  1. The gateway creates a random boot identifier at launch.
  2. The gateway creates a random run nonce after it identifies a newly connected process.
  3. The vault maintains an epoch that changes whenever protected availability changes.
  4. The gateway creates an approval identifier only after the user approves the identified run.

Then the gateway evaluates each action against every value it owns. The client can request an action, but it cannot manufacture an approval that matches a new boot identifier or a new run nonce.

Here is a compact state model that teams can adapt. It deliberately stores the conversation reference as display context, never as an authorization field.

{
  "run": {
    "conversation_ref": "worktree-cleanup-42",
    "process": {
      "pid": 48192,
      "started_at": "2026-07-22T14:18:03Z",
      "signing_requirement": "recorded-at-approval",
      "launch_nonce": "gateway-generated"
    }
  },
  "approval": {
    "id": "gateway-generated",
    "gateway_boot_id": "gateway-generated",
    "vault_epoch": 17,
    "expires_when_process_exits": true
  }
}

Notice what is missing: no reusable resume_authorized flag and no expiration timestamp that can turn a dead process into a live subject. You may retain a short timeout as an additional limit, but timeout is not identity.

When the agent reconnects, make it repeat initialization and process registration. The gateway should then return one of these outcomes:

REAUTH_REQUIRED process_changed
REAUTH_REQUIRED gateway_restarted
REAUTH_REQUIRED vault_state_changed
RETRY_SAFE previous_action_not_started
STATUS_UNKNOWN inspect_activity_journal

The distinction between RETRY_SAFE and STATUS_UNKNOWN matters. A gateway can say an action did not start only if it has durable evidence that it did not start. If it lost power after handing an HTTP request to the network stack, the honest answer may be unknown. The agent should inspect the destination or journal before attempting a duplicate action.

Retrying an action is separate from restoring approval

Contain resumed SSH actions
Its stateless sp-ssh helper executes SSH actions without placing SSH keys in the agent.

A reconnect protocol should restore communication first, establish fresh authority second, and decide whether to retry third. Combining those steps produces duplicate requests and inherited approvals.

Use this sequence for a protected action that loses its connection:

  1. The agent reconnects and initializes a new gateway relationship.
  2. The gateway identifies the current process and compares it with any current run record.
  3. The gateway checks its boot identifier and current vault epoch.
  4. If any authority fact changed, the gateway requests new session approval before credential use.
  5. The gateway reports whether it knows the earlier action did not start, completed, or has unknown status.

The ordering matters. Do not ask the agent to resend the original request before establishing current authorization. That gives a reconnecting client a chance to create pressure around an old request: "I was already approved, just finish it." A human often sees the same words and assumes the request is a harmless continuation. The gateway should make the changed condition visible before it frames the action.

For HTTP APIs, use an idempotency key when the destination supports one. Generate the key for the logical business action, record it durably before sending, and reuse it only when you have re-established authorization for the retry. An idempotency key helps the remote service detect duplicate delivery. It does not authorize the retry.

For SSH, retries need more care because a remote command may have partially changed a machine before the connection died. Prefer operations that write an explicit marker or query existing state before modifying it again. A command such as mkdir can be made safer with an expected directory check. A command that rotates a credential or restarts a service should usually report unknown status after a transport failure until the agent reads the remote state.

Do not hide this uncertainty behind optimistic language. An agent that says "deployment completed" after it lost the response is fabricating confidence. The journal should say that the action result was not observed, and the agent should investigate.

The ugly failures reveal the correct boundary

The most revealing test is not a clean reconnect. It is a restart in the middle of an action, followed by an agent that tries hard to continue.

Imagine an agent that has session approval to update an issue tracker through an HTTP API. It prepares a request, receives approval, and calls the gateway. The gateway injects the credential and begins sending the request. At that moment, the gateway app restarts. The agent reconnects with its prior conversation reference and a cached local tool state.

A weak design accepts the old reference, restores approval, and retries the request. The issue may receive two comments. Worse, the approval applies to whatever process now supplies the cached state.

A careful design produces a less glamorous result. The restarted gateway creates a new boot identifier. It rejects the old session grant. It asks for approval of the currently connected process. It checks the activity journal. If the journal recorded a completed request, it returns the result. If it recorded that no request left the gateway, it permits a fresh, approved retry. If the action crossed the delivery boundary but the result was lost, it reports unknown status. The agent reads the issue before deciding whether another write is necessary.

That is more work than trusting the resume token. It is also the difference between a traceable action system and a system that hides ambiguity until a user finds duplicate changes.

Test at least these cases before you call your resume behavior safe:

  • Kill the agent after approval, then start a replacement that presents the same conversation reference.
  • Restart the gateway while an approved agent remains alive and attempts another credentialed call.
  • Lock and unlock the vault while an approved agent waits to retry.
  • Start two identical signed agent processes and ensure approval for one does not cover the other.
  • Drop the network response after the gateway begins an external request, then verify that the retry path reports delivery uncertainty.

These tests catch an especially common mistake: engineers test only whether the legitimate client can recover. They do not test whether a different client can borrow the recovery path.

Logs must explain why authority ended

See who requests access
One approval card shows a new process's code-signing authority and lasts only for that run.

An audit log should record authority changes as first-class events, not leave an investigator to infer them from gaps between tool calls.

Record the session approval with the process identity facts you used, the gateway boot identifier, the vault epoch, and the user-visible subject. Record the end event with a specific reason: process exited, process identity mismatch, gateway restarted, vault locked, manual revoke, or approval expired. Record a denied action separately from a missing action. Those are different facts.

A useful activity sequence looks like this:

14:20:11 session_approved run=R31 signer="Example Developer ID" boot=B8 vault=17
14:23:04 gateway_restarted previous_boot=B8 current_boot=C2
14:23:06 action_denied run=R31 reason=gateway_restarted
14:23:09 session_approved run=R32 signer="Example Developer ID" boot=C2 vault=17
14:23:12 http_action_started run=R32 request=Q44
14:23:13 http_action_result run=R32 request=Q44 status=201

The point is not to expose secret values or entire request bodies in every journal entry. The point is to preserve the causal chain: who asked, which authority applied, which state changed, and whether the gateway actually performed the action.

Tamper evidence matters here because an approval history is only useful if someone can detect later rewriting. Sallyport projects its Sessions and Activity journals from a write-blind encrypted, hash-chained audit log, and sp audit verify can verify that chain offline over ciphertext without a vault key. That lets a reviewer verify the sequence without opening the secrets that powered the actions.

Do not make an audit log carry the burden of prevention. A perfect record that says an old approval was reused is evidence of a bad decision, not a fix for it. The live gateway must deny the action before credential injection when its binding no longer matches.

Reapproval should be specific enough to earn the interruption

Reapproval after a meaningful state change is justified, but a vague prompt teaches users to click through it. The approval card should say what changed and who is asking now.

Avoid a generic message such as "Session expired. Approve again?" That wording pushes the user to treat the event as a timer. Show the condition that broke continuity instead: "The gateway restarted. Approve this newly connected process to use the Git hosting API for this run." If the process identity changed, show the new signing authority or state that the program is unsigned. If the vault locked, say that unlocking the vault did not restore the earlier agent grant.

This is also where you decide whether session approval is too broad for a channel. If a resumed agent wants to make a production SSH change, per-call approval may be the sensible choice even after a fresh session decision. The fact that the session survived every check does not make every command equally safe.

Do not attempt to solve approval fatigue by making approvals transferable. Solve it by minimizing unnecessary changes, showing a stable process identity when there is one, and keeping the scope of each decision understandable. The user can approve a concrete request. They cannot safely approve a promise that any future process with an old transcript may act.

The implementation rule fits on a sticky note: preserve context across a resume, but rebuild authority from live evidence. When the process, vault, or gateway changes, the old approval belongs to the past run.

FAQ

Can an AI agent keep approval after a chat session resumes?

No. A conversation transcript tells you what the model remembers, not which executable now sends requests or whether the credential store is available. Treat the resumed run as new until the gateway can bind it to current process identity, vault state, and gateway state.

Does restarting an agent require a new approval?

A process restart should invalidate per-process approval even if the agent reconnects with the same project, prompt, and account. A new process has a new lifetime, and it may have a different executable, signature, launch path, environment, or parent process.

Is code signing enough to trust a resumed agent?

No. Code signing can identify the authority that signed an executable, but it does not prove that this particular process is the one you approved earlier. Use signing information as one part of process identity, then bind approval to a specific running process and discard that binding when it exits.

Should vault lock invalidate agent authorization?

Yes. Locking the vault changes whether the gateway can use secrets, so old approval must not turn into a deferred right to act when the vault opens later. The next credentialed call should require the normal current-state checks, and any per-call approval should still happen at the call.

What happens when an action gateway restarts?

Usually, yes. A gateway restart erases the in-memory facts that tied an approval to an active run, and rebuilding those facts from a client-provided token is unsafe. Require the agent to initialize again and present a fresh approval request when its first protected action arrives.

Can a session ID prove that an agent is still authorized?

A session identifier is a routing or continuity handle, not evidence of authority. If a client can replay it after the gateway restarts, it cannot prove the same process is still attached or that the current vault state permits action.

How should an agent reconnect after losing its gateway connection?

Keep the reconnect path narrow: reconnect, initialize, identify the current process, check the vault, request session approval if needed, then retry only the action that did not receive a result. Do not let a reconnect token silently restore authorization.

What should an audit log record for agent approvals?

The approval record should include a process-bound run identifier, signing authority where available, gateway boot identifier, vault epoch, approval time, and the reason it ended. The activity record should separately show every attempted action and whether the gateway denied it before credential use.

When should an agent need approval for every action?

Per-call approval fits irreversible, high-impact, or hard-to-review actions, such as changing production access or sending money. Session approval fits ordinary development work when the process is clearly identified and the user can revoke the run immediately.

Will reapproval after a restart create too much approval fatigue?

No. Requiring a new decision after a changed authority boundary is a security property, not a request for the user to reread the same prompt. Keep the approval card specific: show the process signer, the action class, the target, and why the previous approval no longer applies.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov