feat(grid-agent): add generic LLM tool loop (#119)
Some checks failed
CI / rust-skia (Rust only) (push) Successful in 2m50s
CI / required (push) Failing after 2m46s

This commit is contained in:
2026-08-17 20:45:56 +00:00
parent 1e1e95a58a
commit a46bc42a8f
11 changed files with 2365 additions and 6 deletions

View File

@@ -24,6 +24,8 @@ handles, the control sender, and observable receiver.
| Coordinator task | `ServiceHandle.tasks[1]` | exactly one | shared cancellation token, joined second |
| 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 |
| 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 |
| Authorized avatars | immutable `AgentConfig` set | 1,024 hard ceiling, lower configured limit | malformed, nil, duplicate, and wildcard input rejected |
| Configuration / secret file | loader | 64 KiB / 16 KiB | regular non-symlink file only |
@@ -65,6 +67,10 @@ shutdown.
- World changes cross only `WorldMutator::apply`, which always receives the
proposed call and an explicit `PolicyDecision`. This issue supplies no live
mutation implementation.
- LLM traffic crosses one exact configured URL through `LlmClient`. Redirects
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.
- Signals and console output belong to the binary. The reusable core relies on
no terminal, Unix socket, Unix signal, separator, or fixed platform path.
@@ -80,8 +86,14 @@ AgentService -> bounded Tokio channels/tasks -> injected GridBackend
typed config/events/policy boundaries live-grid feature boundary
| |
+-----------------> libremetaverse-types +--> libremetaverse::GridClient
avatar session -> bounded ToolLoop -> exact-endpoint LlmClient
|
+-> validated ToolExecutor boundary
```
The package has no build script or direct native dependency. The focused
`dependency_policy` test rejects subprocess launch sites, unsafe/native ABI
source, build scripts, and unreviewed direct dependency names in this package.
The precise LLM compatibility and cancellation contract is documented in
[`grid-agent-llm.md`](grid-agent-llm.md).

68
docs/grid-agent-llm.md Normal file
View File

@@ -0,0 +1,68 @@
# Grid-agent LLM transport and tool loop
The grid agent talks to one operator-supplied OpenAI-compatible chat-completion
endpoint. `llm.endpoint_url` is the complete request URL and `llm.api_key` is
the bearer credential. The client sends `POST` to that exact URL. It does not
append a path, select a provider, discover models, send a model field, or retry
against another service. Endpoint routing and model selection remain operator
responsibilities.
## Compatibility envelope
Requests contain only the normalized `messages` and `tools` members. Message
roles map to `system`, `user`, `assistant`, and `tool`. Content is an array of
`text` or `image_url` parts. Tool definitions use the standard function name,
description, and JSON-schema parameters envelope. Tool observations carry the
original `tool_call_id`. The client accepts the first response choice, optional
text, function tool calls, and optional token usage. Unknown response members
are ignored; missing required members, unsupported empty choices, malformed
JSON, duplicate call IDs, and oversized values return typed errors.
The HTTP client uses Rustls and performs no automatic content decompression
because no compression feature is enabled. Redirect following is disabled, so
a bearer credential can never be forwarded to either a same-origin or
cross-origin redirect target. Connect, whole-request, response-idle, pool-idle,
and total elapsed timeouts are independently bounded. Prompt bytes, response
bytes, concurrent requests, retry count, and retry delay also have validated
hard ceilings. Only transport failures, timeouts, and HTTP 408, 425, 429, 500,
502, 503, or 504 are retryable. `Retry-After` seconds are honored only up to the
configured delay ceiling; otherwise bounded deterministic jitter is used.
## Tool-loop safety
`ToolLoop` validates every registered schema before use. A proposed call must
have a unique session call ID, a registered name, valid JSON arguments, and
arguments matching that registered schema before it reaches `ToolExecutor`.
Unknown names, malformed arguments, and schema mismatches become bounded tool
observations for the next model turn and never call the executor. Tool
executions are sequential, making the simultaneous execution ceiling one.
Turn count, calls per turn and session, history messages, history bytes,
wall-clock time, and collected usage records are bounded. When history exceeds its
configured envelope, an injected deterministic summarizer may compact it. A
failed summarizer inserts a fixed bounded truncation marker and retains recent
context. An ambiguous mutating result terminates the loop immediately; it is
never retried or turned into another model request.
Shutdown, operator cancellation, disconnect, session expiry, and avatar-session
replacement use cancellation plus `SessionGeneration`. Superseding a generation
cancels its in-flight HTTP request or executor and prevents a late result from
starting another action. Request and correlation IDs are deterministic and the
completion exposes token usage, latency, and attempt count. The final returned
message is the model-authored action summary. The wire mapping has no reasoning
or chain-of-thought field and does not retain or expose hidden reasoning.
## Focused verification
The `llm_transport` integration suite runs only against bounded loopback fake
endpoints. It covers exact URL and authorization behavior, secret redaction,
fragmented responses, images and tool schemas, malformed and oversized input,
redirect refusal, transient-only retry, cancellation and timeout races,
concurrency, complete tool round trips, invalid-call observations, endless
loops, duplicate IDs, ambiguous mutation, supersession, and safe history
compaction.
```sh
cargo test --locked -p metacrate-grid-agent --test llm_transport
cargo clippy --locked -p metacrate-grid-agent --all-targets -- -D warnings
```