Skip to content
premium html-article 160 pages of implementation detail

The Copilot Life OS Blueprint

Turn GitHub Copilot CLI into a persistent, multi-agent life management system

Your AI assistant forgets everything the moment you close the terminal. This blueprint changes that. Learn how to build a persistent, always-on life OS using GitHub Copilot CLI — with extensions for any capability, agents that remember across sessions, autonomous scheduling, multi-agent coordination, and governance systems that keep it all running safely. Every pattern comes from a real production system managing a family of 5 with 50+ agents.

Copilot CLI Multi-Agent Systems Life Automation Extensions Scheduling Memory Persistence
// who this is for

Intermediate-to-advanced developers already using GitHub Copilot CLI who want to build persistent automation systems. You understand JS/TS, have used the CLI for coding tasks, but haven't explored extensions, multi-agent patterns, or scheduling. You want a real-world architecture reference — not a toy tutorial.

// the problem

You use Copilot CLI for one-off coding tasks and it works great — for 20 minutes. Then you close the terminal and it forgets everything. Your "AI assistant" has no memory, no schedule, no way to coordinate with other agents, and no way to reach you when you're not at your desk. You want a system that manages your life autonomously — tasks, calendar, meals, finances, content, health — but there's no guide for how to build one. This blueprint is that guide, built from a real system running 50+ agents daily.

// the assistant problem

Every "AI assistant" I've ever used has the same fatal flaw: it forgets. You have a brilliant 20-minute conversation, close the terminal, and the next morning you're explaining yourself from scratch. There's no memory, no schedule, no proactive behavior. It's a chatbot, not an assistant.

An assistant that only exists when you're looking at it isn't an assistant. It's a search bar with personality.

This blueprint is the architecture I built to fix that. I run more than 50 agents on top of GitHub Copilot CLI — they manage my family's calendar, meals, finances, content production, repos, and health. They wake up on cron, talk to each other through a mesh, persist memory across sessions, and message me on Telegram when something needs my attention. The CLI itself isn't doing anything magical. The system around the CLI is.

You'll learn the same patterns — extensions, scheduling, 4-tier memory, multi-agent orchestration, constitutional governance, and the self-improvement loop that lets the system get better over time. Every code snippet is sanitized from a real production system that's been running daily for over a year.

Clear problem statement: the CLI is a kernel, not an OS. This blueprint shows you how to build the OS on top of it.

I Part One

Foundation — From CLI to Life OS

The three pieces that turn a terminal session into a persistent assistant: the kernel, extensions, and a UI that lives in your pocket.

1 Chapter

Copilot CLI — Your OS Layer

Stop thinking of the CLI as a chatbot. Start thinking of it as an orchestration kernel.

The mental model shift

Most people see GitHub Copilot CLI and think "AI assistant in a terminal." That's wrong — or at least, that's selling it incredibly short. What you actually have is a process that:

  • Hosts an LLM with tool-calling capability
  • Runs locally on your machine, with full access to your filesystem and shell
  • Exposes a programmatic extension API (joinSession) that lets your code intercept every tool call, inject context, register new tools, and react to lifecycle events

Read that list again. That's not a chatbot. That's a kernel. The CLI is the orchestration layer between an AI brain and the outside world — and you can plug anything you want into it.

The CLI is a kernel. Your extensions are device drivers. Your agents are processes. Build accordingly.

Why local execution matters

Almost every "AI agent platform" you've seen runs in a cloud somewhere. That's fine for stateless inference, but it's terrible for a life OS. Local execution gives you three things you can't get anywhere else:

  • Full filesystem access — your agents can read your real notes, your real receipts, your real codebase
  • Shell access — they can run git, gh, curl, ffmpeg, your test suite, your build, anything
  • Persistent state on disk — memory, schedules, logs, all in plain files you control

You don't need a database, a vector store, an embedding service, or a SaaS account. You need a folder structure and a few hooks.

Concept illustration showing GitHub Copilot CLI as the orchestration kernel surrounded by agents, extensions, memory, scheduling, and Telegram
Figure 1: The life OS emerges from the systems around Copilot CLI — durable memory, extensions, agent processes, scheduling, and a pocket-sized Telegram UI.

The minimal extension

Here's the smallest possible extension. It joins a Copilot session and approves every permission request. That's it — but it's enough to start intercepting things.

{`// .github/extensions/hello/extension.mjs
import { joinSession } from "@github/copilot-sdk/extension";
import { approveAll } from "@github/copilot-sdk";

const session = await joinSession({
  onPermissionRequest: approveAll,
  hooks: {
    onSessionStart: async () => {
      return {
        additionalContext: "Hello from your first extension.",
      };
    },
  },
  tools: [],
});
`}

