Thuta Learning
Local AI / Local LLM
ProjectsAIbeginner

Project: Safe Permission ပါတဲ့ Local Coding Agent

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Project: Safe Permission ပါတဲ့ Local Coding Agent concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး architecture ထဲမှာ data/request ဘယ်လိုစီးဆင်းသလဲ ခြေရာခံနိုင်ရန်
  • ကိုယ့် hardware/use case အတွက် ဘယ်လို ရွေးချယ်သင့်သလဲ ဆုံးဖြတ်နိုင်ရန်

နားလည်ထားရမယ့် အချက်

ဒီ project ဟာ tool calling ကို Lesson 18 ရဲ့ agent-safety principle တွေနဲ့ ပေါင်းစပ်ထားတာပါ၊ ဒီပေါင်းစပ်မှုကိုယ်တိုင်ကလည်း အဓိကအချက်ပါပဲ — capable ဖြစ်တဲ့ local coding agent တစ်ခုနဲ့ safe ဖြစ်တဲ့ agent တစ်ခုက အစိတ်အပိုင်း အတူတူကနေ တည်ဆောက်ထားတာဖြစ်ပြီး supervision ထက် capability မကျော်လွှားနိုင်အောင် စီစဉ်ထားတာပါ။

Agent chapter က tool calling ကြောင့် local model က ပြောရုံထက် ပိုအလုပ်လုပ်နိုင်တာပါ — readFile, listDirectory, runTests call တွေကို တောင်းဆိုနိုင်ပြီး application က ၎င်းတို့ကို execute လုပ်ပေးပြီး model နောက်တစ်ဆင့်မှာ reasoning ဆက်လုပ်ဖို့ real result တွေ ပြန်ပေးပါတယ်။

ဒီနေရာမှာ tool set ကို တမင် သေးငယ်၊ ရှင်းလင်းအောင် ထားထားပါတယ် — model ခေါ်နိုင်တဲ့ tool တိုင်းက read-only (သို့) low-risk ဖြစ်ပြီး dynamic discovery မဟုတ်ဘဲ fixed allowlist ပေါ်မှာပဲ ရှိပါတယ်၊ ဒါကြောင့် "model ဆုံးဖြတ်ချက်ချ" ကနေ "shell command ကြိုက်တာ run" ဆီ ရောက်တဲ့ path လုံးဝ မရှိပါဘူး။

Workspace sandbox တစ်ခုက tool တိုင်းကို call တိုင်းမတိုင်ခင် resolve ပြီး check ထားတဲ့ project directory တစ်ခုတည်းအထဲပဲ ကန့်သတ်ပေးပါတယ်၊ ဒါကြောင့် ../../.ssh/id_rsa လို path တစ်ခုက model ဘယ်လိုပဲ တောင်းဆို တောင်းဆို workspace အပြင်ဘက်က file ကို touch ဖို့ တောင်းဆိုသလိုပဲ reject ခံရပါတယ်။

Tool တစ်ခုတည်း ခေါ်တာထက် agent တကယ် ဖြစ်စေတဲ့ အပိုင်းကတော့ loop ပါ — model က tool တစ်ခု ခေါ်၊ result ဖတ်၊ ပြီးရင် အဖြေမပေးခင် tool နောက်တစ်ခု ခေါ်ဖို့ ဆုံးဖြတ်နိုင်ပါတယ်၊ readFile နဲ့ runTests call တွေကို file ရှင်းပြခြင်း၊ likely bug ညွှန်ပြခြင်း၊ fix draft ဖြစ်ခြင်းအတွက် လုံလောက်တဲ့ info ရအောင် ဆက်တိုက် ခေါ်နိုင်ပါတယ်။

Loop က destructive action မတိုင်ခင် ရပ်တန့်သည်

ဒါပေမယ့် fix draft ဆွဲတဲ့နေရာမှာ loop က သူ့ဘာသာသူ ရပ်ပါတယ်။ ရေးခြင်း (သို့) ဖျက်ခြင်း တစ်ခုခုအတွက် human approve လုပ်ရမယ့် confirmation step တိကျစွာ လိုအပ်ပါတယ် — Lesson 18 က တည်ထောင်ခဲ့တဲ့ never-autonomous-destructive-action rule နဲ့ ဆန့်ကျင်မနေဘဲ ကိုက်ညီနေပါတယ်။

text
LOCAL CODING AGENT PERMISSION LOOP
----------------------------------
USER REQUEST
      |
      v
