Semantic Backpropagation Attributor
Overview
Agents are graphs, not pipelines. The dangerous failure mode in a self-evolving
system is not a bad update. It is a good update to one node that silently breaks
another. In a deeply interconnected graph, improving a component in isolation
causes "action at a distance" failures that are hard to trace. Semantic
backpropagation is the mechanism that prevents this.
The idea adapts the chain rule. In numerical backpropagation, gradients flow
backward through a computational graph, updating each parameter by how it
contributed to the loss. Semantic backpropagation does the same in natural
language: the gradient is a structured description of the required change, and it
flows backward through the execution graph from the point of failure.
Neighbor-awareness is the decisive part. When generating feedback for node v
based on what successor w needed, the feedback includes not just v's output and
w's error but the outputs of ALL OTHER predecessors of w. That context is what
makes the feedback precise.
The chapter's concrete example: an Extractor pulls "Revenue: $10M", a
CurrencyConverter converts it to "EUR 9.5M", and a Validator (which also received
a DateChecker's "Date: 2022") flags that the exchange rate was 0.9, not 0.95.
Without neighbor context, feedback to the Extractor reads "your $10M led to a
conversion error" and the Extractor might wrongly change its extraction. With
neighbor context, the error is correctly assigned to the CurrencyConverter's rate
lookup and the Extractor is left unchanged. the neighbor-aware feedback example shows the same shape for
a DevOps CausalAttributionNode, with ChangelogRetrieval and KnowledgeGraphQuery
as the neighbor predecessors.
Honesty note on the metaphor: "backpropagation" here is an analogy, not a
mechanism. A numerical gradient is exact and deterministic; this skill's
"gradient" is credit assignment produced by LLM judgment over the execution
graph — structured, neighbor-aware, and far better than unstructured blame, but
still a hypothesis about causality, not a derivative. Treat every attribution
as a claim to verify (rerun the trace with the blamed node patched) before
committing an intervention on it. What IS deterministic in this skill: the
graph traversal, the neighbor-context assembly, and the routing of the verdict.
When to Use
- After a diagnostic report has localized a failing node and you need feedback
that will not break the node's neighbors
- Multi-node agent graphs where a fix to one node could ripple ("action at a
distance")
- Before any intervention (prompt refinement, SEAL curriculum, fine-tune) so the
change is grounded in what every connected node needed
- Attributing a surfaced error to its true origin when the node that flagged it
is not the node that caused it
Phrases: "semantic backpropagation", "neighbor-aware feedback", "which node
caused this", "credit assignment", "textual gradient", "action at a distance",
"leave the Extractor unchanged".
When NOT to Use
- Single-node or linear pipelines with no sibling predecessors: there is no
neighbor context to add and no action-at-a-distance to prevent
- As the intervention itself: this decides WHERE and WHAT should change; SEAL /
TPT / prompt refinement make the change stick (chapter, A Suite of
Self-Improvement Frameworks)
- Before diagnosis: run the execution-graph and diagnostic layers first so you
have a localized failing node to backpropagate from
- When neighbor outputs are untrusted external content: sanitize first (see
Security Posture) before folding them into feedback
Process
| Step |
Input |
Action |
Output |
Verification |
| 1 |
edges (parent, child), node_id |
lib.predecessors_of(edges, node_id) |
parents of node_id, in edge order |
returns [Extractor] for CurrencyConverter; [CurrencyConverter, DateChecker] for Validator |
| 2 |
edges, node_outputs, target_node, successor |
lib.neighbor_context_for(...) |
dict "_output" of every OTHER predecessor of successor |
target_node excluded; sibling predecessor included |
| 3 |
edges, node_outputs, failure_node, predicted, actual |
lib.attribute(...) |
responsible node_id (may differ from failure_node) |
currency case returns CurrencyConverter, not Extractor or Validator |
| 4 |
target_node, successor, edges, node_outputs, predicted, actual, feedback_text |
lib.generate_feedback(...) |
SemanticFeedback (the neighbor-aware feedback example shape) |
neighbor_context populated; empty feedback_text synthesizes a neighbor-grounded string |
| 5 |
edges, node_outputs, failure_node, predicted, actual, feedback_text |
lib.backprop(...) |
attribute then generate_feedback for the responsible node |
devops case targets CausalAttributionNode with both neighbor outputs |
| 6 |
SemanticFeedback |
.to_dict() / SemanticFeedback.from_dict(d) |
serialize / deserialize |
round-trip preserves all four fields |
| 7 |
list of NodeIO |
lib.outputs_from(nodes) |
node_outputs mapping |
keys match node ids, values are output strings |
Rationalizations
| Agent rationalization |
Documented rebuttal |
| "The node that flagged the error is the node to fix." |
The chapter is explicit: the Validator surfaces the error but "the error originated in the CurrencyConverter's rate lookup." Attribution is a graph question, not a which-node-raised-it question. |
| "Feedback to a node only needs that node's output and the error." |
That is exactly the failure mode the chapter names. Without the DateChecker's output as neighbor context, the Extractor "might reasonably conclude it should have extracted a different number, a wrong fix." Include all other predecessors of the successor. |
| "Improving one node in isolation is fine if its own tests pass." |
Ch7: "improving a component in isolation can cause 'action at a distance' failures." A change to one node must be evaluated in the context of what every other connected node needs. |
| "A vague 'consider more factors' directive is enough feedback." |
the neighbor-aware feedback example: the neighbor_context (timeout 30s->10s present in input, no batch_charge usage found) is "what makes the feedback specific enough to generate a targeted prompt update rather than a vague directive." |
| "Attribution and the intervention are the same step." |
Ch7: "semantic backpropagation determines where to change and what the change should accomplish. The remaining question is how to make that change stick." Different frameworks (SEAL / TPT / prompt refinement) own the how. |
Red Flags
- Feedback sent to the node that surfaced the error rather than the node that
caused it. Attribution collapsed to failure_node. Check that a predecessor
carrying the wrong value is being implicated.
- neighbor_context is empty on a node with sibling predecessors. The
successor was mis-identified or predecessors_of returned nothing. The feedback
is now un-grounded and can trigger a wrong fix.
- neighbor_context includes the target node's own output. The exclusion in
neighbor_context_for was bypassed. Neighbor evidence must be about the
siblings, not the target.
- Attribution changes the Extractor in the currency case. Incorrect credit
assignment. The DateChecker + Validator evidence exonerates the Extractor; the
CurrencyConverter's rate lookup is the origin.
- Synthesized feedback names no neighbor evidence. An empty feedback_text
produced a generic string. The default must weave in the neighbor outputs.
Non-Negotiable Verification
- Run the benchmark battery.
python cli.py benchmark must report 6/6:
predecessors_of returns correct parents; neighbor_context_for excludes the
target and includes the sibling predecessor; the currency case attributes to
CurrencyConverter (not Extractor); the devops feedback contains the neighbor
evidence (timeout 30s->10s present in input); SemanticFeedback.to_dict
round-trips.
- Run the currency scenario.
python cli.py scenario currency shows
attribution landing on CurrencyConverter with the Extractor left unchanged.
- Run the devops scenario.
python cli.py scenario devops-prediction
emits the the neighbor-aware feedback example neighbor-aware feedback for the CausalAttributionNode.
- Verify CLI help.
python cli.py --help exits 0 and prints the SKILL.md
description.
Security Posture
- Prompt injection. node_outputs and neighbor evidence are folded into the
textual gradient. If any node output originates from untrusted external
content (a scraped changelog, a retrieved document), sanitize it before it
enters the feedback string. Treat neighbor outputs as untrusted until
validated; a malicious changelog line could inject instructions into the
synthesized feedback that a downstream prompt-update step would then apply.
- Data exfiltration.
lib.py makes no network calls and no file writes. The
CLI reads a caller-supplied --path JSON and prints results to stdout; the
caller owns downstream piping. Nothing leaves the process.
- Privilege escalation. No shell invocation, no concatenated input to a
shell, no file writes outside the given paths. Attribution is a deterministic
numeric-evidence heuristic over in-memory dicts; the production swap to an LLM
judge is a documented seam, not an ambient capability.
Composition
- Composes on top of the execution-graph primitive: the edges and
node_outputs it operates over are the graph that primitive captures.
- Composes after the diagnostic report: attribution starts from the
localized failing node the diagnosis produced.
- Feeds into the intervention frameworks (SEAL targeted curriculum, TPT,
prompt refinement): the SemanticFeedback is their input. This skill decides
where and what; they make the change stick.
- Generator pattern / Ghosh Workflow. It generates a structured feedback
artifact by orchestrating graph traversal, attribution, and synthesis across
several nodes, one workflow layer above a single primitive.
Source Attribution
Distilled from Agentic GraphRAG (O'Reilly, by Anthony Alcaraz and Sam Julien) Ch7 — Semantic
Backpropagation: Coherent Evolution Across the Graph, plus the neighbor-aware feedback example
(Neighbor-aware semantic feedback for the causal attribution node). The chapter
credits TextGrad for the foundational insight that textual feedback can serve as
a gradient signal, and adapts the chain rule so a structured natural-language
gradient flows backward through the execution graph with neighbor-aware context.
1---2name: semantic-backprop-attributor3description: Ch7 self-evolution primitive: attribute a failure to the node that actually caused it, then generate NEIGHBOR-AWARE textual feedback that flows backward through the execution graph from the point of failure. Adapts TextGrad's textual-gradient insight (feedback as a gradient signal) plus the chain rule: when generating feedback for a node based on what its successor needed, the feedback includes the outputs of ALL OTHER predecessors of that successor. That neighbor context is what prevents incorrect credit assignment. Use AFTER a diagnostic report has localized a failing node and you need coherent, cross-graph feedback before an intervention. NOT for single-node pipelines with no neighbors (there is no action-at-a-distance to prevent), NOT the intervention itself (this decides where and what should change, SEAL/TPT/prompt refinement make the change stick).4---56# Semantic Backpropagation Attributor78## Overview910Agents are graphs, not pipelines. The dangerous failure mode in a self-evolving11system is not a bad update. It is a good update to one node that silently breaks12another. In a deeply interconnected graph, improving a component in isolation13causes "action at a distance" failures that are hard to trace. Semantic14backpropagation is the mechanism that prevents this.1516The idea adapts the chain rule. In numerical backpropagation, gradients flow17backward through a computational graph, updating each parameter by how it18contributed to the loss. Semantic backpropagation does the same in natural19language: the gradient is a structured description of the required change, and it20flows backward through the execution graph from the point of failure.2122Neighbor-awareness is the decisive part. When generating feedback for node v23based on what successor w needed, the feedback includes not just v's output and24w's error but the outputs of ALL OTHER predecessors of w. That context is what25makes the feedback precise.2627The chapter's concrete example: an Extractor pulls "Revenue: $10M", a28CurrencyConverter converts it to "EUR 9.5M", and a Validator (which also received29a DateChecker's "Date: 2022") flags that the exchange rate was 0.9, not 0.95.30Without neighbor context, feedback to the Extractor reads "your $10M led to a31conversion error" and the Extractor might wrongly change its extraction. With32neighbor context, the error is correctly assigned to the CurrencyConverter's rate33lookup and the Extractor is left unchanged. the neighbor-aware feedback example shows the same shape for34a DevOps CausalAttributionNode, with ChangelogRetrieval and KnowledgeGraphQuery35as the neighbor predecessors.3637**Honesty note on the metaphor:** "backpropagation" here is an analogy, not a38mechanism. A numerical gradient is exact and deterministic; this skill's39"gradient" is credit assignment produced by LLM judgment over the execution40graph — structured, neighbor-aware, and far better than unstructured blame, but41still a hypothesis about causality, not a derivative. Treat every attribution42as a claim to verify (rerun the trace with the blamed node patched) before43committing an intervention on it. What IS deterministic in this skill: the44graph traversal, the neighbor-context assembly, and the routing of the verdict.4546## When to Use4748- After a diagnostic report has localized a failing node and you need feedback49 that will not break the node's neighbors50- Multi-node agent graphs where a fix to one node could ripple ("action at a51 distance")52- Before any intervention (prompt refinement, SEAL curriculum, fine-tune) so the53 change is grounded in what every connected node needed54- Attributing a surfaced error to its true origin when the node that flagged it55 is not the node that caused it5657Phrases: "semantic backpropagation", "neighbor-aware feedback", "which node58caused this", "credit assignment", "textual gradient", "action at a distance",59"leave the Extractor unchanged".6061## When NOT to Use6263- Single-node or linear pipelines with no sibling predecessors: there is no64 neighbor context to add and no action-at-a-distance to prevent65- As the intervention itself: this decides WHERE and WHAT should change; SEAL /66 TPT / prompt refinement make the change stick (chapter, A Suite of67 Self-Improvement Frameworks)68- Before diagnosis: run the execution-graph and diagnostic layers first so you69 have a localized failing node to backpropagate from70- When neighbor outputs are untrusted external content: sanitize first (see71 Security Posture) before folding them into feedback7273## Process7475| Step | Input | Action | Output | Verification |76|------|-------|--------|--------|--------------|77| 1 | edges (parent, child), node_id | `lib.predecessors_of(edges, node_id)` | parents of node_id, in edge order | returns [Extractor] for CurrencyConverter; [CurrencyConverter, DateChecker] for Validator |78| 2 | edges, node_outputs, target_node, successor | `lib.neighbor_context_for(...)` | dict "<node>_output" of every OTHER predecessor of successor | target_node excluded; sibling predecessor included |79| 3 | edges, node_outputs, failure_node, predicted, actual | `lib.attribute(...)` | responsible node_id (may differ from failure_node) | currency case returns CurrencyConverter, not Extractor or Validator |80| 4 | target_node, successor, edges, node_outputs, predicted, actual, feedback_text | `lib.generate_feedback(...)` | `SemanticFeedback` (the neighbor-aware feedback example shape) | neighbor_context populated; empty feedback_text synthesizes a neighbor-grounded string |81| 5 | edges, node_outputs, failure_node, predicted, actual, feedback_text | `lib.backprop(...)` | attribute then generate_feedback for the responsible node | devops case targets CausalAttributionNode with both neighbor outputs |82| 6 | `SemanticFeedback` | `.to_dict()` / `SemanticFeedback.from_dict(d)` | serialize / deserialize | round-trip preserves all four fields |83| 7 | list of `NodeIO` | `lib.outputs_from(nodes)` | node_outputs mapping | keys match node ids, values are output strings |8485## Rationalizations8687| Agent rationalization | Documented rebuttal |88|------------------------|--------------------|89| "The node that flagged the error is the node to fix." | The chapter is explicit: the Validator surfaces the error but "the error originated in the CurrencyConverter's rate lookup." Attribution is a graph question, not a which-node-raised-it question. |90| "Feedback to a node only needs that node's output and the error." | That is exactly the failure mode the chapter names. Without the DateChecker's output as neighbor context, the Extractor "might reasonably conclude it should have extracted a different number, a wrong fix." Include all other predecessors of the successor. |91| "Improving one node in isolation is fine if its own tests pass." | Ch7: "improving a component in isolation can cause 'action at a distance' failures." A change to one node must be evaluated in the context of what every other connected node needs. |92| "A vague 'consider more factors' directive is enough feedback." | the neighbor-aware feedback example: the neighbor_context (timeout 30s->10s present in input, no batch_charge usage found) is "what makes the feedback specific enough to generate a targeted prompt update rather than a vague directive." |93| "Attribution and the intervention are the same step." | Ch7: "semantic backpropagation determines where to change and what the change should accomplish. The remaining question is how to make that change stick." Different frameworks (SEAL / TPT / prompt refinement) own the how. |9495## Red Flags9697- **Feedback sent to the node that surfaced the error rather than the node that98 caused it.** Attribution collapsed to failure_node. Check that a predecessor99 carrying the wrong value is being implicated.100- **neighbor_context is empty on a node with sibling predecessors.** The101 successor was mis-identified or predecessors_of returned nothing. The feedback102 is now un-grounded and can trigger a wrong fix.103- **neighbor_context includes the target node's own output.** The exclusion in104 `neighbor_context_for` was bypassed. Neighbor evidence must be about the105 siblings, not the target.106- **Attribution changes the Extractor in the currency case.** Incorrect credit107 assignment. The DateChecker + Validator evidence exonerates the Extractor; the108 CurrencyConverter's rate lookup is the origin.109- **Synthesized feedback names no neighbor evidence.** An empty feedback_text110 produced a generic string. The default must weave in the neighbor outputs.111112## Non-Negotiable Verification1131141. **Run the benchmark battery.** `python cli.py benchmark` must report 6/6:115 predecessors_of returns correct parents; neighbor_context_for excludes the116 target and includes the sibling predecessor; the currency case attributes to117 CurrencyConverter (not Extractor); the devops feedback contains the neighbor118 evidence (timeout 30s->10s present in input); SemanticFeedback.to_dict119 round-trips.1202. **Run the currency scenario.** `python cli.py scenario currency` shows121 attribution landing on CurrencyConverter with the Extractor left unchanged.1223. **Run the devops scenario.** `python cli.py scenario devops-prediction`123 emits the the neighbor-aware feedback example neighbor-aware feedback for the CausalAttributionNode.1244. **Verify CLI help.** `python cli.py --help` exits 0 and prints the SKILL.md125 description.126127## Security Posture128129- **Prompt injection.** node_outputs and neighbor evidence are folded into the130 textual gradient. If any node output originates from untrusted external131 content (a scraped changelog, a retrieved document), sanitize it before it132 enters the feedback string. Treat neighbor outputs as untrusted until133 validated; a malicious changelog line could inject instructions into the134 synthesized feedback that a downstream prompt-update step would then apply.135- **Data exfiltration.** `lib.py` makes no network calls and no file writes. The136 CLI reads a caller-supplied `--path` JSON and prints results to stdout; the137 caller owns downstream piping. Nothing leaves the process.138- **Privilege escalation.** No shell invocation, no concatenated input to a139 shell, no file writes outside the given paths. Attribution is a deterministic140 numeric-evidence heuristic over in-memory dicts; the production swap to an LLM141 judge is a documented seam, not an ambient capability.142143## Composition144145- **Composes on top of** the execution-graph primitive: the edges and146 node_outputs it operates over are the graph that primitive captures.147- **Composes after** the diagnostic report: attribution starts from the148 localized failing node the diagnosis produced.149- **Feeds into** the intervention frameworks (SEAL targeted curriculum, TPT,150 prompt refinement): the SemanticFeedback is their input. This skill decides151 where and what; they make the change stick.152- **Generator pattern / Ghosh Workflow.** It generates a structured feedback153 artifact by orchestrating graph traversal, attribution, and synthesis across154 several nodes, one workflow layer above a single primitive.155156## Source Attribution157158Distilled from *Agentic GraphRAG* (O'Reilly, by Anthony Alcaraz and Sam Julien) Ch7 — Semantic159Backpropagation: Coherent Evolution Across the Graph, plus the neighbor-aware feedback example160(Neighbor-aware semantic feedback for the causal attribution node). The chapter161credits TextGrad for the foundational insight that textual feedback can serve as162a gradient signal, and adapts the chain rule so a structured natural-language163gradient flows backward through the execution graph with neighbor-aware context.