symfonic.capabilities.delegation.lockdown¶
lockdown ¶
No delegated child holds a prompt-block write surface, on any path.
A prompt block has exactly one writer. That is the whole guarantee the block layer offers an operator: the rules the agent runs under are written by a human (authored tiers) or by the consolidation timeline (learned tiers), and the agent reads them. A delegated child that could write one breaks it twice over — it is a second writer, and a writer the parent never sees, because the child runs its own loop with its own palette.
Two paths, and only one of them can be fixed¶
:func:deny_child_self_edit covers configs the parent builds: it returns a
config with the request cleared. :func:assert_no_write_surface covers children
the parent is handed: those cannot be sanitised — already constructed, palette
already frozen — so it refuses. Continuing quietly would leave the caller
believing in a lock that did not hold.
Both run at construction, never per run. The palette is advertised to the model when the graph compiles; a check that fired at delegation time would fire after the thing it was meant to restrict had already been offered.
The vocabulary is declared here, not imported¶
The reserved names live in the framework's block taxonomy, which a capability may not import. Duplicating a security vocabulary is a real risk, so it is duplicated and pinned: a contract test compares this predicate against the shipped one over the whole near-miss corpus, in the test tree, where importing both is legal. Alignment asserted from outside beats either a forbidden import or a silent divergence.
SELF_EDIT_FIELD
module-attribute
¶
The config field naming the (always denied) child write surface.
WRITE_SURFACE_NAMESPACE
module-attribute
¶
Reserved tool-name prefix. Fail-closed on the whole prefix, not a fixed set: a verb added under it tomorrow is caught without editing this module.
WRITE_SURFACE_TOOL_NAMES
module-attribute
¶
WRITE_SURFACE_TOOL_NAMES: frozenset[str] = frozenset({'memory_block_append', 'memory_block_replace', 'memory_block_rewrite'})
The named write tools. Documentation, not the check — see the pattern.
assert_no_write_surface ¶
Refuse pre-built child agent if it can write a prompt block.
Returns the child, so a caller can register in one expression and a reader can see that registration passed through the lock rather than around it.
Raises:
| Type | Description |
|---|---|
ChildLockdownError
|
If the child carries a write tool, or a config requesting one. |
Source code in src/symfonic/capabilities/delegation/lockdown.py
deny_child_self_edit ¶
Return config with the write-surface request cleared.
The common path allocates nothing and preserves object identity: a config that never asked is returned unchanged, which matters for callers that compare configs by identity.
Raises:
| Type | Description |
|---|---|
UnsanitisableChildConfigError
|
If the flag is set and the object
exposes no |
Source code in src/symfonic/capabilities/delegation/lockdown.py
is_write_surface_tool_name ¶
True if a tool called name may write a prompt block.
Matched case-insensitively against a stripped name. Nothing on the path
from a registered tool to this predicate normalises either, so a guard that
a shift key defeats would be no guard: MEMORY_BLOCK_APPEND reaches a
child exactly as easily as the lowercase spelling.
This is a name heuristic and says so. It cannot detect a write tool given a
name outside the vocabulary entirely (grant_edit). No name check can —
the reserved namespace exists so that the framework's own tools are always
inside it, and the verb family covers the near-misses an adopter wrapping
the write API would plausibly reach for.
Source code in src/symfonic/capabilities/delegation/lockdown.py
registered_write_surface_tools ¶
Names of prompt-block write tools registered on agent.
The authoritative check, because it reads what the model will actually be
offered. A tool handed straight to a child's constructor never passed
through :data:SELF_EDIT_FIELD, so a config-only guard waves it through.
Source code in src/symfonic/capabilities/delegation/lockdown.py
wants_block_self_edit ¶
True if config explicitly requests the block write surface.
Compared with is True rather than coerced. A duck-typed child that
synthesises attributes on access — a mock, a lazy or remote proxy — answers
any attribute read with a truthy object, and coercion reported such a child
as requesting a surface it had never heard of.