چرا هوشمندی مدل به‌تنهایی کافی نیست و چگونه ساخت یک Agent Harness مهندسی‌شده تفاوت بین یک دموی ناپایدار و یک سیستم نرم‌افزاری واقعی را رقم می‌زند؟

نویسنده و منبع: این مقاله ترجمه و بازنویسی فارسی از پست «How to Build an AI Agent Harness 2.0 and Engineer Better Than 99% of Developers» به قلم Sachin Kasana (منتشر شده در سپتامبر ۲۰۲۶ در Medium) است. برای مطالعه نسخه اصلی می‌توانید به مقاله اصلی در Medium مراجعه فرمایید.

کد کامپایل می‌شود. تست‌ها با موفقیت اجرا می‌شوند. Pull Request هم خوب به نظر می‌رسد.

آن را Merge می‌کنید.

چند ساعت بعد متوجه چیز عجیبی می‌شوید.

API جدید کار می‌کند، اما Agent:

هیچ چیزی Crash نکرده است.

Agent صرفاً کاری را انجام داده که فکر می‌کرد درست است.

و مشکل دقیقاً همین است.

ما دائماً تلاش می‌کنیم AI Agentها را باهوش‌تر کنیم.

مدل‌های بهتر. Promptهای بهتر. Context Windowهای بزرگ‌تر.

اما اگر مدل بزرگ‌ترین مشکل نباشد چه؟

اگر مشکل واقعی، محیطی باشد که Agent را در آن قرار می‌دهیم چه؟

اینجاست که Agent Harness وارد می‌شود.

Agent Harness چیست؟

یک AI Agent را مانند یک توسعه‌دهنده در نظر بگیرید که می‌تواند استدلال کند، کد بنویسد، Command اجرا کند و از ابزارها استفاده کند.

Harness محیط مهندسی‌ای است که اطراف آن توسعه‌دهنده قرار دارد.

                 ┌─────────────────┐
                 │    AI Agent     │
                 └────────┬────────┘
                          │
       ┌──────────────────┼──────────────────┐
       ↓                  ↓                  ↓
    Context              Tools          Constraints
       ↓                  ↓                  ↓
    Memory            Execution        Verification
       ↓                  ↓                  ↓
    Rules              Feedback          Guardrails
       └──────────────────┼──────────────────┘
                          ↓
                   Agent Harness

یک Agent ساده:

Prompt → Model → Code

یک Agent بهتر:

Task
 ↓
Context
 ↓
Agent
 ↓
Tools
 ↓
Code
 ↓
Verification
 ↓
Feedback
 ↺

این Loop همه‌چیز را تغییر می‌دهد.

Agent دیگر فقط کد تولید نمی‌کند.

بلکه مشاهده می‌کند، عمل می‌کند، صحت کارش را بررسی می‌کند و در صورت شکست بازیابی می‌شود.

بیایید یکی بسازیم.

۱. با Context شروع کنید

فرض کنید یک Backend با TypeScript داریم.

توسعه‌دهنده درخواست می‌کند:

Add an endpoint to create a new customer.

بدون Context اضافی، Agent مجبور است حدس بزند:

و Agentها در حدس زدن بسیار خوب هستند.

دقیقاً همین چیزی است که نمی‌خواهیم.

پس باید دستورالعمل‌های مخصوص پروژه را در اختیار Agent قرار دهیم.

برای مثال:

my-project/
├── AGENTS.md
├── src/
│   ├── controllers/
│   ├── services/
│   ├── repositories/
│   └── models/
├── tests/
└── package.json

فایل AGENTS.md ما می‌تواند شامل این موارد باشد:

# Engineering Rules

- Use TypeScript.
- Controllers must not access the database directly.
- Business logic belongs in services.
- Database access belongs in repositories.
- Every new API endpoint requires tests.
- Use Zod for request validation.
- Run lint, typecheck, and tests before finishing.
- Never modify generated files.

این از قبل بهتر است.

اما یک مشکل وجود دارد.

این‌ها هنوز فقط دستورالعمل هستند.

Agent می‌تواند آن‌ها را نادیده بگیرد.

بنابراین باید از Instructions به سمت Enforcement حرکت کنیم.

