symfonic.evals¶
evals ¶
Reusable, evidence-based regression evaluations for agent applications.
AgentTarget ¶
AgentTarget(agent: Any, *, close_agent: bool = True, sessions: dict[str, str] | None = None, resume: ResumeSeam | None = None, evidence: TargetEvidenceAdapter | None = None, evidence_scope: Any = 'default', resources: Any = None)
Drive a public Agent and retain one transcript per conversation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resume
|
ResumeSeam | None
|
the deployment's own public redemption operation, taking
the person's answer. Optional and never invented: a compiled
|
None
|
Source code in src/symfonic/evals/targets.py
capabilities
property
¶
The names the wrapped agent folded, never a declared feature list.
operations
property
¶
The non-turn operations this target can actually perform.
capability_evidence
async
¶
Read optional-pack evidence from the compiled agent itself.
Source code in src/symfonic/evals/targets.py
resume
async
¶
Answer one outstanding pause through the deployment's own seam.
Source code in src/symfonic/evals/targets.py
AssertionResult
dataclass
¶
One assertion verdict with a payload-free explanation.
AttributeCount
dataclass
¶
AttributeCount(attribute: str, minimum: int = 0, maximum: int | None = None, name: str = 'attribute_count')
Require an integer or collection-valued evidence attribute to be bounded.
AttributeEquals
dataclass
¶
Require target-supplied evidence without rendering its value.
BookClaimGrounded
dataclass
¶
BookClaimGrounded(claim: BookClaim, retrieved_attribute: str = 'retrieved_source_ids', assertions_attribute: str = 'response_claims', derivation_attribute: str = 'derivation_inputs_used', name: str = 'book_claim_grounded')
Require answer truth, admitted/cited provenance and normalized safety.
BookFixture
dataclass
¶
BookFixture(id: str, title: str, version: str, sections: tuple[BookSection, ...], claims: tuple[BookClaim, ...] = (), relations: tuple[BookRelation, ...] = (), negative_facts: tuple[NegativeFact, ...] = ())
BookSection
dataclass
¶
BookSection(id: str, ordinal: int, kind: SectionKind, title: str, filename: str, sha256: str, text: str, status: SectionStatus = SectionStatus.CURRENT, supersedes: tuple[str, ...] = (), superseded_by: str | None = None, signed_by: str | None = None)
One addressable source section and the checksum that pins its text.
mentions ¶
CanaryAbsent
dataclass
¶
Reject a canary in the response, events, or named safe evidence fields.
Source code in src/symfonic/evals/security_assertions.py
CapabilitiesPresent
dataclass
¶
Require capabilities derived from the target's compiled agent.
Source code in src/symfonic/evals/assertions.py
CapabilityEvidence
dataclass
¶
CapabilityEvidence(capabilities: frozenset[str] = frozenset(), turn_inputs: frozenset[str] = frozenset(), traits: frozenset[str] = frozenset(), operations: frozenset[str] = frozenset(), capability_tools: frozenset[tuple[str, str]] = frozenset(), evidence_channels: frozenset[str] = frozenset())
What one compiled agent demonstrably offers an evaluation.
from_agent
classmethod
¶
from_agent(agent: Any, *, traits: Iterable[str] = (), operations: Iterable[str] = (), evidence_channels: Iterable[str] = ()) -> CapabilityEvidence
Read names and attributed tools off a compiled Agent.
Traits come from :func:symfonic.evals.traits.compiled_evidence, which
reads the plan rather than accepting them here as a claim.
Source code in src/symfonic/evals/applicability.py
from_observation
classmethod
¶
Read the evidence a target published alongside one answer.
A target that publishes neither attribute yields empty evidence, and every optional pack then resolves to not-applicable by name. That is the honest reading: nothing about that turn showed a capability.
Source code in src/symfonic/evals/applicability.py
missing ¶
missing(*, capabilities: Iterable[str] = (), turn_inputs: Iterable[str] = (), traits: Iterable[str] = (), operations: Iterable[str] = (), capability_tools: Iterable[tuple[str, str]] = (), evidence_channels: Iterable[str] = ()) -> tuple[str, ...]
Required evidence this agent did not produce, in stable order.
Each kind is reported with its own prefix, because "the capability is absent", "the capability composed nothing", and "this target cannot deliver it" send an operator to three different places.
Source code in src/symfonic/evals/applicability.py
CitationsSupported
dataclass
¶
CitationsSupported(attribute: str = 'retrieved_source_ids', minimum: int = 1, name: str = 'citations_supported')
Require response citations to name only evidence retrieved for this turn.
ClosedCorpusGrounded
dataclass
¶
ClosedCorpusGrounded(fact: NegativeFact, marker: str = 'NOT IN MANUAL', unsupported_attribute: str = 'unsupported_fact_ids', unsupported_count_attribute: str = 'unsupported_claim_count', retrieved_attribute: str = 'retrieved_source_ids', response_claims_attribute: str = 'response_claims', name: str = 'closed_corpus_grounded')
Require an explicit unsupported verdict with no invented proposition.
CompletedTurn
dataclass
¶
CompletedTurn(observation: Observation, result: Any, scope: Any, conversation: str, session_id: str, resources: Any = None)
The runtime-owned values from one completed turn.
Payload-bearing values are withheld from repr. They are handed only
to the explicitly configured adapter and never copied into a report unless
that adapter returns them as evidence.
ConcurrencyRestartSafe
dataclass
¶
ConcurrencyRestartSafe(attribute: str = 'concurrency_restart', minimum_workers: int = 4, name: str = 'concurrency_restart_safe')
Require isolated concurrent writes and identical durable restart state.
ConsolidationIdempotent
dataclass
¶
ConsolidationIdempotent(*mutation_counts: str, attribute: str = 'consolidation_cycles', complete_graph: bool = False)
Require a mutation-producing cycle followed by a stable no-op cycle.
Source code in src/symfonic/evals/consolidation_assertions.py
ConsolidationLedgerComplete
dataclass
¶
ConsolidationLedgerComplete(*required_phases: str, attribute: str = 'consolidation_ledger', status: str = 'clean', allow_skipped: bool = True, exact: bool = False)
Require a cycle ledger to account for every expected phase.
Source code in src/symfonic/evals/consolidation_ledger.py
ConsolidationStoreMutation
dataclass
¶
ConsolidationStoreMutation(attribute: str = 'consolidation_mutation', cycle: str = 'quick', name: str = 'consolidation_store_mutation')
Require a completed cycle to match an observable scoped store change.
Evidence contains identifiers and digests, never memory text. A populated comparison scope must remain stable, so an empty neighbor is not vacuous isolation evidence.
EffectReceipt
dataclass
¶
Whether this attempt applied the effect or found it already applied.
EvalAssertion ¶
Bases: Protocol
A deterministic check over one turn observation.
EvalOperation ¶
Bases: StrEnum
What a step asks the target to do.
Two, because a turn and a redemption are different operations against different seams. An approval evaluation that sent the person's answer as another prompt would prove the model can be told about an answer; only the deployment's own resume operation proves the paused run was rebuilt from its checkpoint and that the token was spent exactly once.
EvalProfile ¶
Bases: StrEnum
Standard cost and infrastructure tiers for evaluation suites.
EvalReport
dataclass
¶
Complete result of an evaluation suite.
EvalStatus ¶
Bases: StrEnum
Stable aggregate and trial outcomes.
EvalStep
dataclass
¶
EvalStep(prompt: str = '', assertions: tuple[EvalAssertion, ...] = (), conversation: str = 'default', scope: str = 'default', restart_before: bool = False, expected_error: str | None = None, output_type: type[Any] | None = None, state: Mapping[str, Any] = (lambda: MappingProxyType({}))(), attachments: tuple[Any, ...] = (), operation: EvalOperation = EvalOperation.OBSERVE, answer: Mapping[str, Any] | None = None)
One prompt and its required evidence assertions.
EvalSuite
dataclass
¶
EvalSuite(scenarios: tuple[Scenario, ...], target_factory: Callable[[], Any] | Mapping[str, Callable[[], Any]], pack_factory: Callable[[CapabilityEvidence, str], Sequence[PackResolution]] | None = None)
Discoverable suite definition consumed by :command:symfonic eval.
target_for ¶
Resolve the target explicitly; never guess among infrastructure tiers.
Source code in src/symfonic/evals/model.py
EvalTarget ¶
Bases: Protocol
Application boundary driven by the runner.
resume
async
¶
Redeem one outstanding pause through the deployment's own seam.
Separate from :meth:observe because it is a separate operation: it
takes no prompt, it continues a turn that already ran, and the
evidence it publishes is about the redemption rather than about an
answer the model produced. A target that cannot do it says so by
publishing no resume in operations, which makes an approval
pack not-applicable instead of failing it on delivery.
Source code in src/symfonic/evals/runner.py
EventsCorrelated
dataclass
¶
Require every observed event to belong to the response run.
Evidence
dataclass
¶
Evidence(events: tuple[Any, ...] = (), attributes: Mapping[str, Any] = (lambda: MappingProxyType({}))())
Payload-free events and attributes loaded from a deployed application.
ExecutionTraceJoined
dataclass
¶
ExecutionTraceJoined(*, required_stages: Sequence[str] = (), required_tools: Sequence[str] = (), attribute: str = 'observability')
Require exact per-event parity across stream, UI, storage and OTel.
Source code in src/symfonic/evals/observability_assertions.py
FixtureDocument
dataclass
¶
FixtureDocument(document_id: str, title: str, text: str, revision: str = '', media_type: str = 'text/plain')
A document value consumed structurally by the public knowledge bridge.
FixtureDocumentStore
dataclass
¶
A checksum-versioned public DocumentStore over one book fixture.
fetch ¶
select_ids ¶
Validate an exact, duplicate-free subset for knowledge_sources.
Source code in src/symfonic/evals/fixture_ingestion.py
FixtureIngestionComplete
dataclass
¶
FixtureIngestionComplete(fixture: BookFixture = load_book_fixture(), attribute: str = 'fixture_ingestion_records', name: str = 'fixture_ingestion_complete')
Require one canonical, checksum-matching record per fixture section.
Targets expose only identity evidence under fixture_ingestion_records:
section_id, source_sha256, record_id and layer. Memory
content never enters the report.
FixtureIngestionMode ¶
Bases: StrEnum
The public application seam used to ingest the fixture.
FixtureIngestionReport
dataclass
¶
Applicability for both fixture ingestion routes, never a silent skip.
not_applicable
property
¶
Stable mode names that were not offered by this deployment.
as_dict ¶
Return a JSON-safe, content-free report.
Source code in src/symfonic/evals/fixture_ingestion.py
FixtureScopeFresh
dataclass
¶
FixtureScopeFresh(scope_attribute: str = 'physical_scope_id', count_attribute: str = 'fixture_record_count_before', name: str = 'fixture_scope_fresh')
Require a physical scope identity and zero fixture rows before ingestion.
FreshConversationConsistency
dataclass
¶
FreshConversationConsistency(attribute: str = 'fresh_conversation_claims', minimum_conversations: int = 2, maximum_contradiction_rate: float = 0.0, name: str = 'fresh_conversation_consistency')
Bound contradictions across normalized claims from fresh conversations.
GovernanceDecisionObserved
dataclass
¶
GovernanceDecisionObserved(decisions: Any, rule_id: str, state: str, tool: str | None = None, times: int = 1, name: str = 'governance_decision_observed')
Require a named rule to have produced a structured decision.
HostTarget ¶
HostTarget(host_factory: Callable[[], Any], scope: Any | Mapping[str, Any] | Callable[[str], Any], *, evidence_factory: EvidenceFactory | None = None, resume: HostResumeSeam | None = None)
Start a public AgentHost and evaluate named tenant scopes.
Source code in src/symfonic/evals/host_target.py
capability_evidence
async
¶
Compile one scoped agent and report what the host actually serves.
Source code in src/symfonic/evals/host_target.py
HttpChatTarget ¶
HttpChatTarget(base_url: str, *, headers_for_scope: HeaderFactory, restart: RestartHook | None = None, evidence_for_run: EvidenceLoader | None = None, client_factory: Callable[[], AsyncClient] | None = None)
Evaluate the shipped JSON chat API with explicit scope credentials.
headers_for_scope is the authorization boundary: a scenario names a
harmless alias and the deployment decides which authenticated headers that
alias receives. Raw credentials therefore never enter reports or fixtures.
It reports no capabilities and no turn_inputs, which is the honest
answer for this protocol: the API carries a query and returns an answer, so
nothing about a turn through it evidences a composed capability. Optional
packs therefore resolve to not-applicable against it by name rather than
failing on a step it would refuse to deliver.
Source code in src/symfonic/evals/http_target.py
InfrastructureUnavailable ¶
Bases: RuntimeError
The scenario could not ask its question because a dependency is absent.
IngestionApplicability
dataclass
¶
One explicit applicable/not-applicable ingestion verdict.
IsolationBoundaryObserved
dataclass
¶
IsolationBoundaryObserved(boundary: str, attribute: str = 'isolation_evidence', name: str = 'isolation_boundary_observed')
Require positive source evidence and a zero-admission isolated read.
MeaningfulMemoryEdges
dataclass
¶
Require typed edges with labels distinct from their opaque identifiers.
Source code in src/symfonic/evals/consolidation_assertions.py
MemoryCategorySeparated
dataclass
¶
MemoryCategorySeparated(expected: Mapping[str, str], *, attribute: str = 'memory_classifications', minimum_per_category: int = 1, allow_extra: bool = False)
Require memory records to retain their declared category and subject.
Evidence is a sequence of records with record_id, category and
subject fields. Expected values are category-to-subject declarations.
A record identity appearing in multiple categories is always a failure.
Source code in src/symfonic/evals/memory_assertions.py
Observation
dataclass
¶
Observation(response: str, events: tuple[Any, ...] = (), attributes: Mapping[str, Any] = (lambda: MappingProxyType({}))())
One response and the safe evidence produced while obtaining it.
PackResolution
dataclass
¶
PackResolution(pack: str, applicable: bool, scenarios: tuple[Scenario, ...] = (), missing: tuple[str, ...] = ())
One pack's verdict: scenarios to run, or the evidence that was absent.
PromptMemoryCategorized
dataclass
¶
Bases: ResponseMemoryCategorized
Require the fact and validated category in the same recalled record line.
PromptRecallContains
dataclass
¶
PromptRecallContains(*fragments: str, attribute: str = 'prompt_recall', case_sensitive: bool = False)
Require fragments in the exact recall text supplied to the model.
The target opts in by placing the delimited recall contribution in
attributes[attribute]. This deliberately checks model input rather
than store contents or the answer, either of which can produce a false
positive while prompt injection is broken.
Source code in src/symfonic/evals/memory_assertions.py
PromptRecallExcludes
dataclass
¶
PromptRecallExcludes(*fragments: str, attribute: str = 'prompt_recall', case_sensitive: bool = False)
Require content not to reach the model, reporting only its digest.
Source code in src/symfonic/evals/memory_assertions.py
ProviderEvidenceRecorder ¶
Wrap a model provider and retain what its chat models actually receive.
This is evaluation instrumentation, not inference logic. It captures in a LangChain callback at the provider boundary, after prompt assembly and attachment encoding. Reading the original eval prompt would be easier but would turn a dropped context block into a false green.
Source code in src/symfonic/evals/provider_evidence.py
calls
property
¶
All captured batches, for diagnostics owned by the evaluation.
calls_since ¶
Actual message batches delivered after cursor.
Source code in src/symfonic/evals/provider_evidence.py
cursor ¶
get_chat_model ¶
Return the provider's model with the capture callback installed.
ResponseContains
dataclass
¶
Require all fragments in the answer, optionally case-insensitively.
Source code in src/symfonic/evals/response_assertions.py
ResponseDoesNotContradictRecall
dataclass
¶
ResponseDoesNotContradictRecall(recall_attribute: str = 'recall_claims', response_attribute: str = 'response_claims', minimum: int = 1, name: str = 'response_does_not_contradict_recall')
Compare target-normalized response claims with recalled claims.
Both attributes are mappings from a stable claim id to a normalized value.
Only claim ids present in both mappings are compared. minimum prevents
an empty response-claim mapping from passing vacuously.
ResponseEquals
dataclass
¶
Require an exact answer without copying it into a failed report.
ResponseJson
dataclass
¶
Require valid JSON and optionally an exact object shape.
ResponseMemoryCategorized
dataclass
¶
Require each fact fragment and its category on the same table row.
Used with a prompt asking for subject, memory_category and detail columns. Mentioning all categories elsewhere, or labelling a fact with its storage layer, cannot pass. Expectations never appear in diagnostic output.
RubricJudge
dataclass
¶
RubricJudge(judge: RubricJudgePort | Callable[[RubricRequest], Awaitable[RubricVerdict]], rubric_id: str, rubric_version: str, criteria: tuple[str, ...], minimum_score: float = 1.0, name: str = 'rubric_judge', nondeterministic: bool = True)
Opt-in nondeterministic assertion, subordinate to deterministic evidence.
RubricRequest
dataclass
¶
Pinned rubric plus the answer an adopter explicitly sends to a judge.
RubricVerdict
dataclass
¶
Provider-neutral structured judge response.
Scenario
dataclass
¶
Scenario(name: str, steps: tuple[EvalStep, ...], policy: TrialPolicy = TrialPolicy(), tags: frozenset[str] = frozenset())
An ordered behaviour specification executed as one isolated trial.
ScenarioResult
dataclass
¶
ScenarioResult(name: str, status: EvalStatus, trials: tuple[TrialResult, ...], required_passes: int, tags: frozenset[str] = frozenset())
Aggregate verdict across all configured trials.
ScopedRecords
dataclass
¶
ScopedRecords(attribute: str = 'memory_records', scope_path: str = '', minimum: int = 1, name: str = 'scoped_records')
Require store evidence to be visible only from an expected scope.
Targets place record-shaped values in an observation attribute. This check reads identity metadata only; it never examines or reports record content.
SideEffectCount
dataclass
¶
SideEffectCount(ledger: SideEffectLedger, attempts: int, effects: int, duplicates: int, name: str = 'side_effect_count')
Assert attempted, applied and duplicate counts without exposing keys.
SideEffectLedger ¶
In-process fixture proving a tool honors a business idempotency key.
This is evaluation infrastructure, not a production durability mechanism.
A deployed tool should use its transactional database or durable command
ledger with the same apply_once contract. The fixture deliberately
counts attempts separately from applied effects, so a replay cannot pass
merely because the second request disappeared before reaching the tool.
Source code in src/symfonic/evals/effects.py
apply_once
async
¶
Apply operation at most once for key within this fixture.
Source code in src/symfonic/evals/effects.py
SourceRouteAdmitted
dataclass
¶
Require named sources to have reached the prompt through one route.
Source code in src/symfonic/evals/book_retrieval_assertions.py
StageObserved
dataclass
¶
StageObserved(stage_id: str, *, outcome: str | None = None, minimum_counts: dict[str, int] | None = None, exact_counts: dict[str, int] | None = None)
Require a stage outcome and optional minimum integer counters.
Source code in src/symfonic/evals/assertions.py
StepResult
dataclass
¶
Safe result of one scenario step.
StructuredOutputMatches
dataclass
¶
StructuredOutputMatches(output_type: type, expected: Mapping[str, Any] | None = None, name: str = 'structured_output_matches')
Require the target's validated structured value and optional exact fields.
TargetEvidenceAdapter ¶
ToolCallIdsUnique
dataclass
¶
Require emitted tool calls to have non-empty, observation-unique IDs.
ToolCalled
dataclass
¶
ToolCalled(tool_name: str, times: int = 1, arguments: Mapping[str, Any] | None = None, name: str = 'tool_called')
Require an exact number of calls to a named tool.
ToolFailed
dataclass
¶
Require tool execution to return an error outcome, without exposing it.
ToolSucceeded
dataclass
¶
ToolSucceeded(tool_name: str, times: int = 1, arguments: Mapping[str, Any] | None = None, name: str = 'tool_succeeded')
Require an exact number of completed, error-free tool executions.
TracePrivacyModes
dataclass
¶
Require metadata-default and explicit, bounded, redacted content mode.
TraitProbe
dataclass
¶
A public operation and whether its feature door exists.
available=False is the explicit feature-absent answer. Once the door
exists, an exception is an operational failure and propagates; collapsing
it into absence would silently remove the pack from a broken deployment.
TrialPolicy
dataclass
¶
How often a scenario runs and how many successful trials it requires.
required_passes
property
¶
Smallest integer number of passes satisfying the threshold.
TrialResult
dataclass
¶
TrialResult(index: int, status: EvalStatus, steps: tuple[StepResult, ...] = (), reason: str = '', duration_ms: float = 0.0)
Result of one isolated scenario attempt.
UntrustedContentObserved
dataclass
¶
UntrustedContentObserved(fragment: str, *, source: str = 'memory.recall', attribute: str = 'prompt_recall')
Require a canary inside one exact untrusted prompt contribution.
Source code in src/symfonic/evals/security_assertions.py
applicability_report ¶
A JSON-safe row per pack, so a not-applicable pack is published.
Reported next to :func:~symfonic.evals.reporters.report_dict, because a
pack that resolved to not-applicable contributes no scenario and would
otherwise leave no trace at all in the run's evidence.
Source code in src/symfonic/evals/applicability.py
applicable_scenarios ¶
Every scenario the applicable packs contributed, in resolution order.
Source code in src/symfonic/evals/applicability.py
approval_resume_pack ¶
approval_resume_pack(evidence: CapabilityEvidence, *, pause_prompt: str, answer: Mapping[str, Any], interaction_tool: str, resumable: bool = True, resume_attribute: str = 'resume_outcome', resume_outcome: str = 'resumed', continued_attribute: str = 'resume_continued', checkpoint_attribute: str = 'resume_checkpoint', replay_error: str = 'PauseTokenReplayedError', conversation: str = 'approval', policy: TrialPolicy = _DEFAULT_POLICY) -> PackResolution
Pause for a person, resume through the public seam, refuse the replay.
Three steps, and the middle one is the reason this pack exists.
The pause is read off the run's own terminal event, not off the answer: a
turn that described a pause and finished anyway is exactly the failure this
pack exists to catch. pause_resumable is the capability's measured
report that the stopped turn reached a checkpointer, so a deployment that
publishes a pause nobody can answer fails here rather than at the moment a
person tries.
The resume is an :attr:~symfonic.evals.model.EvalOperation.RESUME step
carrying the person's answer, so it goes through the deployment's own
redemption operation and nothing else. Two earlier shapes are both
excluded by construction: another prompt would only show that the model
can be told about an answer, and spending the token directly would skip
every check between authenticating it and consuming it. The step therefore
requires three separate facts, because a deployment can satisfy any two of
them while failing a person:
- the outcome came back at all;
- the token was bound to a checkpoint;
- the paused turn was rebuilt from it. A deployment that validates the answer, spends the token, and has no recorded turn state to continue reports a perfectly successful redemption and leaves the run stopped forever.
The third step redeems the same retained token again and requires the named refusal, so a token that redeemed twice fails even though both redemptions returned an outcome.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
answer
|
Mapping[str, Any]
|
what the person replies. It is validated by the deployment's registered response schema and against the recorded question, so an answer that does not fit fails the resume rather than being quietly accepted. |
required |
interaction_tool
|
str
|
the registered interaction name. No default -- the
built-in |
required |
resumable
|
bool
|
what the deployment claims its pause is. Stated rather than
defaulted-away, because |
True
|
Source code in src/symfonic/evals/execution_packs.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
attachment_delivery_manifest ¶
Return ordered kind/source/MIME/digest rows without attachment content.
Accepts public Attachment values and provider-wire blocks. The digest
is over decoded bytes for base64 content and over the URL bytes otherwise;
therefore the same MIME type carrying different content never compares
equal, while reports and assertion failures disclose no payload.
Source code in src/symfonic/evals/attachment_evidence.py
attested_traits ¶
Read narrow traits from the immutable manifest the agent retained.
Unlike :func:plan_traits, this needs no second fold. It is therefore the
route for targets that receive an already-built agent from an
:class:~symfonic.platform.AgentHost and do not own its capability config
objects. Only payload-free fields attested by composition_manifest
participate.
Source code in src/symfonic/evals/traits.py
build_book_response_evidence ¶
build_book_response_evidence(response: str, fixture: BookFixture | None = None) -> Mapping[str, object]
Parse response annotations and grade their normalized propositions.
Supported annotations are [assert:fact=value],
[unsupported:negative-fact-id] and [input:value]. Duplicate fact
declarations or malformed annotation prefixes invalidate the evidence.
Source code in src/symfonic/evals/book_evidence.py
compiled_evidence ¶
compiled_evidence(agent: Any, capabilities: Sequence[Any], *, effect_grants: Iterable[str], target: Any = None, traits: Iterable[str] = (), options: Mapping[str, Any] | None = None) -> CapabilityEvidence
Evidence for agent, with traits read off the fold it compiled.
A second fold must reproduce the payload-free canonical digest retained by
the actual Agent. Matching capability names alone is insufficient: two
prompting capabilities may compile different sources, and two extension
bundles may contribute different executable tools under the same umbrella.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traits
|
Iterable[str]
|
additional traits established elsewhere, such as the ones
:func: |
()
|
Source code in src/symfonic/evals/traits.py
delegation_behavior_journey ¶
Exercise success, containment, lineage, depth and tenant scope.
Source code in src/symfonic/evals/delegation_journeys.py
extension_tool_pack ¶
extension_tool_pack(evidence: CapabilityEvidence, *, prompt: str, tool_name: str, arguments: dict[str, object] | None = None, expected_fragments: Sequence[str] = (), conversation: str = 'extensions', policy: TrialPolicy = _DEFAULT_POLICY) -> PackResolution
Run one tool attributed to the compiled extension contribution.
The umbrella capability is insufficient: an empty bundle, or one that contributes a different tool, is explicitly not applicable. When present, an extension reaches a turn as an executable tool, so the evidence is a call and an error-free result. Requiring only the call would pass for a declaration bound with nothing behind it -- the failure the extensions door was rebuilt to make impossible -- and requiring only the answer would pass for a model that described the tool it never called.
Source code in src/symfonic/evals/execution_packs.py
fixture_ingestion_applicability ¶
fixture_ingestion_applicability(*, fixture: BookFixture | None = None, registered_tools: Iterable[str] = (), document_store: FixtureDocumentStore | None = None) -> FixtureIngestionReport
Report which ingestion routes the adopter actually supplied.
knowledge is not a compiled capability name in Symfonic; documents are
sources composed through prompting. Applicability therefore follows the
concrete public adapter, not a fictitious capability flag. The default
route follows the registered tool palette for the same reason.
Source code in src/symfonic/evals/fixture_ingestion.py
injection_resistance_journey ¶
Prove stored instructions remain data and cannot authorize an effect.
Source code in src/symfonic/evals/security_journeys.py
isolation_journey ¶
Prove volatile chat state and all tenant state stop at their boundaries.
Source code in src/symfonic/evals/security_journeys.py
json_report ¶
Serialize a report as deterministic formatted JSON.
junit_report ¶
Serialize scenarios as JUnit test cases without prompt or response payloads.
Source code in src/symfonic/evals/reporters.py
knowledge_grounding_pack ¶
knowledge_grounding_pack(evidence: CapabilityEvidence, *, grounded_prompt: str, grounded_fragments: Sequence[str], source_fragment: str, unsupported_prompt: str, refusal_fragments: Sequence[str], context_attribute: str = 'prompt_context', conversation: str = 'knowledge', policy: TrialPolicy = _DEFAULT_POLICY, source_trait: str | None = None) -> PackResolution
Answer from the composed sources, and decline outside them.
Two steps, because either one alone is passable by an agent that is broken in the other direction: a corpus answer proves nothing about fabrication, and a refusal proves nothing about retrieval. The first also requires the source text in the compiled context -- an answer that was right while the source never reached the prompt was right from the model's weights, and will stop being right when the corpus changes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_fragment
|
str
|
text the composed knowledge source contributes, as it must appear in the model's input. |
required |
refusal_fragments
|
Sequence[str]
|
how this deployment declines. Its own wording, not a phrase invented here. |
required |
Source code in src/symfonic/evals/content_packs.py
load_book_fixture
cached
¶
Load a fixture shipped inside this package by its identifier.
The identifier selects a directory, so it is checked as a filename before it is used as one: an absolute or traversing id names something that is not a packaged fixture, whether or not it exists.
Source code in src/symfonic/evals/fixtures/loader.py
multimodal_attachment_pack ¶
multimodal_attachment_pack(evidence: CapabilityEvidence, *, prompt: str, attachments: Sequence[Any], delivered_media_types: Sequence[str], expected_fragments: Sequence[str] = (), delivery_attribute: str = 'attachment_delivery', conversation: str = 'multimodal', policy: TrialPolicy = _DEFAULT_POLICY) -> PackResolution
Require the attachment to reach the provider, not the call to be accepted.
Agent.run(attachments=[...]) returning without raising proves the
signature; it does not prove a block was built or that it carried the
bytes, and a dropped attachment produces a confident answer about a
picture the model never saw. So the assertion is on delivery evidence the
target publishes from the outgoing side of the turn, and the response
check is additional rather than sufficient.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
delivered_media_types
|
Sequence[str]
|
exactly the media types the wire must carry, in attachment order. Retained as a declaration check; delivery itself is asserted with ordered payload-safe content digests. |
required |
delivery_attribute
|
str
|
where the target publishes what actually went out. |
'attachment_delivery'
|
Source code in src/symfonic/evals/content_packs.py
operations_of ¶
Which non-turn operations this target publishes.
Read from the target rather than from the agent, because an operation such as resume is a deployment seam: the compiled agent pauses, and something else redeems the token. A target that cannot resume must make an approval pack not-applicable by name rather than fail it on delivery.
Source code in src/symfonic/evals/traits.py
paused_observation ¶
paused_observation(paused: tuple[Any, Any], events: tuple[Any, ...], evidence: Mapping[str, Any]) -> Observation
A run that stopped to ask a person, as evidence and not as a failure.
A pause is a terminal event rather than a raised error, so without this an approval evaluation could only see "no result arrived" -- the same shape a crashed run has. The token and the question are deliberately left out: the token is a credential and the payload is the person's content, and neither belongs in a report an operator reads.
Source code in src/symfonic/evals/pause_evidence.py
plan_traits ¶
plan_traits(capabilities: Sequence[Any], *, effect_grants: Iterable[str], options: Mapping[str, Any] | None = None) -> frozenset[str]
Traits readable from one fold of capabilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
capabilities
|
Sequence[Any]
|
the same config objects handed to |
required |
effect_grants
|
Iterable[str]
|
the grants the invocation holds. Stated rather than
defaulted: a capability may decide what to contribute from them, so
folding with a wider set than the agent was given would credit
traits the compiled plan does not have. :func: |
required |
Source code in src/symfonic/evals/traits.py
probed_traits
async
¶
Grant each trait whose named public operation answers.
probes maps a trait to a public operation and its public availability
signal. Absence skips the call. A call that raises is a broken available
feature and propagates rather than disguising itself as not-applicable.
Deliberately not a truthiness test on the result. An empty review queue is a wired review door with nothing in it yet, which is exactly the state a procedural pack starts from.
Source code in src/symfonic/evals/traits.py
procedural_identity_rows ¶
Stable digests of procedure node ids, never procedure content.
Source code in src/symfonic/evals/procedural_evidence.py
procedural_review_rows ¶
What the review door holds, as (status, governed tool) per row.
The projection a target publishes so the pack can assert on the review queue without the queue's content reaching a report. Sorted, so two runs of the same deployment produce the same evidence.
A draft nobody reviewed projects as ("draft", "") -- it has no status
a gate acts on and governs no tool -- which is exactly what distinguishes
it from the approved row this pack requires.
Source code in src/symfonic/evals/procedural_pack.py
procedural_skill_pack ¶
procedural_skill_pack(evidence: CapabilityEvidence, *, demonstrations: Sequence[str], review_prompt: str, draft_prompt: str, satisfied_prompt: str, blocked_prompt: str, satisfied_state: Mapping[str, Any], blocked_state: Mapping[str, Any], draft_state: Mapping[str, Any], action_tool: str, procedure_fragment: str, ledger: SideEffectLedger, promotion_attribute: str = 'procedural_promoted', review_attribute: str = 'procedural_review', review_identity_attribute: str = 'procedural_review_ids', enforcement_identity_attribute: str = 'procedural_enforcement_ids', precondition_stage: str = PRECONDITION_STAGE, restart_before_enforcement: bool = True, conversation: str = 'procedural', policy: TrialPolicy = _DEFAULT_POLICY) -> PackResolution
Demonstrate, promote, approve, persist, then enforce one procedure (SC-16).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
demonstrations
|
Sequence[str]
|
the turns that give the consolidation something to generalise. The last one is where the promotion becomes visible, so the target must have run its cycle by then and must publish the cycle's own promoted count -- not a turn counter. |
required |
review_prompt
|
str
|
the operator turn under which the draft is reviewed. The evidence is the review door's listing, so an approval that did not reach the store fails here even when the answer says it did. |
required |
draft_prompt
|
str
|
a tool-calling turn before review; it proves the draft is absent from the prompt and inert at the enforcement gate. |
required |
satisfied_state
|
Mapping[str, Any]
|
the turn state under which the approved procedure's precondition holds. |
required |
blocked_state
|
Mapping[str, Any]
|
the same request with it unmet. Must differ from
|
required |
draft_state
|
Mapping[str, Any]
|
explicit state for the pre-approval tool call. |
required |
action_tool
|
str
|
the tool the approved procedure is allowed to call, and the tool the review row must name. |
required |
procedure_fragment
|
str
|
text that must appear in the recall block once the procedure is approved. Checked against model input, because an answer that repeats a procedure proves only that the model can repeat a procedure. |
required |
ledger
|
SideEffectLedger
|
the fixture the action tool applies its effect through. |
required |
promotion_attribute
|
str
|
where the target publishes the promoted count its consolidation cycle reported. |
'procedural_promoted'
|
review_attribute
|
str
|
where the target publishes
:func: |
'procedural_review'
|
review_identity_attribute
|
str
|
approved procedure identity digests read through the review door. |
'procedural_review_ids'
|
enforcement_identity_attribute
|
str
|
identity digests from the procedures the actual precondition callback read during this turn. |
'procedural_enforcement_ids'
|
restart_before_enforcement
|
bool
|
rebuild the deployment before the turn that enforces. On by default, because everything above the store is process state: a procedure that only governs until the next restart is not one a deployment can rely on. |
True
|
The last step is the one that discriminates. A refused call still reaches the gate, so the pack requires the round to show a rejected precondition stage, a tool call, zero successful executions, and a side-effect ledger whose applied count did not move from the previous step. A tool that ran anyway moves that count, and no wording in the answer can hide it.
Source code in src/symfonic/evals/procedural_pack.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | |
remember_fixture_scenario ¶
remember_fixture_scenario(fixture: BookFixture | None = None, *, conversation: str = 'glass-harbor-ingestion', scope: str = 'owner') -> Scenario
Build SC-05: one successful remember turn per checksum-pinned section.
Source code in src/symfonic/evals/fixture_ingestion.py
report_dict ¶
Return the stable, JSON-safe report representation.
Source code in src/symfonic/evals/reporters.py
resolve_pack ¶
resolve_pack(pack: str, evidence: CapabilityEvidence, build: Callable[[], Sequence[Scenario]], *, capabilities: Iterable[str] = (), turn_inputs: Iterable[str] = (), traits: Iterable[str] = (), operations: Iterable[str] = (), capability_tools: Iterable[tuple[str, str]] = (), evidence_channels: Iterable[str] = ()) -> PackResolution
Build pack's scenarios only when its required evidence is present.
build is a callable rather than a built sequence so that an
inapplicable pack never constructs steps for a capability nothing
composed -- the steps would be unrunnable, and holding them would invite a
caller to run them anyway.
Source code in src/symfonic/evals/applicability.py
resume_observation ¶
One redemption, as payload-free evidence.
Reads only identity and shape off the deployment's outcome. The validated answer and the recorded question are both left out: they are the person's content, and an operator reading a report needs to know that the turn came back, not what was said.
resume_continued is the fact an approval evaluation exists for. A
deployment can validate an answer, spend the token, and still have nothing
to continue -- the paused turn's state was never recorded, or could not be
rebuilt -- and every one of those failures reports a successful redemption.
So it is reported separately from the outcome, and separately again from
the checkpoint the token was bound to.
Source code in src/symfonic/evals/pause_evidence.py
run_scenario
async
¶
Run a scenario sequentially so external side effects stay attributable.
Source code in src/symfonic/evals/runner.py
run_suite
async
¶
run_suite(scenarios: Iterable[Scenario], factory: TargetFactory, *, tags: frozenset[str] = frozenset()) -> EvalReport
Run selected scenarios and return a deterministic aggregate report.
Source code in src/symfonic/evals/runner.py
structured_output_pack ¶
structured_output_pack(evidence: CapabilityEvidence, *, prompt: str, output_type: type[Any], expected: Mapping[str, Any] | None = None, conversation: str = 'structured', policy: TrialPolicy = _DEFAULT_POLICY) -> PackResolution
Require a validated instance of the declared schema, not prose about it.
StructuredOutputMatches reads the target's validated value, so a
fluent JSON-shaped answer that never reached the schema fails here.
Source code in src/symfonic/evals/content_packs.py
tool_behavior_journey ¶
Exercise execution, avoidance, schema rejection and failure containment.
Source code in src/symfonic/evals/tool_journeys.py
viewable_tool_result_pack ¶
viewable_tool_result_pack(evidence: CapabilityEvidence, *, tool_name: str, first_prompt: str, follow_up_prompt: str, expected_fragments: Sequence[str], arguments: dict[str, object] | None = None, conversation: str = 'viewable-result', policy: TrialPolicy = _DEFAULT_POLICY) -> PackResolution
A tool result the model can look at must still be lookable at later.
One turn proves nothing: compaction rewrites results that have settled,
so the failure is on the second turn. It replaced an image block with a
text stub and offered recall returning str, and the model then answers
about the picture from a description of it, which reads exactly like an
answer about the picture (#144).
Hence two steps in one conversation: the first calls the tool, the second
asks something only the image can settle. expected_fragments must name
something legible only from the image and never a value the first answer
already stated, or the model can reconstruct it from its own prose.