AGENT (local chat model, tool-calling loop)
      |
      |--- tool call --->  ALLOWLISTED TOOLS  (sandboxed to ./workspace)
      |                      readFile            read-only
      |                      listDirectory       read-only
      |                      runTests            low-risk, timeout-limited
      |<-- tool result ------------|
      |
   (loop repeats: model may call more tools before it answers)
      |
      v
PROPOSED ANSWER
   - explanation / likely-bug pointer  -->  shown to user directly
   - proposed file write or delete     -->  NOT executed yet, shown as diff
                    |
                    v
          HUMAN CONFIRMATION GATE  (review diff, click Approve)
                    |
                    v
      only now: writeFile / deleteFile actually runs on disk
      (writeFile/deleteFile were never tools the model could call itself)

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

Project directory တစ်ခုပတ်ပတ်လည်မှာ အပိုင်းသုံးပိုင်းကို တည်ဆောက်ပါ — tool layer, agent loop, confirmation gate။

Tool layer တည်ဆောက်ခြင်း

readFile(path), listDirectory(path), runTests() လို function fixed set သေးသေးလေးတစ်ခု သတ်မှတ်ပါ — function တစ်ခုစီက real filesystem/process work မလုပ်ခင် တောင်းဆိုထားတဲ့ path ကို workspace root နဲ့ resolve လုပ်ပြီး အပြင်ဘက်ကို resolve ဖြစ်သွားရင် ပယ်ချပါတယ်။

Agent loop run ခြင်း

User request နဲ့ tool definition တွေကို local model ရဲ့ chat completions endpoint ဆီ ပို့ပြီး response ထဲ tool call ပါလာတိုင်း matching function ကို run, tool result ကို conversation ထဲ ဆက်ထည့်ပြီး model ကို ထပ်ခေါ်ပါတယ်။

Plain answer မှာ ရပ်ခြင်း

Model က tool call အစား plain answer ပြန်ပေးတဲ့အထိ loop ကို ဆက်လုပ်ပါတယ်။

Destructive change တွေကို gate ခြင်း

Agent ရဲ့ အဖြေမှာ proposed file write (သို့) delete ပါလာရင် execute မလုပ်ဘဲ diff အနေနဲ့ render ပြီး human ရဲ့ ရှင်းလင်းတဲ့ approve action ကို စောင့်ပါတယ်။

Approve ပြီးမှသာ apply လုပ်ခြင်း

Approve ရပြီးမှသာ model ကိုယ်တိုင် callable tool အနေနဲ့ လုံးဝ မရရှိခဲ့တဲ့ သီးခြား writeFile (သို့) deleteFile function ကို ခေါ်ပါတယ်။

ဒီ project အတွက် "Done" ဆိုတာက အောက်ပါအချက်များ ပြည့်စုံရမည်ဖြစ်သည် —

  • file တစ်ခုကို ရှင်းပြခိုင်း (သို့) likely bug ရှာခိုင်းရင် real tool result တွေကနေ တည်ဆောက်ထားတဲ့ မှန်ကန်တဲ့ grounded အဖြေရခြင်း (မှန်းဆချက်မဟုတ်ဘဲ)
  • ပြင်ခိုင်းရင် reviewable diff ရပြီး အဲ့နေရာမှာပဲ ရပ်ခြင်း
  • project directory အပြင်ဘက် path တစ်ခုနဲ့ ကြိုးစားရင် tool layer ကိုယ်တိုင်က ပယ်ချခြင်း
  • human approve ကို click မလုပ်ဘဲ disk ပေါ်က file ဘာမှ မပြောင်းလဲခြင်း မရှိခြင်း

အတူတူ စမ်းရေးကြည့်မယ်

typescript
// A local coding agent: read-only tools, a workspace sandbox, and a human
// confirmation gate before anything on disk actually changes.
import { readFile, readdir } from "node:fs/promises";
import path from "node:path";

const CHAT_URL = "http://localhost:11434/api/chat";
const CHAT_MODEL = "qwen2.5-coder:7b";
const WORKSPACE_ROOT = path.resolve("./workspace");

// Reject any path that resolves outside the workspace, however it's phrased.
function resolveInWorkspace(relativePath: string): string {
  const resolved = path.resolve(WORKSPACE_ROOT, relativePath);
  if (!resolved.startsWith(WORKSPACE_ROOT + path.sep)) {
    throw new Error(`Path escapes workspace sandbox: ${relativePath}`);
  }
  return resolved;
}