۲. ابزارهای مناسب را در اختیار Agent قرار دهید

یک Agent فقط به اندازه Actionهایی که می‌تواند انجام دهد مفید است.

به‌جای اینکه دسترسی نامحدود به همه‌چیز به آن بدهیم، باید ابزارهای مشخص و هدفمند در اختیارش قرار دهیم.

برای مثال:

tools = [
    read_file,
    search_code,
    list_files,
    apply_patch,
    run_tests,
    run_linter,
    run_typecheck,
]

حالا Agent می‌تواند از طریق قابلیت‌های مشخص و شناخته‌شده با Repository تعامل داشته باشد.

یک تعریف ساده از ابزار می‌تواند چنین باشد:

def run_tests():

    result = subprocess.run(
        ["npm", "test"],
        capture_output=True,
        text=True
    )

    return {
        "success": result.returncode == 0,
        "stdout": result.stdout,
        "stderr": result.stderr
    }

و یک ابزار جست‌وجوی کد:

def search_code(query):

    result = subprocess.run(
        ["rg", query, "src"],
        capture_output=True,
        text=True
    )

    return result.stdout

حالا Agent می‌تواند بگوید:

Search for existing customer endpoints.

به‌جای اینکه کورکورانه یک Endpoint جدید ایجاد کند.

این تفاوت اهمیت زیادی دارد.

۳. Context باید Retrieved شود، نه اینکه یک‌جا Dump شود

یک اشتباه رایج این است که همه‌چیز را در اختیار Agent قرار دهیم.

Here are 500 files.

Here are all the logs.

Here is the entire repository.

Good luck.

Context بیشتر لزوماً به معنی Reasoning بهتر نیست.

در عوض باید یک Retrieval Layer ایجاد کنیم.

def get_context(task):

    relevant_files = search_code(
        extract_keywords(task)
    )

    architecture = read_file(
        "docs/architecture.md"
    )

    rules = read_file(
        "AGENTS.md"
    )

    return {
        "task": task,
        "rules": rules,
        "architecture": architecture,
        "relevant_files": relevant_files
    }

هدف ساده است:

Task

 ↓

Find relevant context

 ↓

Give agent only what it needs

این کار Context را متمرکز نگه می‌دارد و احتمال اینکه اطلاعات مهم در میان حجم زیادی از اطلاعات گم شود را کاهش می‌دهد.

۴. به Agent حافظه بدهید

Context و Memory یک چیز نیستند.

Context به این سؤال پاسخ می‌دهد:

«الان چه چیزهایی را باید بدانم؟»

Memory به این سؤال پاسخ می‌دهد:

«تا الان چه چیزهایی یاد گرفته‌ایم؟»

یک پروژه ساده می‌تواند ساختاری شبیه این داشته باشد:

agent-memory/
├── decisions.md
├── progress.md
├── failures.md
└── architecture.md

مثلاً:

# decisions.md

## Customer API

We use repository classes for all database access.

Controllers should never call Prisma directly.

Reason:

Keeps persistence concerns separate from

business logic and makes services easier to test.

حالا فرض کنید Agent با یک Failure مواجه می‌شود.

به‌جای اینکه با پایان Task این دانش از بین برود:

# failures.md

## Customer API

Previous implementation attempted to access

Prisma directly from the controller.

Rejected because this violates the repository pattern.

اجرای بعدی Agent می‌تواند از این تجربه یاد بگیرد.

این بسیار مفیدتر از صرفاً بزرگ‌تر کردن Context Window است.

۵. Rules را به Constraints تبدیل کنید

اینجا یک تفاوت مهم وجود دارد.

یک Instruction می‌گوید:

Always write tests.

اما یک Constraint می‌گوید:

The task cannot be completed unless tests pass.

این دو، دو سیستم کاملاً متفاوت هستند.

می‌توانیم این موضوع را با یک Verification Function پیاده‌سازی کنیم:

def verify():

    checks = [
        run_tests(),
        run_typecheck(),
        run_linter(),
    ]

    return all(
        check["success"]
        for check in checks
    )

حالا Agent نمی‌تواند صرفاً بگوید:

Done!

Harness می‌پرسد:

