Repository navigation
Release 0.5.0: Inspect AI and OpenEnv integrations, target extension points - #130
Merged
Merged
Conversation
Records, for 28 audit configurations, everything the engine sends to the target, auditor and judge clients, the stored results and the on_turn events, and compares them with JSON files in tests/snapshots/core/. The snapshots were generated on the code before the integration seams land, so the next commit can show that existing payloads do not change. Run ids, image bytes and the checkout path are normalised. Regenerate with SIMPLEAUDIT_UPDATE_SNAPSHOTS=1 when a change is intended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seams for auditing agents and running inside other frameworks. Every addition is optional; existing payloads are unchanged (the snapshots from the previous commit pass as recorded). - Targets may define async start_scenario(context) / end_scenario(context). run_scenario awaits start after scenario validation and end after the last turn, before judging; end also runs on failure and cancellation. Hooks are looked up on the target's class and must be coroutine functions, so MagicMock targets are unaffected. run_scenario now binds self.target once per scenario, because the property builds a new ModelTarget on each access. - TargetResponse.tool_events: an agent's tool calls and results for the turn. Stored beside the reply, rendered for the judge only as a TOOL ACTIVITY block before the assistant turn, and stripped from history before provider calls. - TargetResponse.end_conversation: the target ended the conversation; the loop stops and the transcript so far is judged. - ModelAuditor(judge_client=..., auditor_client=...): ready-made clients replace AnyLLM creation for that role. An injected auditor client wins over every fallback; without one, the existing branches apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simpleaudit.integrations.inspect, behind the new `inspect` extra: - `simpleaudit/audit` task (registered through an inspect_ai entry point). The task model is the target, the `grader` role the judge (required, so the target never judges itself) and the `auditor` role the probe writer (defaults to the grader). Each sample runs the unchanged SimpleAudit loop through InspectModelClient, an acompletion shim over an Inspect Model, injected via judge_client/auditor_client. Epochs reduce to the modal severity; metrics are the 0-100 safety score and counts per severity. - Inspect keeps control of failures: the shim records the first exception from a model call and the solver re-raises it, so token limits, terminations and provider errors behave as in any Inspect eval instead of becoming SimpleAudit ERROR results. - load_inspect_log turns any Inspect log into RepeatedExperimentResults, one run per epoch. SimpleAudit-task samples come back exactly as stored; other samples are rebuilt from their messages with tool calls and results as tool_events, so rejudge, reframing_check and perturbations work on Inspect transcripts. CI installs the extra so the tests run there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenEnv's HarnessEnvironment (RFC 005) runs a real agent harness such as Claude Code and serves it the environment's tools over MCP. OpenEnvTarget puts that agent in SimpleAudit's target seat: - one fresh episode per scenario, opened in start_scenario and closed in end_scenario, either in process (env_factory) or on a server (base_url, through GenericEnvClient) - each auditor message is one HarnessAction step; the reply comes from metadata["response"] and the turn's tool calls and results become tool_events, so the judge sees what the agent did - a failed turn raises (scenario ERROR); an episode the agent ends sets end_conversation; scenarios with attachments are rejected - reset_kwargs can depend on the scenario name; max_sessions defaults to 1 for a server, which holds one session - openenv is imported only when a scenario starts, so `import simpleaudit` stays free of it examples/openenv_agent_audit.py audits a caseworker agent in a mock benefits case system (scripted agent or Claude Code) and prints whether each scenario changed the world. The `openenv` extra pulls the package; CI installs it so the tests run there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
any-llm-sdk 1.40.1 requires anthropic<1 in its base dependencies, not only in its [anthropic] extra, so every SimpleAudit install pins it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run_scenario passes the whole conversation as history, current turn
included, with SimpleAudit's markers beside the text. _build_body
forwarded it as-is, so the app received the current user turn twice
(appended again in messages mode, repeated in history otherwise) and
the markers: documents with their evaluation-only marks, file_uri paths
and decision answers.
History now goes out as plain {role, content} turns, without a trailing
copy of the current turn; direct callers that pass earlier turns only
are unchanged. A multi-turn scenario's documents are taken from the
history marker and sent through documents_field as text, like a
context-grounding run's, and fail the same way when no documents_field
is set.
Fixes #124
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sync dev with main (0.4.2)
- Tool events are stored as a JSON-safe copy (non-JSON values become strings, so results can always be saved) and must be a list of dicts; anything else fails that turn with a clear error. - The judge sees at most 50 tool events per turn. Values are rendered as JSON, strings included, so tool output stays on one line and cannot pose as a transcript turn; the block is labelled as tool data. An id of 0 and string error messages are kept, and a turn with no tool call or result adds no block. - Tool events are left out of the history passed to targets, and provider messages are reduced to role and content in one place. - end_scenario now runs after the evidence resolver, so a resolver can still read the target's session, and a failing end_scenario raises a RuntimeWarning instead of logging only with verbose=True. - Hooks are looked up on the target itself (proxies work); a hook defined without async is not called and warns. - With an injected judge_client, an auditor of its own needs auditor_client or both auditor_model and auditor_provider, instead of falling back to the judge's labels. - reframing_check rejects transforms that change tool_events. - README: the hook rules as implemented, and judge_client/auditor_client in the parameter table.
A turn with more than 50 tool events now shows the first 25 and the last 25 with a count of the ones in between. An agent's last actions are often the decisive ones, and cutting the tail hid them from the judge.
- Epochs reduce with modal_severity, which breaks ties toward the more severe verdict like aggregate_severities; Inspect's mode reducer kept whichever tied epoch came first, so a pass/critical split reported pass. - The solver stores the result and the display transcript before it re-raises a model error or limit, so a stopped sample keeps what the target said. - load_inspect_log returns ERROR with an empty transcript for a simpleaudit/audit sample that never stored a result, instead of presenting the hidden scenario description as a user turn. Samples are recognised by a simpleaudit_pack metadata key. - Tool errors from other evals keep Inspect's error message; severities from scores or a severity= mapper are normalised (None is ungraded). - The scorer leaves out only ERROR and ungraded, and scores an off-ladder severity as medium, as AuditResults.score does. severity_counts sends a value between two steps to the more severe one. - A judge schema Inspect's JSONSchema cannot express whole ($defs, $ref, const, ...) is not sent, with a warning, instead of being sent stripped. - The transcript shown in Inspect View names attached files instead of reading them again after judging, and is built with role/content only (the previous helper call broke on messages with tool events). - Scenarios are read once per file version and checked for unique names in one place. - examples/inspect_log_robustness.py checks --epoch.
- Input tokens include Anthropic-style cached prompt tokens (cache_read/cache_creation), which Claude Code reports apart from input_tokens. - max_sessions is validated (>= 1) and enforced with a thread semaphore polled without blocking, so the limit holds across threads and the event loops of repeated sync runs, and a cancelled wait never keeps a slot. The per-loop asyncio semaphore is gone. - In-process and server episodes close through one AsyncExitStack; aclose() closes every episode and raises the first failure afterwards. - message_timeout_s bounds an in-process turn as well (asyncio.wait_for). - A tool result's error message is kept, and harness error events in the middle of a turn reach the judge as tool errors. - env_factory may take a scenario_name keyword; the example uses it to key each world by scenario instead of by start order. - The example rejects --agent claude-code without --openenv-repo, adds the adapter path once, and removes the episode working directories. - The server test serves on the socket it bound, so a parallel test cannot take the port in between. - README: install line, sessions and timeouts, factory scenario_name, and the closing caveat.
Follow-up to the history fix, from review: - Only the engine's markers (documents, file_uri, decision, tool_events) are removed from history; other fields stay, so direct callers sending OpenAI tool messages (tool_calls, tool_call_id, name) or turns without content get the body they built. - Documents go through parse_documents like the model and decision targets: a dict without a string text fails, empty documents keep their position, and an explicit documents=[] is not replaced by the history marker. The error no longer assumes a context-grounding run. - A scenario with file_uri now fails with an error; the app could never receive the file, so it was dropped silently. - Target.send documents the history contract, and history_documents() in targets/base.py replaces the copies in http.py and decision.py.
Core seams for integrations: target lifecycle hooks, tool events, injected clients
Inspect AI integration: run packs as Inspect tasks, read Inspect logs back
HTTPAppTarget: send each turn once and no transcript markers (fixes #124)
# Conflicts: # .github/workflows/tests.yml # README.md
OpenEnvTarget: audit agents hosted in OpenEnv environments
- CallableTarget gives the function the history without the engine's markers, the scenario's documents as text through `documents`, and its files through `file_uri`, so no evaluation-only document mark reaches it (#126). - HTTPAppTarget sends the audit's system prompt: as a leading system message in "messages" mode, after which a `messages` list from request_template comes before the conversation; in other modes through the new `system_field`, with a warning when there is none (#127). - A `history` list in HTTPAppTarget's request_template comes before the conversation's earlier turns instead of replacing them (#128). - targets/base.py gains ENGINE_MARKERS, plain_history() and history_file_uri(), shared by both targets. Fixes #126 Fixes #127 Fixes #128
- system_field is honoured in "messages" mode too, so APIs that take the system prompt as a top-level field (Anthropic's native Messages API) work. A value request_template already sets at that field comes first; content-block lists get the prompt appended as a text block. - Without system_field, the audit's prompt joins the template's leading system message instead of adding a second one; many chat templates reject two system messages in a row. - The missing-system_field warning fires on every call when warnings are errors, instead of failing once and then dropping the prompt silently. - A non-list "history" in request_template is an error instead of being replaced by the conversation. - CallableTarget passes file_uri on the turn that attaches it only, and that turn keeps its file_uri in the history, so a function can attach the files where they belong. - context_marks.document_texts() replaces the copies in the callable and HTTP targets; DecisionTarget treats an explicit documents=[] as given; OpenEnvTarget uses the shared history helpers. Stale comments and the README are updated.
Targets: clean history and documents for CallableTarget, system prompt and template history for HTTPAppTarget
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 0.5.0: everything merged into
devsince 0.4.2, plus the version bump.mainhas no commits of its own thatdevlacks, so this merges cleanly. After merging, a GitHub Releasev0.5.0publishes the package to PyPI.Added
Inspect AI integration (Inspect AI integration: run packs as Inspect tasks, read Inspect logs back #122, extra
simpleaudit[inspect])inspect eval simpleaudit/audit -T pack=...runs any scenario pack as an Inspect task. The task model is the target, thegraderrole is the judge, and theauditorrole writes the probes.load_inspect_logreads any Inspect log back as SimpleAudit results, sorejudge,reframing_checkand the stability report work on it.OpenEnvTarget(OpenEnvTarget: audit agents hosted in OpenEnv environments #123, extrasimpleaudit[openenv]) audits an agent hosted in an OpenEnv environment, in process or through a server. The judge sees the agent's tool calls.Target extension points (Core seams for integrations: target lifecycle hooks, tool events, injected clients #121)
start_scenario/end_scenariohooks;TargetResponse.tool_events, shown to the judge as tool activity;TargetResponse.end_conversation;judge_client=/auditor_client=for ready-made model clients.All are optional, and existing judge, auditor and target payloads are unchanged;
tests/test_core_snapshots.pychecks that.HTTPAppTarget(system_field=...)(Targets: clean history and documents for CallableTarget, system prompt and template history for HTTPAppTarget #129) names the request field for the system prompt.Fixed
HTTPAppTargetsent the current user turn twice and forwarded transcript markers, including evaluation-only document marks.CallableTargetpassed evaluation-only document marks to the wrapped function.HTTPAppTargetignored the audit's system prompt.historykey inHTTPAppTarget'srequest_templatereplaced the conversation.These issues close when this merges into
main.Behaviour changes
HTTPAppTargetmessagesmode it now sends the audit's system prompt.documents_field.file_uriis an error.historyinrequest_templateis an error.CallableTargetfunctions get documents as text throughdocuments, and files throughfile_urion the turn that attaches them, instead of finding them as markers inhistory.DecisionTargettreats an explicitdocuments=[]as given.Dependencies
inspect(inspect-ai>=0.3.283) andopenenv(openenv>=0.10). The core install is unchanged.anthropic>=2, while any-llm-sdk 1.40.1 requiresanthropic<1. The README describes the workaround for Inspect runs.Verification
devafter the last merge.pytest -n autowith all extras: 1725 passed.[inspect,openenv](Python 3.12). Inspect resolvessimpleaudit/auditthrough the entry point.claude-haiku-5-5, as described in each PR.