Low-Level Runtime
Use query() and drainQuery() directly in @namzu/sdk when you need verification gates, sandbox providers, plugin wiring, event streaming, or query-only runtime controls.
ReactiveAgent.run() is the best default for most users, but it is intentionally not the entire kernel surface. The lower-level runtime entrypoints are query() and drainQuery(). Use them when you need features that live below the high-level agent wrappers.
1. When to Drop Below ReactiveAgent
Use the low-level runtime when you need:
verificationGatepolicy before tool execution- a
sandboxProviderthat injects a real sandbox into tool context - direct
RunEventstreaming - plugin manager, task router, agent bus, or compaction wiring
- custom resume-handler behavior for HITL review or checkpoints
If you only need messages, tools, provider, IDs, and a final result, stay with ReactiveAgent.run().
2. ReactiveAgent.run() vs drainQuery()
| Surface | Best for | Notable limits |
|---|---|---|
ReactiveAgent.run() | Standard app integrations and quickstarts | Does not expose query-only runtime fields such as verificationGate or sandboxProvider |
drainQuery() | Low-level runtime control with a final AgentRun result | You supply more runtime wiring yourself |
query() | Full async-generator control over every emitted event | You manage iteration over the generator directly |
3. Minimal drainQuery() Example
This example shows the main low-level boundary:
runConfigstill carries model, budget, and permission settings- query-only fields such as
verificationGateandsandboxProviderlive beside that config drainQuery()still returns the same finalAgentRunshape that high-level agent flows assemble
4. What drainQuery() Gives You
drainQuery() is the convenience wrapper around query():
- it consumes the async generator for you
- it forwards every
RunEventto an optional listener - it returns the final
AgentRun - it falls back to
autoApproveHandlerif you omitresumeHandler
That makes it the best low-level entrypoint when you still want one final result object.
5. Use query() for Generator-Level Control
If you need full control over the event stream, use query() directly:
Use this pattern when a transport layer or UI needs every incremental event as it happens.
6. Query-Only Fields You Do Not Get Through ReactiveAgent.run()
QueryParams exposes extra runtime controls that are not currently surfaced on ReactiveAgentConfig:
| Field | Purpose |
|---|---|
verificationGate | Rule-based allow, deny, or review decisions before tool execution |
sandboxProvider | Create a sandbox for the run and inject it into tool context |
pluginManager | Run plugin hooks and plugin-contributed runtime behavior |
taskRouter | Task-specific model routing |
agentBus | Concurrency coordination and lock-style runtime controls |
compactionConfig | Working-state compaction and message compression policy |
contextCache | Prompt cache and context reuse controls |
That is the main reason this page exists: these are real public runtime features, but they are lower-level than the first-run agent API.
7. Resume Handlers and HITL
Low-level runtime control is also where human-in-the-loop policy becomes explicit.
Use autoApproveHandler only when the runtime should continue automatically.
8. Verification and Sandbox Boundaries
Two low-level runtime fields are easy to confuse:
| Field | Role |
|---|---|
verificationGate | Decide whether a tool call should proceed |
sandboxProvider | Constrain what sandbox-aware tools can do if the call proceeds |
This separation matters operationally:
- verification is policy
- sandboxing is containment
Both are lower-level runtime concerns, which is why they are wired through query() and drainQuery() instead of defineTool() alone.
9. Event Streaming and SSE Mapping
query() and drainQuery() emit normalized RunEvent values. If you need a wire-friendly event shape, use mapRunToStreamEvent(event, runId).
Important nuance:
- many incremental runtime events map cleanly to wire events
- final completion still comes from the async generator return value or the
drainQuery()result
That means stream transport code usually needs both:
- mapped incremental events during execution
- the final
AgentRunwhen execution completes
10. Common Mistakes
| Mistake | Why it breaks |
|---|---|
assuming ReactiveAgent.run() exposes every runtime field | query-only controls such as verificationGate and sandboxProvider are lower-level |
forgetting resumeHandler when calling query() | query() requires it directly, unlike drainQuery() |
skipping workingDirectory | filesystem tools and path layout lose their stable base path |
treating mapRunToStreamEvent() as the final result channel | completion still comes from generator completion or drainQuery() |