feat(local): self-introspecting local-agent sandbox evals - #234
feat(local): self-introspecting local-agent sandbox evals#234jakepresent wants to merge 56 commits into
Conversation
- exclude api_key_env name from inference fingerprint hash (CodeQL flag); use a boolean has-auth flag instead - sanitize non-streaming OpenAI endpoint raw response before persisting - record tool_calls-only assistant turns as tool_call evidence even when no following tool-result message arrives - parse standard streamed delta.tool_calls in addition to custom hermes.tool.progress events
|
@jakepresent — audit-only observations from a security/readiness pass on Main blocker — endpoint SSRF: Two sandbox items before landing local-exec: snapshot staging trusts CI is green and I didn't see (Trace note: endpoint execution doesn't currently wrap |
Add AgentRuntimeConfig + load_agent_config: an agent declares what to copy in a declarative YAML (real-machine paths; ASSERT owns translation). create_snapshot_from_config consumes it, applying a built-in secret floor (catches auth.json, the prior Hermes gap) plus config-declared excludes. Wire 'snapshot create --config <agent.yaml>' alongside the discovery path. Proven end-to-end against the real OpenClaw install via a hand-written config: 52,618 files to stable destinations, secrets excluded.
Three decisions for the agent-config snapshot path: - #1 copy blindly: remove node_modules/venv from the exclude floor so a runtime's own dependencies are preserved (a broken clone is worse than a big one). Floor now drops only secrets + pure churn (sessions/logs/.git). - #2 absolute-path excludes: agents emit real paths; normalize absolute excludes that fall under a root into root-relative globs so they fire. - #3 external_dependencies: new schema section, consumed by the snapshot as copy roots and recorded with kind=external_dependency. TDD: 4 new tests, 80 local pass. Verified end-to-end through the CLI against real Hermes paths.
Self-introspected agent configs declare a free-form id and omit explicit
root dests, so the OpenClaw descriptor must:
- locate the runtime package by content at the derived basename ('openclaw/'),
not only the canonical 'runtime/openclaw-package'
- skip the strict manifest-target equality check when the manifest was
produced by an agent config (source == 'agent_config'); --target selects
the descriptor and staged-content validation is the real guard
Unblocks the full discover->snapshot->sandbox->smoke->spec->run e2e on a
genuinely self-introspected OpenClaw config.
Generic --runtime-config launches died at the auth-proxy step because
RuntimeLaunchConfig had no rampart_root field, so the backend fell back
to Path('.') and could not find scripts/run_auth_proxy.py. Add the field
through the config, loader, and descriptor so a non-OpenClaw runtime
(e.g. Hermes) can point at the RAMPART scripts and clear that step.
A runtime's interpreter is often a symlink (e.g. a venv's bin/python -> an externally managed cpython). The copier dropped all symlinks, so the cloned runtime had no interpreter. Now file symlinks are dereferenced to real content; directory symlinks stay excluded (cycle/explosion safety); broken links are recorded, not copied.
A live agent tree churns under the snapshot walk (the agent is running while its files are copied). A path enumerated by rglob can disappear or become unreadable before copy2 runs. With symlink dereferencing this surfaced as FileNotFoundError that aborted the entire snapshot. Now such single-file races are recorded as 'source_unavailable' in excluded_files and skipped. TDD regression test included.
|
Fresh refresh after merging
Still treating this as design/demo evidence, not claiming the PR is ready to merge as one unsliced unit. |
|
Fresh RAMPART/Docker Sandbox validation on the same refreshed branch head (
This refreshes the stronger RAMPART/full-evidence path separately from the earlier |
Why this matters
Agent evaluation is moving from single model-call scoring toward whole-system evaluation: multi-turn workflows, tool use, traces, production monitoring, regression suites, and runtime guardrails. That shift is right, but most current approaches still test slices of the agent. Benchmarks test generic tasks. Observability tools inspect what happened after the fact. Guardrails validate specific controls. Endpoint evals test a model/API surface.
The gap is the configured agent itself. A real agent is not just a model endpoint; it is the runtime plus instruction files, memory, tools, local context, provider routing, credentials, and side-effect boundaries. That is where a lot of behavior comes from, and it is also where many failures only appear.
ASSERT is already close to the right shape because it can turn behavior requirements into generated cases, run a target, judge the results, and preserve evidence. The sandbox direction extends that from “evaluate this endpoint/callable” to “evaluate this configured agent operating environment safely.”
The hard part is safety. If ASSERT generates tests from the agent's actual rules, those tests may ask the agent to use tools, edit files, send messages, or touch sensitive context. We should not point that at the live local profile. The eval target needs to be a copied, disposable runtime with credentials kept out of the sandbox and model calls routed through a controlled host-side proxy.
Summary
This PR adds a local-agent eval path for ASSERT. The technical unlock is self-introspection: instead of ASSERT trying to hardcode every local agent layout, the configured agent describes itself in an
agent.yaml; ASSERT snapshots that declared runtime, starts a copied clone in an isolated backend, routes model calls through a host-side auth proxy, builds an ASSERT spec from copied files, and runs the normal ASSERT pipeline against the clone.High-level flow:
Major changes
Endpoint target support
protocol: openai_chat, endpoint URL, model, and env-based bearer auth.eventsas tool-call/tool-result evidence when a target emits them.Local-agent self-introspection and snapshotting
AgentRuntimeConfig/agent.yamlsupport for self-described runtimes:assert-ai local snapshot create --config <agent.yaml>.Sandbox/runtime backends
docker-runbackend for faster local dogfood and generic launch proof:sandbox start --config <agent.yaml>so a self-described config can drive runtime launch.Required config materialization
Some runtime config files are both required and secret-bearing. This PR adds materialization/patching so ASSERT can avoid copying raw secrets while still producing a runtime-valid config.
Concrete OpenClaw example:
The generic materializer can create a sanitized config with endpoint auth, model routing, and provider proxy settings.
Spec build and CLI polish
smoke/stopwhen unambiguous.What is proven
1. OpenClaw on RAMPART: full-evidence path
This is the strongest demo path today.
Verified against a copied, configured OpenClaw runtime using live Copilot/gpt-5.5 routing. The RAMPART path captures tool/action evidence from the OpenClaw bridge and remains the full-evidence proof.
2. Hermes on docker-run: second-runtime generic proof
Hermes self-introspected, produced an
agent.yaml, snapshotted successfully, booted from the copied/redacted profile throughdocker-run, routed model calls through the host auth proxy, and completed a Level-1 final-answerassert-ai run.3. OpenClaw on docker-run: genericity proof against the original bespoke runtime
OpenClaw also completed a Level-1 final-answer run on the same generic
docker-runbackend after required config materialization. This proves OpenClaw is not only working because of the bespoke RAMPART descriptor.Validation
Local validation on the current branch:
Full generic Hermes Level-1 run:
OpenClaw docker-run Level-1 run:
Earlier OpenClaw RAMPART proof run produced canonical ASSERT artifacts and viewer output with runtime/tool evidence.
Known boundaries
This is demo/design-review ready, not production complete.
docker-runis Level-1 final-answer eval. Generic tool/action evidence remains Level 2 and should use the OTel/gen_ai path.docker-runis a faster local dogfood backend, not a RAMPART replacement.agent-config doctorverifier so ASSERT checks what the agent reported before trusting it.Review focus
I’m looking for design feedback on:
docker-runshould be treated as an advanced dogfood backend while RAMPART remains the stronger evidence backend.