mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-06-21 04:33:36 +08:00
* feat: s01-s14 docs quality overhaul — tool pipeline, single-agent, knowledge & resilience Rewrite code.py and README (zh/en/ja) for s01-s14, each chapter building incrementally on the previous. Key fixes across chapters: - s01-s04: agent loop, tool dispatch, permission pipeline, hooks - s05-s08: todo write, subagent, skill loading, context compact - s09-s11: memory system, system prompt assembly, error recovery - s12-s14: task graph, background tasks, cron scheduler All chapters CC source-verified. Code inherits fixes forward (PROMPT_SECTIONS, json.dumps cache, real-state context, can_start dep protection, etc.). * feat: s15-s19 docs quality overhaul — multi-agent platform: teams, protocols, autonomy, worktree, MCP tools Rewrite code.py and README (zh/en/ja) for s15-s19, the multi-agent platform chapters. Each chapter inherits all previous fixes and adds one mechanism: - s15: agent teams (TeamCreate, teammate threads, shared task list) - s16: team protocols (plan approval, shutdown handshake, consume_inbox) - s17: autonomous agents (idle polling, auto-claim, consume_lead_inbox) - s18: worktree isolation (git worktree, bind_task, cwd switching, safety) - s19: MCP tools (MCPClient, normalize_mcp_name, assemble_tool_pool, no cache) All appendix source code references verified against CC source. Config priority corrected: claude.ai < plugin < user < project < local. * fix: 5 regressions across s05-s19 — glob safety, todo validation, memory extraction, protocol types, dep crash - s05-s09: glob results now filter with is_relative_to(WORKDIR) (inherited from s02) - s06-s08: todo_write validates content/status required fields (inherited from s05) - s09: extract_memories uses pre-compression snapshot instead of compacted messages - s16: submit_plan docstring clarifies protocol-only (not code-level gate) - s17-s19: match_response restores type mismatch validation (from s16) - s17-s19: claim_task deps list handles missing dep files without crashing * fix: s12 Todo V2 logic reversal, s14/s15 cron range validation, s18/s19 worktree name validation - s12 README (zh/en/ja): fix Todo V2 direction — interactive defaults to Task, non-interactive/SDK defaults to TodoWrite. Fix env var name to CLAUDE_CODE_ENABLE_TASKS (not TODO_V2). - s14/s15: add _validate_cron_field with per-field range checks (minute 0-59, hour 0-23, dom 1-31, month 1-12, dow 0-6), step > 0, range lo <= hi. Replace old try/except validation that only caught exceptions. - s18/s19: add validate_worktree_name() to remove_worktree and keep_worktree, not just create_worktree. * fix: align s16-s19 teaching tool consistency * fix pr265 chapter diagrams * Add comprehensive s20 harness chapter * Fix chapter smoke test regressions * Clarify README tutorial track transition --------- Co-authored-by: Haoran <bill-billion@outlook.com>
This commit is contained in:
254
s10_system_prompt/README.en.md
Normal file
254
s10_system_prompt/README.en.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# s10: System Prompt — Assembled at Runtime, Never Hardcoded
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s08 → s09 → `s10` → [s11](../s11_error_recovery/) → s12 → ... → s20
|
||||
> *"prompt is assembled, not hardcoded"* — Sections + on-demand assembly + caching.
|
||||
>
|
||||
> **Harness Layer**: Prompt — assembled at runtime, never hardcoded.
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
From s01 to s09, the system prompt was always one hardcoded line:
|
||||
|
||||
```python
|
||||
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks."
|
||||
```
|
||||
|
||||
That worked for s01 — only bash, read, write. But by s09, the agent has memory, compression, skill loading. The prompt needs to describe more and more capabilities:
|
||||
|
||||
```python
|
||||
SYSTEM = (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
"Use tools to solve tasks. Act, don't explain. "
|
||||
"Before starting any multi-step task, use todo_write. "
|
||||
"Skills are available via list_skills and load_skill. "
|
||||
"Relevant memories are injected below when available. "
|
||||
# ... add a capability, add a line
|
||||
)
|
||||
```
|
||||
|
||||
Three problems:
|
||||
|
||||
1. **Switching projects requires rewriting the entire prompt** — no way to know what to change and what to keep
|
||||
2. **One change can break others** — adding a tool description might conflict with earlier instructions
|
||||
3. **Every request carries everything** — even when the current conversation doesn't need certain sections, they waste tokens
|
||||
|
||||
The system prompt should be a configuration assembled at runtime based on current state: which tools are enabled, which context is visible, which memories are relevant, and which content must remain stable to hit prompt cache.
|
||||
|
||||
---
|
||||
|
||||
## The Solution
|
||||
|
||||

