Chapter 14 — Final Polish & Personal Developer Workspace
ทำไมต้องชื่อนี้และเรียนไปเพื่ออะไร?
การสร้างระบบ Agent ให้รันผ่านเทสในห้องแล็บ (Day 13) เป็นเพียงครึ่งทางของความสำเร็จ แต่ในชีวิตการทำงานจริงของ Software Engineer คุณคงไม่อยากต้องพิมพ์ cd ~/se_agent && source .venv/bin/activate && python main.py -t ... ทุกครั้งที่ต้องการตรวจโค้ด!
Day 14 คือหมุดหมายปิดฉาก Phase 1.5 ที่จะเปลี่ยนสคริปต์ในโฟลเดอร์ให้กลายเป็น "Personal Developer Workspace" ที่ติดตั้งถาวรใน macOS:
1. System-Wide CLI Alias & Wrapper Script: สามารถเปิด Terminal ในโฟลเดอร์โปรเจกต์ใดก็ได้บนเครื่อง Mac แล้วสั่ง se-agent "ตรวจบั๊กในโฟลเดอร์นี้" ได้ทันที
2. Auto-Detect Project Root: ฉลาดพอที่จะสแกนหา pom.xml, package.json, หรือ .git ในโฟลเดอร์ปัจจุบันที่ผู้ใช้เปิดอยู่ โดยไม่ต้องระบุพาท
3. Persistent Audit Logging & Configuration: บันทึกประวัติการตัดสินใจลง .agent_history.jsonl แยกตามโปรเจกต์ พร้อมไฟล์ config.json สำหรับสลับโมเดลหรือปรับแต่งโดยไม่ต้องแก้โค้ด Python!
ใน Day 14 เราจะเพิ่มไฟล์ Configuration, สร้างโมดูลตรวจจับ Workspace อัตโนมัติ, สร้าง Shell Launcher Script และติดตั้ง Alias ลงใน ~/.zshrc:
ทำหน้าที่ 2 ประการ: 1) ไต่ไดเรกทอรีขึ้นไปเพื่อหาจุดปักหมุดโปรเจกต์จริง (เช่น โฟลเดอร์ที่มี pom.xml หรือ .git) และ 2) บันทึก Audit Log ลงในไฟล์ .agent_history.jsonl ทุกครั้งที่ Agent รันงานสำเร็จ เพื่อให้วิศวกรย้อนดูการตัดสินใจได้ตลอดเวลา
ทำหน้าที่เป็น Global Entrypoint ในระบบ macOS โดยจะเรียกใช้ Python Virtual Environment ใน ~/se_agent/.venv ให้โดยอัตโนมัติ ทำให้คุณเปิด Terminal ที่โปรเจกต์ใดในเครื่องก็ได้ แล้วพิมพ์คำสั่ง se-agent "..." ได้ทันที
ผู้ใช้เรียกคำสั่งจากโฟลเดอร์งานใดก็ได้ผ่าน Zsh Alias
สแกนหา `pom.xml` / `.git` เพื่อล็อกขอบเขต Workspace
โหลดค่าคอนฟิก เชื่อมต่อ M3 และเตรียม Tools ครบชุด
บันทึกการตัดสินใจ รายงานผล Rich Panel เรียบร้อย
1. ทุกครั้งที่จะใช้งานจริง ต้องจำพาท เปิด venv ด้วยมือ เสียเวลาและไม่คล่องตัว
2. พาทไฟล์ติดตรึงอยู่กับโฟลเดอร์ตัวอย่าง นำไปใช้กับโปรเจกต์งานบริษัทจริงไม่ได้
3. สุดท้ายคุณจะยอมแพ้และกลับไปเสียเงินซื้อ Copilot เดือนละ $20 เหมือนเดิม
1. คำสั่ง se-agent กลายเป็นส่วนหนึ่งของ macOS ใช้งานได้จากทุกโฟลเดอร์
2. มีระบบตรวจหา Root อัตโนมัติ รันเทส แก้บั๊กจริง ปลอดภัย 100% ไร้การรั่วไหล
3. ฟรีตลอดชีพ ไม่จำกัด Token และทำงานออฟไลน์ได้ 100% บน Mac M3 คู่ใจ!
{
"model_name": "qwen2.5-coder:7b",
"max_iterations": 6,
"memory_window": 6,
"temperature": 0.1,
"auto_backup": true,
"audit_logging": true
}
import json
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, Any
class WorkspaceManager:
"""
ผู้จัดการสภาพแวดล้อมโปรเจกต์ (Workspace Manager)
ตรวจหา Root Directory ของโปรเจกต์ที่ผู้ใช้กำลังเปิดอยู่ และจัดเก็บบันทึก Audit Trail
"""
ROOT_INDICATORS = ["pom.xml", "package.json", "build.gradle", "pyproject.toml", "Cargo.toml", ".git"]
@classmethod
def find_project_root(cls, start_path: Optional[str] = None) -> Path:
"""
ไต่ระดับโฟลเดอร์ย้อนกลับขึ้นไป (Upward Traversal) เพื่อหา Root Directory ที่แท้จริง
"""
current = Path(start_path).resolve() if start_path else Path.cwd().resolve()
# วนลูปตรวจสอบ current และ parent directories
for directory in [current] + list(current.parents):
for indicator in cls.ROOT_INDICATORS:
if (directory / indicator).exists():
return directory
# หากไม่พบ indicator ใดๆ เลย ให้ใช้ไดเรกทอรีปัจจุบัน
return current
@classmethod
def load_config(cls, agent_home: Path) -> Dict[str, Any]:
"""โหลดไฟล์คอนฟิกจากโฟลเดอร์ติดตั้งหลักของ Agent"""
config_file = agent_home / "config.json"
default_config = {
"model_name": "qwen2.5-coder:7b",
"max_iterations": 6,
"memory_window": 6,
"temperature": 0.1,
"auto_backup": true,
"audit_logging": true
}
if config_file.exists():
try:
data = json.loads(config_file.read_text(encoding="utf-8"))
default_config.update(data)
except Exception:
pass
return default_config
@classmethod
def log_session(cls, workspace_root: Path, task: str, final_answer: str, steps: int) -> None:
"""บันทึกประวัติการทำงานลงไฟล์ .agent_history.jsonl ในโปรเจกต์"""
log_entry = {
"timestamp": datetime.now().isoformat(),
"workspace": str(workspace_root),
"task": task,
"steps": steps,
"summary": final_answer[:300]
}
log_path = workspace_root / ".agent_history.jsonl"
try:
with open(log_path, "a", encoding="utf-8") as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
except Exception:
pass
ชำแหละไวยากรณ์สำคัญใน core/workspace.py
[current] + list(current.parents)
Upward Path Traversal: คุณสมบัติของ pathlib.Path.parents ช่วยให้โปรแกรมเดินย้อนขึ้นไปทีละชั้น (เช่น src/main/java ➔ src/main ➔ src ➔ sample_maven_project) เพื่อสแกนหาไฟล์เอกลักษณ์ประจำโปรเจกต์ เช่น pom.xml ได้อย่างแม่นยำ
with open(log_path, "a", encoding="utf-8") as f: ... jsonl
JSON Lines Append Logging: การบันทึกแบบ .jsonl บรรทัดละ 1 JSON Object ช่วยให้เก็บบันทึก Audit Trail ได้อย่างเสถียร ไม่เปลืองแรม และต่อยอดทำ Analytics ย้อนหลังได้สะดวก
default_config.update(data)
Safe Config Fallback: การอัปเดต Dictionary ทับค่า Default ช่วยการันตีว่าแม้ผู้ใช้จะเผลอลบคอนฟิกบางคีย์ใน config.json ระบบก็จะยังคงมีค่าเริ่มต้นที่ปลอดภัยทำงานได้เสมอ
import sys
import argparse
from pathlib import Path
from core import (
OllamaLLMClient,
ToolDispatcher,
ReActAgentEngine,
MaxIterationsReachedError,
TerminalUI
)
from core.workspace import WorkspaceManager
from core.tools import read_file, run_maven_test, apply_code_patch, rollback_file
AGENT_HOME = Path(__file__).resolve().parent
DEFAULT_SYSTEM_PROMPT = (
"You are an Autonomous Senior Software Engineering Agent running locally on Apple Silicon Mac.\n"
"You operate in a strict ReAct loop (Reason, Act, Observe).\n"
"You have access to tools to inspect local code, run Maven tests, and safely apply refactoring patches.\n\n"
"Available Tools:\n"
"1. read_file(filepath: string): Reads file content safely from workspace.\n"
"2. run_maven_test(project_dir: string): Runs Maven unit tests for a Java project.\n"
"3. apply_code_patch(filepath: string, new_code: string): Safely applies code patch with automatic backup and diff.\n"
"4. rollback_file(filepath: string): Instantly restores original file from .bak backup.\n\n"
"Response Rules:\n"
"- When taking action, reply strictly with JSON:\n"
"{\n"
' "thought": "Your reasoning why you take this step",\n'
' "tool": "tool_name",\n'
' "arguments": { ... }\n'
"}\n\n"
"- When task is completed, reply strictly with JSON:\n"
"{\n"
' "thought": "Summary of achievements",\n'
' "final_answer": "Comprehensive conclusion and explanation for human developer"\n'
"}\n\n"
"Respond ONLY with valid JSON."
)
def create_agent(config: dict, workspace_root: Path, ui: TerminalUI) -> ReActAgentEngine:
"""Factory Function: ประกอบร่างระบบและล็อกขอบเขตความปลอดภัยของ Workspace"""
with ui.status(f"กำลังเชื่อมต่อ Ollama Daemon และตรวจสอบโมเดล '{config['model_name']}'..."):
client = OllamaLLMClient(model_name=config["model_name"])
if not client.check_health():
ui.print_error(f"ไม่พบโมเดล '{config['model_name']}' หรือ Ollama Daemon ไม่ได้เปิดทำงาน!\nกรุณารันคำสั่ง 'ollama serve' หรือ 'ollama list'")
sys.exit(1)
dispatcher = ToolDispatcher(workspace_root=str(workspace_root))
dispatcher.register("read_file", lambda filepath: read_file(filepath, workspace_root=str(workspace_root)))
dispatcher.register("run_maven_test", lambda project_dir=".": run_maven_test(project_dir=str(workspace_root / project_dir)))
dispatcher.register("apply_code_patch", lambda filepath, new_code: apply_code_patch(filepath, new_code, workspace_root=str(workspace_root)))
dispatcher.register("rollback_file", lambda filepath: rollback_file(filepath, workspace_root=str(workspace_root)))
return ReActAgentEngine(
client=client,
dispatcher=dispatcher,
max_iterations=config["max_iterations"],
memory_window=config["memory_window"]
)
def run_task(agent: ReActAgentEngine, task: str, workspace_root: Path, config: dict, ui: TerminalUI) -> None:
ui.console.print(f"\n[bold cyan]🎯 ปฏิบัติการในโฟลเดอร์:[/bold cyan] [underline white]{workspace_root}[/underline white]")
ui.console.print(f"[bold cyan]💬 ภารกิจ:[/bold cyan] [italic white]\"{task}\"[/italic white]\n" + "─" * 60)
try:
final_answer = agent.run(user_prompt=task, system_prompt=DEFAULT_SYSTEM_PROMPT)
ui.print_final_answer(final_answer)
if config.get("audit_logging", True):
WorkspaceManager.log_session(workspace_root, task, final_answer, steps=len(agent.messages))
except MaxIterationsReachedError as err:
ui.print_error(f"Guardrail Alert: {err}")
except KeyboardInterrupt:
ui.console.print("\n[yellow]⚠️ ผู้ใช้สั่งยกเลิกงานกลางคัน (Ctrl+C)[/yellow]")
except Exception as err:
ui.print_error(f"เกิดข้อผิดพลาดไม่คาดคิด: {err}")
def main():
parser = argparse.ArgumentParser(description="Local SE Agent Personal Developer Workspace")
parser.add_argument("task_pos", nargs="?", type=str, help="คำสั่งงานด่วน เช่น: se-agent 'ตรวจบั๊กในโปรเจกต์'")
parser.add_argument("-t", "--task", type=str, help="คำสั่งงานเดี่ยว")
parser.add_argument("-i", "--interactive", action="store_true", help="เปิดโหมด Interactive REPL Prompt")
parser.add_argument("-w", "--workspace", type=str, help="กำหนด Workspace Root ด้วยตนเอง")
args = parser.parse_args()
ui = TerminalUI()
# 1. โหลดคอนฟิก
config = WorkspaceManager.load_config(AGENT_HOME)
# 2. ค้นหา Project Root อัตโนมัติจากโฟลเดอร์ที่ผู้ใช้สั่งรัน
workspace_root = Path(args.workspace).resolve() if args.workspace else WorkspaceManager.find_project_root()
# 3. แสดง Banner
ui.print_banner(model_name=config["model_name"], max_iterations=config["max_iterations"])
# 4. สร้าง Agent
agent = create_agent(config=config, workspace_root=workspace_root, ui=ui)
task = args.task_pos or args.task
if task:
run_task(agent, task, workspace_root, config, ui)
else:
ui.console.print(f"\n[bold magenta]💬 เข้าสู่โหมด Interactive Shell[/bold magenta] (พิกัด: [underline white]{workspace_root}[/underline white])")
ui.console.print("[slate-500]พิมพ์ 'exit' หรือ 'quit' เพื่อออก[/slate-500]\n" + "─" * 60)
while True:
user_input = ui.prompt_user()
if not user_input:
continue
if user_input.lower() in ["exit", "quit", "q"]:
ui.console.print("\n[bold green]👋 ลาก่อนครับ! ขอบคุณที่ใช้งาน Local SE Agent Workspace[/bold green]")
break
run_task(agent, user_input, workspace_root, config, ui)
if __name__ == "__main__":
main()
#!/usr/bin/env bash
# ค้นหาพิกัดที่แท้จริงของโฟลเดอร์ se_agent บนเครื่อง Mac
AGENT_DIR="$HOME/se_agent"
VENV_PYTHON="$AGENT_DIR/.venv/bin/python3"
if [ ! -f "$VENV_PYTHON" ]; then
echo "❌ ไม่พบ Virtual Environment ที่ $VENV_PYTHON"
echo "👉 กรุณารัน: cd ~/se_agent && python3 -m venv .venv && pip install -r requirements.txt"
exit 1
fi
# รัน main.py พร้อมส่งต่อ Arguments ทั้งหมด ($@)
exec "$VENV_PYTHON" "$AGENT_DIR/main.py" "$@"
เปิด Terminal บน MacBook M3 แล้วทำตาม 5 ขั้นตอนนี้เพื่อติดตั้ง Agent เข้าสู่สภาพแวดล้อมระบบ macOS ให้พร้อมใช้งานจากทุกโฟลเดอร์ทันที:
cd ~/se_agent
source .venv/bin/activate
touch config.json core/workspace.py se-agent
chmod +x se-agent
คำสั่ง chmod +x se-agent คือการมอบสิทธิ์ Exe ให้สคริปต์สามารถรันได้โดยตรง
- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 1) ไปวางใน config.json
- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 2) ไปวางใน core/workspace.py
- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 3) ไปอัปเดตทับไฟล์ main.py
- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 4) ไปวางใน se-agent
# เพิ่ม alias เข้าสู่ไฟล์คอนฟิกเชลล์ของ macOS
echo 'alias se-agent="$HOME/se_agent/se-agent"' >> ~/.zshrc
source ~/.zshrc
ตอนนี้ระบบปฏิบัติการ macOS รู้จักคำสั่ง se-agent ทั่วทั้งเครื่องแล้ว!
cd ~
se-agent "ช่วยรันเทสในโปรเจกต์ ~/se_agent/sample_maven_project แล้วสรุปผลให้ฟัง"
🎉 ผลลัพธ์: คุณจะเห็น Cockpit สว่างวาบขึ้นมาใน Terminal ทันที ไม่ว่าคุณจะอยู่ที่โฟลเดอร์ไหน Agent จะทำงานผ่าน Mac M3 โลคอล 100% ฟรี 100% และบันทึกประวัติลง .agent_history.jsonl อย่างสมบูรณ์แบบ!
ข้อที่ 1: วัตถุประสงค์หลักของ Day 14: Final Polish & Personal Developer Workspace คืออะไร?
ข้อที่ 2: การสร้างคำสั่งลัดผ่าน Shell Alias ในไฟล์ `~/.zshrc` มีประโยชน์อย่างไรต่อการทำงานประจำวัน?
ข้อที่ 3: หากเปิด Terminal ในโฟลเดอร์โปรเจกต์ใดก็ตาม เมธอด `find_project_root()` ตรวจหา Root Directory ด้วยสิ่งใด?
ข้อที่ 4: ไฟล์ `se-agent` ที่เขียนด้วย Bash Script ทำหน้าที่อะไรในกระบวนการทำงาน?
ข้อที่ 5: ตลอด 14 วันที่ผ่านมา องค์ประกอบสำคัญที่ทำให้เราเลิกพึ่งพา Cloud Copilot ได้คืออะไร?
ข้อที่ 6: การแยกไฟล์คอนฟิก `config.json` มีประโยชน์อย่างไรต่อการปรับแต่งโมเดล?
ข้อที่ 7: ข้อดีของการบันทึก Session Log ลงในไฟล์ `.agent_history.jsonl` คืออะไร?
ข้อที่ 8: ในการบริหารหน่วยความจำบน Mac M3 16GB สถาปัตยกรรมใดที่เราวางไว้เพื่อป้องกันไม่ให้แรมเต็ม?
ข้อที่ 9: พารามิเตอร์ `exec "$VENV_PYTHON" "$AGENT_DIR/main.py" "$@"` ในสคริปต์ wrapper มีความหมายอย่างไร?
ข้อที่ 10: อะไรคือความแตกต่างที่ยิ่งใหญ่ที่สุดระหว่างการใช้ Web Chat กับการมี Autonomous SE Agent ใน Workspace?
ข้อที่ 11: หากในอนาคตต้องการเพิ่มภาษาใหม่ (เช่น รองรับ Go หรือ Python pytest นอกเหนือจาก Java Maven) เราต้องทำอย่างไร?
ข้อที่ 12: การที่เราไม่พึ่งพา Framework ใหญ่ๆ อย่าง LangChain แต่เขียนแกน ReAct เอง ส่งผลดีอย่างไรต่อความเสถียรของ Workspace?
ข้อที่ 13: บทเรียนสำคัญที่สุดจากแนวคิด Micro-learning 14 วันที่ผ่านมาคืออะไร?
ข้อที่ 14: ตอนนี้ หากบริษัทมีนโยบายห้ามใช้ Cloud AI หรือจำกัด Token รายเดือน คุณจะรับมืออย่างไร?
ข้อที่ 15: ในฐานะผู้สำเร็จการศึกษาหลักสูตร Local SE Agent Masterclass Phase 1.5 ความรู้สึกของคุณในวันนี้คืออะไร?
ขอแสดงความยินดีด้วยครับ! คุณสำเร็จ Phase 1.5 อย่างสมบูรณ์แบบแล้ว!
ตลอด 14 วันของการเดินทาง คุณได้สร้าง Autonomous Local Software Engineering Agent บน MacBook M3 16GB ของตัวเองได้สำเร็จจริง! คุณได้ติดตั้งระบบลงในเชลล์ของ macOS พร้อมเรียกใช้งานได้จากทุกโฟลเดอร์ด้วยคำสั่ง se-agent และพร้อมลุยโปรเจกต์งานจริงในชีวิตประจำวันแล้วครับ!