痛み: 午後の時間をかけてエージェントを調整したのに、翌朝になると白紙状態で — 昨日がなかったかのように。
学べること: 4段階の進化(Prompt → Context → Harness → Loop)と、メモリを永続化する50行のループエージェント。
0. 前提条件
- Python ≥ 3.10
-
pip install openai(openai ≥ 1.0.0) - OpenAI API Key
- OS: macOS / Linux / Windows WSL
目標: コードをコピー&ペーストして実行し、忘れないループエージェントを確認する。
1. 痛み: エージェントが一晩で忘れる理由
午前2時、ようやく多段階ワークフローが動作した。エージェントは丹念に設計したプロンプトに従い — データ取得、クリーニング、分析、可視化。満足してノートPCを閉じる。
翌朝、期待に胸を膨らませて会話を開くと、エージェントは白紙状態で — まるで何も起こらなかったかのように。
ログを確認する。エラーも例外もない。エージェントはすべてを再生成した — 昨日どこで止まったかを「忘れて」しまった。
これは冗談ではない。本気のAgent開発者なら誰でも経験する悪夢だ。根本原因は「モデルが賢くない」ではない。より根本的な事実: エージェントは夜を越えるよう設計されていなかった。
2. 4段階の進化: Prompt → Context → Harness → Loop
これを理解するため、シンプルな進化フレームワークを使う:
| Stage | What You Do | Fatal Flaw |
|---|---|---|
| Prompt Engineering | タスク記述、例、形式をプロンプトに記述する | 予期しない入力で出力が破綻する |
| Context Engineering | 履歴+中間結果をコンテキストウィンドウに詰め込む | トークンコストが線形に増大し、ウィンドウ上限に到達する |
| Harness Engineering | ツール呼び出し、構造化出力、エラー捕捉を追加 | フレームワークはできたが、エージェントは依然として「ワンショット」 |
| Loop Engineering | 閉じたループを構築: state + memory + feedback + retry + persistence | 本当のエンジニアリング — エージェントが「生き始める」 |
Loop EngineeringはPrompt Engineeringの否定ではなく、超越である。 Promptは依然として重要だ。ただしそれはエンジンであり、エンジンを車のように扱うことはできない。
3. 最小限の実行可能ループエージェント(50行)
以下が完全な、コピー&ペースト可能な、実行可能なループエージェントである。まず実行し、その後1行ずつ理解してほしい。
import json, os, time
from pathlib import Path
from datetime import datetime
from openai import OpenAI
client = OpenAI() # reads OPENAI_API_KEY from env
STATE_FILE = Path("./agent_state.json")
MEMORY_FILE = Path("./agent_memory.json")
MAX_RETRIES = 3
def load_memory() -> dict:
if MEMORY_FILE.exists():
return json.loads(MEMORY_FILE.read_text())
return {"facts": {}, "errors": []}
def save_memory(mem: dict):
MEMORY_FILE.write_text(json.dumps(mem, indent=2, ensure_ascii=False))
def load_state() -> dict:
if STATE_FILE.exists():
return json.loads(STATE_FILE.read_text())
return {"state": "idle", "step": 0}
def save_state(state: str, step: int):
STATE_FILE.write_text(json.dumps({
"state": state, "step": step,
"updated_at": datetime.now().isoformat()
}, indent=2, ensure_ascii=False))
def execute(task: str, memory: dict, error_ctx: str = "") -> str:
system = f"You are a task-execution agent. Known facts: {json.dumps(memory.get('facts', {}), ensure_ascii=False)}"
if error_ctx:
system += f"\nLast error: {error_ctx}\nPlease fix."
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system},
{"role": "user", "content": task}]
)
return resp.choices[0].message.content
def check(output: str, keywords: list[str]) -> tuple[bool, str]:
missing = [kw for kw in keywords if kw not in output]
if missing:
return False, f"Missing: {missing}"
if len(output) < 20:
return False, "Output too short"
return True, ""
def run(task: str, keywords: list[str]):
memory = load_memory()
save_state("running", 0)
for i in range(1, MAX_RETRIES + 1):
error = memory["errors"][-1]["reason"] if memory["errors"] else ""
output = execute(task, memory, error)
ok, reason = check(output, keywords)
if ok:
save_state("done", i)
memory["facts"][task[:30]] = output[:100]
save_memory(memory)
return f"OK on attempt {i}:\n{output}"
else:
save_state("retrying", i)
memory["errors"].append(
{"task": task, "reason": reason, "attempt": i}
)
save_memory(memory)
print(f"Retry {i} failed: {reason}")
time.sleep(1)
save_state("failed", MAX_RETRIES)
return f"All {MAX_RETRIES} attempts failed"
if __name__ == "__main__":
result = run(
task="List 3 Python web frameworks and their features",
keywords=["Flask", "Django", "FastAPI"]
)
print(result)
Enter fullscreen mode Exit fullscreen mode
loop_agent.py にコピーして実行する。
4. 検証
# 1. インストール
pip install openai
# 2. APIキーの設定
export OPENAI_API_KEY="sk-..."
# 3. 初回実行
python loop_agent.py
Enter fullscreen mode Exit fullscreen mode
期待される出力:
OK on attempt 1:
The 3 mainstream Python web frameworks:
1. **Flask**: lightweight micro-framework...
2. **Django**: full-stack, batteries included...
3. **FastAPI**: modern async, auto OpenAPI docs...
Enter fullscreen mode Exit fullscreen mode
永続化の検証 — ターミナルを閉じ、再び開いて再度実行:
# 4. 状態ファイルの確認
cat agent_state.json
Enter fullscreen mode Exit fullscreen mode
期待される内容:
{
"state": "done",
"step": 1,
"updated_at": "2026-07-01T12:00:00.000000"
}
Enter fullscreen mode Exit fullscreen mode
# 5. メモリファイルの確認
cat agent_memory.json
Enter fullscreen mode Exit fullscreen mode
期待される内容: facts 辞書にタスクと出力が含まれている。
# 6. 再度実行 — エージェントは自動的にメモリを読み込む
python loop_agent.py
Enter fullscreen mode Exit fullscreen mode
エージェントは agent_memory.json から facts を読み込み、既知のコンテキストとして渡す。これが「一晩で記憶を保持する」仕組みである。
5. コードにマッピングされた6つのコンポーネント
| Component | Code Location | Description |
|---|---|---|
| Memory Store |
load_memory() / save_memory()
|
JSONへのメモリ永続化 |
| State Machine |
load_state() / save_state()
|
JSONへの状態永続化 |
| Executor | execute() |
OpenAI APIの呼び出し |
| Checker | check() |
キーワードの存在確認 |
| Task Scheduler | run() |
リトライループ+状態遷移 |
| Guardrails | MAX_RETRIES = 3 |
リトライ上限 |
6. よくあるエラー
エラー1: ModuleNotFoundError: No module named 'openai'
pip install openai
Enter fullscreen mode Exit fullscreen mode
エラー2: openai.AuthenticationError: 401
APIキーが設定されていない、または無効。
export OPENAI_API_KEY="sk-..."
Enter fullscreen mode Exit fullscreen mode
エラー3: Agent出力にFlask/Django/FastAPIが含まれていない
これは正常です!LLMは指示に完璧に従うわけではない。これこそがLoop Agentの価値 — Checkerが欠落キーワードを検知し、出力を拒否して自動リトライを行う。次のような出力が表示される:
Retry 1 failed: Missing: ['Flask', 'Django', 'FastAPI']
Retry 2 failed: Missing: ['Django']
OK on attempt 3:
...
Enter fullscreen mode Exit fullscreen mode
エラー4: APIタイムアウトまたはレートリミット
gpt-4o-mini は非常に安価(1回あたり約$0.00015)である。レート制限を受けた場合は time.sleep(1) を増やすか、OpenAIダッシュボードを確認する。
7. 50行からの次のステップ
これで完全な実行可能ループエージェントが完成した。永続化にプレーンなJSONファイルを使用しており、手で各コンポーネントに触れられる。
拡張の方向性:
- JSON → ChromaDBへの置き換え(ベクトルメモリ)
- Celeryとの接続(本物の非同期タスクキュー)
- Feishu/Slack webhookの追加(完了通知)
- 構造化ログ+Trace IDの追加(可観測性)
Loop Engineeringはインストールするパッケージではない。アーキテクチャ思考のシフトである。 この50行から、あなたはステージ4の入り口に立っている。
次の記事: エージェントにどれだけのメモリが必要か? — メモリストア選択ガイド
著者について: Wu Ji (无记) — Agentエンジニアリング、Loop Engineering、デジタルトランスフォーメーションに注力するAI&デジタル化の実践者。実践的でハンズオンなチュートリアル — 実際に動く。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.