Welcome back! in Chapter 4: Host Adapter, we built the central hub that connects our AI to the Operating System. We can now control the mouse, check permissions, and log actions.
However, computers are multitasking environments. What happens if you accidentally open two terminal windows and run the AI agent in both of them simultaneously?
Imagine two people sitting in the driver's seat of a car.
If they both grab the steering wheel at the same time, the car crashes. Similarly, if two AI agents try to control your mouse simultaneously, the cursor will jitter wildly, clicking things neither of them intended.
To prevent this, we need a "Bathroom Door" system:
To implement this, we use the File System as our source of truth.
computer-use.lock. If this file exists, the computer is "Occupied."12345). We write this number inside the lock file.12345 still alive? If not, it means the previous session crashed, and we can safely delete the lock (break down the door).
In our application, we don't manually manage the file. We use two simple functions exported from computerUseLock.ts.
Before the AI performs a "Turn" (a sequence of actions), it tries to get the lock.
// From your main loop
import { tryAcquireComputerUseLock } from './computerUseLock'
// Try to grab the "steering wheel"
const result = await tryAcquireComputerUseLock()
if (result.kind === 'blocked') {
console.log(`System occupied by session: ${result.by}`)
// Stop! Do not move the mouse.
} else {
// Safe to proceed!
}
Explanation: tryAcquire does all the hard work. It returns acquired if you got it, or blocked if someone else has it.
When the AI finishes its turn or the user exits the application, we must release the lock.
// From your cleanup logic
import { releaseComputerUseLock } from './computerUseLock'
// We are done driving
await releaseComputerUseLock()
console.log("Steering wheel released.")
Explanation: This deletes the lock file, allowing other sessions to take control.
How do we ensure this is safe even if the computer crashes?
Let's look at computerUseLock.ts to see the three critical mechanisms.
O_EXCL)
The most dangerous bug in concurrency is a "Race Condition" (two people checking the door at the same time). We solve this using the filesystem flag wx.
// From computerUseLock.ts
async function tryCreateExclusive(lock: ComputerUseLock): Promise<boolean> {
try {
// 'w' = write, 'x' = fail if path exists (Exclusive)
await writeFile(getLockPath(), jsonStringify(lock), { flag: 'wx' })
return true
} catch (e: unknown) {
// If error is "EEXIST", it means file is there
if (getErrnoCode(e) === 'EEXIST') return false
throw e
}
}
Explanation: The 'wx' flag is the magic. The Operating System guarantees that only one process can succeed in this write.
What if the lock file exists, but the program that made it crashed? We don't want to be locked out forever.
// From computerUseLock.ts
function isProcessRunning(pid: number): boolean {
try {
// Sending signal 0 doesn't kill the process,
// it just checks if it exists.
process.kill(pid, 0)
return true
} catch {
return false // Process is dead/gone
}
}
Explanation: process.kill(pid, 0) is a standard Unix trick. It asks the OS, "Can I signal this process?" If the process is gone, this throws an error, and we know the lock is "Stale" (abandoned).
Here is the main logic that ties it together.
// From computerUseLock.ts
export async function tryAcquireComputerUseLock(): Promise<AcquireResult> {
// 1. Try to create the file fresh
if (await tryCreateExclusive(myLockData)) {
return FRESH
}
// 2. If failed, read who is holding it
const existing = await readLock()
// 3. Check if that process is actually alive
if (existing && isProcessRunning(existing.pid)) {
return { kind: 'blocked', by: existing.sessionId }
}
// 4. If dead, delete the file and try again!
await unlink(getLockPath()).catch(() => {})
if (await tryCreateExclusive(myLockData)) {
return FRESH
}
return { kind: 'blocked', by: 'unknown' }
}
Explanation:
In this chapter, we added a critical safety layer for multitasking:
We have the Brain, the Hands, the Safety Switch, the Adapter, and the Lock. The system is fully operational.
But waitβwhen the AI does something, how do we show the user what's happening? A raw JSON log isn't very pretty. We need a way to render the output nicely.
Generated by Code IQ