esc
The Baskets That Learned To Talk
Becoming Lifelog

The Baskets That Learned To Talk

Becoming Lifelog, in which the architecture has an identity crisis, tools find their true home, and the laundromat gets smarter --- Previously on Becoming Lifelog... The [[The Checkbox That Wanted...

January 2, 2026

Becoming Lifelog, in which the architecture has an identity crisis, tools find their true home, and the laundromat gets smarter


Previously on Becoming Lifelog…

The The Checkbox That Wanted More. Telegram arrived. The status bar learned to spin. Syncs flowed through the laundromat β€” GitHub to Issues, Readwise to Captures, Calendar to Events, Contacts to People.

But something was bothering the architecture.


The Question Nobody Asked

It started with a bug. The kind that makes you question everything.

Qwen had replied to a user. The auto-title feature grabbed the response, cleaned it up, and produced:

Sync Issues Overview<|im_end|>

Model tokens. Leaking into titles. The fix was simple:

.replace(/<\|.*?\|>/g, '')  // Strip <|im_end|>, <|endoftext|>, etc.

But fixing bugs is like cleaning β€” you start with one spot and suddenly you’re reorganizing the whole room.


The Nesting Problem

Agent responses were appearing at the wrong level. Users typed a question, the agent replied, and the conversation became a flat mess:

User question here
**Claude πŸ€–:**
The response that belongs under the marker
But it's at the same level
Confusing everyone

The fix: responses become children of the marker.

// Before: createLineItem(null, ...) β€” sibling
// After:  createLineItem(labelItem, ...) β€” child

Now conversations nest properly. The agent speaks under its name. The cursor lands at the right indent for the user’s next message.

Small fixes. Big clarity.


The Object That Wouldn’t Stringify

Console logs showed [object Object] where titles should be. The culprit: ref segments.

// Text segments: s.text is a string
// Ref segments:  s.text is { guid: '...', title: '...' }

One type check. Problem solved. But the real question was lurking beneath these surface bugs.


The Identity Crisis

The plugins were confused about who they were.

GitHub plugin: “I sync issues. I also provide tools to query issues.”
Readwise plugin: “I sync captures. I also provide tools to query captures.”
Google Calendar: “I sync events. I also provide tools to query events.”
Google Contacts: “I sync people. I also provide tools to query people.”

Four plugins. Each doing two jobs. Each duplicating tool logic that had nothing to do with syncing.

And then the question that changed everything:

What happens when someone builds a Jira plugin?

They’d sync to Issues. Same collection. But they’d have to rebuild all the query tools. find(). search(). summarize_open(). Copy-paste from GitHub plugin. Maintain two copies.

What about GitLab?

Copy-paste again. Three copies now.

Outlook Calendar?

The tools are the same. The source is different. Why are we coupling them?


The Revelation

Collections aren’t just schemas. They can have code.

collections/
  issues/
    collection.json   ← the schema (always had this)
    plugin.js         ← the tools (NEW)

The Issues collection doesn’t care if data came from GitHub, GitLab, or Jira. It just knows how to query issues. find() by state. search() by text. summarize_open() for the morning standup.

One set of tools. Any number of sources.

GitHub  ──┐
GitLab  ──┼──→ Issues Collection ──→ find(), search(), get()
Jira    β”€β”€β”˜
Linear  β”€β”€β”˜

The sync plugins become pure. They do one thing: fetch data and fill baskets. The baskets know how to be searched.


The Refactor

Four new files. Four cleaner plugins.

Created:

collections/issues/plugin.js    β€” find, get, search, summarize_open
collections/captures/plugin.js  β€” find, search, recent, by_book
collections/calendar/plugin.js  β€” find, today, upcoming, needs_followup, search
collections/people/plugin.js    β€” find, search, needs_contact, at_organization

Deleted (from sync plugins):

  • 745 lines of tool registration
  • 4 copies of similar query logic
  • The confusion about who owns what

Added:

  • 958 lines in the right place
  • Source-agnostic queries
  • Future-proof architecture

Net change: +213 lines. But the clarity gained is immeasurable.


The New Diagram

                    THE SMART LAUNDROMAT

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚                   AGENT HUB                         β”‚
    β”‚    Claude, Qwen, Llama β€” chat on any page          β”‚
    β”‚                β”‚                                    β”‚
    β”‚                β”‚ uses collection tools              β”‚
    β”‚                β–Ό                                    β”‚
    β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
    β”‚   β”‚              COLLECTIONS                     β”‚  β”‚
    β”‚   β”‚         (baskets with built-in tools)        β”‚  β”‚
    β”‚   β”‚                                              β”‚  β”‚
    β”‚   β”‚  ISSUES      CAPTURES    CALENDAR   PEOPLE   β”‚  β”‚
    β”‚   β”‚  find()      find()      today()    find()   β”‚  β”‚
    β”‚   β”‚  search()    search()    upcoming() search() β”‚  β”‚
    β”‚   β”‚  get()       recent()    search()   needs_   β”‚  β”‚
    β”‚   β”‚  summarize() by_book()   followup() contact()β”‚  β”‚
    β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
    β”‚                β–²                                    β”‚
    β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
    β”‚   β”‚         SYNC PLUGINS                β”‚          β”‚
    β”‚   β”‚  GitHub β†’ Issues                    β”‚          β”‚
    β”‚   β”‚  Readwise β†’ Captures                β”‚          β”‚
    β”‚   β”‚  Google Calendar β†’ Calendar         β”‚          β”‚
    β”‚   β”‚  (future: Jira, GitLab, Outlook...) β”‚          β”‚
    β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The baskets learned to talk. The machines just wash.


The Scorecard

Before After
Tools in sync plugins Tools in collections
4 copies of query logic 1 copy per collection
Add Jira = rebuild tools Add Jira = just sync
745 lines scattered 958 lines organized
Confusion Clarity

The Philosophy

Every plugin was asking: “Who am I?”

GitHub plugin thought it was a sync engine AND a query engine. It was suffering an identity crisis, split between two responsibilities that had nothing to do with each other.

The fix wasn’t code. It was clarity.

Sync plugins sync. Collections provide tools. Each does one thing well.

The reptile brain doesn’t ask “what COULD this do?” It asks “what SHOULD this do?”


What Shipped

  • βœ… Model token stripping (<|im_end|> bug)
  • βœ… Nested agent responses (children of marker)
  • βœ… Link resolution fix ([object Object] bug)
  • βœ… Cursor positioning (user types at right level)
  • βœ… Collection tools architecture
    • Issues: find, get, search, summarize_open
    • Captures: find, search, recent, by_book
    • Calendar: find, today, upcoming, needs_followup, search
    • People: find, search, needs_contact, at_organization, recent_contacts
  • βœ… Updated README with new architecture
  • βœ… Sync plugins cleaned (tool code removed)

The Commits

bc862da Add collection tools for all plugins + AgentHub announcement
8ce308f Refactor: Move collection tools from sync plugins to collection plugins
040b3ff Update README with new collection tools architecture

Three commits. One identity crisis resolved.


Next Time on Becoming Lifelog…

Someone builds a Jira plugin. The tools are already waiting.

“You got issues? We got answers. Regardless of where they came from.”


See also:


Day 2 of 2026

In which plugins learned what they were

And collections learned what they could do

And the baskets finally spoke

🦎 > 🧺

storyline: Becoming Lifelog