Drop that file in .github/extensions/hello/extension.mjs with a matching package.json declaring "type": "module", start the CLI, and your extension loads. It runs in-process. It can do anything Node can do.

💡
Key Insight

The extension API isn't a plugin system in the traditional sense — it's a session-level event bus with permission control. Once you internalize that, the architectural possibilities open up dramatically.

2 Chapter

Extensions — The Capability Layer

Every external capability your agents have — Telegram, calendar, SMS, cron, SQLite — is an extension. Here's how to build them right.

File structure

Every extension lives in a folder under .github/extensions/. Minimum contents:

{`.github/extensions/my-extension/
├── extension.mjs       # the joinSession entry point
├── package.json        # { "type": "module", "name": "my-extension" }
└── README.md           # how to configure it
`}

The CLI auto-discovers every folder under .github/extensions/ at startup and runs each extension.mjs as a separate Node process attached to the session. They run in parallel. They don't share memory. If one crashes, the others keep going.

Registering tools the model can call

The tools array is where you expose new capabilities to the LLM. Each tool gets a name, a description (this is what the model reads to decide when to call it), a JSON schema for parameters, and a handler.

{`import { joinSession } from "@github/copilot-sdk/extension";
import { approveAll } from "@github/copilot-sdk";

const session = await joinSession({
  onPermissionRequest: approveAll,
  tools: [
    {
      name: "telegram_send_message",
      description:
        "Send a message via Telegram to a family member. " +
        "Use 'speak: true' for short voice messages to YOUR_PRIMARY_USER. " +
        "Never use 'speak' for messages to YOUR_SECONDARY_USER.",
      parameters: {
        type: "object",
        properties: {
          chat_id: { type: "string", description: "Telegram chat ID" },
          text: { type: "string", description: "Message body (Markdown ok)" },
          speak: { type: "boolean", description: "Send TTS voice note" },
        },
        required: ["chat_id", "text"],
      },
      handler: async ({ chat_id, text, speak }) => {
        const token = process.env.TELEGRAM_BOT_TOKEN;
        if (!token) return { error: "TELEGRAM_BOT_TOKEN not set" };

        const url = \`https://api.telegram.org/bot${"\${"}token}/sendMessage\`;
        const res = await fetch(url, {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({ chat_id, text, parse_mode: "Markdown" }),
        });
        const data = await res.json();
        return { ok: data.ok, message_id: data.result?.message_id };
      },
    },
  ],
});
`}

Tool descriptions are part of the model's context window — they're effectively prompt engineering. Spell out when to use it, when not to use it, and any non-obvious behavior.

Hooks — intercepting the agent loop

Hooks are how you change agent behavior without changing the model. The four you'll use most:

  • onSessionStart — inject context once at session boot (load memory, set the date, declare available capabilities)
  • onPreToolUse — inspect or block a tool call before it executes (security, rate limits, confirmation gates)
  • onPostToolUse — observe results and inject follow-up context (e.g., run lint after every edit, surface errors)
  • onUserMessage — react to incoming user input (route to a sub-agent, log to memory, prepend context)
{`hooks: {
  onPreToolUse: async (input) => {
    if (input.toolName === "powershell") {
      const cmd = String(input.toolArgs?.command || "");
      if (/rm\s+-rf\s+\//.test(cmd)) {
        return {
          permissionDecision: "deny",
          permissionDecisionReason: "Recursive root delete blocked.",
        };
      }
    }
  },
  onPostToolUse: async (input) => {
    if (input.toolName === "edit") {
      return {
        additionalContext:
          "Reminder: every source change needs a corresponding test update.",
      };
    }
  },
}
`}

Environment variables and the zero-dependency rule

Extensions read configuration from process.env. The CLI inherits your shell environment, so a .env file loaded once in your shell startup is all you need.

The harder rule: keep extensions zero-dependency where possible. Every npm install in an extension folder is a load-time penalty, a security surface, and a maintenance burden. Node 20+ has fetch, node:fs/promises, node:child_process, node:sqlite, and a real test runner built in. You almost never need anything else.

🚫
Anti-Pattern

Don't pull in axios, lodash, dotenv, or framework SDKs. They make extensions slow to load, hard to audit, and prone to dependency rot. Use built-in fetch, native ES modules, and explicit env reads.

🚫
Anti-Pattern

Don't use blocking I/O in hooks. Hooks run on the critical path of every tool call. Synchronous file reads or long network calls in onPreToolUse will turn a snappy session into a sluggish one. Always async, always with timeouts.

🚫
Anti-Pattern

Don't name tools generically. send_message, get_data, do_action — the model has dozens of tools to choose from. Name yours telegram_send_message, gcal_create_event, finance_log_expense. The model picks based on the name almost as much as the description.

3 Chapter

Making Copilot Portable — Telegram as UI