// --- the allowlisted tool set: read-only / low-risk only ---
const tools = {
  async readFile(relativePath: string) {
    return readFile(resolveInWorkspace(relativePath), "utf-8");
  },
  async listDirectory(relativePath: string) {
    return readdir(resolveInWorkspace(relativePath));
  },
  async runTests() {
    // Runs the project's existing test command in a subprocess with a
    // timeout; output is returned as text, never executed as new code.
    return runShellWithTimeout("npm test", WORKSPACE_ROOT, 30_000);
  },
};

const toolSchema = [
  { name: "readFile", description: "Read a file's contents.", parameters: { path: "string" } },
  { name: "listDirectory", description: "List files in a directory.", parameters: { path: "string" } },
  { name: "runTests", description: "Run the project's test suite.", parameters: {} },
];

type ToolCall = { name: keyof typeof tools; arguments: Record<string, string> };
type AgentMessage = { role: "system" | "user" | "assistant" | "tool"; content: string };

// --- the tool-calling loop ---
export async function runAgent(userRequest: string): Promise<string> {
  const messages: AgentMessage[] = [
    {
      role: "system",
      content:
        "You are a coding assistant restricted to the current workspace. " +
        "Use the provided tools to gather real evidence before answering. " +
        "You may explain code, point at a likely bug, or propose a fix as " +
        "a diff -- but you must never claim a file was changed. Only a " +
        "human-approved confirmation step actually writes or deletes files.",
    },
    { role: "user", content: userRequest },
  ];

  for (let step = 0; step < 8; step++) {
    const res = await fetch(CHAT_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: CHAT_MODEL,
        stream: false,
        messages,
        tools: toolSchema,
      }),
    });
    if (!res.ok) throw new Error(`Agent request failed: ${res.status}`);

    const data = (await res.json()) as {
      message: { content: string; tool_calls?: ToolCall[] };
    };

    if (!data.message.tool_calls?.length) {
      return data.message.content; // plain answer -- loop ends here
    }

    for (const call of data.message.tool_calls) {
      const result = await tools[call.name](
        ...(Object.values(call.arguments) as [string]),
      );
      messages.push({ role: "tool", content: JSON.stringify(result) });
    }
  }

  return "Agent stopped after too many tool-call steps without an answer.";
}

// --- confirmation gate: called only after a human clicks "Approve" ---
// writeFile/deleteFile are never in `tools`, so the model cannot call them.
export async function applyApprovedChange(
  relativePath: string,
  newContents: string,
): Promise<void> {
  const { writeFile } = await import("node:fs/promises");
  await writeFile(resolveInWorkspace(relativePath), newContents, "utf-8");
}

async function runShellWithTimeout(
  command: string,
  cwd: string,
  timeoutMs: number,
): Promise<string> {
  const { execFile } = await import("node:child_process");
  return new Promise((resolve) => {
    const child = execFile(command, { cwd, timeout: timeoutMs, shell: true });
    let output = "";
    child.stdout?.on("data", (d) => (output += d));
    child.stderr?.on("data", (d) => (output += d));
    child.on("close", () => resolve(output));
  });
}

Run လုပ်လို့မရနိုင်ပါ

ဒီ code ဟာ ကိုယ်ပိုင် local AI server (ဥပမာ Ollama) ကို ခေါ်သုံးပါတယ် — ဒီ site ရဲ့ browser playground ကနေ visitor ရဲ့ စက်ပေါ်က local server ကို လှမ်းမခေါ်နိုင်ပါ (network access ကန့်သတ်ထားလို့ပါ)။ ကိုယ်တိုင် server run ထားတဲ့ terminal (သို့) file ထဲမှာ ဒီ code ကို run ပြီး စမ်းကြည့်ပါ။

You should see
Agent က မှန်ကန်စွာ အလုပ်လုပ်နေရင် file တစ်ခုကို ရှင်းပြခိုင်းရင် readFile/listDirectory tool result တွေအပေါ် အခြေခံထားတဲ့ concrete အဖြေရပါတယ်၊ likely bug ရှာခိုင်းရင်လည်း code ထဲက real line/function ကို ညွှန်ပြပါတယ်။ Fix တစ်ခု တောင်းဆိုရင် agent က diff ပုံစံနဲ့ ပြင်ဆင်ချက်ကို ပြသပြီး Approve/Reject ခလုတ်ကို စောင့်ပါတယ် — Approve ကို click မလုပ်မချင်း disk ပေါ်က file ဘာမှ မပြောင်းလဲပါဘူး။ Model က workspace အပြင်ဘက်က file (ဥပမာ ../../.env) ကို ဖတ်ဖို့ ကြိုးစားရင် tool layer က ချက်ချင်း reject ပြီး sandbox violation error ကို ပြန်ပေးပါတယ်၊ agent က tool result အနေနဲ့ ဒီ error ကို လက်ခံရပါတယ်။

