Coding agent berbasis Large Language Model (LLM) sering mengalami halusinasi logika atau silent regression—kondisi ketika agent melaporkan tugas perbaikan selesai karena sintaks valid, namun fungsionalitas sistem rusak akibat hilangnya konteks edge cases. LLM secara inheren bersifat probabilistik dan cenderung mengasumsikan keberhasilan modifikasi tanpa verifikasi runtime mandiri.

Pola Think-Act-Prove memecahkan limitasi ini dengan memisahkan penalaran instruksi, manipulasi file, dan validasi eksekusi secara ketat. Artikel ini menyajikan implementasi verification harness minimalis menggunakan Python untuk mengunci feedback loop agent berbasis exit code deterministik.

Akar Masalah: Bias Konfirmasi pada ReAct Tradisional

Pola tradisional seperti ReAct (Reason-Act) sering kali berhenti segera setelah agent menjalankan aksi penulisan file atau perubahan patch. Agent membaca status penulisan sukses dari tool file system, lalu mengasumsikan kode tersebut dapat dijalankan secara benar.

Kelemahan pendekatan tersebut meliputi:

  • Sycophancy dan Konfirmasi Palsu: Model cenderung memvalidasi penalarannya sendiri tanpa umpan balik nyata dari interpreter atau compiler.
  • Breakage pada Dependensi Tersembunyi: Refaktor lokal merusak kontrak API di modul lain tanpa disadari oleh model yang bekerja pada window konteks terbatas.
  • State Hallucination: Agent berasumsi runtime dependencies, environment variables, atau database migrations sudah sinkron secara otomatis.

Solusinya adalah menerapkan prinsip zero-trust: perubahan kode tidak pernah dianggap selesai sampai test runner lokal mengeluarkan return code 0.

Anatomi Siklus Think-Act-Prove

Pola Think-Act-Prove membagi eksekusi tugas ke dalam tiga fase eksplisit yang berjalan di dalam bounded retry loop:

  1. Think: Agent menganalisis failure trace atau instruksi user, membaca file target, dan merumuskan rencana modifikasi spesifik.
  2. Act: Agent menerapkan patch atau mutasi file menggunakan diff patch engine atau overwrite aman.
  3. Prove: Harness mengeksekusi test runner deterministik (misalnya pytest), menangkap exit_code, dan merangkum stdout/stderr. Jika verifikasi gagal, payload log diteruskan kembali ke fase Think sebagai umpan balik objektif.

Implementasi Verification Harness dengan Python

Harness evaluasi bertindak sebagai supervisor eksternal di luar kontrol agent. Harness mengeksekusi testing suite lokal, membatasi timeout eksekusi, serta memformat output failure agar hemat token.

import subprocess
import json
from dataclasses import dataclass
from typing import Optional, List, Dict, Any

@dataclass
class ExecutionResult:
    passed: bool
    exit_code: int
    stdout: str
    stderr: str

class TestRunnerHarness:
    def __init__(self, test_cmd: List[str], timeout_seconds: int = 30):
        self.test_cmd = test_cmd
        self.timeout_seconds = timeout_seconds

    def prove(self) -> ExecutionResult:
        """Menjalankan test suite deterministik dan menangkap exit code."""
        try:
            process = subprocess.run(
                self.test_cmd,
                capture_output=True,
                text=True,
                timeout=self.timeout_seconds
            )
            return ExecutionResult(
                passed=(process.returncode == 0),
                exit_code=process.returncode,
                stdout=process.stdout,
                stderr=process.stderr
            )
        except subprocess.TimeoutExpired:
            return ExecutionResult(
                passed=False,
                exit_code=-1,
                stdout="",
                stderr=f"Test execution timed out after {self.timeout_seconds}s"
            )

    @staticmethod
    def format_feedback(result: ExecutionResult, max_chars: int = 1500) -> Dict[str, Any]:
        """Memangkas log error panjang untuk menghemat budget token prompt."""
        raw_output = result.stderr if result.stderr.strip() else result.stdout
        truncated_output = raw_output[-max_chars:] if len(raw_output) > max_chars else raw_output
        
        return {
            "status": "PASSED" if result.passed else "FAILED",
            "exit_code": result.exit_code,
            "error_context": truncated_output.strip()
        }

Orchestrator Loop dengan Budget Limit

Jika agent melakukan perbaikan yang memicu error baru, loop perbaikan berulang tanpa batasan (infinite repair loop) akan menguras budget API tokens. Tambahkan loop guard menggunakan threshold batas iterasi (max_attempts).

class ThinkActProveAgent:
    def __init__(self, harness: TestRunnerHarness, max_attempts: int = 3):
        self.harness = harness
        self.max_attempts = max_attempts

    def think(self, feedback: Optional[Dict[str, Any]]) -> str:
        # ponytail: integrasi call LLM (OpenAI/Anthropic SDK) di sini.
        # Placeholder representasi rencana perubahan oleh LLM
        return "Plan: Fix edge case in auth token expiry validation."

    def act(self, plan: str) -> None:
        # ponytail: terapkan patch git/unified diff ke file target.
        # Simulasi mutasi file lokal
        pass

    def run(self) -> bool:
        feedback = None
        
        for attempt in range(1, self.max_attempts + 1):
            # 1. THINK
            plan = self.think(feedback)
            
            # 2. ACT
            self.act(plan)
            
            # 3. PROVE
            eval_result = self.harness.prove()
            
            if eval_result.passed:
                return True
                
            feedback = self.harness.format_feedback(eval_result)
        
        # Rollback perubahan lokal jika limit tercapai dan kode masih gagal
        return False

# Runner eksekusi
if __name__ == "__main__":
    harness = TestRunnerHarness(test_cmd=["pytest", "tests/test_auth.py", "-q"])
    agent = ThinkActProveAgent(harness=harness, max_attempts=3)
    success = agent.run()
    assert success or not success  # Deterministic check exit

Alternatif Ringkas (The Lazier Path)

Gunakan git hook lokal (pre-commit) atau bash-level loop until pytest; do agent-fix; done jika kompleksitas arsitektur Python runner tidak diperlukan untuk use case lokal personal.

Praktik Terbaik dan Trade-Offs

  • Test Suite Dependency: Pola Prove hanya seandal cakupan unit test yang tersedia. Jika unit test tidak mencakup regression case, harness tetap menghasilkan false positive. Terapkan mutation testing secara berkala untuk mengevaluasi kualitas suite.
  • Sandbox Isolation: Menjalankan subprocess.run() langsung pada mesin host memicu risiko keamanan saat agent menghasilkan destructive commands. Eksekusi tahap Prove di dalam container Docker atau ephemeral environment terisolasi.
  • Token Overhead: Menyisipkan raw stack trace ke LLM context window memakan kuota token. Pangkas output hanya pada assertion failure dan traceback esensial menggunakan regex sebelum menyuntikkannya ke prompt berikutnya.