---
title: "The Shared Shape · example of The Contract · Agentic Atlas"
description: "The Shared Shape: two steps import one shared file defining what they send and return."
canonical: "https://agentic-atlas.dev/nodes/shared-shape"
last-updated: "2026-09-23"
---

1. [Agentic Atlas](https://agentic-atlas.dev/)
2. [Patterns in the Agentic Atlas](https://agentic-atlas.dev/atlas)
3. [Foundations](https://agentic-atlas.dev/nodes/foundations)
4. [The Contract](https://agentic-atlas.dev/nodes/contract-documentation)
5. The Shared Shape
1. example of [The Contract](https://agentic-atlas.dev/nodes/contract-documentation)
   # The Shared Shape
   **How do you keep both sides of an interface in agreement?**
   When both sides import one contract, a change to the handshake happens once and reaches both together.
   Hook
   two steps import one shared file defining what they send and return
   Laws & fences
   - Separate copies stay in sync only through effort; one shared file keeps both sides in sync by structure.
   - A contract needs rules for consuming each status, not just field shapes, to specify the handshake.
   - A shared file puts the agreement in one place; enforcing it is a separate job.
   - What matters is one shared source for the contract, not the language used to write it.
   When to reach
   - Reach for this when two steps each describe the data they exchange, and those descriptions drift apart.
   - Do not reach for this for runtime validation or recovery from a bad return; those are separate decisions.
   Provenance
   [shared-shape/result](https://agentic-atlas.dev/nodes/shared-shape#result) · v1.0.11
   Addresses
   atlas_cards shared-shape
2. [The Contract Keystone](https://agentic-atlas.dev/nodes/the-contract-keystone)
3. [The Contract](https://agentic-atlas.dev/nodes/contract-documentation)

The card, in place · its connections drawn edges from atlas_links shared-shape

On this plate

[context](https://agentic-atlas.dev/nodes/shared-shape#context) [problem-signal](https://agentic-atlas.dev/nodes/shared-shape#problem-signal) [choice](https://agentic-atlas.dev/nodes/shared-shape#choice) [before](https://agentic-atlas.dev/nodes/shared-shape#before) [implementation](https://agentic-atlas.dev/nodes/shared-shape#implementation) [result](https://agentic-atlas.dev/nodes/shared-shape#result) [verification](https://agentic-atlas.dev/nodes/shared-shape#verification) [lessons](https://agentic-atlas.dev/nodes/shared-shape#lessons) [relationships](https://agentic-atlas.dev/nodes/shared-shape#relationships)

Every section is addressable on its own. Read only the ground you need.

## Context

[Permalink to Context section](https://agentic-atlas.dev/nodes/shared-shape#context)

A `write_brief` step asks a `collect_evidence` step to research one question. The caller sends a request; the research step sends findings back. They are implemented in separate files and can change independently, but both live in the same repository and can import a shared module.

The [seam](https://agentic-atlas.dev/glossary/seam) has two directions:

```
┌────────────────────────┐       ┌────────────────────────┐
│ caller: write_brief    │       │ step: collect_evidence │
│ imports Request        │──────▶│ imports Request        │
│ imports Return         │◀──────│ imports Return         │
└───────────┬────────────┘       └────────────┬───────────┘
            │                                 │
            └──────── both import ────────────┘
                              │
             contracts/research_handoff.py
                 ┌──────────────────────┐
                 │ ResearchRequest      │
                 │ ResearchReturn       │
                 │ consumption rules    │
                 └──────────────────────┘
```

## Problem signal

[Permalink to Problem signal section](https://agentic-atlas.dev/nodes/shared-shape#problem-signal)

The first implementation gives each side its own description of the crossing. The caller expects `sources`; the research step has renamed that field `evidence`:

```
# caller.py                         # research_step.py
class ResearchReturn(TypedDict):    class ResearchReturn(TypedDict):
    sources: list[Source]               evidence: list[Evidence]
```

Neither file is malformed in isolation. **The disagreement exists only at the seam:** a change that looked local altered the handshake for one participant and not the other.

## Choice

[Permalink to Choice section](https://agentic-atlas.dev/nodes/shared-shape#choice)

Make the agreement seam-owned. Move the request shape, return shape, and the rules for consuming each status into `contracts/research_handoff.py`. Both sides import those names instead of restating them.

The nearest alternative is synchronized copies: keep one type beside the caller and another beside the research step, then remember to edit both. That is two declarations with a process promise between them. The shared module is one declaration.

## Before

[Permalink to Before section](https://agentic-atlas.dev/nodes/shared-shape#before)

The baseline has two sources of truth:

```
caller/ResearchReturn  ≈  research_step/ResearchReturn
```

The `≈` is the problem. Agreement depends on two files remaining coincidentally equivalent, and review must reconstruct the seam by comparing them.

## Implementation

[Permalink to Implementation section](https://agentic-atlas.dev/nodes/shared-shape#implementation)

The single decision is **relocating both private descriptions into one shared contract file**. The standing agreement carries the two shapes:

```
class ResearchRequest(TypedDict):
    question: str
    allowed_sources: list[str]
    as_of: str

class ResearchReturn(TypedDict):
    status: Literal["complete", "blocked"]
    findings: list[Finding]
    unanswered: list[str]
```

It also carries how the receiver consumes the result: `complete` means `unanswered` is empty; `blocked` means the caller surfaces the unanswered questions instead of filling them silently. Shape without consumption rules would specify bytes, not the handshake.

Both participants now point at the same artifact:

```
# caller.py
from contracts.research_handoff import ResearchRequest, ResearchReturn

# research_step.py
from contracts.research_handoff import ResearchRequest, ResearchReturn
```

The request and return values are the per-crossing instances. Their `as_of` and evidence fields carry the freshness and provenance required for this particular handoff; the shared module is the standing agreement that requires those fields on every handoff.

## Result

[Permalink to Result section](https://agentic-atlas.dev/nodes/shared-shape#result)

The after-state has one equality, not an approximation:

```
caller ──imports──▶ research_handoff ◀──imports── research_step
```

Renaming `findings` now changes the seam once. Both implementations encounter the same new shape through the same import, and a reviewer can inspect the whole handshake without diffing participant-owned copies.

The shared file does not make the crossing correct by itself. It makes the agreement singular and addressable; enforcement is a separate role.

## Verification

[Permalink to Verification section](https://agentic-atlas.dev/nodes/shared-shape#verification)

The supporting sketch is executable:

```
python3 demo.py
```

It proves the property this example claims:

- `caller.py` imports both crossing types from the contract;
- `research_step.py` imports the same two types;
- neither participant declares a local request or return shape; and
- one request crosses into the step and one return crosses back.

The sketch does **not** prove runtime validation, compatibility across deployed versions, or recovery from a bad return. Those are different decisions. The [The Contract Keystone](https://agentic-atlas.dev/nodes/the-contract-keystone) begins where an assertion checks this agreement.

## Lessons

[Permalink to Lessons section](https://agentic-atlas.dev/nodes/shared-shape#lessons)

**Put the contract on the edge.** When the sender owns one description and the receiver owns another, synchronization is an activity. When both point to one seam-owned artifact, synchronization is the structure.

The concrete syntax does not travel. A Python workflow can share a `TypedDict`; separate services can generate their local types from one schema; two agent steps can both reference one Markdown or YAML contract. The invariant is the shared source, not the language that expresses it.

The relationships ledger

Evidence-bearing references

## Relationships

Every connection keeps the section where it was found. The map above orients; this ledger carries the evidence.

### Outbound references 1

1. in-slice · occurrence 1
   [The Contract Keystone](https://agentic-atlas.dev/nodes/the-contract-keystone)
   how contracts, cheap checks, and safe discards together make agent handoffs safe
   Evidence: [Verification](https://agentic-atlas.dev/nodes/shared-shape#verification) · occurrence 1

### Inbound references 1

1. in-slice · occurrence 1
   [The Contract](https://agentic-atlas.dev/nodes/contract-documentation#examples)
   two steps import one shared file defining what they send and return
   Evidence: [Examples](https://agentic-atlas.dev/nodes/contract-documentation#examples) · occurrence 1

[↑ back to the top](https://agentic-atlas.dev/nodes/shared-shape#content) [← the survey](https://agentic-atlas.dev/atlas)

Node shared-shape · corpus 78c0e17 · Catalog revision e0cb75881244b1a82193ca738b82a0d508dd62e822524ae82873ec79d51dbb61