چرا هوشمندی مدل بهتنهایی کافی نیست و چگونه ساخت یک 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:
- لایه Repository را دور زده است.
- فایلی را تغییر داده که نباید دست میزد.
- یک تست مهم را اجرا نکرده است.
- یک مشکل امنیتی ایجاد کرده است.
- یک تصمیم معماری موجود را نادیده گرفته است.
هیچ چیزی 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 مجبور است حدس بزند:
- Endpoint باید کجا قرار بگیرد؟
- از کدام لایه Database باید استفاده شود؟
- پروژه از چه کتابخانهای برای Validation استفاده میکند؟
- ساختار پاسخ خطا چگونه است؟
- تستها کجا قرار دارند؟
- پروژه از چه Naming Conventionهایی پیروی میکند؟
و 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
از اینجا میتوانید بهتدریج موارد زیر را اضافه کنید:
- Tool Permissions
- Sandboxing
- Persistent Memory
- Planning
- Rollback
- Observability
- Cost Controls
- Security Policies
- Human Approval
- Parallel Agents
هر بار یک 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ی بسازیم که هرگز اشتباه نکند.
هدف این است که سیستمی بسازیم که در آن:
- اشتباهها بهسرعت شناسایی شوند،
- Failureها قابل Recovery باشند،
- Actionهای ناامن محدود شوند،
- و کارهای موفق بهصورت مستقل Verification شوند.
این تفاوت میان یک AI که میتواند کد بنویسد و یک AI System که واقعاً بتوانید انجام کارهای مهندسی را به آن بسپارید است.