Skip to content

Commit d578b0c

Browse files
committed
refactor: replace score-based decay with TTL for notes and references and update context version hashing
1 parent 4cc04aa commit d578b0c

11 files changed

Lines changed: 310 additions & 201 deletions

File tree

README.ja.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ Vision/Sketch/Milestones/Tasks/Notes で「実行」を支える形に最適化
8888
`get_context()` が diagnostics/top issues を返し、次の手入れが明確。
8989

9090
**⏳ ライフサイクル内蔵**
91-
notes/refs は score により減衰・自動アーカイブされ、鮮度を維持。
91+
notes/refs は ttl により減衰・自動アーカイブされ、鮮度を維持。
9292

9393
**👥 読める Presence**
94-
Presence は単一行・重複排除・長さ制限で、表示が破綻しにくい。
94+
Presence は単一行・重複排除で、表示が破綻しにくい。
9595

9696
</td>
9797
</tr>
@@ -195,7 +195,7 @@ create_task(
195195
commit_updates(ops=[
196196
{"op":"presence.set","agent_id":"peer-a","status":"Auth: implementing session validation; checking edge cases"},
197197
{"op":"task.step","task_id":"T001","step_id":"S2","step_status":"done"},
198-
{"op":"note.add","content":"Edge case: empty header triggers fallback path","score":50}
198+
{"op":"note.add","content":"Edge case: empty header triggers fallback path","ttl":50}
199199
])
200200
```
201201

@@ -214,8 +214,8 @@ commit_updates(ops=[
214214
| | `clear_status()` | 状況をクリア(古い status を残さない)。 |
215215
| **Milestones** | `create_milestone()` / `update_milestone()` / `complete_milestone()` / `remove_milestone()` | フェーズ管理。 |
216216
| **Tasks** | `list_tasks()` / `create_task()` / `update_task()` / `delete_task()` | 成果物と steps を管理。 |
217-
| **Notes / Refs** | `add_note()` / `update_note()` / `remove_note()` | 知見を残す(score ライフサイクル)。 |
218-
| | `add_reference()` / `update_reference()` / `remove_reference()` | 参照(ファイル/URL)を残す(score ライフサイクル)。 |
217+
| **Notes / Refs** | `add_note()` / `update_note()` / `remove_note()` | 知見を残す(ttl ライフサイクル)。 |
218+
| | `add_reference()` / `update_reference()` / `remove_reference()` | 参照(ファイル/URL)を残す(ttl ライフサイクル)。 |
219219

220220
---
221221

@@ -230,13 +230,15 @@ if get_context()["version"] != v:
230230
ctx = get_context()
231231
```
232232

233+
注:`version` はセマンティックです。notes/refs の `ttl` 減衰は意図的に無視し、頻繁な read で version が揺れないようにします。
234+
233235
---
234236

235237
## Diagnostics & Lifecycle(衛生と鮮度)
236238

237239
- **Diagnostics**`get_context()``diagnostics``debt_score``top_issues` など)を返し、手入れポイントが分かる。
238-
- **Score ライフサイクル**:notes/refs は `get_context()` 呼び出しで -1 され、古いものは自動アーカイブ。
239-
- **Presence 正規化**:agent_id は正規化 + 重複排除。status は単一行に整形し、読みやすい長さに制限
240+
- **TTL ライフサイクル**:notes/refs は `get_context()` 呼び出しで -1 され、古いものは自動アーカイブ。
241+
- **Presence 正規化**:agent_id は正規化 + 重複排除。status は単一行に整形し、読みやすく保つ
240242

241243
---
242244

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ your-project/
6262

6363
- **Resume instantly**: agents always start from the same structured context.
6464
- **Coordinate cleanly**: presence shows who’s doing what; tasks show what’s actually done.
65-
- **Stay sane**: diagnostics highlight context debt; score-based lifecycle prevents bloat.
65+
- **Stay sane**: diagnostics highlight context debt; ttl-based lifecycle prevents bloat.
6666

6767
---
6868

