Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
This course is not a grab bag of tricks. It's built around two design patterns that recur through every module, and if you internalize them here you'll know why each subsequent module makes the choices it does. The grace window gates OUTBOUND actions — the bot drafts, notifies you, waits N minutes, then sends unless you override. This is how the bot earns the right to speak for you: not by asking permission every time (that's just a slow email client) and not by never asking (that's a lawsuit), but by giving you a bounded window to catch mistakes. The verify-before-use pattern gates KNOWLEDGE entering the bot's authoritative store — the bot researches something, drafts a high-value answer in your voice, and you approve, edit, or reject before it becomes citable memory. These two patterns generalize: grace-window for anything the bot does to the world, verify-before-use for anything the bot will remember and repeat later. The rest of the course is instantiating them in specific domains.
Pseudocode-shape for both patterns as reusable primitives. The grace window is a scheduled job with an override channel; verify-before-use is an approval workflow that mutates memory only on explicit accept.
Use these three in order. Each builds on the one before.
In one paragraph, explain the difference between the grace window and verify-before-use patterns.
Walk me through how the grace window prevents the two most common personal-agent failures: the bot sending something embarrassing, and the bot spamming a reply loop.
I want the bot to auto-triage my emails AND research a topic AND answer on my behalf. Map each action to grace-window vs. verify-before-use.
// The two patterns as small reusable primitives.
// -- 1) Grace window: schedule a send, allow override during the window.
type GraceJob = {
id: string;
draft: string;
target: string;
scheduledFor: Date;
status: "pending" | "sent" | "canceled" | "edited";
};
const queue = new Map<string, GraceJob>();
function scheduleGraceSend(job: Omit<GraceJob, "id" | "status">) {
const id = crypto.randomUUID();
const fullJob: GraceJob = { ...job, id, status: "pending" };
queue.set(id, fullJob);
const delayMs = job.scheduledFor.getTime() - Date.now();
setTimeout(async () => {
const current = queue.get(id);
if (!current || current.status !== "pending") return; // overridden
await sendMessage(current.target, current.draft);
current.status = "sent";
}, delayMs);
return id;
}
function overrideGraceJob(id: string, action: "cancel" | "edit", newDraft?: string) {
const job = queue.get(id);
if (!job || job.status !== "pending") throw new Error("Not overridable");
if (action === "cancel") job.status = "canceled";
if (action === "edit" && newDraft) { job.draft = newDraft; job.status = "edited"; }
}
// -- 2) Verify-before-use: mark memory authoritative only after explicit accept.
type Memory = {
id: string;
content: string;
source: string;
verified: boolean;
verifiedAt?: Date;
};
async function proposeMemory(content: string, source: string): Promise<Memory> {
const mem: Memory = { id: crypto.randomUUID(), content, source, verified: false };
await db.memories.insert(mem);
await notifyForApproval(mem);
return mem;
}
async function acceptMemory(id: string, edited?: string) {
await db.memories.update(id, {
content: edited ?? undefined,
verified: true,
verifiedAt: new Date(),
});
}
// Retrieval later prefers verified memory; falls back to unverified with a hedge.
async function retrieve(query: string) {
const results = await db.memories.search(query);
return results.map(r => ({
...r,
confidence: r.verified ? "high" : "hedge",
}));
}
async function sendMessage(target: string, draft: string): Promise<void> {}
async function notifyForApproval(mem: Memory): Promise<void> {}
declare const db: any;node main.js