Did the code actually pass verification?

یعنی:

«آیا کد واقعاً Verification را با موفقیت پشت سر گذاشته است؟»

۶. Agent Loop را بسازید

حالا اجزای کافی برای ساختن یک Loop ساده را داریم.

def run_agent(task):

    context = get_context(task)

    for attempt in range(3):

        action = agent.decide(context)

        result = execute(action)

        context.append(result)

        if task_complete(result):

            verification = verify()

            if verification:

                return "Task completed"

            context.append(
                "Verification failed. Fix the issues."
            )

    return "Task failed after 3 attempts"

توجه کنید چه چیزی تغییر کرده است.

دیگر این کار را انجام نمی‌دهیم:

Prompt → Code → Done

بلکه داریم این کار را انجام می‌دهیم:

Prompt
  ↓
Reason
  ↓
Act
  ↓
Verify
  ↓
Fix
  ↓
Verify again
  ↓
Done

این شروع یک Harness واقعی است.

۷. اجازه دهید Agent شکست‌های خودش را ببیند

اینجاست که سیستم‌های Agentic جذاب‌تر می‌شوند.

فرض کنید Agent چنین کدی می‌نویسد:

const customer = await prisma.customer.create({
  data: request.body
});

ممکن است کد کامپایل شود.

اما Architecture ما می‌گوید Controllerها نباید مستقیماً به Prisma دسترسی داشته باشند.

Harness می‌تواند این مسئله را از طریق Linting، Architecture Check یا Testها تشخیص دهد.

Agent changes code
       ↓
Run verification
       ↓
Architecture check fails
       ↓
Error returned to agent
       ↓
Agent fixes code
       ↓
Run verification again

Agent چیزی شبیه این دریافت می‌کند:

Verification failed.

Rule violation:

Controllers must not access Prisma directly.

Move database access into the repository layer.

حالا Agent Feedback قابل‌اقدام دریافت کرده است.

این بسیار بهتر از این است:

Try again.

۸. Hookها Harness را فعال و پیش‌دستانه می‌کنند

همچنین می‌توانیم از Hookها برای Enforcement کردن Rules قبل از Commit استفاده کنیم.

git diff --check

npm run lint

npm run typecheck

npm test

یک Pre-commit Hook می‌تواند چنین باشد:

#!/bin/sh

npm run lint || exit 1

npm run typecheck || exit 1

npm test || exit 1

حالا سیستم دیگر به یادآوری Agent وابسته نیست که:

«آه، احتمالاً باید تست‌ها را هم اجرا کنم.»

محیط این کار را به‌صورت خودکار انجام می‌دهد.

این تفاوت بین این دو است:

Please follow the rules.

و:

You cannot proceed until the rules are satisfied.

۹. Recovery را اضافه کنید

Agentها شکست می‌خورند.

این یک Bug در Harness نیست.

بلکه چیزی است که Harness باید انتظارش را داشته باشد.

یک Recovery Loop مفید می‌تواند چنین باشد:

for attempt in range(MAX_ATTEMPTS):

    result = agent.execute(task)

    verification = verify()

    if verification.success:

        return result

    feedback = {
        "error": verification.error,
        "attempt": attempt
    }

    agent.update_context(feedback)

return "Unable to complete safely"

بخش مهم، Feedback است.

به‌جای اینکه Agent از ابتدا شروع کند، اطلاعات زیر را دریافت می‌کند:

What failed?

Why did it fail?

What changed?

What should be tried next?

یعنی:

چه چیزی شکست خورد؟
چرا شکست خورد؟
چه چیزی تغییر کرده است؟
مرحله بعدی چه چیزی باید امتحان شود؟

این به Agent یک مسیر برای Recovery می‌دهد.

۱۰. اجازه ندهید Agentها برای همیشه Retry کنند

یک مشکل مهندسی مهم دیگر هم وجود دارد.

اگر Agent مدام همان اشتباه را تکرار کند چه اتفاقی می‌افتد؟

Attempt 1 → FAIL

Attempt 2 → FAIL

Attempt 3 → FAIL

Attempt 4 → FAIL

...

در نهایت Agent «خودمختار» شما تبدیل به یک Loop بی‌نهایت و پرهزینه می‌شود.