|
||||
|
||||
s10 focuses on prompt assembly. It builds on the s08-s09 capabilities but doesn't re-implement compression or memory. The core change: split the hardcoded `SYSTEM` into independent sections, assemble them at runtime based on real state, and cache the result.
|
||||
|
||||
Four sections, two loading strategies:
|
||||
|
||||
| Section | Strategy | Content | Condition |
|
||||
|---------|----------|---------|-----------|
|
||||
| identity | always | who you are, how to work | always present |
|
||||
| tools | always | available tool list | `enabled_tools` |
|
||||
| workspace | always | working directory | always present |
|
||||
| memory | on-demand | relevant memory content | whether `.memory/MEMORY.md` exists |
|
||||
|
||||
Key design: whether a section loads depends on real state (tools exist, files exist), not keywords in messages.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
### PROMPT_SECTIONS: Topic-Keyed Fragments
|
||||
|
||||
Split the monolithic string into a dictionary, each key is a topic:
|
||||
|
||||
```python
|
||||
PROMPT_SECTIONS = {
|
||||
"identity": "You are a coding agent. Act, don't explain.",
|
||||
"tools": "Available tools: bash, read_file, write_file.",
|
||||
"workspace": f"Working directory: {WORKDIR}",
|
||||
"memory": "Relevant memories are injected below when available.",
|
||||
}
|
||||
```
|
||||
|
||||
Each section is maintained independently. Changing `tools` doesn't affect `identity`; adding `memory` doesn't touch `workspace`.
|
||||
|
||||
### assemble_system_prompt: On-Demand Assembly
|
||||
|
||||
Not every section is needed every turn. No memory files? Loading the memory section just wastes tokens. Assembly is based on real state in context:
|
||||
|
||||
```python
|
||||
def assemble_system_prompt(context: dict) -> str:
|
||||
sections = []
|
||||
|
||||
# Always loaded
|
||||
sections.append(PROMPT_SECTIONS["identity"])
|
||||
sections.append(PROMPT_SECTIONS["tools"])
|
||||
sections.append(PROMPT_SECTIONS["workspace"])
|
||||
|
||||
# On-demand — based on real state, not keywords
|
||||
memories = context.get("memories", "")
|
||||
if memories:
|
||||
sections.append(f"Relevant memories:\n{memories}")
|
||||
|
||||
return "\n\n".join(sections)
|
||||
```
|
||||
|
||||
"Always loaded" sections are needed every turn: identity, tools, workspace. "On-demand" sections are only useful under specific conditions.
|
||||
|
||||
Why not load everything? Tokens have cost (system prompt is billed every turn), and fewer instructions means more focused output (irrelevant instructions are noise).
|
||||
|
||||
### get_system_prompt: Cache to Avoid Re-Assembly
|
||||
|
||||
When context hasn't changed (multiple LLM calls in the same turn with the same context), re-assembling is wasteful. Use deterministic serialization to detect changes and return cached result:
|
||||
|
||||
```python
|
||||
def get_system_prompt(context: dict) -> str:
|
||||
global _last_context_key, _last_prompt
|
||||
key = json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
|
||||
if key == _last_context_key and _last_prompt:
|
||||
return _last_prompt
|
||||
_last_context_key = key
|
||||
_last_prompt = assemble_system_prompt(context)
|
||||
return _last_prompt
|
||||
```
|
||||
|
||||
`json.dumps` instead of `hash()`: Python's built-in `hash()` has process randomization (unsuitable for stable cache keys) and throws `unhashable type` on nested dicts/lists.
|
||||
|
||||
Note: this cache only avoids redundant string assembly within a process. It's not the same as CC's API prompt cache, which uses `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` to separate static and dynamic parts — the static parts hit global cache and don't invalidate when dynamic content changes.
|
||||
|
||||
### context: Real State, Not Keyword Guessing
|
||||
|
||||
Context reflects the actual runtime state:
|
||||
|
||||
```python
|
||||
def update_context(context: dict, messages: list) -> dict:
|
||||
memories = ""
|
||||
if MEMORY_INDEX.exists():
|
||||
content = MEMORY_INDEX.read_text().strip()
|
||||
if content:
|
||||
memories = content
|
||||
return {
|
||||
"enabled_tools": list(TOOL_HANDLERS.keys()),
|
||||
"workspace": str(WORKDIR),
|
||||
"memories": memories,
|
||||
}
|
||||
```
|
||||
|
||||
`enabled_tools` lists actually registered tools. `memories` checks whether `.memory/MEMORY.md` exists. Section loading is based on this real state, not searching for keywords in messages.
|
||||
|
||||
### Putting It Together
|
||||
|
||||
```python
|
||||
def agent_loop(messages: list, context: dict):
|
||||
system = get_system_prompt(context)
|
||||
while True:
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=system, messages=messages,
|
||||
tools=TOOLS, max_tokens=8000)
|
||||
# ... tool execution ...
|
||||
context = update_context(context, messages)
|
||||
system = get_system_prompt(context)
|
||||
```
|
||||
|
||||
At the start of each loop iteration, get the system prompt. If context changed, re-assemble; if not, return cached version.
|
||||
|
||||
---
|
||||
|
||||
## Changes From s09
|
||||
|
||||
| Component | Before (s09) | After (s10) |
|
||||
|-----------|-------------|-------------|
|
||||
| prompt | Hardcoded SYSTEM string | PROMPT_SECTIONS + assemble_system_prompt |
|
||||
| caching | None | get_system_prompt (json.dumps detection + cache) |
|
||||
| new functions | — | assemble_system_prompt, get_system_prompt, update_context |
|
||||
| tools | bash, read_file, write_file (3) | bash, read_file, write_file (3) — unchanged |
|
||||
| loop | Uses fixed SYSTEM | Uses get_system_prompt(context) |
|
||||
|
||||
---
|
||||
|
||||
## Try It
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s10_system_prompt/code.py
|
||||
```
|
||||
|
||||
What to watch for:
|
||||
|
||||
1. Output shows which sections were loaded (`[assembled] sections: ...` label)
|
||||
2. Cache hits show `[cache hit]` during continued conversation
|
||||
3. Creating `.memory/MEMORY.md` makes the memory section appear on the next turn
|
||||
|
||||
Try these prompts:
|
||||
|
||||
1. `Read the file README.md` (observe the three always-loaded sections)
|
||||
2. `Create a file called .memory/MEMORY.md with content "- [test](test.md) — test memory"` (write a memory index)
|
||||
3. `Read the file code.py` (observe whether the memory section appears)
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
System prompts can now be assembled at runtime. But the agent still crashes on errors. Network hiccups, API rate limits, truncated output, context overflow — these aren't bugs, they're normal.
|
||||
|
||||
s11 Error Recovery → four recovery paths. Upgrade tokens, compress context, exponential backoff, switch models.
|
||||
|
||||
<details>
|
||||
<summary>Deep Dive Into CC Source Code</summary>
|
||||
|
||||
> The following is based on analysis of CC source code `constants/prompts.ts` (914 lines), `constants/systemPromptSections.ts` (68 lines), `context.ts` (189 lines), `utils/api.ts` (718 lines), `utils/systemPrompt.ts` (123 lines), and `bootstrap/state.ts`.
|
||||
|
||||
### How many sections does CC's system prompt have?
|
||||
|
||||
The count varies based on feature flags, output style, KAIROS/Proactive mode, user type, token budget, etc. Roughly two categories:
|
||||
|
||||
**Static sections** (always loaded): identity, system, doing_tasks, actions, using_tools, tone_style, output_efficiency, etc.
|
||||
|
||||
**Dynamic sections** (loaded by state): session_guidance, memory, ant_model_override, env_info_simple, language, output_style, mcp_instructions, scratchpad, frc, summarize_tool_results, numeric_length_anchors, token_budget, brief, etc.
|
||||
|
||||
`mcp_instructions` is the only volatile section (created via `DANGEROUS_uncachedSystemPromptSection()`), because MCP servers can connect and disconnect between turns.
|
||||
|
||||
### Assembly Function
|
||||
|
||||
```typescript
|
||||
getSystemPrompt(tools, model, additionalWorkingDirs?, mcpClients?): Promise<string[]>
|
||||
```
|
||||
|
||||
Returns `string[]` (each element is a section), separated by `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` between static and dynamic parts.
|
||||
|
||||
### cache scope
|
||||
|
||||
When global cache boundary is enabled, static sections are merged into one global cache block, and dynamic sections don't use global cache (`cacheScope: null`). Only paths without boundary or skipping global cache fall back to org scope.
|
||||
|
||||
The teaching version's cache only avoids redundant string assembly. CC's three-layer cache:
|
||||
|
||||
1. **lodash memoize**: `getSystemContext` and `getUserContext` cached per session (`context.ts`)
|
||||
2. **Section registry cache**: `STATE.systemPromptSectionCache` caches dynamic section results, cleared on `/clear` or `/compact`
|
||||
3. **API-level cache**: `splitSysPromptPrefix()` (`api.ts`) splits prompt into blocks with different cache scopes via boundary
|
||||
|
||||
### getUserContext vs getSystemContext
|
||||
|
||||
| | getSystemContext | getUserContext |
|
||||
|---|---|---|
|
||||
| Content | gitStatus, cacheBreaker | CLAUDE.md content, currentDate |
|
||||
| Injection | appended to system prompt array | prepended as `<system-reminder>` user message |
|
||||
| When skipped | custom system prompt | always runs |
|
||||
|
||||
### How modes change the prompt
|
||||
|
||||
- **CLAUDE_CODE_SIMPLE**: entire prompt is 2 lines
|
||||
- **Proactive/KAIROS**: compact prompt replaces all standard sections
|
||||
- **Coordinator**: coordinator-specific prompt fully replaces default
|
||||
- **Agent mode**: agent-defined prompt replaces or appends to default
|
||||
|
||||
### Total size
|
||||
|
||||
Standard interactive mode system prompt core is ~20-30KB text. CLAUDE_CODE_SIMPLE is ~150 characters. User context (CLAUDE.md) and system context (git status) add on top.
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
254
s10_system_prompt/README.ja.md
Normal file
254
s10_system_prompt/README.ja.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# s10: System Prompt — 実行時アセンブリ、ハードコードなし
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s08 → s09 → `s10` → [s11](../s11_error_recovery/) → s12 → ... → s20
|
||||
> *"prompt は組み立てるもの、固定するものではない"* — セグメント + オンデマンド結合 + キャッシュ。
|
||||
>
|
||||
> **Harness レイヤー**: プロンプト — 実行時組み立て、ハードコードなし。
|
||||
|
||||
---
|
||||
|
||||
## 課題
|
||||
|
||||
s01 から s09 まで、system prompt は常に 1 行のハードコード:
|
||||
|
||||
```python
|
||||
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks."
|
||||
```
|
||||
|
||||
s01 では十分だった。bash、read、write の 3 ツールのみ。しかし s09 では、Agent に記憶、圧縮、スキル読み込みがある。prompt が説明すべき能力が増え続ける:
|
||||
|
||||
```python
|
||||
SYSTEM = (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
"Use tools to solve tasks. Act, don't explain. "
|
||||
"Before starting any multi-step task, use todo_write. "
|
||||
"Skills are available via list_skills and load_skill. "
|
||||
"Relevant memories are injected below when available. "
|
||||
# ... 能力を追加するたびに 1 行増える
|
||||
)
|
||||
```
|
||||
|
||||
3 つの問題:
|
||||
|
||||
1. **プロジェクトを変えるには prompt 全体を書き直す**必要がある。何を変え、何を残すべきか不明
|
||||
2. **一箇所の変更が全体に影響する**。ツール説明を追加すると、前の指示と矛盾する可能性
|
||||
3. **毎回のリクエストが全内容を送信する**。現在の会話で不要なセクションも token を無駄に消費
|
||||
|
||||
System prompt は、実行時の現在状態に基づいて組み立てられる設定であるべき:どのツールが有効か、どのコンテキストが可視か、どの記憶が関連するか、どの内容を prompt cache に命中させるために安定させるべきか。
|
||||
|
||||
---
|
||||
|
||||
## ソリューション
|
||||
|
||||

|
||||
|
||||
s10 は prompt アセンブリ機構に焦点を当てる。s08-s09 の能力を背景とするが、圧縮や記憶システムは再実装しない。核心の変更:ハードコードされた `SYSTEM` を独立セクションに分割し、実行時に実際の状態に基づいてオンデマンドで組み立て、結果をキャッシュして再組み立てを回避。
|
||||
|
||||
4 つのセクション、2 つの読み込み戦略:
|
||||
|
||||
| セクション | 戦略 | 内容 | 判断基準 |
|
||||
|-----------|------|------|---------|
|
||||
| identity | 常に | あなたは誰か、どう作業するか | 常に存在 |
|
||||
| tools | 常に | 利用可能ツール一覧 | `enabled_tools` |
|
||||
| workspace | 常に | 作業ディレクトリ | 常に存在 |
|
||||
| memory | オンデマンド | 関連記憶内容 | `.memory/MEMORY.md` が存在するか |
|
||||
|
||||
重要な設計:セクションをロードするかどうかは実際の状態(ツールが存在するか、ファイルが存在するか)で決まり、メッセージ内のキーワードではない。
|
||||
|
||||
---
|
||||
|
||||
## 仕組み
|
||||
|
||||
### PROMPT_SECTIONS: トピック別フラグメント
|
||||
|
||||
単一の文字列を辞書に分割、各キーがトピック:
|
||||
|
||||
```python
|
||||
PROMPT_SECTIONS = {
|
||||
"identity": "You are a coding agent. Act, don't explain.",
|
||||
"tools": "Available tools: bash, read_file, write_file.",
|
||||
"workspace": f"Working directory: {WORKDIR}",
|
||||
"memory": "Relevant memories are injected below when available.",
|
||||
}
|
||||
```
|
||||
|
||||
各セクションは独立して管理。`tools` を変更しても `identity` に影響しない。`memory` を追加しても `workspace` はそのまま。
|
||||
|
||||
### assemble_system_prompt: オンデマンド組み立て
|
||||
|
||||
すべてのセクションが毎ターン必要なわけではない。記憶ファイルがなければ、memory セクションをロードしても token の無駄。context の実際の状態に基づいて組み立てる:
|
||||
|
||||
```python
|
||||
def assemble_system_prompt(context: dict) -> str:
|
||||
sections = []
|
||||
|
||||
# 常にロード
|
||||
sections.append(PROMPT_SECTIONS["identity"])
|
||||
sections.append(PROMPT_SECTIONS["tools"])
|
||||
sections.append(PROMPT_SECTIONS["workspace"])
|
||||
|
||||
# オンデマンド — 実際の状態に基づく、キーワードではない
|
||||
memories = context.get("memories", "")
|
||||
if memories:
|
||||
sections.append(f"Relevant memories:\n{memories}")
|
||||
|
||||
return "\n\n".join(sections)
|
||||
```
|
||||
|
||||
「常にロード」は毎ターン必要なもの:アイデンティティ、ツール、作業ディレクトリ。「オンデマンド」は特定条件下でのみ有用。
|
||||
|
||||
なぜ全部ロードしないのか?token にはコストがあり(system prompt は毎ターン課金)、情報が少ないほど LLM は集中する(無関係な指示はノイズ)。
|
||||
|
||||
### get_system_prompt: キャッシュで再組み立てを回避
|
||||
|
||||
コンテキストが変わっていない時(同じターン内で複数の LLM 呼び出し、context が同じ)、再組み立ては無駄。確定的シリアライズで変化を検出し、キャッシュヒット時は即座に返却:
|
||||
|
||||
```python
|
||||
def get_system_prompt(context: dict) -> str:
|
||||
global _last_context_key, _last_prompt
|
||||
key = json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
|
||||
if key == _last_context_key and _last_prompt:
|
||||
return _last_prompt
|
||||
_last_context_key = key
|
||||
_last_prompt = assemble_system_prompt(context)
|
||||
return _last_prompt
|
||||
```
|
||||
|
||||
`hash()` ではなく `json.dumps` を使用:Python 組み込みの `hash()` にはプロセスランダム化があり(安定したキャッシュキーに不適切)、list/dict で `unhashable type` エラーになる。
|
||||
|
||||
注意:このキャッシュは「プロセス内での文字列再組み立ての回避」のみ。CC の API prompt cache とは別物。CC の prompt cache は `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` で静的/動的部分を分離し、静的部分が global cache に命中する。動的内容が変化しても静的部分は無効化されない。
|
||||
|
||||
### context: 実際の状態、キーワード推測ではない
|
||||
|
||||
context は現在の実行時状態の実際の状態を反映:
|
||||
|
||||
```python
|
||||
def update_context(context: dict, messages: list) -> dict:
|
||||
memories = ""
|
||||
if MEMORY_INDEX.exists():
|
||||
content = MEMORY_INDEX.read_text().strip()
|
||||
if content:
|
||||
memories = content
|
||||
return {
|
||||
"enabled_tools": list(TOOL_HANDLERS.keys()),
|
||||
"workspace": str(WORKDIR),
|
||||
"memories": memories,
|
||||
}
|
||||
```
|
||||
|
||||
`enabled_tools` は実際に登録されたツールを一覧。`memories` は `.memory/MEMORY.md` が存在するかを確認。セクションの読み込みはこの実際の状態に基づき、メッセージ内のキーワード検索ではない。
|
||||
|
||||
### 組み合わせて実行
|
||||
|
||||
```python
|
||||
def agent_loop(messages: list, context: dict):
|
||||
system = get_system_prompt(context)
|
||||
while True:
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=system, messages=messages,
|
||||
tools=TOOLS, max_tokens=8000)
|
||||
# ... ツール実行 ...
|
||||
context = update_context(context, messages)
|
||||
system = get_system_prompt(context)
|
||||
```
|
||||
|
||||
各ループ反復の開始時に system prompt を取得。context が変わっていれば再組み立て、変わっていなければキャッシュを返却。
|
||||
|
||||
---
|
||||
|
||||
## s09 からの変更点
|
||||
|
||||
| コンポーネント | 変更前 (s09) | 変更後 (s10) |
|
||||
|-----------|-------------|-------------|
|
||||
| prompt | ハードコード SYSTEM 文字列 | PROMPT_SECTIONS + assemble_system_prompt |
|
||||
| キャッシュ | なし | get_system_prompt(json.dumps 検出 + キャッシュ) |
|
||||
| 新規関数 | — | assemble_system_prompt, get_system_prompt, update_context |
|
||||
| ツール | bash, read_file, write_file (3) | bash, read_file, write_file (3) — 変更なし |
|
||||
| ループ | 固定 SYSTEM を使用 | get_system_prompt(context) を使用 |
|
||||
|
||||
---
|
||||
|
||||
## 試してみよう
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s10_system_prompt/code.py
|
||||
```
|
||||
|
||||
観察のポイント:
|
||||
|
||||
1. 出力にロードされたセクションが表示される(`[assembled] sections: ...` ラベル)
|
||||
2. 継続会話でキャッシュヒット時は `[cache hit]` と表示
|
||||
3. `.memory/MEMORY.md` を作成すると、次のターンで memory セクションが自動ロード
|
||||
|
||||
以下のプロンプトを試してみてください:
|
||||
|
||||
1. `Read the file README.md`(常にロードされる 3 つのセクションを観察)
|
||||
2. `Create a file called .memory/MEMORY.md with content "- [test](test.md) — test memory"`(記憶インデックスを書き込み)
|
||||
3. `Read the file code.py`(memory セクションが表示されるか観察)
|
||||
|
||||
---
|
||||
|
||||
## 次へ
|
||||
|
||||
System prompt を実行時に組み立てられるようになった。しかし Agent はエラーでまだクラッシュする。ネットワークの不安定性、API レート制限、出力の切り詰め、コンテキスト超過、これらはバグではなく日常。
|
||||
|
||||
s11 Error Recovery → 4 つのリカバリパス。token のアップグレード、コンテキスト圧縮、指数バックオフ、モデル切り替え。
|
||||
|
||||
<details>
|
||||
<summary>CC ソースコードの詳細</summary>
|
||||
|
||||
> 以下は CC ソースコード `constants/prompts.ts`(914 行)、`constants/systemPromptSections.ts`(68 行)、`context.ts`(189 行)、`utils/api.ts`(718 行)、`utils/systemPrompt.ts`(123 行)、`bootstrap/state.ts` の分析に基づく。
|
||||
|
||||
### CC の system prompt にはいくつのセクションがあるか?
|
||||
|
||||
数は固定されておらず、feature flag、output style、KAIROS/Proactive モード、ユーザータイプ、token 予算などに影響される。大まかに 2 つのカテゴリ:
|
||||
|
||||
**静的セクション**(常にロード):identity、system、doing_tasks、actions、using_tools、tone_style、output_efficiency など。
|
||||
|
||||
**動的セクション**(状態に応じてロード):session_guidance、memory、ant_model_override、env_info_simple、language、output_style、mcp_instructions、scratchpad、frc、summarize_tool_results、numeric_length_anchors、token_budget、brief など。
|
||||
|
||||
`mcp_instructions` は唯一の揮発性セクション(`DANGEROUS_uncachedSystemPromptSection()` で作成)。MCP server はターン間で接続・切断可能なため。
|
||||
|
||||
### 組み立て関数
|
||||
|
||||
```typescript
|
||||
getSystemPrompt(tools, model, additionalWorkingDirs?, mcpClients?): Promise<string[]>
|
||||
```
|
||||
|
||||
`string[]`(各要素がセクション)を返却。`SYSTEM_PROMPT_DYNAMIC_BOUNDARY` で静的/動的部分を分離。
|
||||
|
||||
### cache scope
|
||||
|
||||
global cache boundary が有効な場合、静的セクションは 1 つの global cache block にマージされ、動的セクションは global cache を使用しない(`cacheScope: null`)。boundary なしまたは global cache をスキップするパスでのみ org scope にフォールバック。
|
||||
|
||||
教学版のキャッシュは文字列の再組み立てを回避するのみ。CC の 3 層キャッシュ:
|
||||
|
||||
1. **lodash memoize**: `getSystemContext` と `getUserContext` がセッション中キャッシュ(`context.ts`)
|
||||
2. **セクション登録キャッシュ**: `STATE.systemPromptSectionCache` が動的セクションの結果をキャッシュ、`/clear` や `/compact` でクリア
|
||||
3. **API レベルキャッシュ**: `splitSysPromptPrefix()`(`api.ts`)が boundary を通じて異なる cache scope のブロックに分割
|
||||
|
||||
### getUserContext vs getSystemContext
|
||||
|
||||
| | getSystemContext | getUserContext |
|
||||
|---|---|---|
|
||||
| 内容 | gitStatus、cacheBreaker | CLAUDE.md 内容、currentDate |
|
||||
| 注入方式 | system prompt 配列に追加 | `<system-reminder>` ユーザーメッセージとして先頭に配置 |
|
||||
| スキップ条件 | カスタム system prompt 時 | 常に実行 |
|
||||
|
||||
### モードによる prompt の変化
|
||||
|
||||
- **CLAUDE_CODE_SIMPLE**: prompt 全体が 2 行のみ
|
||||
- **Proactive/KAIROS**: コンパクト版 prompt が標準セクション全体を置換
|
||||
- **Coordinator**: コーディネータ専用 prompt がデフォルトを完全に置換
|
||||
- **Agent モード**: Agent 定義の prompt がデフォルトを置換または追加
|
||||
|
||||
### 総サイズ
|
||||
|
||||
標準インタラクティブモードの system prompt コアは約 20-30KB テキスト。CLAUDE_CODE_SIMPLE は約 150 文字。ユーザーコンテキスト(CLAUDE.md)とシステムコンテキスト(git status)がこれに加算。
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
254
s10_system_prompt/README.md
Normal file
254
s10_system_prompt/README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# s10: System Prompt — 运行时组装,不硬编码
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s08 → s09 → `s10` → [s11](../s11_error_recovery/) → s12 → ... → s20
|
||||
> *"prompt 是组装出来的, 不是写死的"* — 分段 + 按需拼接 + 缓存。
|
||||
>
|
||||
> **Harness 层**: 提示 — 运行时组装, 不硬编码。
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
从 s01 到 s09,system prompt 都是一行硬编码:
|
||||
|
||||
```python
|
||||
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks."
|
||||
```
|
||||
|
||||
s01 够用,只有 bash、read、write 三个工具。但到 s09,Agent 已经有记忆、有压缩、有技能加载。prompt 该提的能力越来越多:
|
||||
|
||||
```python
|
||||
SYSTEM = (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
"Use tools to solve tasks. Act, don't explain. "
|
||||
"Before starting any multi-step task, use todo_write. "
|
||||
"Skills are available via list_skills and load_skill. "
|
||||
"Relevant memories are injected below when available. "
|
||||
# ... 加一个能力就多一段
|
||||
)
|
||||
```
|
||||
|
||||
三个问题:
|
||||
|
||||
1. **换项目要重写整个 prompt**,不知道哪些该改、哪些该留
|
||||
2. **修改一处可能影响全局**,加一段工具描述可能跟前面的指令冲突
|
||||
3. **每次请求都带全部内容**,即使当前对话用不到某些段落也浪费 token
|
||||
|
||||
System prompt 应该是运行时根据当前状态组装的配置:哪些工具启用、哪些上下文可见、哪些记忆相关、哪些内容必须保持稳定以命中 prompt cache。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||

