You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: introduce pluggable memory providers for agent workflows (#29)
* feat: introduce pluggable memory providers for agent workflows
- Added `MemoryProvider` trait to allow customizable state compaction strategies.
- Implemented default `SlidingWindowMemory` provider to maintain legacy behavior.
- Updated `AgentWorkerBuilder` to support configurable memory providers.
- Removed hardcoded compaction logic from `workflow.rs` and `state.rs`, and moved to `memory.rs`.
* feat: add `tunable_memory_agent` example and enhance documentation for memory providers
- Introduced the `tunable_memory_agent` example to demonstrate custom and aggressive `SlidingWindowMemory` configurations.
- Expanded README with a "Pluggable memory backends" section describing memory provider traits and usage.
- Added documentation for `MemoryProvider` determinism requirements in `AGENTS.md`.
- Enforced consistency checks for `MemoryProvider` across workers in multi-worker setups.
Copy file name to clipboardExpand all lines: AGENTS.md
+35-6Lines changed: 35 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,22 +38,23 @@ Override the LLM endpoint with `OPENAI_BASE_URL` (defaults to `https://api.opena
38
38
39
39
**Module map:**
40
40
-[src/lib.rs](src/lib.rs) — module re-exports.
41
-
-[src/builder.rs](src/builder.rs) — `AgentWorkerBuilder` fluent builder; wires LLM + tools into a Temporal `Worker`.
41
+
-[src/builder.rs](src/builder.rs) — `AgentWorkerBuilder` fluent builder; wires LLM + tools + memory provider into a Temporal `Worker`.
42
42
-[src/workflow.rs](src/workflow.rs) — `AgentWorkflow` with `#[run]`, `#[signal] add_user_message`, `#[query] get_state`, `#[query] turn_count`. Owns the ReAct loop.
43
43
-[src/activities.rs](src/activities.rs) — `AgentActivities::llm_chat` and `AgentActivities::execute_tool`. The *only* place LLM providers and tool implementations execute.
44
44
-[src/llm.rs](src/llm.rs) — translation between local `Message`/`ToolSchema` types and AutoAgents `ChatMessage`/`LlmTool`; native-tool-call parsing with fenced-JSON fallback. The only file that touches `autoagents_llm` types in the hot path (`src/llm.rs:6`).
-[src/memory.rs](src/memory.rs) — `MemoryProvider` trait, default `SlidingWindowMemory` impl, and the `compact_sliding_window` kernel. Pluggable compaction strategy consulted by the workflow before every turn.
46
47
-[src/tool.rs](src/tool.rs) — `ToolRegistry` (immutable name→impl map) and its builder.
47
48
-[src/error.rs](src/error.rs) — `AgentError` with `is_retryable()` to distinguish transient vs. permanent.
-[src/prelude.rs](src/prelude.rs) — convenience re-exports including AutoAgents traits (`ToolT`, `LLMProvider`, `ToolRuntime`, `ToolCallError`) and memory types (`MemoryProvider`, `SlidingWindowMemory`).
49
50
50
-
**Public API surface (what a user actually touches):**`AgentWorkerBuilder`, `AgentWorkflow`, `AgentInput`/`AgentOutput`, `ToolRegistry`. Users supply their own `Arc<dyn LLMProvider>` and `Arc<dyn ToolT>` from AutoAgents.
51
+
**Public API surface (what a user actually touches):**`AgentWorkerBuilder`, `AgentWorkflow`, `AgentInput`/`AgentOutput`, `ToolRegistry`, `MemoryProvider`/`SlidingWindowMemory`. Users supply their own `Arc<dyn LLMProvider>` and `Arc<dyn ToolT>` from AutoAgents.
51
52
52
53
**Non-obvious behaviors to preserve when editing:**
53
54
54
-
-**History compaction.**When `AgentState::history.len()` exceeds `CONTINUE_AS_NEW_THRESHOLD = 200` (`src/workflow.rs:36`), the workflow calls `continue_as_new` with a compacted state: summary prepended to the system prompt, last 20 messages kept (`src/state.rs::compact`). Any change to the message shape needs to round-trip through `compact()`.
55
+
-**History compaction is pluggable.**The workflow consults `MemoryProvider::should_compact` before every turn; on `true` it calls `MemoryProvider::compact` and `continue_as_new` with the returned `AgentInput`. Default provider is `SlidingWindowMemory` (`compact_threshold = 200`, `keep_recent = 20`), preserving the legacy hardcoded behavior. Override via `AgentWorkerBuilder::memory(Arc::new(SlidingWindowMemory::new().with_compact_threshold(N).with_keep_recent(K)))` or supply your own `Arc<dyn MemoryProvider>`. Trait impls MUST be pure and sync — they run inside the deterministic workflow body. The kernel summarizer lives at `src/memory.rs::compact_sliding_window`; any change to the `Message` shape needs to round-trip through it.
55
56
-**Tool error semantics.** Tool-side failures return `Ok(ToolResult { error: Some(...) })` so the LLM can see and recover from them (`src/activities.rs:59-88`). Only infrastructure errors (missing tool, serde failure) surface as activity `Err`, which Temporal retries.
56
-
-**`WORKER_TOOL_CATALOG`.**A process-global `OnceCell` set once at worker init in `build_worker` (`src/builder.rs:34`). The deterministic workflow body reads it on every replay, so it must be set before the worker starts and never mutated after.
57
+
-**Process-global worker config (`WORKER_TOOL_CATALOG`, `WORKER_MEMORY`).**Two `OnceCell`s in `src/builder.rs` published by `build_worker`. The deterministic workflow body reads them on every replay, so they must be set before the worker starts and never mutated after. Building a second worker in the same process with a *different* catalog (compared by `PartialEq`) or a different memory `Arc` (compared by `Arc::ptr_eq`) returns `AgentError::Other` — multi-worker setups in one process must share the same `Arc<dyn MemoryProvider>` and register identical tools in the same order.
57
58
-**Activity timeouts.** Set inside `AgentWorkflow::run` at `src/workflow.rs:66-74`: LLM activity 120s start-to-close / 30s heartbeat, tool activity **3600s** start-to-close (generous on purpose — supports human-in-the-loop tools that block on stdin/HTTP/async-completion).
58
59
-**Mid-conversation user input.** The `add_user_message` signal pushes into `pending_user_messages`, drained at the top of each loop iteration (`src/workflow.rs:145-152`). Don't mutate `history` directly from signal handlers — that races with the in-flight `llm_chat` activity.
59
60
-**Dual LLM response parsing.**`src/llm.rs` tries native tool calls first, then falls back to a fenced `\`\`\`tool_calls` JSON block so non-OpenAI providers still work.
@@ -62,6 +63,34 @@ Override the LLM endpoint with `OPENAI_BASE_URL` (defaults to `https://api.opena
62
63
- Tools must be side-effect-safe on retry.
63
64
-`LLMProvider` and `ToolT` impls must be `Send + Sync + 'static`.
64
65
- Never invoke `LLMProvider` or `ToolT` from workflow code — only from activities.
66
+
-`MemoryProvider` impls must be pure, sync, and stateless (config-only) — `should_compact` and `compact` are called inside the workflow body and must return identical results on replay for the same `AgentState`.
67
+
68
+
## Documentation maintenance
69
+
70
+
After any change large enough to alter the public API surface, observable
71
+
behavior, defaults, or feature set, update the user-facing docs in the same
72
+
PR — stale docs are worse than no docs because they actively mislead.
73
+
Specifically:
74
+
75
+
-**[AGENTS.md](AGENTS.md)** (this file) — update the module map, public API
76
+
surface line, non-obvious behaviors, and determinism contract whenever any
77
+
of them change. Add new module entries here as soon as you create them.
78
+
-**[README.md](README.md)** — update the features list, examples list,
79
+
user-facing determinism contract, and any code snippets affected by API
80
+
changes. If you add a feature with its own knobs (caching, fallback,
81
+
memory backends, etc.), give it a short dedicated section like
82
+
"Pluggable memory backends" so users can find it without reading the
83
+
source.
84
+
-**[examples/](examples/)** — when adding a new top-level feature, ship
85
+
one runnable example that exercises it (model the new example on
86
+
`simple_math_agent` — same worker/client/status mode template, same
87
+
three-terminal flow). Register it in `Cargo.toml` under a new
88
+
`[[example]]` entry and add a one-line description plus a runnable
89
+
invocation to README.md's "Running the examples" block.
90
+
91
+
Rule of thumb: if you touched `src/lib.rs` re-exports, `src/prelude.rs`, or
92
+
`AgentWorkerBuilder`'s public API, you owe at least one edit to each of the
0 commit comments