Feature traits: what a compiled plan wired, not what its capabilities are called.
A capability name is an umbrella. prompting is folded by
PromptingCapability(sources=[]), which compiles nothing and offers a
knowledge evaluation no corpus at all; memory and tools are folded by
every deployment that remembers anything and runs any tool, none of which
implies a procedure was ever learned, reviewed, or allowed to refuse a call.
Gating an optional pack on the umbrella therefore admits deployments whose
behaviour the pack cannot judge, and the pack then fails for the one reason a
regression suite must never fail: the feature is not there.
A trait is narrower and is read from evidence rather than declared:
- :func:
plan_traits folds the same capability configs the Agent folds,
through the kernel's own public :func:fold_contributions, and reads the
plan that comes back. Its payload-free canonical digest must reproduce the
manifest retained by the actual agent. The prompting stage carries its
compiled digest and region count; a procedural gate requires the shipped
implementation identity as well as its public name.
- :func:
probed_traits grants a trait only when a public operation of the
deployment answers. The procedural review door is the case: whether a
reviewer can see a queue is not in the plan at all -- it is whether
MemoryAdminService was built with the layer the promotion phase writes
into -- and the only honest way to know is to ask it.
Neither route accepts a declaration, and both are necessary rather than
sufficient: a pack that resolves applicable still has to prove the behaviour
on a turn.
TraitProbe
dataclass
TraitProbe(available: bool, operation: Callable[[], Any])
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.
attested_traits
attested_traits(agent: Any) -> frozenset[str]
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
| def attested_traits(agent: Any) -> frozenset[str]:
"""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.
"""
manifest = getattr(agent, "composition_manifest", None)
if not isinstance(manifest, Mapping) or not manifest.get("digest"):
return frozenset()
found: set[str] = set()
for row in manifest.get("stages", ()):
if not isinstance(row, (tuple, list)) or len(row) < 7 or row[0] != _PROMPTING:
continue
counters = dict(row[6]) if isinstance(row[6], (tuple, list)) else {}
if any(_count(counters.get(key)) > 0 for key in _SOURCE_COUNTERS):
found.add(KNOWLEDGE_SOURCES)
for row in manifest.get("preconditions", ()):
if (
isinstance(row, (tuple, list))
and len(row) == 2
and tuple(row) == (PROCEDURAL_PRECONDITION_NAME, _SKILL_PRECONDITION)
):
found.add(PROCEDURAL_PRECONDITION)
return frozenset(found)
|
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:probed_traits obtained from a public operation.
|
()
|
Source code in src/symfonic/evals/traits.py
| def 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.
Args:
traits: additional traits established elsewhere, such as the ones
:func:`probed_traits` obtained from a public operation.
"""
folded = frozenset(getattr(agent, "capabilities", ()) or ())
actual = getattr(agent, "composition_manifest", None)
if not isinstance(actual, Mapping) or not actual.get("digest"):
raise TypeError("compiled evidence requires an Agent exposing its composition_manifest")
from symfonic.kernel.contracts.contributions import fold_contributions
from symfonic.kernel.contracts.diagnostics import composition_manifest
stages, _handlers, _grants, tools, names, preconditions = fold_contributions(
tuple(capabilities),
effect_grants=frozenset(effect_grants),
options=options,
)
candidate = composition_manifest(stages, tools, names, preconditions)
if candidate["digest"] != actual["digest"]:
raise ValueError(
"this fold did not reproduce the agent's composition digest, so "
"the traits read from it describe something the agent did not "
"compile. Pass the same capabilities and effect grants the agent "
"was built with."
)
return CapabilityEvidence(
capabilities=folded,
turn_inputs=turn_inputs_of(agent),
traits=_traits_of(stages, preconditions) | frozenset(traits),
operations=operations_of(target) if target is not None else frozenset(),
capability_tools=frozenset(tuple(row) for row in actual.get("tools", ())),
)
|
operations_of
operations_of(target: Any) -> frozenset[str]
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
| def operations_of(target: Any) -> frozenset[str]:
"""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.
"""
declared = getattr(target, "operations", None)
if declared is not None and not isinstance(declared, (str, bytes)):
try:
return frozenset(str(name) for name in declared)
except TypeError: # pragma: no cover - exotic attribute
return frozenset()
return frozenset()
|
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 Agent(...).
contribute() is a declaration by contract -- "anything it needs
to do happens in its handlers, at dispatch" -- so folding them a
second time to read the plan performs nothing.
|
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:compiled_evidence
catches a mismatch against the agent's canonical manifest digest.
|
required
|
Source code in src/symfonic/evals/traits.py
| def plan_traits(
capabilities: Sequence[Any],
*,
effect_grants: Iterable[str],
options: Mapping[str, Any] | None = None,
) -> frozenset[str]:
"""Traits readable from one fold of ``capabilities``.
Args:
capabilities: the same config objects handed to ``Agent(...)``.
``contribute()`` is a declaration by contract -- "anything it needs
to *do* happens in its handlers, at dispatch" -- so folding them a
second time to read the plan performs nothing.
effect_grants: 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:`compiled_evidence`
catches a mismatch against the agent's canonical manifest digest.
"""
from symfonic.kernel.contracts.contributions import fold_contributions
stages, _handlers, _grants, _tools, _names, preconditions = fold_contributions(
tuple(capabilities),
effect_grants=frozenset(effect_grants),
options=options,
)
return _traits_of(stages, preconditions)
|
probed_traits
async
probed_traits(probes: Mapping[str, TraitProbe]) -> frozenset[str]
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
| async def probed_traits(
probes: Mapping[str, TraitProbe],
) -> frozenset[str]:
"""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.
"""
granted: set[str] = set()
for trait, probe in probes.items():
if not trait:
raise ValueError("a trait probe requires a trait name")
if not isinstance(probe, TraitProbe):
raise TypeError("probed_traits requires TraitProbe values")
if not probe.available:
continue
result = probe.operation()
if inspect.isawaitable(result):
await result
granted.add(trait)
return frozenset(granted)
|