၅ မိနစ် စမ်းကြည့်

runTests tool အတွက် output size limit တစ်ခု ထည့်ပါ (ဥပမာ output line 200 ကျော်ရင် truncate) — test output အလွန်ရှည်တာက conversation history ကို context window ကျော်အောင် ဖုံးမိစေတာမျိုး မဖြစ်စေရန်။ Truncate ဖြစ်ရင် ဖြစ်ကြောင်း agent ကို ရှင်းလင်းစွာ ပြောပါ။

သတိလေးတစ်ချက်

writeFile/deleteFile ကို model ကိုယ်တိုင် ခေါ်နိုင်တဲ့ tool list ထဲ ထည့်ခြင်း — confirmation gate ကို ကျော်လွှားပြီး file ကို autonomously ပြောင်းလဲနိုင်စေပါတယ်

Tool result ကို path resolution/sandbox check မလုပ်ဘဲ raw user-supplied path ကို တိုက်ရိုက် filesystem call ထဲ ပေးပို့ခြင်း — ../ လို path traversal ကနေ workspace အပြင်ဘက် file ကို ဖတ်/ရေးနိုင်စေပါတယ်

OpenAI Platform -- Function CallingLocal AI / Local LLM

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • writeFile/deleteFile ကို model ကိုယ်တိုင် ခေါ်နိုင်တဲ့ tool list ထဲ ထည့်ခြင်း — confirmation gate ကို ကျော်လွှားပြီး file ကို autonomously ပြောင်းလဲနိုင်စေပါတယ်
  • Tool result ကို path resolution/sandbox check မလုပ်ဘဲ raw user-supplied path ကို တိုက်ရိုက် filesystem call ထဲ ပေးပို့ခြင်း — ../ လို path traversal ကနေ workspace အပြင်ဘက် file ကို ဖတ်/ရေးနိုင်စေပါတယ်
  • Model (သို့) tool အသစ်တစ်ခုကို production/daily-use workflow ထဲ တိုက်ရိုက်မထည့်ခင် သေးငယ်တဲ့ scale နဲ့ အရင်စမ်းကြည့်ပါ။

လေ့ကျင့်ခန်း

runTests tool အတွက် output size limit တစ်ခု ထည့်ပါ (ဥပမာ output line 200 ကျော်ရင် truncate) — test output အလွန်ရှည်တာက conversation history ကို context window ကျော်အောင် ဖုံးမိစေတာမျိုး မဖြစ်စေရန်။ Truncate ဖြစ်ရင် ဖြစ်ကြောင်း agent ကို ရှင်းလင်းစွာ ပြောပါ။

You'll know it worked when: Agent က မှန်ကန်စွာ အလုပ်လုပ်နေရင် file တစ်ခုကို ရှင်းပြခိုင်းရင် readFile/listDirectory tool result တွေအပေါ် အခြေခံထားတဲ့ concrete အဖြေရပါတယ်၊ likely bug ရှာခိုင်းရင်လည်း code ထဲက real line/function ကို ညွှန်ပြပါတယ်။ Fix တစ်ခု တောင်းဆိုရင် agent က diff ပုံစံနဲ့ ပြင်ဆင်ချက်ကို ပြသပြီး Approve/Reject ခလုတ်ကို စောင့်ပါတယ် — Approve ကို click မလုပ်မချင်း disk ပေါ်က file ဘာမှ မပြောင်းလဲပါဘူး။ Model က workspace အပြင်ဘက်က file (ဥပမာ ../../.env) ကို ဖတ်ဖို့ ကြိုးစားရင် tool layer က ချက်ချင်း reject ပြီး sandbox violation error ကို ပြန်ပေးပါတယ်၊ agent က tool result အနေနဲ့ ဒီ error ကို လက်ခံရပါတယ်။

Project: Safe Permission ပါတဲ့ Local Coding Agent | Thuta Learning