بنابراین باید برای آن Boundary تعیین کنید.

MAX_ATTEMPTS = 3

if attempt >= MAX_ATTEMPTS:

    stop_agent()

همچنین می‌توانید Failureهای تکراری را تشخیص دهید:

if error_hash in previous_errors:

    stop_agent()

previous_errors.add(error_hash)

این جزئیات کوچک می‌تواند تأثیر بزرگی در Production داشته باشد.

Autonomy بدون Boundary به معنی Reliability نیست.

۱۱. Git Awareness را اضافه کنید

یک Coding Agent باید دقیقاً بداند چه چیزهایی را تغییر داده است.

قبل از Execution:

git status

بعد از Execution:

git diff

سپس Harness می‌تواند نتیجه را بررسی کند:

changes = get_git_diff()

if touches_forbidden_files(changes):

    rollback()

برای مثال:

Allowed:

src/customers/

tests/customers/

Not allowed:

.env

generated/

infrastructure/

حالا یک Safety Layer دیگر داریم.

Agent می‌تواند تغییر ایجاد کند.

اما Harness تصمیم می‌گیرد که آیا آن تغییرات قابل‌قبول هستند یا نه.

۱۲. Planning را از Execution جدا کنید

یک بهبود مفید دیگر این است که Agent قبل از تغییر فایل‌ها ابتدا Plan ایجاد کند.

به‌جای:

Task → Modify files

از این ساختار استفاده کنیم:

Task
 ↓
Plan
 ↓
Review plan
 ↓
Execute
 ↓
Verify

یک Plan می‌تواند چنین باشد:

{
  "files": [
    "src/customers/customer.controller.ts",
    "src/customers/customer.service.ts",
    "src/customers/customer.repository.ts",
    "tests/customers/customer.test.ts"
  ],
  "changes": [
    "Add POST /customers",
    "Validate request using Zod",
    "Persist through repository",
    "Add integration tests"
  ]
}

Harness می‌تواند قبل از اینکه Agent شروع به تغییر Repository کند، یک Plan بد را Reject کند.

این کار می‌تواند مقدار زیادی Cleanup بعدی را کاهش دهد.

۱۳. Verification را چندلایه کنید

یک Test Suite به‌تنهایی کافی نیست.

Verification را به‌صورت Layerهای مختلف در نظر بگیرید.

                  Verification
                       │
        ┌──────────────┼──────────────┐
        ↓              ↓              ↓
     Syntax          Behavior      Architecture
        ↓              ↓              ↓
    Typecheck         Tests         Rules
        ↓              ↓              ↓
      Lint          Integration    Security
checks = [
    typecheck(),
    lint(),
    unit_tests(),
    integration_tests(),
    security_scan(),
    architecture_check()
]

یک Build موفق فقط به شما می‌گوید:

«کد احتمالاً می‌تواند اجرا شود.»

اما به شما نمی‌گوید:

«این کد در این Architecture جای درستی دارد.»

به همین دلیل Harness به انواع مختلف Verification نیاز دارد.

۱۴. Harness 2.0 کامل

حالا می‌توانیم همه‌چیز را کنار هم قرار دهیم.

                        Developer
                             │
                             ↓
                          Task
                             │
                             ↓
                    ┌─────────────────┐
                    │  Agent Harness  │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        ↓                    ↓                    ↓
     Context               Memory              Rules
        ↓                    ↓                    ↓
     Retrieval           Decisions           Guardrails
        └────────────────────┼────────────────────┘
                             ↓
                           Agent
                             │
                             ↓
                           Tools
                             │
                             ↓
                       Code Changes
                             │
                             ↓
                       Verification
                             │
                    ┌────────┴────────┐
                    ↓                 ↓
                  FAIL              PASS
                    │                 │
                    ↓                 ↓
                 Feedback           Done
                    │
                    └──────→ Agent

این همان چیزی است که من از Harness 2.0 منظور دارم.

نه یک Prompt بزرگ‌تر.

نه یک System Message دیگر.

بلکه یک محیط اجرای کامل در اطراف Agent.

۱۵. یک Harness حداقلی

لازم نیست از همان روز اول یک Platform عظیم بسازید.

