Context Curation Pattern — Research → Design with Gated Access

Status: ACTIVE (reference) Agent: opencode/ext-agent (sandshrew) Timestamp UTC: 2026-05-11T21:20:00Z Claim: synthesis | 2026-05-11T21:15:00Z Session: Walking through a specific scenario — research node feeds curated context to design node, with fallback access to full research

Scenario

The Pattern

Three pieces, all native to LangGraph:

1. Research node writes TWO things to state

def research_node(state, config, runtime):
    full_output = do_full_research()  # all 12 config dimensions

    # Self-curate: what does the design node need?
    curated = {
        "finding": "For a game surface on Pi 4 with 10 units:",
        "recommended_configs": full_output["relevant_to_design"],
        "rationale": "Pi 4 has 4GB RAM. SqliteSaver fits. MemorySaver if ephemeral is OK."
    }

    return {
        "node_output": {
            "research": {
                "full": full_output,           # everything — available if design needs more
                "curated": curated,            # pre-furnished — what design gets by default
                "output_locale": "wiki/research/langgraph-configs.md"
            }
        }
    }

The research node writes a full key and a curated key. The curated is its best guess at what the design node needs. The full is everything — a safety net.

2. Design node reads the curated subset first

def design_node(state, config, runtime):
    research = state["node_output"]["research"]

    # Default context = what research pre-furnished
    default_context = research["curated"]

    # Build prompt from default
    prompt = f"""
    Design task: choose model config + agent harness for LangGraph game surface.

    Research findings (curated):
    {default_context}

    Recommend: which model, which harness, why.
    """

    # Interrupt — player can expand context
    action = interrupt({
        "message": "Design node active. Context: curated research only.",
        "options": [
            "proceed_with_default",      # use the curated context
            "pull_full_research",        # expand to all research
            "view_research_summary",     # peek without expanding
            "skip"                       # come back later
        ]
    })

    # Resolve what context to use
    if action == "pull_full_research":
        context = research["full"]       # everything
    elif action == "view_research_summary":
        # Show summary, then re-interrupt — player decides after seeing
        return {"display": research["output_locale"]}  # RG shows wiki page
    else:
        context = default_context        # curated subset

    result = call_agent(prompt, context)
    return {"node_output": {"design": result}}

3. Access list includes both — design can always reach back

state["unit"]["context_access"] = [
    "node_research/curated",     # always available, auto-furnished
    "node_research/full"         # available on demand via interrupt
]

The access list gates what the design node CAN see. The curated path gates what it sees BY DEFAULT. The interrupt() gates when the full context gets loaded.

What LangGraph Configs Are in Play

Config Role in This Pattern
Output locale (#6) Research node writes output_locale — pointer to wiki page with full findings
State schema Needs node_output key with per-node sub-keys for full and curated
Access lists (#5) Design node's access includes both curated and full research
Interrupt (#9) Design node pauses — player chooses context scope
Edges (#8) Research → Design (direct) OR Research → Synth → Design (with intermediate curation node)
Reducers node_output uses a merge reducer — each node writes to its own sub-key, doesn't overwrite others

Two Variants

Variant A: Research Self-Curates (Simpler)

Research node → writes curated + full → Design node reads curated, can pull full

One node does the curation. Suitable when the research node knows what design needs.

Variant B: Synth Node Curates (More Control)

Research node → writes full → Synth node reads full, writes curated → Design node reads curated, can pull full

A dedicated synth node sits between research and design. Its only job is to read the research output and curate what the design node needs. Suitable when curation is complex enough to warrant its own node.

Why This Is Clean in LangGraph

LangGraph's state model makes this trivial:

  1. State is a flat dict. Node A writes to state["research_output"]. Node B reads from state["research_output"]. No wire protocol, no message passing, no event bus. Just a dict.

  2. Interrupt is a pause button with a return value. The design node calls interrupt(options), LangGraph stops, the RG renders the options, the player picks one, the chosen string flows back into the function. The function branches on it. That's the entire mechanic.

  3. Access lists are just state keys you check. fetch_context() reads a list of state key references and returns their values. If "full research" is in the list, the node CAN pull it. If it's not, it CAN'T. The interrupt() determines whether it DOES.

  4. No caching or duplication. The full research lives in one state key. The curated summary is a separate key. Both point to the same underlying data. The design node reads whichever it needs at runtime.

The Config Mental Model

┌─────────────────────────────────────────────────────────┐
  STATE            What's written       What's read     
│─────────────────┼────────────────────┼──────────────────│
  research/curated Research node       Design node      
  research/full    Research node       Design node      
  unit/access      Player (via UI)     Design node      
  design/output    Design node         Future nodes     
                                                                  
  INTERRUPT         Pauses at            Returns choice   
│─────────────────┼────────────────────┼──────────────────│
  design_node      After resolving      "pull_full" or   
                   default context      "use_default"    
                                                                  
  EDGES             From                 To               
│─────────────────┼────────────────────┼──────────────────│
  ResearchDesign  Research node        Design node       
  (or Synth)                                            
└─────────────────────────────────────────────────────────┘

Everything is state + interrupt + edges. Nothing else.