Metadata-Version: 2.4
Name: copresence
Version: 0.1.0
Summary: Associative memory for event streams: link moments by co-presence (what was there), not just similarity (what it resembles).
Project-URL: Homepage, https://github.com/myfjin/copresence
Author: Illia Hladkyi
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,associative,context,memory,recall,spreading-activation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Provides-Extra: embeddings
Requires-Dist: sentence-transformers>=2.2; extra == 'embeddings'
Description-Content-Type: text/markdown

# copresence

**Associative memory for event streams: link moments by co-presence (what was
_there_), not just similarity (what it _resembles_). A complement to embedding
recall, not a replacement.**

Zero dependencies. Pure stdlib. `pip install copresence`

## The black t-shirt law

You're trying to remember a conversation from years ago. Searching your memory by
*content* gives you nothing — the conversation doesn't resemble anything you can
name. Then a friend says: *"you were wearing that black t-shirt… the kettle was
on… it was the day the heater broke"* — and the whole context **rebuilds itself**.

The t-shirt never resembled the conversation. It was simply **there**.

Embedding-based recall (RAG, vector search) is content-similarity: it finds what
*resembles* the query. It systematically misses everything that was merely
*co-present* — the peripheral details, the same-day decisions, the parallel
threads. `copresence` adds that missing axis: it links moments **by having shared
a scene, a day, an era**, and recalls by **spreading activation** through those
links.

## Quickstart

```python
from copresence import Store, ref, hash_embedder, probe

events = [  # any event stream: chat logs, journals, tickets, commits…
    {"timestamp": "2026-01-05T10:00:00+00:00", "speaker": "a", "content": "…"},
    # {"timestamp": ISO-8601, "speaker": str, "content": str}
]

store = Store("./layers")
store.link(events)                        # co-presence links: scene + day + era
store.notice(ref(events[3]), "me", "this mattered")   # authored mark

# recall by cascade: seeds spread through co-presence, not similarity
adj = store.graph("me")
context = store.cascade({ref(events[3])}, adj)
```

Or just run the built-in demonstration:

```
pip install copresence
copresence demo
```

which builds a small synthetic corpus where a red kettle whistles near lighthouse
work (separate moments, same scenes), shows that similarity recall ranks the
target poorly for the kettle cue, and then rebuilds it through the co-presence
cascade. Deterministic; runs anywhere; no model download.

## Concepts

- **Layers are sovereign.** Every member (person, agent) gets their own link
  files. `links.jsonl` is *authored* — appended, never rebuilt. `auto_links.jsonl`
  is *derived* — rebuilt by the linker on every run. Structural co-presence is
  shared because it's computable from timestamps alone.
- **"That day" is a memory node.** Co-presence works at three scales: adjacency
  (the scene), day-hubs, era-hubs (the week). Touching one moment can pull what
  else the day held.
- **Presence weighting.** Not every co-occurrence is equal: moments carrying
  gravity markers or notice-marks weigh their links up; incidental co-occurrence
  weighs half.
- **Decay with resurrection.** Unused links fade toward a dormant floor — dormancy,
  never death. When a cascade touches a dormant link, it re-animates the *whole
  scene* around it, not just the node.
- **Warming.** `warm()` scores which *dormant* moments are heating up under the
  current context (heat = kinship-to-now × dormancy), with a `should_interrupt`
  attention boundary — the caller decides whether to speak. Feed it the record's
  **tail**, not a query: continuity anchors in time, not similarity. We learned
  that from a failed probe and kept the law.
- **Discovery.** `discover_regular()` maps the day-to-day roads (themes alive
  across many days); `discover_deep()` hunts dormant chains — strong kinship
  across a long gap, never cross-referenced. These are **candidates, not claims**:
  whether a deep pair "hits" is the member's subjective call.
- **Resonance.** Which moments live in one member's layer (private wealth), two,
  or all? The field is computed; verdicts are not — private wealth is never
  forced shared.

## Embedders

The cascade, linker, notices and layers are pure stdlib. `discover`, `warm` and
`probe` need an embedder — any callable `list[str] -> vectors`:

```python
from copresence import hash_embedder      # zero-dep hashing bag-of-words (a TOY —
                                          # fine for demos/tests, no real semantics)
from copresence import st_embedder        # real: pip install copresence[embeddings]
embed = st_embedder()                     # sentence-transformers MiniLM
```

## Honest notes

- The mechanism is young. In our own (private) corpus of ~520 events, a target
  that content-recall ranked 265th was rebuilt by a two-hop cascade from six
  peripheral cues (similarity 0.03–0.18 to the query), and a negative control —
  the same cues against a different-day target — was correctly *not* reached.
  The bundled demo reproduces the same geometry synthetically on your machine;
  it proves the mechanism, not magic on your data.
- Mood/register link heuristics exist but are **experimental and off by default**
  (`Store(registers=...)`) — our own instance is too young to have proven them.
- The hashing embedder is a toy. Real corpora deserve a real embedder.
- `discover_deep` can surface echo-shaped pairs (a summary of a moment vs the
  moment). The identity cap catches most; your judgment catches the rest.

## License

Apache-2.0