یک نسخه اولیه که به‌طرز شگفت‌آوری مفید باشد می‌تواند چیزی شبیه این باشد:

class AgentHarness:

    def __init__(self, agent):

        self.agent = agent

        self.memory = []

        self.max_attempts = 3

    def run(self, task):

        context = self.load_context(task)

        for attempt in range(self.max_attempts):

            action = self.agent.decide(
                task=task,
                context=context
            )

            result = self.execute(action)

            context.append(result)

            verification = self.verify()

            if verification.success:

                self.save_memory(context)

                return "Success"

            context.append({
                "type": "verification_error",
                "message": verification.error
            })

        return "Failed safely"

این یک Production Implementation کامل نیست.

اما Architecture اصلی همین حالا وجود دارد:

Context → Action → Execution → Verification → Feedback → Recovery

از اینجا می‌توانید به‌تدریج موارد زیر را اضافه کنید:

هر بار یک Layer.

۱۶. تغییر واقعی

یک تغییر ظریف اما مهم در Software Engineering در حال رخ دادن است.

به‌صورت سنتی:

Developer
    ↓
Code
    ↓
Tests
    ↓
Production

اما در Agentic Development:

Developer
    ↓
Harness
    ↓
Agent
    ↓
Code
    ↓
Verification
    ↓
Feedback
    ↺

توسعه‌دهنده دیگر فقط کد نمی‌نویسد.

او محیطی را طراحی می‌کند که در آن یک سیستم دیگر کد می‌نویسد.

این یک مسئله مهندسی متفاوت است.

و به یک Mindset متفاوت نیاز دارد.

۱۷. برای باهوش‌ترین Agent بهینه‌سازی نکنید

شاید این مهم‌ترین درس باشد.

دو سیستم را تصور کنید.

System A

Very powerful model
+
Huge context window
+
Minimal tooling
+
No verification
+
No recovery

System B

Good model
+
Focused context
+
Specialized tools
+
Persistent memory
+
Strong verification
+
Recovery loop
+
Guardrails

من برای Production، System B را انتخاب می‌کنم.

چون Reliability فقط از Intelligence به وجود نمی‌آید.

Reliability از سیستمی که Intelligence را احاطه کرده است به وجود می‌آید.

۱۸. بهترین Agent، Agentی است که بتوانید آن را اصلاح کنید

ما اغلب می‌پرسیم:

«این Agent چقدر باهوش است؟»

اما سؤال بهتر این است:

آیا می‌توانید اشتباه را تشخیص دهید؟

آیا می‌توانید توضیح دهید چرا این اتفاق افتاده است؟

آیا Agent می‌تواند Failure را ببیند؟

آیا می‌تواند Recovery کند؟

آیا می‌توانید جلوی تکرار همان اشتباه را در دفعه بعد بگیرید؟

آیا می‌توانید با خیال راحت Rollback کنید؟

اگر پاسخ این سؤال‌ها «بله» باشد، در حال ساختن یک Engineering System هستید.

اگر پاسخ «نه» باشد، عمدتاً امیدوارید که Model درست عمل کند.

و Hope یک Deployment Strategy نیست.

جمع‌بندی نهایی

AI Agentها در نوشتن Software به شکل شگفت‌آوری خوب شده‌اند.

اما اینکه به یک Agent دسترسی به Repository بدهیم و از آن بخواهیم:

«این Feature را بساز»

کافی نیست.

چالش واقعی Engineering، تمام چیزهایی است که اطراف Model قرار دارند:

Context
   +
Tools
   +
Memory
   +
Constraints
   +
Verification
   +
Recovery
   +
Observability
   =
Agent Harness

Model، Reasoning را فراهم می‌کند.

Harness، Discipline را فراهم می‌کند.

و این موضوع نحوه تفکر ما درباره AI-assisted Development را تغییر می‌دهد.

هدف این نیست که Agentی بسازیم که هرگز اشتباه نکند.

هدف این است که سیستمی بسازیم که در آن:

این تفاوت میان یک AI که می‌تواند کد بنویسد و یک AI System که واقعاً بتوانید انجام کارهای مهندسی را به آن بسپارید است.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *