agentbyte 0.2.1__tar.gz → 0.2.4__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 (106) hide show
  1. {agentbyte-0.2.1 → agentbyte-0.2.4}/CHANGELOG.md +18 -0
  2. {agentbyte-0.2.1 → agentbyte-0.2.4}/PKG-INFO +6 -1
  3. {agentbyte-0.2.1 → agentbyte-0.2.4}/README.md +4 -0
  4. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/progress.md +37 -0
  5. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study/topic_agents.md +119 -2
  6. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study/topic_tools.md +186 -3
  7. agentbyte-0.2.4/logo/agent-byte-avatar-low.png +0 -0
  8. {agentbyte-0.2.1/notebooks → agentbyte-0.2.4/notebooks/concepts/agent}/01-tools-example.ipynb +36 -20
  9. agentbyte-0.2.4/notebooks/concepts/agent/02a-azure-openai-wrapper.ipynb +688 -0
  10. {agentbyte-0.2.1/notebooks → agentbyte-0.2.4/notebooks/concepts/agent}/02b-openai-wrapper.ipynb +138 -175
  11. {agentbyte-0.2.1/notebooks → agentbyte-0.2.4/notebooks/concepts/agent}/03a-simple-agent-no-tool.ipynb +59 -112
  12. {agentbyte-0.2.1/notebooks → agentbyte-0.2.4/notebooks/concepts/agent}/03b-agent-with-tool.ipynb +55 -108
  13. agentbyte-0.2.1/notebooks/03d-multi-tool-agent.ipynb → agentbyte-0.2.4/notebooks/concepts/agent/03c-multi-tool-agent.ipynb +53 -103
  14. agentbyte-0.2.1/notebooks/03e-agent-with-context.ipynb → agentbyte-0.2.4/notebooks/concepts/agent/04-agent-with-context.ipynb +304 -89
  15. agentbyte-0.2.1/notebooks/03f-agent-with-middleware.ipynb → agentbyte-0.2.4/notebooks/concepts/agent/05a-agent-with-middleware.ipynb +5 -80
  16. agentbyte-0.2.1/notebooks/03h-agent-with-otel.ipynb → agentbyte-0.2.4/notebooks/concepts/agent/05b-agent-with-otel-middleware.ipynb +21 -1
  17. agentbyte-0.2.1/notebooks/03g-agent-with-memory.ipynb → agentbyte-0.2.4/notebooks/concepts/agent/06-agent-with-memory.ipynb +22 -78
  18. agentbyte-0.2.4/notebooks/concepts/agent/07-agent-as-tool-coordinator.ipynb +700 -0
  19. agentbyte-0.2.4/notebooks/concepts/agent/sample-data/contracts_asmd_seed_rows.json +92 -0
  20. agentbyte-0.2.4/notebooks/concepts/agent/sample-data/contracts_esmd_seed_rows.json +112 -0
  21. agentbyte-0.2.1/notebooks/03c-multi-turn-agent.ipynb → agentbyte-0.2.4/notebooks/usecases/04.1-multi-turn-agent.ipynb +71 -119
  22. agentbyte-0.2.4/notebooks/usecases/04.2-metadata-extraction-large-doc.ipynb +662 -0
  23. {agentbyte-0.2.1 → agentbyte-0.2.4}/pyproject.toml +1 -0
  24. agentbyte-0.2.4/src/agentbyte/__about__.py +2 -0
  25. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/__init__.py +11 -1
  26. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/agents/agent.py +3 -2
  27. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/agents/agent_as_tool.py +12 -0
  28. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/agents/base.py +18 -1
  29. agentbyte-0.2.4/src/agentbyte/entity.py +27 -0
  30. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/llm/__init__.py +5 -0
  31. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/llm/azure_openai.py +383 -0
  32. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/llm/openai.py +75 -0
  33. agentbyte-0.2.4/src/agentbyte/llm/settings.py +220 -0
  34. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/middleware/__init__.py +4 -0
  35. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/middleware/base.py +106 -0
  36. agentbyte-0.2.4/src/agentbyte/session.py +121 -0
  37. agentbyte-0.2.4/src/agentbyte/tools/memory_tool.py +282 -0
  38. agentbyte-0.2.4/tests/agents/test_agent_as_tool.py +270 -0
  39. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/middleware/test_middleware_chain.py +134 -0
  40. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/test_package_api.py +20 -0
  41. agentbyte-0.2.4/tests/test_session.py +156 -0
  42. agentbyte-0.2.4/tests/tools/test_memory_tool.py +251 -0
  43. {agentbyte-0.2.1 → agentbyte-0.2.4}/uv.lock +2 -0
  44. agentbyte-0.2.1/notebooks/02a-azure-openai-wrapper.ipynb +0 -877
  45. agentbyte-0.2.1/notebooks/pico-init-agent-test.ipynb +0 -135
  46. agentbyte-0.2.1/src/agentbyte/__about__.py +0 -2
  47. agentbyte-0.2.1/src/agentbyte/tools/memory_tool.py +0 -158
  48. agentbyte-0.2.1/tests/agents/test_agent_as_tool.py +0 -119
  49. agentbyte-0.2.1/tests/tools/test_memory_tool.py +0 -109
  50. {agentbyte-0.2.1 → agentbyte-0.2.4}/.github/copilot-instructions.md +0 -0
  51. {agentbyte-0.2.1 → agentbyte-0.2.4}/.gitignore +0 -0
  52. {agentbyte-0.2.1 → agentbyte-0.2.4}/.gitlab-ci.yml +0 -0
  53. {agentbyte-0.2.1 → agentbyte-0.2.4}/.gitmodules +0 -0
  54. {agentbyte-0.2.1 → agentbyte-0.2.4}/.python-version +0 -0
  55. {agentbyte-0.2.1 → agentbyte-0.2.4}/LICENSE +0 -0
  56. {agentbyte-0.2.1 → agentbyte-0.2.4}/docker/jaeger-compose.yml +0 -0
  57. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/book-designing-multi-agent-system.md +0 -0
  58. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/implementation_plan.md +0 -0
  59. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study/topic_approval.md +0 -0
  60. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study/topic_memory.md +0 -0
  61. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study/topic_middleware.md +0 -0
  62. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study/topic_model_client.md +0 -0
  63. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study/topic_otel.md +0 -0
  64. {agentbyte-0.2.1 → agentbyte-0.2.4}/docs/study_plan.md +0 -0
  65. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/azure_openai_usage_example.py +0 -0
  66. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/function_tool_example.py +0 -0
  67. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/llm_client_dependency_injection.py +0 -0
  68. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/memory/memory_usage_example.py +0 -0
  69. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/message_types_example.py +0 -0
  70. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/openai_client_example.py +0 -0
  71. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/otel/agent_with_content_capture.py +0 -0
  72. {agentbyte-0.2.1 → agentbyte-0.2.4}/examples/otel/agent_with_telemetry.py +0 -0
  73. {agentbyte-0.2.1 → agentbyte-0.2.4}/notebooks/agent-knowledge-building.ipynb +0 -0
  74. {agentbyte-0.2.1/notebooks → agentbyte-0.2.4/notebooks/auth}/00-Authentication-and-Identity-of-AI-clients.ipynb +0 -0
  75. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/agents/__init__.py +0 -0
  76. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/agents/types.py +0 -0
  77. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/cancellation_token.py +0 -0
  78. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/context.py +0 -0
  79. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/llm/auth.py +0 -0
  80. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/llm/base.py +0 -0
  81. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/llm/types.py +0 -0
  82. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/memory/__init__.py +0 -0
  83. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/memory/base.py +0 -0
  84. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/messages.py +0 -0
  85. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/middleware/otel.py +0 -0
  86. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/tools/__init__.py +0 -0
  87. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/tools/base.py +0 -0
  88. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/tools/core_tools.py +0 -0
  89. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/tools/decorator.py +0 -0
  90. {agentbyte-0.2.1 → agentbyte-0.2.4}/src/agentbyte/types.py +0 -0
  91. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/agents/test_agent_basic.py +0 -0
  92. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/agents/test_agent_event_types.py +0 -0
  93. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/agents/test_agent_memory_integration.py +0 -0
  94. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/agents/test_agent_middleware_integration.py +0 -0
  95. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/agents/test_agent_stream_events.py +0 -0
  96. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/agents/test_tool_approval.py +0 -0
  97. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/llm/test_azure_client.py +0 -0
  98. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/llm/test_llm_types.py +0 -0
  99. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/llm/test_openai_client.py +0 -0
  100. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/memory/test_memory.py +0 -0
  101. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/middleware/test_otel.py +0 -0
  102. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/test_cancellation_token.py +0 -0
  103. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/test_context.py +0 -0
  104. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/test_messages.py +0 -0
  105. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/test_types.py +0 -0
  106. {agentbyte-0.2.1 → agentbyte-0.2.4}/tests/tools/test_tools.py +0 -0
@@ -4,6 +4,24 @@ All notable changes to Agentbyte are documented in this file.
4
4
 
5
5
  The format follows Keep a Changelog principles and semantic versioning.
6
6
 
7
+ ## [0.2.3] - 2026-03-02
8
+
9
+ ### Changed
10
+ - Unified `Conversation` and `Workspace` into a single `Session` model. Both were structurally identical; domain semantics now live in `Session.metadata` (e.g. `{"name": "Blog Post"}`).
11
+ - `session.py` now contains `Session(Entity)`, `BaseSession(ABC)`, and `InMemorySession` — flat pattern mirroring `memory/base.py`.
12
+ - `AgentContext.conversation_id` field removed. `session_id: Optional[str]` (already present) is the sole context-level session link.
13
+
14
+ ### Removed
15
+ - `src/agentbyte/conversation/` module and all exports (`Conversation`, `BaseConversation`, `InMemoryConversation`).
16
+ - `src/agentbyte/workspace/` module and all exports (`Workspace`, `BaseWorkspace`, `InMemoryWorkspace`).
17
+ - `tests/test_conversation.py`, `tests/test_workspace.py`, `tests/agents/test_agent_conversation_integration.py`.
18
+
19
+ ### Added
20
+ - `tests/test_session.py` — 19 tests covering `Session` identity/equality, `to_context()`/`from_context()`, `InMemorySession` CRUD, copy-safety, and ABC contract.
21
+
22
+ ### Reorganized
23
+ - Notebooks moved from flat layout into `notebooks/agent/`, `notebooks/auth/`, and `notebooks/usecase/` subdirectories.
24
+
7
25
  ## [0.2.1] - 2026-02-27
8
26
 
9
27
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentbyte
3
- Version: 0.2.1
3
+ Version: 0.2.4
4
4
  Summary: A toolkit for designing multiagent systems
5
5
  Project-URL: Homepage, https://gitlab.com/pyninja/aiengineering/agentbyte
6
6
  Project-URL: Repository, https://gitlab.com/pyninja/aiengineering/agentbyte
@@ -10,6 +10,7 @@ Author-email: MrDataPsycho <mr.data.psycho@gmail.com>
10
10
  License-Expression: MIT
11
11
  License-File: LICENSE
12
12
  Requires-Python: >=3.12
13
+ Requires-Dist: pydantic-settings>=2.13.0
13
14
  Requires-Dist: pydantic>=2.12.5
14
15
  Provides-Extra: all
15
16
  Requires-Dist: azure-identity>=1.25.1; extra == 'all'
@@ -28,6 +29,10 @@ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.39.1; extra == 'otel'
28
29
  Requires-Dist: opentelemetry-sdk>=1.39.1; extra == 'otel'
29
30
  Description-Content-Type: text/markdown
30
31
 
32
+ <p align="center">
33
+ <img src="logo/agent-byte-avatar-low.png" alt="Agentbyte" width="200"/>
34
+ </p>
35
+
31
36
  # Agentbyte
32
37
 
33
38
  Agentbyte is a Python toolkit for building and studying multiagent systems with a learning-first, implementation-oriented workflow.
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="logo/agent-byte-avatar-low.png" alt="Agentbyte" width="200"/>
3
+ </p>
4
+
1
5
  # Agentbyte
2
6
 
3
7
  Agentbyte is a Python toolkit for building and studying multiagent systems with a learning-first, implementation-oriented workflow.
@@ -268,3 +268,40 @@ Middleware parity with picoagents (Chapter 4.9), plus Agent integration.
268
268
  - `git push origin v0.2.0`
269
269
  5. Publish (if desired):
270
270
  - `uv publish`
271
+
272
+ ---
273
+
274
+ ## Update (2026-03-02, Session Unification — Conversation/Workspace Removed)
275
+
276
+ ### Design Decision
277
+
278
+ `Conversation` and `Workspace` were structurally identical (`name` was the only difference and
279
+ belongs in `metadata`). Introducing two separate types, two ABCs, two in-memory adapters,
280
+ and a `conversation_id: Optional[UUID]` field on `AgentContext` was over-engineering.
281
+
282
+ Consolidated into a single unified model:
283
+
284
+ - `session.py` now contains `Session`, `BaseSession`, `InMemorySession` (flat, mirrors `memory/base.py`)
285
+ - `AgentContext.conversation_id` removed — `session_id: str` (already present) is sufficient
286
+ - Callers use `metadata` for domain semantics (e.g. `metadata={"name": "Blog Post"}` for workspace-like sessions)
287
+
288
+ ### Source Changes
289
+
290
+ - `src/agentbyte/session.py` — rewritten: `Session(Entity)` + `BaseSession(ABC)` + `InMemorySession`
291
+ - `src/agentbyte/conversation/` — **deleted**
292
+ - `src/agentbyte/workspace/` — **deleted**
293
+ - `src/agentbyte/context.py` — `conversation_id` field and `UUID` import removed
294
+ - `src/agentbyte/__init__.py` — exports updated: `Session`, `BaseSession`, `InMemorySession`
295
+
296
+ ### Tests
297
+
298
+ - `tests/test_conversation.py` — **deleted**
299
+ - `tests/test_workspace.py` — **deleted**
300
+ - `tests/agents/test_agent_conversation_integration.py` — **deleted**
301
+ - `tests/test_package_api.py` — `test_conversation_import_paths` replaced with `test_session_import_paths`
302
+ - `tests/test_session.py` — **new**: 15 tests covering `Session` model, `to_context()`/`from_context()`, `InMemorySession` CRUD, copy-safety, and ABC contract
303
+
304
+ ### Validation Run
305
+
306
+ - Ruff: `All checks passed!`
307
+ - Pytest: **253 passed** (`uv run pytest tests/ -q`)
@@ -12,6 +12,7 @@ This file now contains **agents-only** study material.
12
12
  - Memory content moved to [docs/study/topic_memory.md](../study/topic_memory.md)
13
13
  - Middleware content moved to [docs/study/topic_middleware.md](../study/topic_middleware.md)
14
14
  - Approval deep dive moved to [docs/study/topic_approval.md](../study/topic_approval.md)
15
+ - Session persistence (unified) → `src/agentbyte/session.py` (`Session`, `BaseSession`, `InMemorySession`)
15
16
  - Study gaps are tracked at the end of this same document.
16
17
 
17
18
  ---
@@ -78,6 +79,32 @@ Common checkpoints:
78
79
  3. Before tool execution
79
80
  4. During long-running operations linked to futures/tasks
80
81
 
82
+ ### 4.12 Context Engineering
83
+
84
+ > "If the LLM is the CPU, then the context window is RAM." — Andrej Karpathy
85
+
86
+ As agents execute multi-step tasks, context grows with every tool call, memory retrieval, and conversation turn. Without management this causes two problems:
87
+
88
+ - **Context rot** — performance degrades as token count approaches the model's attention limit (empirically around 32K tokens).
89
+ - **Context explosion** — a long tool-heavy run can accumulate 20K+ tokens, pushing system instructions out of the attention window.
90
+
91
+ **Four management strategies (Chapter 4.12.3):**
92
+
93
+ | Strategy | Mechanism | Where in Agentbyte |
94
+ |---|---|---|
95
+ | Compaction | Summarise + trim old messages at model-call time | `ContextCompactionMiddleware` |
96
+ | Active memory management | Agent explicitly writes/deletes knowledge via tool | `MemoryTool` (6-command CRUD) |
97
+ | Context isolation | Specialist agents run in their own context bubble | `AgentAsTool` / `result_strategy` |
98
+ | Tool result filtering | Truncate large tool outputs before appending to context | Future: agent `_execute_tool_call` hook |
99
+
100
+ **Empirical token reduction (from the book):**
101
+
102
+ - Baseline (no management): 21,633 tokens
103
+ - Compaction middleware: 7,276 tokens (−66%)
104
+ - Isolation via agents-as-tools: 5,892 tokens (−73%)
105
+
106
+ Key takeaway: architectural prevention (isolation) outperforms reactive trimming (compaction).
107
+
81
108
  ---
82
109
 
83
110
  ## Part 2: Agentbyte Implementation Mapping
@@ -90,6 +117,10 @@ Common checkpoints:
90
117
  - `src/agentbyte/agents/types.py`
91
118
  - `src/agentbyte/context.py`
92
119
  - `src/agentbyte/cancellation_token.py`
120
+ - `src/agentbyte/session.py` — `Session`, `BaseSession`, `InMemorySession` unified session persistence
121
+
122
+ - `src/agentbyte/middleware/base.py` — `ContextCompactionMiddleware`, `OperationType`
123
+ - `src/agentbyte/tools/memory_tool.py` — `MemoryBackend`, `MemoryTool` (6 commands)
93
124
 
94
125
  ### Current Behavior Highlights
95
126
 
@@ -100,6 +131,81 @@ Common checkpoints:
100
131
  - Middleware-integrated model/tool execution now uses event-aware `execute_stream(...)`
101
132
  - Middleware events can flow through `run_stream(...)` and pause execution via `ToolApprovalEvent`
102
133
  - Token-level streaming is auto-disabled when middleware is configured (picoagents-aligned fallback)
134
+ - Agent operation strings are constants via `OperationType` enum (`MODEL_CALL`, `TOOL_CALL`, etc.)
135
+
136
+ ### ContextCompactionMiddleware — How It Works
137
+
138
+ **File:** `src/agentbyte/middleware/base.py`
139
+
140
+ The middleware hooks into the `process_request` phase of every `model_call` operation. It does nothing for `tool_call` or other operations.
141
+
142
+ **Decision point:**
143
+
144
+ ```
145
+ estimated_tokens = total_chars_of_all_message_content ÷ 4
146
+ if estimated_tokens > max_tokens:
147
+ apply compaction
148
+ ```
149
+
150
+ The `÷ 4` heuristic avoids a hard tiktoken dependency. It can be swapped for an exact tokeniser in production.
151
+
152
+ **Compaction output structure:**
153
+
154
+ ```
155
+ [messages[0], SystemMessage("[Previous context summarized...]"), *messages[-keep_last:]]
156
+ ```
157
+
158
+ - `messages[0]` — the original system/first message is always preserved.
159
+ - `SystemMessage(...)` — a placeholder that signals to the model that prior context was trimmed.
160
+ - `messages[-keep_last:]` — the most recent `keep_last` messages (default 10) are retained verbatim.
161
+
162
+ **Constructor:**
163
+
164
+ ```python
165
+ ContextCompactionMiddleware(
166
+ max_tokens: int = 32_000, # trigger threshold
167
+ keep_last: int = 10, # messages to retain from tail
168
+ )
169
+ ```
170
+
171
+ **Metadata written on compaction:**
172
+
173
+ ```python
174
+ context.metadata["compaction_applied"] = True
175
+ context.metadata["original_message_count"] = len(messages)
176
+ context.metadata["compacted_message_count"] = len(compacted)
177
+ ```
178
+
179
+ **Usage:**
180
+
181
+ ```python
182
+ from agentbyte.middleware import ContextCompactionMiddleware
183
+
184
+ agent = Agent(
185
+ name="researcher",
186
+ model_client=llm,
187
+ tools=[...],
188
+ middlewares=[ContextCompactionMiddleware(max_tokens=16_000, keep_last=8)],
189
+ )
190
+ ```
191
+
192
+ Combine with `AgentAsTool` for maximum reduction: the specialist compacts internally while the coordinator only ever sees the `result_strategy`-filtered summary.
193
+
194
+ ### OperationType Enum
195
+
196
+ **File:** `src/agentbyte/middleware/base.py`
197
+
198
+ `OperationType(str, Enum)` replaces bare string literals for the `operation` field on `MiddlewareContext`. Values remain plain strings so existing middleware that checks `context.operation == "model_call"` continues to work unchanged.
199
+
200
+ ```python
201
+ class OperationType(str, Enum):
202
+ MODEL_CALL = "model_call"
203
+ TOOL_CALL = "tool_call"
204
+ MEMORY_ACCESS = "memory_access"
205
+ APPROVAL = "approval"
206
+ ```
207
+
208
+ The agent now passes `OperationType.MODEL_CALL` and `OperationType.TOOL_CALL` into `middleware_chain.execute_stream(...)`. Middleware can branch on either the enum or the string — both compare equal.
103
209
 
104
210
  ---
105
211
 
@@ -133,6 +239,14 @@ Common checkpoints:
133
239
  - [x] Resume after `approve_tool(...)`
134
240
  - [x] Handle denial after `deny_tool(...)`
135
241
 
242
+ ### ✅ Context Engineering (Chapter 4.12)
243
+
244
+ - [x] `ContextCompactionMiddleware` — model-call intercept with token estimation and tail-keep strategy
245
+ - [x] `OperationType` enum — canonical operation constants, backward-compatible with plain strings
246
+ - [x] Context isolation via `AgentAsTool` + `result_strategy` (Chapter 4.11)
247
+ - [x] Agent-managed memory via `MemoryTool` 6-command CRUD (Chapter 4.8)
248
+ - [ ] Tool result filtering hook inside `_execute_tool_call` (optional, future)
249
+
136
250
  ---
137
251
 
138
252
  ## Summary
@@ -164,12 +278,13 @@ For memory and middleware deep dives, use the dedicated topic files listed at th
164
278
  - base memory abstractions and models
165
279
  - list/file memory implementations
166
280
  - tests and examples
281
+ - `MemoryTool` / `MemoryBackend` with picoagents 6-command set (`view`, `create`, `str_replace`, `insert`, `delete`, `rename`)
282
+ - path-traversal sandbox and security validation
167
283
 
168
284
  #### Remaining Gaps
169
285
 
170
286
  - [ ] component serialization parity for memory classes/configs
171
287
  - [ ] vector memory backend(s)
172
- - [ ] agent-managed memory tool (`MemoryTool`) and security-tested file operations
173
288
 
174
289
  ### Middleware
175
290
 
@@ -181,12 +296,14 @@ For memory and middleware deep dives, use the dedicated topic files listed at th
181
296
  - middleware `execute_stream(...)` parity baseline
182
297
  - middleware event propagation in agent streaming path
183
298
  - middleware pause handling (`ToolApprovalEvent`) in agent loop
299
+ - `OperationType(str, Enum)` — canonical operation constants with backward compat
300
+ - `ContextCompactionMiddleware` — model-call token budget trimming (Chapter 4.12.3)
184
301
 
185
302
  #### Remaining Gaps
186
303
 
187
304
  - [ ] approval middleware (optional centralization)
188
305
  - [ ] caching middleware (optional)
189
- - [ ] operation-level observability conventions for middleware events
306
+ - [ ] tool result filtering hook inside agent execution loop (Chapter 4.12.3)
190
307
 
191
308
  ### Observability / Production
192
309
 
@@ -1571,6 +1571,176 @@ if __name__ == "__main__":
1571
1571
 
1572
1572
  ---
1573
1573
 
1574
+ ## Part 6: MemoryTool — Agent-Managed Persistent Memory
1575
+
1576
+ ### Motivation
1577
+
1578
+ Agents often need to remember information across sessions or across many turns within a single session. There are two memory models:
1579
+
1580
+ - **Application-managed memory** (`BaseMemory` subclasses): the framework decides what to store and retrieve — transparent to the agent.
1581
+ - **Agent-managed memory** (`MemoryTool`): the agent explicitly calls a tool to read, write, and organise its own knowledge files — the agent is in full control.
1582
+
1583
+ `MemoryTool` implements the second model. It mirrors the six file-system-style commands from Chapter 4.8.3 / picoagents `_memory_tool.py`.
1584
+
1585
+ ### Architecture: Two Classes
1586
+
1587
+ ```
1588
+ MemoryBackend ← pure Python, no async, operates on the real filesystem
1589
+
1590
+ MemoryTool ← BaseTool subclass, wraps MemoryBackend, async execute()
1591
+ ```
1592
+
1593
+ Separating storage logic (`MemoryBackend`) from the tool interface (`MemoryTool`) makes each class easy to test independently and allows future backends (e.g. in-memory, remote) without changing the tool layer.
1594
+
1595
+ ### The Six Commands
1596
+
1597
+ | Command | Required params | Optional params | Purpose |
1598
+ |---|---|---|---|
1599
+ | `view` | `path` | — | Read file content; list directory; `"/"` lists entire root |
1600
+ | `create` | `path`, `file_text` | — | Write (or overwrite) a file; creates parent dirs automatically |
1601
+ | `str_replace` | `path`, `old_str`, `new_str` | — | Replace **first occurrence** of `old_str` with `new_str` |
1602
+ | `insert` | `path`, `insert_line`, `insert_text` | — | Insert text after 1-based line number |
1603
+ | `delete` | `path` | — | Remove a file |
1604
+ | `rename` | `path`, `new_path` | — | Move / rename a file within the sandbox |
1605
+
1606
+ ### Security: Sandbox Model
1607
+
1608
+ All paths are validated by `MemoryBackend._validate_path()`:
1609
+
1610
+ ```python
1611
+ def _validate_path(self, path: str) -> Path:
1612
+ normalized = path.strip().lstrip("/") # strip leading slashes
1613
+ if not normalized:
1614
+ raise ValueError("path cannot be empty")
1615
+ full_path = (self.base_path / normalized).resolve()
1616
+ try:
1617
+ full_path.relative_to(self.base_path) # raises if outside
1618
+ except ValueError as exc:
1619
+ raise ValueError("Access denied: path is outside memory directory") from exc
1620
+ return full_path
1621
+ ```
1622
+
1623
+ **Key points:**
1624
+ - Leading `/` is stripped — `"/etc/passwd"` becomes `"etc/passwd"` inside the sandbox, not the real `/etc/passwd`.
1625
+ - `Path.resolve()` expands `..` — `"../../etc/passwd"` resolves outside `base_path` and raises `ValueError`.
1626
+ - `view("/")` is special-cased **before** `_validate_path` is called, so it lists the root without hitting the empty-path guard.
1627
+
1628
+ ### MemoryBackend Key Design Decisions
1629
+
1630
+ **`create` (auto-mkdir):**
1631
+ ```python
1632
+ def create(self, path: str, file_text: str) -> str:
1633
+ target = self._validate_path(path)
1634
+ target.parent.mkdir(parents=True, exist_ok=True) # ← auto-create subdirs
1635
+ target.write_text(file_text, encoding="utf-8")
1636
+ return f"Created: {path}"
1637
+ ```
1638
+
1639
+ **`str_replace` (first occurrence only):**
1640
+ ```python
1641
+ def str_replace(self, path: str, old_str: str, new_str: str) -> str:
1642
+ content = target.read_text(encoding="utf-8")
1643
+ if old_str not in content:
1644
+ raise ValueError(f"String not found in file: {old_str!r}")
1645
+ updated = content.replace(old_str, new_str, 1) # ← count=1
1646
+ target.write_text(updated, encoding="utf-8")
1647
+ return f"Replaced in: {path}"
1648
+ ```
1649
+ Replacing only the *first* occurrence is intentional: it makes the edit deterministic and avoids unintended bulk replacements — the same design choice picoagents makes.
1650
+
1651
+ **`insert` (1-based line numbers):**
1652
+ ```python
1653
+ def insert(self, path: str, insert_line: int, insert_text: str) -> str:
1654
+ lines = target.read_text(encoding="utf-8").splitlines(keepends=True)
1655
+ # insert_line=0 → prepend; insert_line=len(lines) → append
1656
+ lines.insert(insert_line, text)
1657
+ target.write_text("".join(lines), encoding="utf-8")
1658
+ ```
1659
+ Python's `list.insert(i, v)` treats `i` as the index *before* which to insert, so `insert_line=3` places text after line 3 (0-indexed slot 3 = after the 3rd line). The guard `0 <= insert_line <= len(lines)` keeps the range valid.
1660
+
1661
+ ### MemoryTool Parameters Schema
1662
+
1663
+ The tool exposes all six commands as a single `command` enum plus optional per-command fields:
1664
+
1665
+ ```python
1666
+ @property
1667
+ def parameters(self) -> Dict[str, Any]:
1668
+ return {
1669
+ "type": "object",
1670
+ "properties": {
1671
+ "command": {
1672
+ "type": "string",
1673
+ "enum": ["view", "create", "str_replace", "insert", "delete", "rename"],
1674
+ },
1675
+ "path": {"type": "string"},
1676
+ "file_text": {"type": "string"}, # create
1677
+ "old_str": {"type": "string"}, # str_replace
1678
+ "new_str": {"type": "string"}, # str_replace
1679
+ "insert_line": {"type": "integer"}, # insert
1680
+ "insert_text": {"type": "string"}, # insert
1681
+ "new_path": {"type": "string"}, # rename
1682
+ },
1683
+ "required": ["command"], # ← only command is always required
1684
+ }
1685
+ ```
1686
+
1687
+ Only `command` is globally required; per-command fields are validated at runtime inside `execute()` via `_require_str()` / `isinstance(insert_line, int)`.
1688
+
1689
+ ### Usage Example
1690
+
1691
+ ```python
1692
+ import asyncio
1693
+ from agentbyte.tools import MemoryTool
1694
+
1695
+ async def demo():
1696
+ mem = MemoryTool(base_path="/tmp/agent_memory")
1697
+
1698
+ # Create a file
1699
+ r = await mem.execute({"command": "create", "path": "notes.md",
1700
+ "file_text": "# Notes\nFirst note."})
1701
+ print(r.result) # Created: notes.md
1702
+
1703
+ # Read it back
1704
+ r = await mem.execute({"command": "view", "path": "notes.md"})
1705
+ print(r.result) # # Notes\nFirst note.
1706
+
1707
+ # Patch a line
1708
+ r = await mem.execute({"command": "str_replace", "path": "notes.md",
1709
+ "old_str": "First note.", "new_str": "Updated note."})
1710
+ print(r.result) # Replaced in: notes.md
1711
+
1712
+ # Insert a new line after line 1
1713
+ r = await mem.execute({"command": "insert", "path": "notes.md",
1714
+ "insert_line": 1, "insert_text": "Second note."})
1715
+
1716
+ # List all memory files
1717
+ r = await mem.execute({"command": "view", "path": "/"})
1718
+ print(r.result) # ['notes.md']
1719
+
1720
+ # Rename
1721
+ r = await mem.execute({"command": "rename", "path": "notes.md",
1722
+ "new_path": "archive/notes.md"})
1723
+
1724
+ # Delete
1725
+ r = await mem.execute({"command": "delete", "path": "archive/notes.md"})
1726
+
1727
+ asyncio.run(demo())
1728
+ ```
1729
+
1730
+ ### Difference from Old (4-Command) MemoryTool
1731
+
1732
+ | Old command | New equivalent |
1733
+ |---|---|
1734
+ | `read_knowledge` | `view` |
1735
+ | `write_knowledge` | `create` |
1736
+ | `list_knowledge` | `view` on directory path (or `view("/")`) |
1737
+ | `delete_knowledge` | `delete` |
1738
+ | *(missing)* | `str_replace` |
1739
+ | *(missing)* | `insert` |
1740
+ | *(missing)* | `rename` |
1741
+
1742
+ ---
1743
+
1574
1744
  ## Part 5: Parity Checklist with picoagents
1575
1745
 
1576
1746
  ### ✅ BaseTool Interface
@@ -1615,6 +1785,20 @@ if __name__ == "__main__":
1615
1785
  - [x] Streaming tool outputs are forwarded and finalized into `ToolMessage` via `ToolResult`
1616
1786
  - [x] Covered by agent tests (`tests/agents/test_agent_basic.py`)
1617
1787
 
1788
+ ### ✅ MemoryTool (Agent-Managed Memory)
1789
+ - [x] `MemoryBackend` class — pure Python, no async, real filesystem operations
1790
+ - [x] `MemoryTool` class — `BaseTool` subclass, async `execute()` dispatch
1791
+ - [x] `view` — file content OR directory listing; `view("/")` lists full root
1792
+ - [x] `create` — write/overwrite file, parent dirs auto-created
1793
+ - [x] `str_replace` — replaces **first occurrence** only (deterministic)
1794
+ - [x] `insert` — insert text after 1-based line number (0 = prepend, `len` = append)
1795
+ - [x] `delete` — remove file (directory deletion not allowed via this command)
1796
+ - [x] `rename` — move/rename within sandbox, validates both src and dst
1797
+ - [x] `_validate_path` sandbox — strips leading `/`, `resolve()` + `relative_to()` check
1798
+ - [x] `_require_str` helper — uniform required-field validation in `execute()`
1799
+ - [x] Parameters schema has `command` as only globally-required field
1800
+ - [x] 30+ tests in `tests/tools/test_memory_tool.py` covering all commands and security edge cases
1801
+
1618
1802
  ---
1619
1803
 
1620
1804
  ## Summary
@@ -1627,7 +1811,6 @@ This study document provides:
1627
1811
  4. **Usage Examples**: Practical demonstrations of all features
1628
1812
  5. **Validation**: Test script to verify correctness
1629
1813
  6. **Parity Checklist**: Verification that Agentbyte matches picoagents capabilities
1814
+ 7. **MemoryTool**: Agent-managed persistent file memory with 6-command picoagents-parity set, sandbox security, and full test coverage
1630
1815
 
1631
- The implementation is ready to be added to `src/agentbyte/tools/` following the code provided in Part 3.
1632
-
1633
- Tool system is implemented in `src/agentbyte/tools/` and integrated with `Agent` execution paths.
1816
+ Tool system is fully implemented in `src/agentbyte/tools/` and integrated with `Agent` execution paths.
@@ -16,7 +16,7 @@
16
16
  },
17
17
  {
18
18
  "cell_type": "code",
19
- "execution_count": 2,
19
+ "execution_count": 1,
20
20
  "id": "f7df8759",
21
21
  "metadata": {},
22
22
  "outputs": [],
@@ -38,14 +38,14 @@
38
38
  "id": "406d7dd7",
39
39
  "metadata": {},
40
40
  "source": [
41
- "## Understanding ToolResult\n",
41
+ "## Example 1a: Understanding ToolResult\n",
42
42
  "\n",
43
- "`ToolResult` is the standardized structure for all tool execution outcomes. It's a Pydantic BaseModel with immutable fields.\n"
43
+ "`ToolResult` is the standardized structure for all tool execution outcomes. It's a Pydantic BaseModel with immutable fields."
44
44
  ]
45
45
  },
46
46
  {
47
47
  "cell_type": "code",
48
- "execution_count": 3,
48
+ "execution_count": 2,
49
49
  "id": "17c1e655",
50
50
  "metadata": {},
51
51
  "outputs": [
@@ -112,7 +112,7 @@
112
112
  },
113
113
  {
114
114
  "cell_type": "code",
115
- "execution_count": 4,
115
+ "execution_count": 3,
116
116
  "id": "f414f5ac",
117
117
  "metadata": {},
118
118
  "outputs": [
@@ -149,20 +149,12 @@
149
149
  "id": "ba737a65",
150
150
  "metadata": {},
151
151
  "source": [
152
- "## Example 2: @tool Decorator (No Parentheses)"
153
- ]
154
- },
155
- {
156
- "cell_type": "markdown",
157
- "id": "28cf2bc4",
158
- "metadata": {},
159
- "source": [
160
- "## Example 1b: ToolResult and Error Handling"
152
+ "## Example 1b: Error Handling with FunctionTool"
161
153
  ]
162
154
  },
163
155
  {
164
156
  "cell_type": "code",
165
- "execution_count": 5,
157
+ "execution_count": 4,
166
158
  "id": "df267ff3",
167
159
  "metadata": {},
168
160
  "outputs": [
@@ -200,9 +192,17 @@
200
192
  "await test_error_handling()"
201
193
  ]
202
194
  },
195
+ {
196
+ "cell_type": "markdown",
197
+ "id": "fc9a8a3b",
198
+ "metadata": {},
199
+ "source": [
200
+ "## Example 2a: @tool Decorator (No Parentheses)"
201
+ ]
202
+ },
203
203
  {
204
204
  "cell_type": "code",
205
- "execution_count": 8,
205
+ "execution_count": 5,
206
206
  "id": "e1f229c0",
207
207
  "metadata": {},
208
208
  "outputs": [
@@ -234,9 +234,17 @@
234
234
  "print(f\"\\nDirect call: multiply_numbers(4, 5) = {direct_result}\")"
235
235
  ]
236
236
  },
237
+ {
238
+ "cell_type": "markdown",
239
+ "id": "bc29534f",
240
+ "metadata": {},
241
+ "source": [
242
+ "## Example 2b: Executing @tool Decorator"
243
+ ]
244
+ },
237
245
  {
238
246
  "cell_type": "code",
239
- "execution_count": 11,
247
+ "execution_count": 6,
240
248
  "id": "cf74f858",
241
249
  "metadata": {},
242
250
  "outputs": [
@@ -262,12 +270,12 @@
262
270
  "id": "366fb111",
263
271
  "metadata": {},
264
272
  "source": [
265
- "## Example 3: @tool Decorator (With Parentheses)"
273
+ "## Example 3a: @tool Decorator (With Parentheses)"
266
274
  ]
267
275
  },
268
276
  {
269
277
  "cell_type": "code",
270
- "execution_count": 12,
278
+ "execution_count": 7,
271
279
  "id": "56a451e5",
272
280
  "metadata": {},
273
281
  "outputs": [
@@ -322,9 +330,17 @@
322
330
  "print(f\"Approval Mode is ALWAYS: {reset_admin.approval_mode == ApprovalMode.ALWAYS}\")"
323
331
  ]
324
332
  },
333
+ {
334
+ "cell_type": "markdown",
335
+ "id": "91525edb",
336
+ "metadata": {},
337
+ "source": [
338
+ "## Example 3b: Executing Tools with Approval"
339
+ ]
340
+ },
325
341
  {
326
342
  "cell_type": "code",
327
- "execution_count": 13,
343
+ "execution_count": 8,
328
344
  "id": "1ca85e41",
329
345
  "metadata": {},
330
346
  "outputs": [