DEV Community

Rain S
Rain S

Posted on

AI Execution Governance, Part 10: Can Revocation Stop a Call That Is Already Running?

A tool call has passed the gate and is running. Now the role is revoked.

Does it stop, or does it finish?

Ours finishes. Revocation governs the next call, not this one.

That is not the intent of the design; it is what the current implementation does. Three things follow: what each of the three "undo" acts actually stops, when the gate takes effect, and what would have to be added for a call to be stoppable. If the first sentence is enough, the rest can be skipped.

  1. Three "undo" acts are not one
    Revocation acts on a permission, before a call starts. Evidence it leaves: the next call is refused.
    Cancellation acts on the call itself, while it runs. Evidence it leaves today: none, because there is no cancellation channel.
    Revocation of effects acts after execution. Evidence it leaves: a compensation record that can be checked later.
    Collapse them into one word and the usual false sentence appears: "it is revoked, so it stopped." What stopped may have been only the next call.

  2. When the gate takes effect
    The write path runs four checks at the execution entry:

whether the tool is enabled and the role is allowed (irreversible actions are blocked outright);
whether the arguments stay inside the declared writable field domain and destination allowlist;
whether the destination bound at confirmation time is still the destination now;
whether the approval requirement still holds (a single confirmation is not a second person's approval).
The third applies only to writes that carry a confirmation artifact. An automatic write with no artifact is not subject to it.

All four run before the tool's own code starts. The role is read from the database on every call, so a revocation takes effect on the next call immediately. For the call that has already entered, there is no second look.

It also means revocation during the confirmation or approval wait is effective: the four checks run again before the tool actually executes. Revocation stops being able to stop the call only once execute has begun.

await gate.assertToolAllowed(toolName, userId);
await gate.assertWithinDeclaredScope(toolName, args, userId);
// destination binding, approval requirement...
const result = await tool.execute(args, userId); // from here, no path back to the gate
In the same system, the model HTTP path does have a ceiling: the proxy aborts the request with AbortController when it times out. The tool execution path has nothing equivalent. The two paths do not share a boundary, and that is not a typo.

  1. Three gaps The tool signature is execute(args, userId). No signal, no abort. The executor has no AbortController, no Promise.race, no timeout. The abort on the realtime channel stops the stream, not a tool call already sent. Together: there is no kill switch today.

One boundary worth stating: the module does contain other timeouts — one around evaluation batches, one around probing an external tool's status — but neither sits on the tool execution path. They cannot stand in for the missing one.

  1. What it would take Two options, or both.

A cancellation contract. The tool signature takes a signal; a long call that is aborted returns "aborted", and the side-effect ledger records "aborted" rather than "executed". Passing the signal is the easy part. The hard part is that after an abort the ledger has to say what the target actually did.

A bounded window. Local tools take milliseconds; calls that leave the system get a ceiling, and anything past it is unknown, not success. This one does not require changing every tool signature, but it does require every outbound call to share the same ceiling.

Either option can be written as an acceptance check that can fail: revoke a role during a long call and the call is aborted, with the ledger recording an abort; exceed the window and the conclusion is unknown, never success.

Three questions for your own system:

Does your tool signature carry a cancellation signal?
Does the executor put a ceiling on a single call?
When a role is revoked, does the system ever read a state called "in flight"?

  1. Three limits Cancellation contract, bounded window and an aborted state are all absent today. The verification level here is design. This comes from reading the code, not from an end-to-end test. The cost of changing the tool interface everywhere is not estimated. There are 61 tools; this is not a one-parameter change. Closing Revocation is an act on a permission. Cancellation is an act on an execution. Treating the first as the second is how you discover, at the moment you most need to stop, that nothing stops.

What we can honestly deliver is the ledger: a call that finishes is still recorded, and can be revoked or compensated afterwards. That is weaker than stopping it, but it is not the sentence "already revoked".

The implementation described here comes from the open-source project KeelBase:
https://github.com/rain6fish/KeelBase


Drafted with an AI assistant. The system, the positions and the mistakes are mine.

Top comments (2)

Collapse
 
officialmailkr profile image
오피셜메일 •

권한 철회·실행 취소·효과 보상을 분리하고 코드 독해 수준의 검증이라고 명시한 점이 좋습니다. 취소 신호를 전달해도 외부 수신처가 이미 처리했을 수 있으니 aborted 상태 옆에 대상 처리 확인 여부도 남기면 중단 확정으로 오해하지 않겠네요.

Collapse
 
rain6fish profile image
Rain S •

That is the right next field, and we do not have it. There is no aborted state today; the cancellation contract is still a design option. When it lands, aborted alone would repeat the mistake the article is about: it would say the signal was delivered, not that the target stopped.

We already use the two-part shape on the revoke path: a request that was acknowledged versus one that arrived without an answer. The same split belongs next to cancellation: aborted for the local executor, plus whether the target confirmed it stopped. Without the second field, aborted reads as a confirmed stop, and for an external call it may only mean we stopped waiting.

I will write that into the acceptance check: abort during a long call, then the ledger has to say both that the call was aborted and whether the target confirmed. If it cannot confirm, the answer stays unknown.