Welcome to the final chapter of the permissions project tutorial!
In the previous chapter, Filesystem Security & Sandboxing, we secured the physical boundaries of where the agent can write files. We created a safe environment.
However, there is still one major annoyance: Memory Loss.
Imagine you tell the agent, "Always allow me to run npm test." The agent agrees. But then you restart the agent, and it asks you again. And again. It feels like the movie Groundhog Day.
In this chapter, we will build the Configuration Persistence system. This acts like a stenographer who records your decisions into permanent files, so the agent remembers them forever.
By default, computer programs store data in RAM (Random Access Memory). RAM is fast, but it is volatile. When you close the program, the data vanishes.
If the agent only updates its permissions in RAM:
git status."git status?"We need a system to move these decisions from RAM to the Hard Drive (JSON files).
Where should we save a rule? Not all rules are the same. We define three "Destinations" or scopes for permissions.
Think of them like different types of notebooks:
| Destination | Analogy | Description |
|---|---|---|
| Session | Sticky Note | Temporary. Applies only to this conversation. Throw it away when done. |
| Project | Office Whiteboard | Shared. Applies to everyone working in this specific folder. Saved in projectSettings. |
| User (Global) | Personal Diary | Private. Applies to you specifically, on every project you ever open. Saved in userSettings. |
Here is how we define this in the code (simplified from PermissionUpdateSchema.ts):
// File: PermissionUpdateSchema.ts (Simplified)
export type PermissionUpdateDestination =
| 'session' // Gone when you restart
| 'projectSettings' // Saved in the current folder's config
| 'userSettings' // Saved in your global home directory
Why does this matter? If you allow a dangerous tool in your Global settings, you might accidentally allow it in a sensitive work project. Scope ensures safety.
PermissionUpdate ObjectTo change a permission, we don't just hack the file directly. We create a formal request object. This is like filling out a form for the HR department.
A PermissionUpdate object contains everything the system needs to know.
// Example of a PermissionUpdate object
const update = {
type: 'addRules', // What are we doing?
rules: ['Bash(ls)'], // The content (from Chapter 2)
behavior: 'allow', // Allow or Deny?
destination: 'userSettings' // Where do we file this?
}
This standardization makes it easy to handle adding, removing, or replacing rules using the same pipeline.
When a user says "Always allow this," the system performs two distinct steps:
This ensures the agent feels responsive but the data is safe.
The function applyPermissionUpdate handles the first step (RAM). It takes the current state (Context) and the Update, and calculates the new state.
It uses a concept called Immutability. We don't change the old object; we create a fresh copy with the changes.
// File: PermissionUpdate.ts (Simplified)
export function applyPermissionUpdate(context, update) {
if (update.type === 'addRules') {
// 1. Get the list where this rule belongs (e.g., global allow list)
const currentList = context.alwaysAllowRules[update.destination] || []
// 2. Add the new rules to the list
const newList = [...currentList, ...update.rules]
// 3. Return a new Context with the updated list
return {
...context,
alwaysAllowRules: { ...context.alwaysAllowRules, [update.destination]: newList }
}
}
return context
}
Explanation: This function is pure logic. It doesn't touch the hard drive. It just says: "If I were to add this rule, here is what the agent's brain would look like."
The function persistPermissionUpdate handles the second step (Disk). It actually edits the configuration files.
First, it checks if persistence is even possible. You can't save a "Session" rule to disk because, by definition, session rules are temporary.
// File: PermissionUpdate.ts (Simplified)
export function supportsPersistence(destination) {
// We only write to disk if it's NOT a session rule
return destination !== 'session'
}
Then, it performs the write operation:
// File: PermissionUpdate.ts (Simplified)
export function persistPermissionUpdate(update) {
// 1. Check if we should save this
if (!supportsPersistence(update.destination)) return
// 2. If we are adding rules...
if (update.type === 'addRules') {
// 3. Call the settings manager to write to the file
addPermissionRulesToSettings({
ruleValues: update.rules,
ruleBehavior: update.behavior
},
update.destination // e.g., 'userSettings'
)
}
}
Note: The actual writing to JSON is handled by a helper (addPermissionRulesToSettings) which deals with file locking and formatting, so we don't corrupt the user's config file.
Persistence isn't just about adding rules; it's also about cleaning up.
If you decide Bash is too dangerous, you might want to remove a previously saved "Always Allow" rule.
The logic is almost identical, but instead of pushing to an array, we filter the array.
// File: PermissionUpdate.ts (Simplified Logic)
case 'removeRules': {
// Get existing rules from the file
const existingRules = getSettingsForSource(update.destination)
// Keep only the rules that DO NOT match the one we are removing
const filteredRules = existingRules.filter(
rule => rule !== update.rules[0]
)
// Save the smaller list back to the file
updateSettingsForSource(update.destination, filteredRules)
}
In this final chapter, we learned how to give the agent long-term memory.
Congratulations! You have navigated the entire architecture of the permissions system.
Let's recap your journey:
You now possess the knowledge to contribute to, debug, or extend the security model of this agent. Happy coding!
Generated by Code IQ