Eigenstate
Notes / Ansatz

What it takes to add a feature to an existing system

The feature could start a conversation and accept my reply. Keeping it part of a changing system required more than that first working path.

Jason Shen

I’m building Withmere, a personal AI environment I use through Discord and Telegram for conversation and project work. I wanted its agents to occasionally start a conversation. Usually I sent a message and an agent answered. This feature let it speak first.

The first version, called Outreach, used a scheduling model to decide which persona should speak, when, and about what. It passed the work to the existing scheduler. It worked.

Later, each persona got to decide for itself whether it had something to say. This became Free Turn. Its content rules also evolved to allow ordinary observations, questions and unfinished thoughts, without requiring a useful suggestion every time.

The execution changed too. Initially, the opening and my reply used the same model context. Later, the private instructions for deciding whether to speak were kept separate: the opening context closed, and my reply started another context carrying only the public conversation. Tests checked that the reply worked and the private instructions stayed out.

Then the surrounding system changed. A refactor of conversation recovery and closing introduced a new way of reading stored history. Conversations containing proactive messages could no longer close correctly. During the investigation, a separate conflict appeared: an opening was delivered, but the system refused my reply because it could not establish which conversation it belonged to.

The feature had worked, and replying had been tested. The failures made me reconsider what it takes to add a requirement to an existing project: its behavior has to become something the product definition, architecture and acceptance tests can keep agreeing on as the project changes.

Define how the new behavior fits the old ones

“Let the AI occasionally say something” suggests a short sequence: choose a moment, generate a message, send it. Each step has an understandable completion point.

After receiving the message, I might reply, change models, end the conversation, or restart the process and return later. Those were already supported operations. The new feature had to acquire a place among them.

If an earlier conversation was still valid, should the opening wait or join it? If that conversation had expired, what needed to finish first? Once the private generation context closed, where did my reply belong?

These are product questions because their answers determine what the user can do. Leaving them to separate implementation paths lets each path invent part of the behavior.

The answers also need to survive revisions. Moving from Outreach to Free Turn changed who decided what to say. Moving from one model context to two changed how the conversation was executed. Neither change removed the requirement that I could reply to a public opening.

I would define the interactions a new requirement actually touches, then carry them into design and acceptance. A closed feature task should leave those requirements available to the next person changing the system. The original working path remains useful evidence, with a scope that later changes may require extending.

Give shared decisions an owner

The closing failure came from two meanings assigned to one field. When a proactive message was saved, its source label identified the feature that produced it. The new reader treated that label as a standard conversation-source category. The stored value was outside its accepted vocabulary, so it refused the message.

Reading verified history was a reasonable goal for the refactor. It still required agreement with the writers and records already in the project. Centralizing the reader did not establish that agreement by itself.

The reply conflict exposed a different division. The opening path looked at the conversation in memory and decided it could start a new one. The reply path consulted a recovery record on disk and found an older conversation. Both were deciding which conversation was current, using different evidence.

These failures give an architecture review concrete questions to ask when a requirement arrives. Which shared decisions does the feature need? Who already owns them? What facts will it write, and which later operations must understand those facts? The same questions apply when a shared boundary is refactored around an existing feature.

The repairs separated producer identity from conversation source and gave the relevant readers a common interpretation. Conversation selection, starting and ending were also brought behind a shared boundary. Free Turn could still decide to attempt an opening; the common logic determined which conversation it could enter and whether old work had to finish first.

That makes future changes easier to locate and review. A new entry point can use the conversation rules without independently recreating them. A change to those rules has identifiable callers and behavior to check.

Carry the requirement through the tests

The earlier reply tests were real protection. The question was whether they covered the connections that subsequent changes affected.

During the closing refactor, a test helper built stored messages using the standard source category already available in memory. It supplied exactly what the new reader expected, bypassing the different label produced by the real writer. Implementation and test could agree while the actual connection failed.

Later flow tests started with clean recovery state. They checked that opening, closing and further input occurred in order, but did not bring along the old recoverable conversation present in the incident.

Acceptance therefore needs a longer sequence: existing state, the feature’s intervention, the user’s next action, and any required closing or recovery. In a long-running project, the relevant starting point may precede the feature’s first action.

Tests can change as interfaces change. The question during that rewrite is which test still protects the original behavior, using data and state that can actually reach it. Replacing an inconvenient input with one suited to the new implementation can erase the very disagreement the test needs to expose.

The repair added replays that retained an old conversation and reconstructed the process before attempting a proactive opening. A valid conversation had to remain usable; an expired one had to finish its handoff first. A failed handoff allowed no new generation or delivery. A successful opening could receive a reply in the same logical conversation, through a fresh model context containing only public history.

Those replays exercised real application paths and temporary persistent stores with scripted generation and delivery. They verified the repaired application boundary; at the investigation’s close, the running service had not loaded the repair. This was not evidence that the live incident had recovered.

The next time the closing reader changes, its tests should still hand it a message actually written by the proactive feature, including its real source label. The new reader must understand it, or the change must explicitly address compatibility. A feature’s old message remains an input today’s code is responsible for handling.

History and evidence behind this note

This is an account of the private system developed as AgentOS, now introduced publicly under the product name Withmere. It draws on the author’s development records, Git changes and incident investigation. The repository and conversation logs are not public artifacts. No employer system or customer data is used. The mechanisms are explained here without requiring access to those records.

Outreach was completed on 25 April 2026. The replacement completed on 30 July moved the decision to speak into each persona's ordinary context and explicitly tested an opening followed by an owner reply. The 11 August backend change separated private opening generation from the public reply; its tests checked two backend instances, successful reply and exclusion of the private envelope. Content revisions on 24 August and 6 September changed the available context and phrasing guidance. These content changes are not identified as causes of the lifecycle failures.

The source-reading defect is traceable to the 9 September closing refactor: its reader interpreted the producer field as a conversation-source category, while the new fixture populated that field with the expected category. The 14 September investigation also reproduced a distinct conflict between a newly delivered opening and an older recoverable conversation. The first introducing commit for that second defect has not been established; this note does not attribute both failures to the September refactor.

The investigation records failing-then-passing isolated regression coverage, including restart, expired-conversation handoff, handoff failure and replies using only the public opening. These historical engineering checks were reviewed, not rerun for this article. Their scope is the repaired application boundary with deterministic backend and transport adapters. No live recovery, provider reliability, independent reader study or general validation of the proposed development method is claimed.