feat(grid-agent): isolate conversation memory (#122)
This commit is contained in:
@@ -27,6 +27,7 @@ observable receivers.
|
||||
| Offline session work | live supervisor | 1,024 hard / `reconnect.offline_work_capacity` | read-only queue; mutations rejected while not ready |
|
||||
| Body / message | typed boundary owners | 8 MiB / 64 KiB hard ceilings, with lower configured limits | rejected before enqueue |
|
||||
| Conversation / tool calls | request owner | 256 messages / 64 calls, with lower configured limits | rejected before request |
|
||||
| Per-avatar conversation memory | `ConversationStore` mutex | 4,096 sessions / 64 MiB hard, lower `conversation` limits | monotonic expiry, deterministic compaction/LRU eviction |
|
||||
| LLM request slots | shared `LlmClient` semaphore | 256 hard / configured concurrent requests | async acquire or cancellation |
|
||||
| Reasoning/tool session | `ToolLoop` caller | 32 turns / 256 calls hard, with lower configured limits | total timeout, cancellation, or supersession |
|
||||
| Policy tools / approvals / schedules | `PolicyGateway` mutex | 64 tools / 4,096 approval records / 1,024 scheduler grants hard | deny before opaque authorization |
|
||||
@@ -84,6 +85,10 @@ cleanup, fencing old events and late LLM/tool results. See
|
||||
are refused, response bodies are bounded while streaming, bearer secrets are
|
||||
redacted, and provider/model discovery does not exist. Proposed calls cross
|
||||
`ToolExecutor` only after registered-name and schema validation.
|
||||
- Conversation context is keyed by immutable avatar UUID and either public chat
|
||||
or direct IM. Group channels are not representable. The LLM projection can
|
||||
retrieve only one exact key, and recovered/untrusted summaries remain user-role
|
||||
prompt data rather than system authority.
|
||||
- Signals and console output belong to the binary. The reusable core relies on
|
||||
no terminal, Unix socket, Unix signal, separator, or fixed platform path.
|
||||
|
||||
@@ -116,3 +121,5 @@ The origin/capability matrix and opaque mutation boundary are documented in
|
||||
[`grid-agent-policy.md`](grid-agent-policy.md).
|
||||
The live lifecycle and generation contract is documented in
|
||||
[`grid-agent-session.md`](grid-agent-session.md).
|
||||
The conversation isolation and persistence contract is documented in
|
||||
[`grid-agent-conversation.md`](grid-agent-conversation.md).
|
||||
|
||||
55
docs/grid-agent-conversation.md
Normal file
55
docs/grid-agent-conversation.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Grid-agent conversation memory
|
||||
|
||||
`ConversationStore` owns bounded conversational context independently of the
|
||||
grid connection generation. Its key is the immutable avatar UUID plus exactly
|
||||
one channel kind: public chat or direct IM. Group and conference conversations
|
||||
are deliberately not representable. Public sessions expire at exactly 30
|
||||
minutes of monotonic inactivity and direct IM sessions at exactly 24 hours. A
|
||||
subsequent turn creates a cryptographically random new session ID and receives
|
||||
none of the expired transcript.
|
||||
|
||||
The mutex-protected store assigns one sequence order to concurrent turns. It
|
||||
stores wall-clock timestamps, normalized avatar/agent/tool roles, visible agent
|
||||
responses, bounded tool summaries, and externally meaningful action results.
|
||||
Avatar input and untrusted tool data remain untrusted when compacted. There is
|
||||
no system-message, model-scratchpad, hidden-reasoning, credential, capability
|
||||
URL, or binary-asset input variant. Text is bounded and sensitive URL/token
|
||||
forms are redacted before allocation in the store.
|
||||
|
||||
Configured limits lower hard ceilings for active sessions, turns, per-session
|
||||
bytes, aggregate bytes, tool-result count and size, summary bytes, and total
|
||||
snapshot storage. Old records compact deterministically into a smaller factual
|
||||
summary followed by recent context. Session and aggregate pressure evict the
|
||||
least-recently-active key with UUID/channel tie-breaking. Expiry, compaction,
|
||||
eviction, quarantine, and operator deletion publish stable reason codes through
|
||||
a bounded event queue.
|
||||
|
||||
`list_metadata` returns session ID, UUID, channel, timestamps, turn count, and
|
||||
byte count but never content. Operators can delete or expire an exact key.
|
||||
`context` is the separate LLM-facing projection and can read only that key;
|
||||
untrusted summaries are emitted as explicitly marked user-role data.
|
||||
|
||||
Persistence is local and opt-in through `conversation.persistence_enabled`.
|
||||
`ConversationStore::from_config` uses `storage_path/conversations`; callers
|
||||
flush at their durability boundary. A flush writes and syncs a new immutable,
|
||||
versioned generation before an atomic rename, then retains only generations
|
||||
whose aggregate bytes fit the configured storage ceiling. Linux and other Unix
|
||||
targets force directory mode 0700 and file mode 0600. Rust standard library
|
||||
does not expose a portable Windows ACL editor, so Windows emits the explicit
|
||||
`PermissionsNotVerified` event and operators must restrict the directory ACL to
|
||||
the service identity.
|
||||
|
||||
Restart recovery validates schema, UUIDs, IDs, unique sequences, timestamps,
|
||||
roles, bounds, and redaction before any record can become LLM context. A
|
||||
truncated, corrupt, oversized, or unsupported generation is quarantined and an
|
||||
older valid generation is tried. If the wall clock moved backwards, recovered
|
||||
age is zero; forward elapsed time is applied to the channel TTL. Neither case
|
||||
can grant policy authority or prevent startup.
|
||||
|
||||
Focused gates:
|
||||
|
||||
```sh
|
||||
cargo test --locked -p metacrate-grid-agent --lib conversation_tests
|
||||
cargo test --locked -p metacrate-grid-agent --test conversation_memory
|
||||
cargo clippy --locked -p metacrate-grid-agent --all-targets -- -D warnings
|
||||
```
|
||||
Reference in New Issue
Block a user