Interrupts
Interrupts let you pause your code and ask for user approval.
def writeFile(filename: string, content: string) {
raise interrupt(`Are you sure you want to write to this file?: ${filename}`)
// write to file
}A lot of functions in the Agency Standard Library raise interrupts, such as the read and write functions. Earlier, we had just auto-approved reading a file with the shorthand with approve syntax. Here's an example where we ask the user for approval instead.
node main() {
handle {
const results = read("./README.md")
print(results)
} with (data) {
printJSON(data)
const decision = input("Do you want to continue? (yes/no): ")
if (decision == "yes") {
return approve()
}
return reject()
}
}There's a couple new things here that we will discuss in detail, but here's a quick summary for now:
- We wrap the code that could throw an interrupt inside a
handleblock. - If an interrupt is raised, we print it.
- We ask for user input using the
inputfunction. - If the user approves, we return
approve. - Otherwise, we return
reject.
Now you might be thinking, "couldn't I just use the input function to ask the user for input directly?".
Or you might be thinking, "so the user has to approve every little action? That doesn't seem like a very good user experience."
We'll address both of these questions, but lets look at a couple more examples of interrupts first.
Other ways to approve or reject interrupts
Shorthand syntax
We had seen this earlier.
const results = read("./README.md") with approveShorthand syntax with a block
handle {
const results = read("./README.md")
print(results)
} with approveNamed function as handler
def handleInterrupt(data: any) {
return approve()
}
handle {
const results = read("./README.md")
print(results)
} with handleInterruptAsking for user input
You can also use interrupts to get user input:
def writeFile(content: string) {
const filename = interrupt("Where do you want to write this content?")
// write to file
}Now when you call approve, pass in the filename as an argument:
handle {
const filename = writeFile("Hello, world!")
print(`Wrote to file: ${filename}`)
} with (data) {
// filename = "myfile.txt"
return approve("myfile.txt")
}Rejecting with a message
When you reject an interrupt, it gets rejected with a generic "interrupt rejected" error. You can reject with a specific message if you would like instead.
handle {
const filename = writeFile("Hello, world!")
print(`Wrote to file: ${filename}`)
} with (data) {
return reject("Don't write any files to disk!")
}Interrupts in tool calls
Interrupts get raised in tool calls as well. This is what makes them such critical safety infrastructure. You remember that when we read a file, we approved our own read:
const result = read("./README.md") with approveBut suppose you passed the read and write functions to an LLM instead to use as tools:
const result = llm("summarize README.md", tools: [read, write])You wouldn't want it to be able to read and write any file on your file system. With interrupts, it will need to ask you for permission before reading or writing any file.
All of these examples so far involve auto-approving or asking the user for input. Let's see other ways to make reads and writes safe without needing user input.
What happens when you approve or reject an interrupt?
- When you approve, that function or node keeps executing as normal.
- If you reject, that function or node halts execution immediately, and returns a failure.
- If you reject an interrupt during a tool call, the tool halts execution immediately and we send a message to the LLM explaining that the tool call was rejected.
- If you reject with a message, and the interrupt was raised by a tool call, then your message will get sent to the LLM along with the rejection.