ata-coder 2.4.6__tar.gz → 2.4.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. {ata_coder-2.4.6/ata_coder.egg-info → ata_coder-2.4.7}/PKG-INFO +8 -4
  2. {ata_coder-2.4.6 → ata_coder-2.4.7}/README.md +7 -3
  3. {ata_coder-2.4.6 → ata_coder-2.4.7}/agent.py +39 -30
  4. ata_coder-2.4.7/agent_compact.py +159 -0
  5. {ata_coder-2.4.6 → ata_coder-2.4.7/ata_coder.egg-info}/PKG-INFO +8 -4
  6. {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/SOURCES.txt +2 -0
  7. {ata_coder-2.4.6 → ata_coder-2.4.7}/config.py +7 -0
  8. ata_coder-2.4.7/context_manager.py +230 -0
  9. {ata_coder-2.4.6 → ata_coder-2.4.7}/main.py +1 -1
  10. {ata_coder-2.4.6 → ata_coder-2.4.7}/memory.py +73 -20
  11. {ata_coder-2.4.6 → ata_coder-2.4.7}/pyproject.toml +1 -1
  12. {ata_coder-2.4.6 → ata_coder-2.4.7}/setup_wizard.py +1 -1
  13. {ata_coder-2.4.6 → ata_coder-2.4.7}/system_prompt_builder.py +94 -34
  14. {ata_coder-2.4.6 → ata_coder-2.4.7}/token_counter.py +79 -43
  15. ata_coder-2.4.6/agent_compact.py +0 -195
  16. {ata_coder-2.4.6 → ata_coder-2.4.7}/LICENSE +0 -0
  17. {ata_coder-2.4.6 → ata_coder-2.4.7}/MANIFEST.in +0 -0
  18. {ata_coder-2.4.6 → ata_coder-2.4.7}/__init__.py +0 -0
  19. {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_controller.py +0 -0
  20. {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_extension.py +0 -0
  21. {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_routing.py +0 -0
  22. {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_subsystems.py +0 -0
  23. {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_tools.py +0 -0
  24. {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_undo.py +0 -0
  25. {ata_coder-2.4.6 → ata_coder-2.4.7}/anthropic_client.py +0 -0
  26. {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/dependency_links.txt +0 -0
  27. {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/entry_points.txt +0 -0
  28. {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/requires.txt +0 -0
  29. {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/top_level.txt +0 -0
  30. {ata_coder-2.4.6 → ata_coder-2.4.7}/change_tracker.py +0 -0
  31. {ata_coder-2.4.6 → ata_coder-2.4.7}/clawd_integration.py +0 -0
  32. {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/__init__.py +0 -0
  33. {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_core.py +0 -0
  34. {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_safety.py +0 -0
  35. {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_settings.py +0 -0
  36. {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_workflow.py +0 -0
  37. {ata_coder-2.4.6 → ata_coder-2.4.7}/core/__init__.py +0 -0
  38. {ata_coder-2.4.6 → ata_coder-2.4.7}/core/events.py +0 -0
  39. {ata_coder-2.4.6 → ata_coder-2.4.7}/core/queue.py +0 -0
  40. {ata_coder-2.4.6 → ata_coder-2.4.7}/core/state.py +0 -0
  41. {ata_coder-2.4.6 → ata_coder-2.4.7}/event_queue.py +0 -0
  42. {ata_coder-2.4.6 → ata_coder-2.4.7}/extension.py +0 -0
  43. {ata_coder-2.4.6 → ata_coder-2.4.7}/extensions/__init__.py +0 -0
  44. {ata_coder-2.4.6 → ata_coder-2.4.7}/extensions/hello_skill.py +0 -0
  45. {ata_coder-2.4.6 → ata_coder-2.4.7}/fool_proof.py +0 -0
  46. {ata_coder-2.4.6 → ata_coder-2.4.7}/git_workflow.py +0 -0
  47. {ata_coder-2.4.6 → ata_coder-2.4.7}/gui.py +0 -0
  48. {ata_coder-2.4.6 → ata_coder-2.4.7}/llm_client.py +0 -0
  49. {ata_coder-2.4.6 → ata_coder-2.4.7}/mcp_client.py +0 -0
  50. {ata_coder-2.4.6 → ata_coder-2.4.7}/model_registry.py +0 -0
  51. {ata_coder-2.4.6 → ata_coder-2.4.7}/model_router.py +0 -0
  52. {ata_coder-2.4.6 → ata_coder-2.4.7}/permissions.py +0 -0
  53. {ata_coder-2.4.6 → ata_coder-2.4.7}/privilege.py +0 -0
  54. {ata_coder-2.4.6 → ata_coder-2.4.7}/project.py +0 -0
  55. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompt_template.py +0 -0
  56. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/auto-mode.md +0 -0
  57. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/coding-rules.md +0 -0
  58. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/execution-guardrails.md +0 -0
  59. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/memory-system.md +0 -0
  60. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/output-style.md +0 -0
  61. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/safety.md +0 -0
  62. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/slash-commands.md +0 -0
  63. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/sub-agents.md +0 -0
  64. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/system-reminders.md +0 -0
  65. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/system.md +0 -0
  66. {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/tool-policy.md +0 -0
  67. {ata_coder-2.4.6 → ata_coder-2.4.7}/py.typed +0 -0
  68. {ata_coder-2.4.6 → ata_coder-2.4.7}/repl_theme.py +0 -0
  69. {ata_coder-2.4.6 → ata_coder-2.4.7}/repl_tracker.py +0 -0
  70. {ata_coder-2.4.6 → ata_coder-2.4.7}/repl_ui.py +0 -0
  71. {ata_coder-2.4.6 → ata_coder-2.4.7}/safety_guard.py +0 -0
  72. {ata_coder-2.4.6 → ata_coder-2.4.7}/self_correct.py +0 -0
  73. {ata_coder-2.4.6 → ata_coder-2.4.7}/server.py +0 -0
  74. {ata_coder-2.4.6 → ata_coder-2.4.7}/server_session.py +0 -0
  75. {ata_coder-2.4.6 → ata_coder-2.4.7}/server_shell.py +0 -0
  76. {ata_coder-2.4.6 → ata_coder-2.4.7}/session.py +0 -0
  77. {ata_coder-2.4.6 → ata_coder-2.4.7}/settings.py +0 -0
  78. {ata_coder-2.4.6 → ata_coder-2.4.7}/setup.cfg +0 -0
  79. {ata_coder-2.4.6 → ata_coder-2.4.7}/skill_extension.py +0 -0
  80. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/architect/SKILL.md +0 -0
  81. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/code-reviewer/SKILL.md +0 -0
  82. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/codecraft/SKILL.md +0 -0
  83. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/debugger/SKILL.md +0 -0
  84. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/doc-writer/SKILL.md +0 -0
  85. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/general-coder/SKILL.md +0 -0
  86. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/README.md +0 -0
  87. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/SKILL.md +0 -0
  88. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/handler.py +0 -0
  89. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/prompts/system.md +0 -0
  90. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/requirements.txt +0 -0
  91. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/resources/constants.json +0 -0
  92. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/tests/test_handler.py +0 -0
  93. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/security-auditor/SKILL.md +0 -0
  94. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/test-writer/SKILL.md +0 -0
  95. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/README.md +0 -0
  96. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/handler.py +0 -0
  97. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/manifest.json +0 -0
  98. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/prompts/system_prompt.txt +0 -0
  99. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
  100. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/requirements.txt +0 -0
  101. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/resources/city_list.json +0 -0
  102. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/resources/error_messages.json +0 -0
  103. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/tests/test_handler.py +0 -0
  104. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/weather_utils.py +0 -0
  105. {ata_coder-2.4.6 → ata_coder-2.4.7}/skills.py +0 -0
  106. {ata_coder-2.4.6 → ata_coder-2.4.7}/sub_agent.py +0 -0
  107. {ata_coder-2.4.6 → ata_coder-2.4.7}/sub_agent_manager.py +0 -0
  108. {ata_coder-2.4.6 → ata_coder-2.4.7}/task_planner.py +0 -0
  109. {ata_coder-2.4.6 → ata_coder-2.4.7}/terminal.py +0 -0
  110. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_agent.py +0 -0
  111. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_change_tracker.py +0 -0
  112. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_config.py +0 -0
  113. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_event_queue.py +0 -0
  114. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_extension.py +0 -0
  115. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_fibonacci.py +0 -0
  116. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_fool_proof.py +0 -0
  117. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_llm_client.py +0 -0
  118. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_memory.py +0 -0
  119. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_model_registry.py +0 -0
  120. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_permissions.py +0 -0
  121. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_privilege.py +0 -0
  122. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_prompt_template.py +0 -0
  123. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_safety_guard.py +0 -0
  124. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_server.py +0 -0
  125. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_skill_handlers.py +0 -0
  126. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_sub_agent.py +0 -0
  127. {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_tools.py +0 -0
  128. {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/__init__.py +0 -0
  129. {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/definitions.py +0 -0
  130. {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/executor.py +0 -0
  131. {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/result.py +0 -0
  132. {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/strategy.py +0 -0
  133. {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/subagent.py +0 -0
  134. {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/web.py +0 -0
  135. {ata_coder-2.4.6 → ata_coder-2.4.7}/types.py +0 -0
  136. {ata_coder-2.4.6 → ata_coder-2.4.7}/utils.py +0 -0
  137. {ata_coder-2.4.6 → ata_coder-2.4.7}/web/css/style.css +0 -0
  138. {ata_coder-2.4.6 → ata_coder-2.4.7}/web/index.html +0 -0
  139. {ata_coder-2.4.6 → ata_coder-2.4.7}/web/js/app.js +0 -0
  140. {ata_coder-2.4.6 → ata_coder-2.4.7}/web/package-lock.json +0 -0
  141. {ata_coder-2.4.6 → ata_coder-2.4.7}/web/package.json +0 -0
  142. {ata_coder-2.4.6 → ata_coder-2.4.7}/web/tsconfig.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ata-coder
3
- Version: 2.4.6
3
+ Version: 2.4.7
4
4
  Summary: ATA Coder — AI-powered coding assistant
5
5
  Author: ATA Coder Team
6
6
  License-Expression: MIT
@@ -21,13 +21,15 @@ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
21
21
  Requires-Dist: tiktoken>=0.5.0; extra == "dev"
22
22
  Dynamic: license-file
23
23
 
24
- # ATA Coder v2.4.6
24
+ # ATA Coder v2.4.7
25
25
 
26
26
  **AI-powered coding assistant — async, AST-aware, single config file.**
27
27
 
28
28
  [English](#english) | [中文](#中文)
29
29
 
30
- > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
30
+ > **v2.4.7** — **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
31
+ >
32
+ > > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
31
33
  >
32
34
  > > **v2.4.5** — 🤖 **Self-Bootstrapped Audit**: ATA Coder found 19 bugs in its own source code and fixed them all — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
33
35
  >
@@ -550,7 +552,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
550
552
 
551
553
  ## 中文
552
554
 
553
- > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
555
+ > **v2.4.7** — **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
556
+ >
557
+ > > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
554
558
  >
555
559
  > > **v2.4.5** — 🤖 **自举审计**: ATA Coder 审计自身源码发现 19 个 bug 并全部修复 — 线程安全、SSRF IPv6、速率限制器泄漏、认证加固、DRY 重构、CI 覆盖。12 个文件变更。
556
560
  >
@@ -1,10 +1,12 @@
1
- # ATA Coder v2.4.6
1
+ # ATA Coder v2.4.7
2
2
 
3
3
  **AI-powered coding assistant — async, AST-aware, single config file.**
4
4
 
5
5
  [English](#english) | [中文](#中文)
6
6
 
7
- > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
7
+ > **v2.4.7** — **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
8
+ >
9
+ > > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
8
10
  >
9
11
  > > **v2.4.5** — 🤖 **Self-Bootstrapped Audit**: ATA Coder found 19 bugs in its own source code and fixed them all — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
10
12
  >
@@ -527,7 +529,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
527
529
 
528
530
  ## 中文
529
531
 
530
- > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
532
+ > **v2.4.7** — **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
533
+ >
534
+ > > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
531
535
  >
532
536
  > > **v2.4.5** — 🤖 **自举审计**: ATA Coder 审计自身源码发现 19 个 bug 并全部修复 — 线程安全、SSRF IPv6、速率限制器泄漏、认证加固、DRY 重构、CI 覆盖。12 个文件变更。
533
537
  >
@@ -44,6 +44,7 @@ from .agent_compact import CompactionMixin
44
44
  from .agent_tools import ToolExecutionMixin
45
45
  from .agent_routing import ModelRoutingMixin
46
46
  from .agent_extension import ExtensionMixin
47
+ from .context_manager import ContextManager
47
48
 
48
49
  # ── Event types & Agent state ──────────────────────────────────────────
49
50
  from .core import ( # noqa: F401 — re-exported for external use
@@ -148,6 +149,10 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
148
149
  self._cached_system_prompt: str | None = None # invalidated on new build / compact
149
150
  self._cached_allowed_tools: set[str] | None = None # invalidated on skill change
150
151
 
152
+ # ── Context manager (O(1) token tracking, segment-split, adaptive compact) ──
153
+ self._context_manager = ContextManager(self.config.agent)
154
+ self._summary_llm = None # lazily created summarisation client
155
+
151
156
  # Build the combined tool list
152
157
  self._all_tools = list(TOOL_DEFINITIONS)
153
158
  if self.mcp:
@@ -227,7 +232,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
227
232
  if not reset_context and self._state.messages:
228
233
  # Append new user message to existing conversation; keep system
229
234
  # prompt and all prior messages intact.
230
- self._state.messages.append({"role": "user", "content": task})
235
+ self._append_message({"role": "user", "content": task})
231
236
  # Rebuild system prompt for updated memory context but don't
232
237
  # replace the original system message (memory/git context may
233
238
  # have changed, but conversation integrity is paramount).
@@ -291,10 +296,12 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
291
296
  self._cached_system_prompt = system_prompt # pre-seed cache
292
297
  self._cached_allowed_tools = None # invalidate on new run
293
298
 
294
- self._state.messages = [
299
+ initial_msgs = [
295
300
  {"role": "system", "content": system_prompt},
296
301
  {"role": "user", "content": task},
297
302
  ]
303
+ self._state.messages = initial_msgs
304
+ self._context_manager.replace_all(initial_msgs)
298
305
 
299
306
  logger.info("Agent run: skill=%s, model=%s, session=%s, task=%.100s",
300
307
  self.skills.active_skill.name if self.skills and self.skills.active_skill else "default",
@@ -352,24 +359,19 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
352
359
  # Clawd: model is generating, show thinking animation
353
360
  get_clawd().thinking()
354
361
 
355
- # Auto-compact when approaching the effective context limit.
356
- # effective_context_tokens (default 200k) reflects the range where
357
- # the model actually pays attention, not the theoretical 1M window.
358
- # We compact at 80% of effective limit, which is well below the
359
- # theoretical max_context_tokens.
360
- est_tokens = self.get_token_estimate()
361
- max_tokens = self.config.agent.max_context_tokens
362
- effective = self.config.agent.effective_context_tokens
363
- if est_tokens > effective:
362
+ # Auto-compact when approaching the effective context limit (O(1) check).
363
+ if self._context_manager.should_compact():
364
+ est = self._context_manager.token_total
365
+ max_t = self.config.agent.max_context_tokens
364
366
  logger.warning("Token budget: %d/%d effective (%.0f%% of %d max), auto-compacting",
365
- est_tokens, effective, est_tokens / max(max_tokens, 1) * 100, max_tokens)
367
+ est, self.config.agent.effective_context_tokens,
368
+ est / max(max_t, 1) * 100, max_t)
366
369
  await self.compact()
367
- # Re-estimate AFTER compaction — the message list has changed
368
- est_tokens = self.get_token_estimate()
369
370
  # Hard ceiling: if compaction didn't help enough, force-truncate
370
- if est_tokens > max_tokens * 0.95:
371
+ if self._context_manager.needs_force_truncate():
371
372
  logger.critical("Hard token ceiling: %d > 95%% of %d max. Force-truncating.",
372
- est_tokens, max_tokens)
373
+ self._context_manager.token_total,
374
+ self.config.agent.max_context_tokens)
373
375
  self._force_truncate()
374
376
 
375
377
  # Get allowed tools from multi-skill intersection
@@ -437,7 +439,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
437
439
  }
438
440
  if response.get("reasoning_content"):
439
441
  assistant_msg["reasoning_content"] = response["reasoning_content"]
440
- self._state.messages.append(assistant_msg)
442
+ self._append_message(assistant_msg)
441
443
  for tc, result in zip(tool_calls, results, strict=True):
442
444
  self._warn_if_large_result(result, tc["function"]["name"])
443
445
  self._store_tool_result(result, tc["id"])
@@ -463,7 +465,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
463
465
  }
464
466
  if response.get("reasoning_content"):
465
467
  assistant_msg["reasoning_content"] = response["reasoning_content"]
466
- self._state.messages.append(assistant_msg)
468
+ self._append_message(assistant_msg)
467
469
  for tc, result in zip(tool_calls, batch_results, strict=True):
468
470
  self._store_tool_result(result, tc["id"])
469
471
 
@@ -579,25 +581,23 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
579
581
  Mirrors the main run() loop: skill tool filtering, token compaction,
580
582
  consecutive-failure detection, and circuit breaker.
581
583
  """
582
- self._state.messages.append({"role": "user", "content": message})
584
+ self._append_message({"role": "user", "content": message})
583
585
 
584
586
  SAFETY_LIMIT = 999 # circuit breaker
585
587
  _consecutive_failures = 0
586
588
  _MAX_CONSECUTIVE_FAILURES = 5
587
589
 
588
590
  while self._state.tool_call_count < SAFETY_LIMIT:
589
- # ── Token budget: auto-compact when approaching the limit ────
590
- est_tokens = self.get_token_estimate()
591
- max_tokens = self.config.agent.max_context_tokens
592
- effective = self.config.agent.effective_context_tokens
593
- if est_tokens > effective:
591
+ # ── Token budget: auto-compact when approaching the limit (O(1)) ──
592
+ if self._context_manager.should_compact():
594
593
  logger.warning("chat(): token budget %d/%d effective, auto-compacting",
595
- est_tokens, effective)
594
+ self._context_manager.token_total,
595
+ self.config.agent.effective_context_tokens)
596
596
  await self.compact()
597
- est_tokens = self.get_token_estimate()
598
- if est_tokens > max_tokens * 0.95:
597
+ if self._context_manager.needs_force_truncate():
599
598
  logger.critical("chat(): hard ceiling %d > 95%% of %d, force-truncating",
600
- est_tokens, max_tokens)
599
+ self._context_manager.token_total,
600
+ self.config.agent.max_context_tokens)
601
601
  self._force_truncate()
602
602
 
603
603
  # ── Skill tool filtering ────────────────────────────────────
@@ -639,7 +639,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
639
639
  batch_results.append(result)
640
640
  self._warn_if_large_result(result, tool_name)
641
641
 
642
- self._state.messages.append({
642
+ self._append_message({
643
643
  "role": "assistant",
644
644
  "content": text or None,
645
645
  "tool_calls": [tc],
@@ -850,8 +850,15 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
850
850
 
851
851
  # ── Change tracking helper → agent_tools.py (ToolExecutionMixin._read_old_content)
852
852
 
853
+ def _append_message(self, msg: Message) -> None:
854
+ """Append a message to state AND context manager (O(1) token update)."""
855
+ self._state.messages.append(msg)
856
+ self._context_manager.append(msg)
857
+
853
858
  def get_token_estimate(self) -> int:
854
- """Estimate total tokens in the conversation."""
859
+ """O(1) token total from ContextManager. Falls back to LLM count if stale."""
860
+ if self._context_manager.messages:
861
+ return self._context_manager.token_total
855
862
  return self.llm.count_tokens_approx(self._state.messages)
856
863
 
857
864
  def get_conversation_summary(self) -> str:
@@ -880,5 +887,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
880
887
  # Clawd: final SessionEnd
881
888
  get_clawd().shutdown()
882
889
  await self.llm.close()
890
+ if self._summary_llm:
891
+ await self._summary_llm.close()
883
892
  if self.mcp:
884
893
  await self.mcp.stop_all()
@@ -0,0 +1,159 @@
1
+ """Context compaction and token budget management — mixin for CoderAgent.
2
+
3
+ Delegates all context operations to ContextManager. This mixin is now a
4
+ thin wrapper that provides the same public API while eliminating duplicated
5
+ logic, avoiding deep copies, and reusing the summarisation LLM client.
6
+ """
7
+
8
+ import copy
9
+ import logging
10
+
11
+ from .types import Message
12
+ from .clawd_integration import get_clawd
13
+ from .model_router import get_subagent_model
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class CompactionMixin:
19
+ """Context window compaction — thin wrapper around ContextManager."""
20
+
21
+ # ── Compaction token budget (class-level defaults, overridable) ───────
22
+ RECENT_TOKEN_BUDGET = 80_000 # max tokens to keep in the recent segment
23
+ COMPACT_IF_FEWER_THAN = 6 # skip compaction if fewer than this many msgs
24
+
25
+ # ── Core compaction ───────────────────────────────────────────────────
26
+
27
+ async def compact(self) -> str:
28
+ """Compact conversation by summarising old messages.
29
+
30
+ Strategy: keep system prompt + recent messages up to
31
+ RECENT_TOKEN_BUDGET tokens, summarise everything in between using
32
+ a cheap LLM call. Falls back to a lightweight extractive summary
33
+ if the API call fails.
34
+
35
+ Delegates segment-splitting to ContextManager to avoid the
36
+ duplicated walk-backwards logic that was previously shared with
37
+ _force_truncate.
38
+ """
39
+ cm = self._context_manager
40
+ if not cm.can_compact():
41
+ return "Already compact."
42
+
43
+ # Clawd: PreCompact
44
+ get_clawd().compact()
45
+
46
+ system_msg, recent, archive = cm.split_into_segments()
47
+
48
+ if not archive:
49
+ return "Already compact (all messages fit in recent budget)."
50
+
51
+ # Extract summary metadata from the archive segment
52
+ tool_count = sum(1 for m in archive if m.get("tool_calls"))
53
+ user_msgs = [m.get("content", "")[:200] for m in archive if m.get("role") == "user"]
54
+ file_ops = cm.collect_file_ops(archive)
55
+
56
+ summary = await self._summarise_messages(archive, file_ops, user_msgs, tool_count)
57
+
58
+ old_count = len(cm.messages)
59
+ old_tokens = cm.token_total
60
+
61
+ truncated: list[Message] = []
62
+ if system_msg:
63
+ truncated.append(system_msg)
64
+ truncated.append({
65
+ "role": "user",
66
+ "content": "[Conversation summary]\n" + summary,
67
+ })
68
+ truncated.append({
69
+ "role": "assistant",
70
+ "content": "Understood. I'll continue with the remaining context using the summary above.",
71
+ })
72
+ truncated.extend(recent)
73
+
74
+ cm.replace_all(truncated)
75
+ self._cached_system_prompt = None # system msg may have shifted
76
+ self._state.messages = cm.messages # sync for backward compat
77
+
78
+ new_tokens = cm.token_total
79
+ logger.info("Compacted: %d→%d msgs, ~%d→%d tokens (files: %d, tools: %d)",
80
+ old_count, len(truncated), old_tokens, new_tokens,
81
+ len(file_ops), tool_count)
82
+ return (f"Compacted from {old_count}→{len(truncated)} messages "
83
+ f"(~{old_tokens:,}→~{new_tokens:,} tokens, {len(file_ops)} files, {tool_count} tool calls).")
84
+
85
+ def _force_truncate(self) -> None:
86
+ """Drop the oldest non-system messages when we exceed 95% of max tokens.
87
+
88
+ Called only as a last resort after compaction has already run.
89
+ Delegates to ContextManager.build_truncated_list() — no more
90
+ duplicated walk-backwards.
91
+ """
92
+ cm = self._context_manager
93
+ if len(cm.messages) <= 6:
94
+ return
95
+ truncated, result = cm.build_truncated_list()
96
+ cm.replace_all(truncated)
97
+ self._cached_system_prompt = None
98
+ self._state.messages = cm.messages # sync
99
+ logger.warning("Force-truncated: %d → %d messages (~%d tokens kept)",
100
+ result.old_count, result.new_count, result.new_tokens)
101
+
102
+ # ── Token estimation helpers (delegate to ContextManager) ────────────
103
+
104
+ def _estimate_message_tokens(self, msg: Message) -> int:
105
+ """Rough token estimate for a single message (via ContextManager cache)."""
106
+ return self._context_manager.get_msg_tokens(msg)
107
+
108
+ @staticmethod
109
+ def _collect_file_ops(messages: list[Message]) -> list[str]:
110
+ """Collect files modified in a message list (static, delegates to CM)."""
111
+ from .context_manager import ContextManager
112
+ return ContextManager.collect_file_ops(messages)
113
+
114
+ # ── Summarisation (reuses a single cheap LLM client) ──────────────────
115
+
116
+ async def _summarise_messages(self, archive: list[Message], file_ops: list[str],
117
+ user_msgs: list[str], tool_count: int) -> str:
118
+ """Generate a summary of the archive conversation segment.
119
+
120
+ Attempts a cheap LLM call first; falls back to a lightweight extractive
121
+ summary so the user never loses context entirely. The summarisation
122
+ client is created once and reused across compactions.
123
+ """
124
+ # ── LLM-based summary (best effort) ──────────────────────────────
125
+ try:
126
+ summary_prompt = (
127
+ "Summarise this conversation segment in 3-5 bullet points. "
128
+ "Focus on: what the user asked, what files were changed, what "
129
+ "decisions were made, and any unresolved issues. "
130
+ "Be concise — this summary will replace the full conversation "
131
+ "history to save context tokens.\n\n"
132
+ f"Files modified: {', '.join(file_ops) if file_ops else 'none'}\n"
133
+ f"Tool calls: {tool_count}\n"
134
+ f"User requests: {'; '.join(user_msgs[:5])}\n"
135
+ )
136
+ sc = getattr(self, '_summary_llm', None)
137
+ if sc is None:
138
+ from .llm_client import LLMClient
139
+ summary_config = copy.deepcopy(self.llm.config)
140
+ summary_config.model = get_subagent_model()
141
+ sc = LLMClient(summary_config)
142
+ self._summary_llm = sc # cache for reuse
143
+ resp = await sc.chat([{"role": "user", "content": summary_prompt}], tools=[])
144
+ llm_summary = (resp.get("content") or "").strip()
145
+ if llm_summary:
146
+ parts = [llm_summary]
147
+ if file_ops:
148
+ parts.append(f"\nFiles touched: {', '.join(file_ops[:10])}")
149
+ return "\n".join(parts)
150
+ except Exception:
151
+ logger.debug("LLM summarisation unavailable, using extractive fallback")
152
+
153
+ # ── Extractive fallback ─────────────────────────────────────────
154
+ parts = [f"Summarised {len(archive)} messages ({tool_count} tool calls)."]
155
+ if user_msgs:
156
+ parts.append(f"Topics: {'; '.join(user_msgs[:5])}")
157
+ if file_ops:
158
+ parts.append(f"Files modified: {', '.join(file_ops[:10])}")
159
+ return "\n".join(parts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ata-coder
3
- Version: 2.4.6
3
+ Version: 2.4.7
4
4
  Summary: ATA Coder — AI-powered coding assistant
5
5
  Author: ATA Coder Team
6
6
  License-Expression: MIT
@@ -21,13 +21,15 @@ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
21
21
  Requires-Dist: tiktoken>=0.5.0; extra == "dev"
22
22
  Dynamic: license-file
23
23
 
24
- # ATA Coder v2.4.6
24
+ # ATA Coder v2.4.7
25
25
 
26
26
  **AI-powered coding assistant — async, AST-aware, single config file.**
27
27
 
28
28
  [English](#english) | [中文](#中文)
29
29
 
30
- > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
30
+ > **v2.4.7** — **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
31
+ >
32
+ > > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
31
33
  >
32
34
  > > **v2.4.5** — 🤖 **Self-Bootstrapped Audit**: ATA Coder found 19 bugs in its own source code and fixed them all — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
33
35
  >
@@ -550,7 +552,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
550
552
 
551
553
  ## 中文
552
554
 
553
- > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
555
+ > **v2.4.7** — **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
556
+ >
557
+ > > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
554
558
  >
555
559
  > > **v2.4.5** — 🤖 **自举审计**: ATA Coder 审计自身源码发现 19 个 bug 并全部修复 — 线程安全、SSRF IPv6、速率限制器泄漏、认证加固、DRY 重构、CI 覆盖。12 个文件变更。
556
560
  >
@@ -14,6 +14,7 @@ anthropic_client.py
14
14
  change_tracker.py
15
15
  clawd_integration.py
16
16
  config.py
17
+ context_manager.py
17
18
  event_queue.py
18
19
  extension.py
19
20
  fool_proof.py
@@ -65,6 +66,7 @@ utils.py
65
66
  ./change_tracker.py
66
67
  ./clawd_integration.py
67
68
  ./config.py
69
+ ./context_manager.py
68
70
  ./event_queue.py
69
71
  ./extension.py
70
72
  ./fool_proof.py
@@ -120,6 +120,13 @@ class AgentConfig:
120
120
  self, "effective_context_tokens",
121
121
  max(10000, int(self.max_context_tokens * 0.9)),
122
122
  )
123
+ # Compaction/context budgets (passed to ContextManager)
124
+ recent_token_budget: int = field(
125
+ default_factory=lambda: int(_from_settings("recent_token_budget", 80000))
126
+ )
127
+ compact_if_fewer_than: int = field(
128
+ default_factory=lambda: int(_from_settings("compact_if_fewer_than", 6))
129
+ )
123
130
  max_message_output_chars: int = field(
124
131
  default_factory=lambda: int(_from_settings("max_message_output_chars", 8000))
125
132
  )