---
title: "The Shared Shape · Agentic Atlas"
description: "The Shared Shape: both sides of a seam import one shared request-and-return shape."
canonical: "https://agentic-atlas.dev/nodes/shared-shape"
last-updated: "2026-08-19"
---

[← the survey](https://agentic-atlas.dev/atlas)

1. example of [The Contract](https://agentic-atlas.dev/nodes/contract-documentation)
   The Shared Shape
   Hook
   both sides of a seam import one shared request-and-return shape
   Thesis
   Both sides of a seam import one seam-owned request-and-return shape, so the agreement is one declaration rather than two synchronized copies held together by a process promise.
   Laws & fences
  - 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 shared contract carries how the receiver consumes the result as well as the shapes: complete means unanswered is empty, blocked means the caller surfaces the unanswered questions instead of filling them silently, and shape without consumption rules would specify bytes, not the handshake.
  - The shared file does not make the crossing correct by itself; it makes the agreement singular and addressable, and enforcement is a separate role.
  - The concrete syntax does not travel: a Python workflow can share a TypedDict, separate services can generate local types from one schema, and two agent steps can reference one Markdown or YAML contract; the invariant is the shared source, not the language.
   When to reach
  - Reach for it when each side of a seam keeps its own description of the crossing and a change that looked local altered the handshake for one participant and not the other.
  - Do not reach for it to prove runtime validation, compatibility across deployed versions, or recovery from a bad return; those are different decisions, and a separate pattern begins where an assertion checks this agreement.
   Provenance
   [shared-shape/choice](https://agentic-atlas.dev/nodes/shared-shape#choice) · v1.0.2
   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#context)[problem-signal](https://agentic-atlas.dev#problem-signal)[choice](https://agentic-atlas.dev#choice)[before](https://agentic-atlas.dev#before)[implementation](https://agentic-atlas.dev#implementation)[result](https://agentic-atlas.dev#result)[verification](https://agentic-atlas.dev#verification)[lessons](https://agentic-atlas.dev#lessons) [relationships](https://agentic-atlas.dev#relationships)

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

## Context

[](https://agentic-atlas.dev#context)

context.md

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

[](https://agentic-atlas.dev#problem-signal)

problem-signal.md

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

[](https://agentic-atlas.dev#choice)

choice.md

Make the agreement seam-owned. Move the request shape, return shape, and the rules for consuming each status into [`contracts/research_handoff.py`](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

[](https://agentic-atlas.dev#before)

before.md

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

[](https://agentic-atlas.dev#implementation)

implementation.md

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

[](https://agentic-atlas.dev#result)

result.md

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

[](https://agentic-atlas.dev#verification)

verification.md

The supporting sketch is executable:

```
python3 demo.py
```

It proves the property this example claims:

- [`caller.py`](caller.py) imports both crossing types from the contract;
- [`research_step.py`](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

[](https://agentic-atlas.dev#lessons)

lessons.md

**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)
   contract ↔ verification ↔ re-dispatch
   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)
   both sides of a seam import one shared request-and-return shape
   Evidence: [Examples](https://agentic-atlas.dev/nodes/contract-documentation#examples) · occurrence 1

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

Node shared-shape · corpus 31de4cb · Catalog revision 35263c4c415da742953d0462804fb14424e2244dae4c63efd27e468988de70ab