@@ -88,10 +88,10 @@ Designed around how agents actually work: vision, blueprint, milestones, tasks,
8888
`get_context()` emits diagnostics + top issues so agents know what to fix.
8989

9090
**⏳ Lifecycle Built-in**
91-
Notes/refs decay by score and auto-archive, keeping context fresh.
91+
Notes/refs decay by ttl and auto-archive, keeping context fresh.
9292

9393
**👥 Presence That Stays Readable**
94-
Presence is normalized (single-line, de-duped, length-capped) by design.
94+
Presence is normalized (single-line, de-duped) by design.
9595

9696
</td>
9797
</tr>
@@ -195,7 +195,7 @@ create_task(
195195
commit_updates(ops=[
196196
{"op":"presence.set","agent_id":"peer-a","status":"Auth: implementing session validation; checking edge cases"},
197197
{"op":"task.step","task_id":"T001","step_id":"S2","step_status":"done"},
198-
{"op":"note.add","content":"Edge case: empty header triggers fallback path","score":50}
198+
{"op":"note.add","content":"Edge case: empty header triggers fallback path","ttl":50}
199199
])
200200
```
201201

@@ -214,8 +214,8 @@ commit_updates(ops=[
214214
| | `clear_status()` | Clear your status (remove stale/finished status). |
215215
| **Milestones** | `create_milestone()` / `update_milestone()` / `complete_milestone()` / `remove_milestone()` | Manage coarse phases. |
216216
| **Tasks** | `list_tasks()` / `create_task()` / `update_task()` / `delete_task()` | Track deliverables with steps. |
217-
| **Notes / Refs** | `add_note()` / `update_note()` / `remove_note()` | Preserve lessons/decisions with score lifecycle. |
218-
| | `add_reference()` / `update_reference()` / `remove_reference()` | Bookmark key files/URLs with score lifecycle. |
217+
| **Notes / Refs** | `add_note()` / `update_note()` / `remove_note()` | Preserve lessons/decisions with ttl lifecycle. |
218+
| | `add_reference()` / `update_reference()` / `remove_reference()` | Bookmark key files/URLs with ttl lifecycle. |
219219

220220
---
221221

@@ -231,13 +231,15 @@ if get_context()["version"] != v:
231231
ctx = get_context()
232232
```
233233

234+
Note: `version` is semantic. It intentionally ignores notes/refs `ttl` decay so frequent reads don’t churn the hash.
235+
234236
---
235237

236238
## Diagnostics & Lifecycle (Context Hygiene)
237239

238240
- **Diagnostics**: `get_context()` returns `diagnostics` (including `debt_score` and `top_issues`) so agents can keep the context clean.
239-
- **Score-based lifecycle**: notes and references decay by 1 each `get_context()` call and auto-archive when stale, preventing “memory bloat”.
240-
- **Presence normalization**: agent IDs are canonicalized and de-duped; status is normalized to a single concise line and length-capped for readability.
241+
- **TTL-based lifecycle**: notes and references decay by 1 each `get_context()` call and auto-archive when stale, preventing “memory bloat”.
242+
- **Presence normalization**: agent IDs are canonicalized and de-duped; status is normalized to a single concise line for readability.
241243

242244
---
243245

README.zh-CN.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ your-project/
8888
`get_context()` 输出 diagnostics/top issues,明确“下一步该修哪里”。
8989

9090
**⏳ 内建生命周期**
91-
笔记/引用按 score 衰减并自动归档,避免“记忆膨胀”。
91+
笔记/引用按 ttl 衰减并自动归档,避免“记忆膨胀”。
9292

9393
**👥 Presence 可读**
94-
Presence 默认规范化:单行、去重、限长,避免 header 被长文本撑爆。
94+
Presence 默认规范化:单行、去重,避免 header 被长文本撑爆。
9595

9696
</td>
9797
</tr>
@@ -195,7 +195,7 @@ create_task(
195195
commit_updates(ops=[
196196
{"op":"presence.set","agent_id":"peer-a","status":"Auth: implementing session validation; checking edge cases"},
197197
{"op":"task.step","task_id":"T001","step_id":"S2","step_status":"done"},
198-
{"op":"note.add","content":"Edge case: empty header triggers fallback path","score":50}
198+
{"op":"note.add","content":"Edge case: empty header triggers fallback path","ttl":50}
199199
])
200200
```
201201

@@ -214,8 +214,8 @@ commit_updates(ops=[
214214
| | `clear_status()` | 清空状态(避免旧状态残留误导)。 |
215215
| **Milestones** | `create_milestone()` / `update_milestone()` / `complete_milestone()` / `remove_milestone()` | 管理阶段时间线。 |
216216
| **Tasks** | `list_tasks()` / `create_task()` / `update_task()` / `delete_task()` | 管理任务与 steps。 |
217-
| **Notes / Refs** | `add_note()` / `update_note()` / `remove_note()` | 知识沉淀(带 score 生命周期)。 |
218-
| | `add_reference()` / `update_reference()` / `remove_reference()` | 路径/链接书签(带 score 生命周期)。 |
217+
| **Notes / Refs** | `add_note()` / `update_note()` / `remove_note()` | 知识沉淀(带 ttl 生命周期)。 |
218+
| | `add_reference()` / `update_reference()` / `remove_reference()` | 路径/链接书签(带 ttl 生命周期)。 |
219219

220220
---
221221

@@ -230,13 +230,15 @@ if get_context()["version"] != v:
230230
ctx = get_context()
231231
```
232232

233+
注意:`version` 是“语义版”哈希,刻意忽略 notes/refs 的 `ttl` 衰减,避免频繁读取导致 version 抖动。
234+
233235
---
234236

235237
## Diagnostics & Lifecycle(上下文卫生)
236238

237239
- **Diagnostics**`get_context()` 返回 `diagnostics`(含 `debt_score``top_issues`),提示 agent “上下文哪里需要维护”。
238-
- **Score 生命周期**:notes/references 每次 `get_context()` 调用会 -1,过旧会自动归档,防止上下文无限膨胀。
239-
- **Presence 规范化**:agent_id 规范化 + 去重;status 单行化并限长,保证可读性。
240+
- **TTL 生命周期**:notes/references 每次 `get_context()` 调用会 -1,过旧会自动归档,防止上下文无限膨胀。
241+
- **Presence 规范化**:agent_id 规范化 + 去重;status 单行化,保证可读性。
240242

241243
---
242244

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "ccontext-mcp"
7-
version = "0.1.11"
7+
version = "0.1.12"
88
description = "MCP server for AI agents to manage project execution context"
99
readme = "README.md"
1010
license = "MIT"

src/ccontext_mcp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""ccontext-mcp: MCP server for AI agents to manage project execution context."""
22

3-
__version__ = "0.1.11"
3+
__version__ = "0.1.12"

src/ccontext_mcp/schema.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,35 @@ class Note(BaseModel):
5656
"""A note entry (lesson, discovery, warning, etc.)."""
5757
id: str = Field(..., description="Note ID (N001, N002...)")
5858
content: str = Field(..., description="Note content")
59-
score: int = Field(default=15, ge=-100, le=100, description="Score (10-100 initial, decays)")
59+
ttl: int = Field(
60+
default=30,
61+
ge=0,
62+
le=100,
63+
description="TTL turns (recommended: 10 short-term, 30 normal, 100 long-term; decays)",
64+
)
6065

6166
@property
6267
def expiring(self) -> bool:
63-
"""Check if the note is expiring (score <= 0)."""
64-
return self.score <= 0
68+
"""Check if the note is expiring soon (ttl <= 3)."""
69+
return self.ttl <= 3
6570

6671

6772
class Reference(BaseModel):
6873
"""A reference entry (useful file/URL)."""
6974
id: str = Field(..., description="Reference ID (R001, R002...)")
7075
url: str = Field(..., description="File path or URL")
7176
note: str = Field(..., description="Description of why this is useful")
72-
score: int = Field(default=15, ge=-100, le=100, description="Score (10-100 initial, decays)")
77+
ttl: int = Field(
78+
default=30,
79+
ge=0,
80+
le=100,
81+
description="TTL turns (recommended: 10 short-term, 30 normal, 100 long-term; decays)",
82+
)
7383

7484
@property
7585
def expiring(self) -> bool:
76-
"""Check if the reference is expiring (score <= 0)."""
77-
return self.score <= 0
86+
"""Check if the reference is expiring soon (ttl <= 3)."""
87+
return self.ttl <= 3
7888

7989

8090
# =============================================================================

src/ccontext_mcp/server.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -284,12 +284,12 @@ async def list_tools() -> list[Tool]:
284284
"type": "object",
285285
"properties": {
286286
"content": {"type": "string", "description": "Note content"},
287-
"score": {
287+
"ttl": {
288288
"type": "integer",
289289
"minimum": 10,
290290
"maximum": 100,
291-
"default": 15,
292-
"description": "Initial score (10-100, higher = persists longer)",
291+
"default": 30,
292+
"description": "Initial ttl (recommended: 10 short-term, 30 normal, 100 long-term)",
293293
},
294294
},
295295
"required": ["content"],
@@ -303,11 +303,11 @@ async def list_tools() -> list[Tool]:
303303
"properties": {
304304
"note_id": {"type": "string", "description": "Note ID (N001, N002...)"},
305305
"content": {"type": "string", "description": "New content"},
306-
"score": {
306+
"ttl": {
307307
"type": "integer",
308-
"minimum": 10,
308+
"minimum": 0,
309309
"maximum": 100,
310-
"description": "New score",
310+
"description": "New ttl",
311311
},
312312
},
313313
"required": ["note_id"],
@@ -333,12 +333,12 @@ async def list_tools() -> list[Tool]:
333333
"properties": {
334334
"url": {"type": "string", "description": "File path or URL"},
335335
"note": {"type": "string", "description": "Description"},
336-
"score": {
336+
"ttl": {
337337
"type": "integer",
338338
"minimum": 10,
339339
"maximum": 100,
340-
"default": 15,
341-
"description": "Initial score (10-100)",
340+
"default": 30,
341+
"description": "Initial ttl (recommended: 10 short-term, 30 normal, 100 long-term)",
342342
},
343343
},
344344
"required": ["url", "note"],
@@ -353,11 +353,11 @@ async def list_tools() -> list[Tool]:
353353
"reference_id": {"type": "string", "description": "Reference ID (R001...)"},
354354
"url": {"type": "string", "description": "New URL"},
355355
"note": {"type": "string", "description": "New description"},
356-
"score": {
356+
"ttl": {
357357
"type": "integer",
358-
"minimum": 10,
358+
"minimum": 0,
359359
"maximum": 100,
360-
"description": "New score",
360+
"description": "New ttl",
361361
},
362362
},
363363
"required": ["reference_id"],
@@ -464,13 +464,13 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
464464
elif name == "add_note":
465465
result = tools.add_note(
466466
content=arguments["content"],
467-
score=arguments.get("score", 15),
467+
ttl=arguments.get("ttl", 30),
468468
)
469469
elif name == "update_note":
470470
result = tools.update_note(
471471
note_id=arguments["note_id"],
472472
content=arguments.get("content"),
473-
score=arguments.get("score"),
473+
ttl=arguments.get("ttl"),
474474
)
475475
elif name == "remove_note":
476476
result = tools.remove_note(note_id=arguments["note_id"])
@@ -480,14 +480,14 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
480480
result = tools.add_reference(
481481
url=arguments["url"],
482482
note=arguments["note"],
483-
score=arguments.get("score", 15),
483+
ttl=arguments.get("ttl", 30),
484484
)
485485
elif name == "update_reference":
486486
result = tools.update_reference(
487487
reference_id=arguments["reference_id"],
488488
url=arguments.get("url"),
489489
note=arguments.get("note"),
490-
score=arguments.get("score"),
490+
ttl=arguments.get("ttl"),
491491
)
492492
elif name == "remove_reference":
493493
result = tools.remove_reference(reference_id=arguments["reference_id"])

0 commit comments

Comments
 (0)