|
||||
|
||||
s10 聚焦 prompt 组装机制。以 s08-s09 的能力为背景,但不重复实现压缩和记忆系统。核心变动:把硬编码的 `SYSTEM` 拆成独立段落(section),运行时根据真实状态按需拼接,缓存结果避免重复组装。
|
||||
|
||||
四个 section,两种加载策略:
|
||||
|
||||
| Section | 加载策略 | 内容 | 判断依据 |
|
||||
|---------|---------|------|---------|
|
||||
| identity | 始终 | 你是谁、怎么做事 | 始终存在 |
|
||||
| tools | 始终 | 可用工具列表 | `enabled_tools` |
|
||||
| workspace | 始终 | 工作目录 | 始终存在 |
|
||||
| memory | 按需 | 相关记忆内容 | `.memory/MEMORY.md` 是否存在 |
|
||||
|
||||
关键设计:section 是否加载取决于真实状态(工具是否存在、文件是否存在),不是消息里的关键词。
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
### PROMPT_SECTIONS: 分段定义
|
||||
|
||||
把一大段字符串拆成字典,每个 key 是一个主题:
|
||||
|
||||
```python
|
||||
PROMPT_SECTIONS = {
|
||||
"identity": "You are a coding agent. Act, don't explain.",
|
||||
"tools": "Available tools: bash, read_file, write_file.",
|
||||
"workspace": f"Working directory: {WORKDIR}",
|
||||
"memory": "Relevant memories are injected below when available.",
|
||||
}
|
||||
```
|
||||
|
||||
每个 section 独立维护。修改 `tools` 不影响 `identity`,新增 `memory` 不动 `workspace`。
|
||||
|
||||
### assemble_system_prompt: 按需拼接
|
||||
|
||||
不是所有 section 每次都需要。当前没有记忆文件,加载 memory section 只是浪费 token。根据 context 的真实状态决定加载哪些:
|
||||
|
||||
```python
|
||||
def assemble_system_prompt(context: dict) -> str:
|
||||
sections = []
|
||||
|
||||
# 始终加载
|
||||
sections.append(PROMPT_SECTIONS["identity"])
|
||||
sections.append(PROMPT_SECTIONS["tools"])
|
||||
sections.append(PROMPT_SECTIONS["workspace"])
|
||||
|
||||
# 按需加载 — 基于真实状态,不是关键词
|
||||
memories = context.get("memories", "")
|
||||
if memories:
|
||||
sections.append(f"Relevant memories:\n{memories}")
|
||||
|
||||
return "\n\n".join(sections)
|
||||
```
|
||||
|
||||
"始终加载"的是每轮都需要的:身份、工具、工作目录。"按需加载"的只在特定条件下才有用。
|
||||
|
||||
为什么不全加载?token 有成本(system prompt 每轮计费),信息越少 LLM 越专注(无关指令是噪音)。
|
||||
|
||||
### get_system_prompt: 缓存避免重复拼接
|
||||
|
||||
上下文没变时(同一轮对话的多次 LLM 调用,context 相同),重新拼接是浪费。用确定性序列化检测变化,命中缓存直接返回:
|
||||
|
||||
```python
|
||||
def get_system_prompt(context: dict) -> str:
|
||||
global _last_context_key, _last_prompt
|
||||
key = json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
|
||||
if key == _last_context_key and _last_prompt:
|
||||
return _last_prompt
|
||||
_last_context_key = key
|
||||
_last_prompt = assemble_system_prompt(context)
|
||||
return _last_prompt
|
||||
```
|
||||
|
||||
用 `json.dumps` 而不是 `hash()`:Python 内置 `hash()` 有进程随机化,不适合做稳定 cache key,而且遇到 list/dict 会报 `unhashable type`。
|
||||
|
||||
注意:这里的缓存只是"避免重复拼接字符串",和 CC 的 API prompt cache 不是一回事。CC 的 prompt cache 通过 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 分隔静态和动态部分,静态部分命中 global cache,不因动态内容变化而失效。
|
||||
|
||||
### context: 真实状态,不是关键词猜测
|
||||
|
||||
context 反映当前运行态的真实状态:
|
||||
|
||||
```python
|
||||
def update_context(context: dict, messages: list) -> dict:
|
||||
memories = ""
|
||||
if MEMORY_INDEX.exists():
|
||||
content = MEMORY_INDEX.read_text().strip()
|
||||
if content:
|
||||
memories = content
|
||||
return {
|
||||
"enabled_tools": list(TOOL_HANDLERS.keys()),
|
||||
"workspace": str(WORKDIR),
|
||||
"memories": memories,
|
||||
}
|
||||
```
|
||||
|
||||
`enabled_tools` 列出实际注册的工具。`memories` 检查 `.memory/MEMORY.md` 是否存在。section 加载基于这些真实状态,不在消息里搜关键词。
|
||||
|
||||
### 合起来跑
|
||||
|
||||
```python
|
||||
def agent_loop(messages: list, context: dict):
|
||||
system = get_system_prompt(context)
|
||||
while True:
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=system, messages=messages,
|
||||
tools=TOOLS, max_tokens=8000)
|
||||
# ... 工具执行 ...
|
||||
context = update_context(context, messages)
|
||||
system = get_system_prompt(context)
|
||||
```
|
||||
|
||||
每轮循环开头拿一次 system prompt。context 变了就重新组装,没变就返回缓存。
|
||||
|
||||
---
|
||||
|
||||
## 相对 s09 的变更
|
||||
|
||||
| 组件 | 之前 (s09) | 之后 (s10) |
|
||||
|------|-----------|-----------|
|
||||
| prompt | 硬编码 SYSTEM 字符串 | PROMPT_SECTIONS + assemble_system_prompt |
|
||||
| 缓存 | 无 | get_system_prompt(json.dumps 检测 + 缓存) |
|
||||
| 新函数 | — | assemble_system_prompt, get_system_prompt, update_context |
|
||||
| 工具 | bash, read_file, write_file (3) | bash, read_file, write_file (3) — 不变 |
|
||||
| 循环 | 用固定 SYSTEM | 用 get_system_prompt(context) |
|
||||
|
||||
---
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s10_system_prompt/code.py
|
||||
```
|
||||
|
||||
观察重点:
|
||||
|
||||
1. 输出中能看到哪些 section 被加载了(`[assembled] sections: ...` 标签)
|
||||
2. 连续对话时,缓存命中显示 `[cache hit]`
|
||||
3. 创建 `.memory/MEMORY.md` 文件后,下一轮 memory section 自动加载
|
||||
|
||||
试试这些 prompt:
|
||||
|
||||
1. `Read the file README.md`(观察始终加载的三个 section)
|
||||
2. `Create a file called .memory/MEMORY.md with content "- [test](test.md) — test memory"`(写入记忆索引)
|
||||
3. `Read the file code.py`(观察 memory section 是否出现)
|
||||
|
||||
---
|
||||
|
||||
## 接下来
|
||||
|
||||
System prompt 可以运行时组装了,但 Agent 碰到错误还是会崩。网络抖动、API 限流、输出被截断、上下文超限,这些不是 bug,是常态。
|
||||
|
||||
s11 Error Recovery → 四条恢复路径。升级 token、压缩上下文、指数退避、切换模型。
|
||||
|
||||
<details>
|
||||
<summary>深入 CC 源码</summary>
|
||||
|
||||
> 以下基于 CC 源码 `constants/prompts.ts`(914 行)、`constants/systemPromptSections.ts`(68 行)、`context.ts`(189 行)、`utils/api.ts`(718 行)、`utils/systemPrompt.ts`(123 行)、`bootstrap/state.ts` 的分析。
|
||||
|
||||
### CC 的 system prompt 有多少 section?
|
||||
|
||||
数量不固定,受 feature flag、output style、KAIROS/Proactive 模式、用户类型、token 预算等影响。大致分两类:
|
||||
|
||||
**静态 section**(始终加载):identity、system、doing_tasks、actions、using_tools、tone_style、output_efficiency 等。
|
||||
|
||||
**动态 section**(按状态加载):session_guidance、memory、ant_model_override、env_info_simple、language、output_style、mcp_instructions、scratchpad、frc、summarize_tool_results、numeric_length_anchors、token_budget、brief 等。
|
||||
|
||||
`mcp_instructions` 是唯一的易失性 section(通过 `DANGEROUS_uncachedSystemPromptSection()` 创建),因为 MCP server 可以在轮次间连接和断开。
|
||||
|
||||
### 组装函数
|
||||
|
||||
```typescript
|
||||
getSystemPrompt(tools, model, additionalWorkingDirs?, mcpClients?): Promise<string[]>
|
||||
```
|
||||
|
||||
返回 `string[]`(每个元素是一个 section),由 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 分隔静态和动态部分。
|
||||
|
||||
### cache scope
|
||||
|
||||
启用 global cache boundary 时,静态 section 合并成一个 global cache block,动态 section 不使用 global cache(`cacheScope: null`)。没有 boundary 或跳过 global cache 的路径才会走 org scope。
|
||||
|
||||
教学版的缓存只避免重复拼接字符串。CC 的三层缓存:
|
||||
|
||||
1. **lodash memoize**:`getSystemContext` 和 `getUserContext` 在会话中缓存(`context.ts`)
|
||||
2. **section 注册缓存**:`STATE.systemPromptSectionCache` 缓存动态 section 结果,`/clear` 或 `/compact` 时清除
|
||||
3. **API 级缓存**:`splitSysPromptPrefix()`(`api.ts`)把 prompt 按 boundary 分成不同 cache scope 的块
|
||||
|
||||
### getUserContext vs getSystemContext
|
||||
|
||||
| | getSystemContext | getUserContext |
|
||||
|---|---|---|
|
||||
| 内容 | gitStatus、cacheBreaker | CLAUDE.md 内容、currentDate |
|
||||
| 注入方式 | 追加到 system prompt 数组 | 前置为 `<system-reminder>` 用户消息 |
|
||||
| 何时跳过 | 自定义 system prompt 时 | 始终运行 |
|
||||
|
||||
### 模式如何改变 prompt
|
||||
|
||||
- **CLAUDE_CODE_SIMPLE**:整个 prompt 只有 2 行
|
||||
- **Proactive/KAIROS**:用紧凑版 prompt 替换所有标准 section
|
||||
- **Coordinator**:用协调器专用 prompt 完全替换
|
||||
- **Agent 模式**:Agent 定义的 prompt 替换或追加到默认 prompt
|
||||
|
||||
### 总大小
|
||||
|
||||
标准交互模式下 system prompt 核心约 20-30KB 文本。CLAUDE_CODE_SIMPLE 约 150 字符。用户上下文(CLAUDE.md)和系统上下文(git status)在此基础上累加。
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
218
s10_system_prompt/code.py
Normal file
218
s10_system_prompt/code.py
Normal file
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
s10: System Prompt — Runtime prompt assembly with caching.
|
||||
|
||||
Run: python s10_system_prompt/code.py
|
||||
Need: pip install anthropic python-dotenv + .env with ANTHROPIC_API_KEY
|
||||
|
||||
Changes from s09:
|
||||
- PROMPT_SECTIONS: topic-keyed dict of prompt fragments
|
||||
- assemble_system_prompt(context): select + join sections by real state
|
||||
- get_system_prompt(context): deterministic cache via json.dumps
|
||||
- agent_loop uses get_system_prompt(context) instead of hardcoded SYSTEM
|
||||
|
||||
Memory section loads when .memory/MEMORY.md exists (real state, not keywords).
|
||||
"""
|
||||
|
||||
import os, subprocess, json
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import readline
|
||||
readline.parse_and_bind('set bind-tty-special-chars off')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from anthropic import Anthropic
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(override=True)
|
||||
if os.getenv("ANTHROPIC_BASE_URL"):
|
||||
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
|
||||
|
||||
WORKDIR = Path.cwd()
|
||||
MEMORY_DIR = WORKDIR / ".memory"
|
||||
MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"
|
||||
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
|
||||
MODEL = os.environ["MODEL_ID"]
|
||||
|
||||
|
||||
# ── Prompt Sections ──
|
||||
|
||||
PROMPT_SECTIONS = {
|
||||
"identity": "You are a coding agent. Act, don't explain.",
|
||||
"tools": "Available tools: bash, read_file, write_file.",
|
||||
"workspace": f"Working directory: {WORKDIR}",
|
||||
"memory": "Relevant memories are injected below when available.",
|
||||
}
|
||||
|
||||
|
||||
def assemble_system_prompt(context: dict) -> str:
|
||||
"""Select and join prompt sections based on current context."""
|
||||
sections = []
|
||||
|
||||
# Always loaded — identity, tools, workspace
|
||||
sections.append(PROMPT_SECTIONS["identity"])
|
||||
sections.append(PROMPT_SECTIONS["tools"])
|
||||
sections.append(PROMPT_SECTIONS["workspace"])
|
||||
|
||||
# Conditional — memory loaded when MEMORY.md exists and has content
|
||||
memories = context.get("memories", "")
|
||||
if memories:
|
||||
sections.append(f"Relevant memories:\n{memories}")
|
||||
|
||||
return "\n\n".join(sections)
|
||||
|
||||
|
||||
_last_context_key = None
|
||||
_last_prompt = None
|
||||
|
||||
|
||||
def get_system_prompt(context: dict) -> str:
|
||||
"""Cache wrapper — reassemble only when context changes.
|
||||
|
||||
Uses json.dumps for deterministic serialization, not Python's hash()
|
||||
which has process randomization and fails on nested dicts/lists.
|
||||
This cache only avoids redundant string assembly within a process.
|
||||
Real Claude Code additionally protects API-level prompt cache via
|
||||
stable section ordering and SYSTEM_PROMPT_DYNAMIC_BOUNDARY.
|
||||
"""
|
||||
global _last_context_key, _last_prompt
|
||||
key = json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
|
||||
if key == _last_context_key and _last_prompt:
|
||||
print(" \033[90m[cache hit] system prompt unchanged\033[0m")
|
||||
return _last_prompt
|
||||
_last_context_key = key
|
||||
_last_prompt = assemble_system_prompt(context)
|
||||
|
||||
loaded = ["identity", "tools", "workspace"]
|
||||
if context.get("memories"):
|
||||
loaded.append("memory")
|
||||
print(f" \033[32m[assembled] sections: {', '.join(loaded)}\033[0m")
|
||||
return _last_prompt
|
||||
|
||||
|
||||
# ── Tools ──
|
||||
|
||||
def safe_path(p: str) -> Path:
|
||||
path = (WORKDIR / p).resolve()
|
||||
if not path.is_relative_to(WORKDIR):
|
||||
raise ValueError(f"Path escapes workspace: {p}")
|
||||
return path
|
||||
|
||||
|
||||
def run_bash(command: str) -> str:
|
||||
try:
|
||||
r = subprocess.run(command, shell=True, cwd=WORKDIR,
|
||||
capture_output=True, text=True, timeout=120)
|
||||
out = (r.stdout + r.stderr).strip()
|
||||
return out[:50000] if out else "(no output)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return "Error: Timeout (120s)"
|
||||
|
||||
|
||||
def run_read(path: str, limit: int | None = None) -> str:
|
||||
try:
|
||||
lines = safe_path(path).read_text().splitlines()
|
||||
if limit and limit < len(lines):
|
||||
lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
|
||||
return "\n".join(lines)
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
def run_write(path: str, content: str) -> str:
|
||||
try:
|
||||
file_path = safe_path(path)
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.write_text(content)
|
||||
return f"Wrote {len(content)} bytes to {path}"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
TOOLS = [
|
||||
{"name": "bash", "description": "Run a shell command.",
|
||||
"input_schema": {"type": "object",
|
||||
"properties": {"command": {"type": "string"}},
|
||||
"required": ["command"]}},
|
||||
{"name": "read_file", "description": "Read file contents.",
|
||||
"input_schema": {"type": "object",
|
||||
"properties": {"path": {"type": "string"},
|
||||
"limit": {"type": "integer"}},
|
||||
"required": ["path"]}},
|
||||
{"name": "write_file", "description": "Write content to a file.",
|
||||
"input_schema": {"type": "object",
|
||||
"properties": {"path": {"type": "string"},
|
||||
"content": {"type": "string"}},
|
||||
"required": ["path", "content"]}},
|
||||
]
|
||||
|
||||
TOOL_HANDLERS = {"bash": run_bash, "read_file": run_read, "write_file": run_write}
|
||||
|
||||
|
||||
# ── Context ──
|
||||
|
||||
def update_context(context: dict, messages: list) -> dict:
|
||||
"""Derive context from real state: which tools exist, whether memory files exist."""
|
||||
memories = ""
|
||||
if MEMORY_INDEX.exists():
|
||||
content = MEMORY_INDEX.read_text().strip()
|
||||
if content:
|
||||
memories = content
|
||||
return {
|
||||
"enabled_tools": list(TOOL_HANDLERS.keys()),
|
||||
"workspace": str(WORKDIR),
|
||||
"memories": memories,
|
||||
}
|
||||
|
||||
|
||||
# ── Agent Loop ──
|
||||
|
||||
def agent_loop(messages: list, context: dict):
|
||||
"""Main loop — uses assembled system prompt instead of hardcoded SYSTEM."""
|
||||
system = get_system_prompt(context)
|
||||
while True:
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=system, messages=messages,
|
||||
tools=TOOLS, max_tokens=8000)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
print(f"\033[36m> {block.name}\033[0m")
|
||||
handler = TOOL_HANDLERS.get(block.name)
|
||||
output = handler(**block.input) if handler else f"Unknown: {block.name}"
|
||||
print(str(output)[:200])
|
||||
results.append({"type": "tool_result",
|
||||
"tool_use_id": block.id, "content": output})
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
# Re-evaluate context and prompt after each tool round
|
||||
context = update_context(context, messages)
|
||||
system = get_system_prompt(context)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("s10: system prompt — runtime assembly")
|
||||
print("Enter a question, press Enter to send. Type q to quit.\n")
|
||||
history = []
|
||||
context = update_context({}, [])
|
||||
while True:
|
||||
try:
|
||||
query = input("\033[36ms10 >> \033[0m")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
break
|
||||
history.append({"role": "user", "content": query})
|
||||
agent_loop(history, context)
|
||||
context = update_context(context, history)
|
||||
for block in history[-1]["content"]:
|
||||
if getattr(block, "type", None) == "text":
|
||||
print(block.text)
|
||||
print()
|
||||
107
s10_system_prompt/images/system-prompt-overview.en.svg
Normal file
107
s10_system_prompt/images/system-prompt-overview.en.svg
Normal file
@@ -0,0 +1,107 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 420" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#059669"/>
|
||||
</linearGradient>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#059669"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="760" height="420" fill="#fafbfc" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
|
||||
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">System Prompt — PROMPT_SECTIONS + On-Demand Assembly + Cache</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s09 Preserved</text>
|
||||
<rect x="160" y="56" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
|
||||
<text x="178" y="66" fill="#059669" font-size="10" font-weight="600">s10 New</text>
|
||||
|
||||
<!-- ===== Prompt Assembly (green, s10) ===== -->
|
||||
|
||||
<!-- PROMPT_SECTIONS -->
|
||||
<rect x="40" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="125" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">PROMPT_SECTIONS</text>
|
||||
<text x="55" y="116" fill="#065f46" font-size="9">✓ identity (always)</text>
|
||||
<text x="55" y="130" fill="#065f46" font-size="9">✓ tools (always)</text>
|
||||
<text x="55" y="144" fill="#065f46" font-size="9">✓ workspace (always)</text>
|
||||
<text x="55" y="158" fill="#6b7280" font-size="9">○ memory</text>
|
||||
|
||||
<!-- arrow → assemble -->
|
||||
<line x1="210" y1="126" x2="235" y2="126" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- assemble_system_prompt -->
|
||||
<rect x="238" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="323" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">assemble_system_prompt</text>
|
||||
<text x="253" y="118" fill="#065f46" font-size="9">Input: context dict</text>
|
||||
<text x="253" y="132" fill="#065f46" font-size="9">Always: identity + tools + workspace</text>
|
||||
<text x="253" y="146" fill="#065f46" font-size="9">On-demand: memory</text>
|
||||
<text x="253" y="160" fill="#6b7280" font-size="9">Output: "\n\n".join(selected)</text>
|
||||
|
||||
<!-- arrow → cache -->
|
||||
<line x1="408" y1="126" x2="433" y2="126" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- get_system_prompt -->
|
||||
<rect x="436" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="521" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">get_system_prompt</text>
|
||||
<text x="451" y="118" fill="#065f46" font-size="9">json.dumps(context)</text>
|
||||
<text x="451" y="132" fill="#065f46" font-size="9">Hit → return cached</text>
|
||||
<text x="451" y="146" fill="#065f46" font-size="9">Miss → assemble + store</text>
|
||||
<text x="451" y="160" fill="#6b7280" font-size="9">(s10 new)</text>
|
||||
|
||||
<!-- Arrow: cache → LLM -->
|
||||
<path d="M 521 170 L 521 195 L 410 195 L 410 212" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
<text x="462" y="189" fill="#059669" font-size="9">system=get_system_prompt(context)</text>
|
||||
|
||||
<!-- ===== s09 Agent Loop (blue) ===== -->
|
||||
|
||||
<!-- messages[] -->
|
||||
<rect x="30" y="214" width="100" height="46" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="80" y="241" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- arrow → compression+loading -->
|
||||
<line x1="130" y1="237" x2="155" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- compression + loading -->
|
||||
<rect x="158" y="206" width="170" height="62" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="243" y="228" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">Compression + Loading</text>
|
||||
<text x="243" y="242" fill="#64748b" font-size="9" text-anchor="middle">snip → micro → budget → auto</text>
|
||||
<text x="243" y="256" fill="#94a3b8" font-size="8" text-anchor="middle">→ load memory (s09)</text>
|
||||
|
||||
<!-- arrow → LLM -->
|
||||
<line x1="328" y1="237" x2="358" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="360" y="214" width="100" height="46" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="410" y="231" fill="#1e3a5f" font-size="12" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="410" y="246" fill="#64748b" font-size="8" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="410" y="258" fill="#059669" font-size="8" text-anchor="middle">system assembled</text>
|
||||
|
||||
<!-- arrow → TOOLS -->
|
||||
<line x1="460" y1="237" x2="490" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="466" y="229" fill="#64748b" font-size="8">yes</text>
|
||||
|
||||
<!-- TOOL_HANDLERS -->
|
||||
<rect x="493" y="206" width="130" height="62" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="558" y="228" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
<text x="558" y="242" fill="#64748b" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
<text x="558" y="256" fill="#94a3b8" font-size="8" text-anchor="middle">(s09 preserved)</text>
|
||||
|
||||
<!-- ===== Loop back ===== -->
|
||||
<path d="M 623 237 L 660 237 L 660 312 L 80 312 L 80 260" fill="none" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="370" y="328" fill="#64748b" font-size="10" text-anchor="middle">Tool results → messages[] → compress → load memory → assemble prompt → LLM</text>
|
||||
|
||||
<!-- ===== Bottom notes ===== -->
|
||||
<rect x="40" y="350" width="680" height="56" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<rect x="60" y="362" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="80" y="372" fill="#475569" font-size="10">s09 Preserved: loop, compression pipeline, memory loading, tool execution</text>
|
||||
<rect x="60" y="382" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
|
||||
<text x="80" y="392" fill="#475569" font-size="10">s10 New: PROMPT_SECTIONS (4 sections) + assemble_system_prompt + get_system_prompt (cache)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
107
s10_system_prompt/images/system-prompt-overview.ja.svg
Normal file
107
s10_system_prompt/images/system-prompt-overview.ja.svg
Normal file
@@ -0,0 +1,107 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 420" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#059669"/>
|
||||
</linearGradient>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#059669"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="760" height="420" fill="#fafbfc" rx="8"/>
|
||||
|
||||
<!-- タイトル -->
|
||||
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
|
||||
<text x="380" y="28" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">System Prompt — PROMPT_SECTIONS + オンデマンド組み立て + キャッシュ</text>
|
||||
|
||||
<!-- 凡例 -->
|
||||
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s09 保持</text>
|
||||
<rect x="140" y="56" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
|
||||
<text x="158" y="66" fill="#059669" font-size="10" font-weight="600">s10 新規</text>
|
||||
|
||||
<!-- ===== プロンプトアセンブリ(緑、s10) ===== -->
|
||||
|
||||
<!-- PROMPT_SECTIONS -->
|
||||
<rect x="40" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="125" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">PROMPT_SECTIONS</text>
|
||||
<text x="55" y="116" fill="#065f46" font-size="9">✓ identity (常時)</text>
|
||||
<text x="55" y="130" fill="#065f46" font-size="9">✓ tools (常時)</text>
|
||||
<text x="55" y="144" fill="#065f46" font-size="9">✓ workspace (常時)</text>
|
||||
<text x="55" y="158" fill="#6b7280" font-size="9">○ memory</text>
|
||||
|
||||
<!-- 矢印 → assemble -->
|
||||
<line x1="210" y1="126" x2="235" y2="126" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- assemble_system_prompt -->
|
||||
<rect x="238" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="323" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">assemble_system_prompt</text>
|
||||
<text x="253" y="118" fill="#065f46" font-size="9">入力: context dict</text>
|
||||
<text x="253" y="132" fill="#065f46" font-size="9">常時: identity + tools + workspace</text>
|
||||
<text x="253" y="146" fill="#065f46" font-size="9">オンデマンド: memory</text>
|
||||
<text x="253" y="160" fill="#6b7280" font-size="9">出力: "\n\n".join(selected)</text>
|
||||
|
||||
<!-- 矢印 → cache -->
|
||||
<line x1="408" y1="126" x2="433" y2="126" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- get_system_prompt -->
|
||||
<rect x="436" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="521" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">get_system_prompt</text>
|
||||
<text x="451" y="118" fill="#065f46" font-size="9">json.dumps(context)</text>
|
||||
<text x="451" y="132" fill="#065f46" font-size="9">ヒット → キャッシュ返却</text>
|
||||
<text x="451" y="146" fill="#065f46" font-size="9">ミス → assemble + 保存</text>
|
||||
<text x="451" y="160" fill="#6b7280" font-size="9">(s10 新規)</text>
|
||||
|
||||
<!-- 矢印: cache → LLM -->
|
||||
<path d="M 521 170 L 521 195 L 410 195 L 410 212" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
<text x="462" y="189" fill="#059669" font-size="9">system=get_system_prompt(context)</text>
|
||||
|
||||
<!-- ===== s09 Agent Loop(青) ===== -->
|
||||
|
||||
<!-- messages[] -->
|
||||
<rect x="30" y="214" width="100" height="46" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="80" y="241" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- 矢印 → compression+loading -->
|
||||
<line x1="130" y1="237" x2="155" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- 圧縮 + ロード -->
|
||||
<rect x="158" y="206" width="170" height="62" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="243" y="228" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">圧縮 + ロード</text>
|
||||
<text x="243" y="242" fill="#64748b" font-size="9" text-anchor="middle">snip → micro → budget → auto</text>
|
||||
<text x="243" y="256" fill="#94a3b8" font-size="8" text-anchor="middle">→ 記憶ロード (s09)</text>
|
||||
|
||||
<!-- 矢印 → LLM -->
|
||||
<line x1="328" y1="237" x2="358" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="360" y="214" width="100" height="46" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="410" y="231" fill="#1e3a5f" font-size="12" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="410" y="246" fill="#64748b" font-size="8" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="410" y="258" fill="#059669" font-size="8" text-anchor="middle">system assembled</text>
|
||||
|
||||
<!-- 矢印 → TOOLS -->
|
||||
<line x1="460" y1="237" x2="490" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="466" y="229" fill="#64748b" font-size="8">あり</text>
|
||||
|
||||
<!-- TOOL_HANDLERS -->
|
||||
<rect x="493" y="206" width="130" height="62" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="558" y="228" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
<text x="558" y="242" fill="#64748b" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
<text x="558" y="256" fill="#94a3b8" font-size="8" text-anchor="middle">(s09 保持)</text>
|
||||
|
||||
<!-- ===== ループバック ===== -->
|
||||
<path d="M 623 237 L 660 237 L 660 312 L 80 312 L 80 260" fill="none" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="370" y="328" fill="#64748b" font-size="10" text-anchor="middle">ツール結果 → messages[] → 圧縮 → 記憶ロード → プロンプト組み立て → LLM</text>
|
||||
|
||||
<!-- ===== 下部ノート ===== -->
|
||||
<rect x="40" y="350" width="680" height="56" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<rect x="60" y="362" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="80" y="372" fill="#475569" font-size="10">s09 保持:ループ、圧縮パイプライン、記憶ロード、ツール実行</text>
|
||||
<rect x="60" y="382" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
|
||||
<text x="80" y="392" fill="#475569" font-size="10">s10 新規:PROMPT_SECTIONS(4 セクション)+ assemble_system_prompt + get_system_prompt(キャッシュ)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
107
s10_system_prompt/images/system-prompt-overview.svg
Normal file
107
s10_system_prompt/images/system-prompt-overview.svg
Normal file
@@ -0,0 +1,107 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 420" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#059669"/>
|
||||
</linearGradient>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#059669"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="760" height="420" fill="#fafbfc" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
|
||||
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">System Prompt — PROMPT_SECTIONS + 按需拼接 + 缓存</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s09 保留</text>
|
||||
<rect x="140" y="56" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
|
||||
<text x="158" y="66" fill="#059669" font-size="10" font-weight="600">s10 新增</text>
|
||||
|
||||
<!-- ===== Prompt Assembly (green, s10) ===== -->
|
||||
|
||||
<!-- PROMPT_SECTIONS -->
|
||||
<rect x="40" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="125" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">PROMPT_SECTIONS</text>
|
||||
<text x="55" y="116" fill="#065f46" font-size="9">✓ identity (始终)</text>
|
||||
<text x="55" y="130" fill="#065f46" font-size="9">✓ tools (始终)</text>
|
||||
<text x="55" y="144" fill="#065f46" font-size="9">✓ workspace (始终)</text>
|
||||
<text x="55" y="158" fill="#6b7280" font-size="9">○ memory</text>
|
||||
|
||||
<!-- arrow → assemble -->
|
||||
<line x1="210" y1="126" x2="235" y2="126" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- assemble_system_prompt -->
|
||||
<rect x="238" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="323" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">assemble_system_prompt</text>
|
||||
<text x="253" y="118" fill="#065f46" font-size="9">输入: context dict</text>
|
||||
<text x="253" y="132" fill="#065f46" font-size="9">始终: identity + tools + workspace</text>
|
||||
<text x="253" y="146" fill="#065f46" font-size="9">按需: memory</text>
|
||||
<text x="253" y="160" fill="#6b7280" font-size="9">输出: "\n\n".join(selected)</text>
|
||||
|
||||
<!-- arrow → cache -->
|
||||
<line x1="408" y1="126" x2="433" y2="126" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
<!-- get_system_prompt -->
|
||||
<rect x="436" y="82" width="170" height="88" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
|
||||
<text x="521" y="100" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">get_system_prompt</text>
|
||||
<text x="451" y="118" fill="#065f46" font-size="9">json.dumps(context)</text>
|
||||
<text x="451" y="132" fill="#065f46" font-size="9">命中 → 返回缓存</text>
|
||||
<text x="451" y="146" fill="#065f46" font-size="9">未命中 → assemble + 存</text>
|
||||
<text x="451" y="160" fill="#6b7280" font-size="9">(s10 新增)</text>
|
||||
|
||||
<!-- Arrow: cache → LLM -->
|
||||
<path d="M 521 170 L 521 195 L 410 195 L 410 212" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
<text x="462" y="189" fill="#059669" font-size="9">system=get_system_prompt(context)</text>
|
||||
|
||||
<!-- ===== s09 Agent Loop (blue) ===== -->
|
||||
|
||||
<!-- messages[] -->
|
||||
<rect x="30" y="214" width="100" height="46" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="80" y="241" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- arrow → compression+loading -->
|
||||
<line x1="130" y1="237" x2="155" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- compression + loading -->
|
||||
<rect x="158" y="206" width="170" height="62" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="243" y="228" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">压缩 + Loading</text>
|
||||
<text x="243" y="242" fill="#64748b" font-size="9" text-anchor="middle">snip → micro → budget → auto</text>
|
||||
<text x="243" y="256" fill="#94a3b8" font-size="8" text-anchor="middle">→ 加载记忆 (s09)</text>
|
||||
|
||||
<!-- arrow → LLM -->
|
||||
<line x1="328" y1="237" x2="358" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="360" y="214" width="100" height="46" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="410" y="231" fill="#1e3a5f" font-size="12" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="410" y="246" fill="#64748b" font-size="8" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="410" y="258" fill="#059669" font-size="8" text-anchor="middle">system assembled</text>
|
||||
|
||||
<!-- arrow → TOOLS -->
|
||||
<line x1="460" y1="237" x2="490" y2="237" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="466" y="229" fill="#64748b" font-size="8">是</text>
|
||||
|
||||
<!-- TOOL_HANDLERS -->
|
||||
<rect x="493" y="206" width="130" height="62" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="558" y="228" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
<text x="558" y="242" fill="#64748b" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
<text x="558" y="256" fill="#94a3b8" font-size="8" text-anchor="middle">(s09 保留)</text>
|
||||
|
||||
<!-- ===== Loop back ===== -->
|
||||
<path d="M 623 237 L 660 237 L 660 312 L 80 312 L 80 260" fill="none" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="370" y="328" fill="#64748b" font-size="10" text-anchor="middle">工具结果 → messages[] → 压缩 → 加载记忆 → 组装 prompt → LLM</text>
|
||||
|
||||
<!-- ===== Bottom notes ===== -->
|
||||
<rect x="40" y="350" width="680" height="56" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<rect x="60" y="362" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="80" y="372" fill="#475569" font-size="10">s09 保留:循环、压缩管线、记忆加载、工具执行</text>
|
||||
<rect x="60" y="382" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
|
||||
<text x="80" y="392" fill="#475569" font-size="10">s10 新增:PROMPT_SECTIONS(4 段)+ assemble_system_prompt + get_system_prompt(缓存)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
Reference in New Issue
Block a user