An assistant that only exists in your terminal isn't an assistant. Here's how to put it in your pocket.

Why Telegram, why not a custom app

I tried building a custom app. Twice. Both times I quit because the cost of a chat UI — auth, push notifications, voice, file uploads, end-to-end encryption — is enormous, and Telegram already does all of it for free with a bot API a 12-year-old can use.

The bot bridge does three things:

  1. Polls Telegram for incoming messages
  2. Forwards each message into the Copilot session as a user turn
  3. Provides tools the agent can use to send messages back

The long-polling loop

The bridge runs as part of the extension. It uses Telegram's long-polling endpoint so you don't need a public webhook URL.

{`const TOKEN = process.env.TELEGRAM_BOT_TOKEN;
const ALLOWED_CHATS = new Set(
  (process.env.TELEGRAM_ALLOWED_CHATS || "").split(",").filter(Boolean)
);
let offset = 0;

async function pollLoop(session) {
  while (true) {
    try {
      const url = \`https://api.telegram.org/bot${"\${"}TOKEN}/getUpdates\` +
                  \`?timeout=30&offset=${"\${"}offset}\`;
      const res = await fetch(url);
      const { result } = await res.json();
      for (const update of result || []) {
        offset = update.update_id + 1;
        const msg = update.message;
        if (!msg?.text) continue;
        if (!ALLOWED_CHATS.has(String(msg.chat.id))) continue;

        await session.send({
          role: "user",
          content:
            \`[telegram from ${"\${"}msg.from.first_name} \` +
            \`(chat ${"\${"}msg.chat.id})]: ${"\${"}msg.text}\`,
        });
      }
    } catch (err) {
      console.error("[telegram] poll error", err);
      await new Promise((r) => setTimeout(r, 5000));
    }
  }
}

pollLoop(session); // fire and forget
`}

The key line is session.send — that's how you inject a turn into the running Copilot session from outside the CLI process. The agent picks it up like any other user message and responds.

Ask-via-Telegram intercept

Sometimes the agent needs to ask the human a question — but the human isn't at the terminal. Wrap that with an onPreToolUse hook on a custom ask_human tool that sends the question to Telegram and waits for a reply.

{`tools: [
  {
    name: "ask_human_via_telegram",
    description: "Ask the user a question on Telegram and wait for the reply.",
    parameters: {
      type: "object",
      properties: {
        question: { type: "string" },
        chat_id: { type: "string" },
      },
      required: ["question", "chat_id"],
    },
    handler: async ({ question, chat_id }) => {
      const messageId = await sendTelegram(chat_id, question);
      const reply = await waitForReply(chat_id, messageId, 300_000); // 5 min
      return reply
        ? { reply }
        : { error: "Timed out waiting for human reply." };
    },
  },
],
`}

TTS for short messages

Telegram has native voice notes. Use any TTS provider — I use ElevenLabs because the voice quality is worth the cost — to turn short messages into audio. The trick is to gate it on a speak parameter and keep voice messages to one or two sentences. Nobody wants to listen to a paragraph.

{`if (speak) {
  const audio = await synthesizeVoice(text, process.env.TTS_VOICE_ID);
  await fetch(
    \`https://api.telegram.org/bot${"\${"}TOKEN}/sendVoice\`,
    { method: "POST", body: makeFormData({ chat_id, voice: audio }) }
  );
}
`}

Architecture

{`  Phone           Cloud           Local Machine
┌────────┐    ┌──────────┐    ┌────────────────────┐
│Telegram│◄──►│ Telegram │◄──►│ telegram-bridge    │
│   App  │    │   API    │    │  extension         │
└────────┘    └──────────┘    │      │             │
                              │      ▼             │
                              │  Copilot CLI       │
                              │   (LLM + tools)    │
                              │      │             │
                              │      ▼             │
                              │  Other extensions  │
                              │  (cron, sqlite,    │
                              │   gcal, etc.)      │
                              └────────────────────┘
`}
📁
Starter Template

The full Telegram bridge extension — long polling, allowlist, TTS, ask-human, message threading, photo handling — is in the copilot-life-os-starters repo under extensions/telegram-bridge/.

// the rest is waiting for you

Get the full blueprint

You've seen the foundation. The full blueprint covers 160 pages of implementation detail — from context engineering to deterministic safety, delegated agents, production workflows, and the complete transformation path.

$129 one-time purchase
// what's included
  • Telegram bridge extension starter
  • Cron scheduler extension starter
  • Multi-agent templates (4 types)
  • 4-tier memory system templates
  • Task system with SQLite schema
  • Content pipeline orchestration pattern
  • Constitution & governance templates
  • Architecture diagrams for every system

Instant access after purchase · Questions? hector.flores@htek.dev

Already purchased? Get a fresh access link: