Skip to content
C.W.K.
Stream
Lesson 05 of 05 · published

Follow-ups Never Reopen

~12 min · state-machines, design, messaging, reporting

Level 0Loose Note
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Talking About Finished Work

A done report arrives. Dad reads the Korean lines on his phone and has a question: why did you pick that approach, did you check the iPad too, where is the build. That is a perfectly normal thing to want. The worker that did the job still holds the context, and asking it is far cheaper than asking anyone else.

It is also a trap. The work is closed. Its binding is resolved, its obligation paid, its notification sent. If a question about that work could quietly turn it back into live work, every casual why would risk resurrecting a task nobody decided to resurrect, with a worker taking new actions on a contract that has already been settled. So Coop lets the conversation happen and makes sure it stays a conversation.

One Door Chooses the Path

Dad's replies through his Pippa chat and through Telegram both go through the same control facade, and Control.reply decides what a reply becomes. It starts the same way every time: the reply is recorded in the item's thread. Then it looks at the most recent binding for that item.

  • No binding at all. The reply is recorded_only. Nobody ever worked on this through Coop, so there is nobody to tell.
  • The worker's session is not a managed, actively enrolled chat. Still recorded_only, with the detail The recorded worker is not connected. No other session was selected.
  • A todo whose binding is still active. This is the previous lesson: a reply_review, marked as a reply to Dad, because a reply on live work may change the contract.
  • Anything else (a resolved todo, or a request): a followup envelope to that exact session, carrying the reply and a closed flag that is true when the binding is no longer active.

That second bullet is a design choice worth noticing. When the worker that did the job is gone, the facade does not pick another session to answer in its place. The module's first line says it has no inferred recipient. A different chat answering a question about work it never saw would be guessing, and its guess would arrive looking like an answer.

What the Worker Is Told

The follow-up prompt asks the worker to answer with COOP_REPLY and the envelope's id, on its own hidden line like every other token, and to write the answer in plain Korean lines, because Coop relays it to Dad as written. When the flag says closed, it adds one more sentence: The work is closed. Discuss its result; do not reopen it or start new work. The workflow document states the rule the same way: a follow-up does not reopen the task or authorize new work, and new work belongs in a new todo.

What the Code Does Not Do

The sentence in the prompt is not the guard. The guard is that the follow-up path has nothing to reopen with. When the adapter sees a final carrying the right COOP_REPLY, it calls relay_reply, which strips the token, runs the text through the same dad_text that keeps report JSON away from Dad, and enqueues an attention line with reason worker_reply. If the worker said nothing usable, the line reads The worker acknowledged the follow-up. Then the envelope is marked applied.

That is the entire branch. It does not touch the binding, does not create an obligation, does not change the item's status. It does read report JSON, if the worker pasted any: dad_text decodes it to lift the for_dad lines out for Dad. But nothing on this path commits a report or ever calls Engine.report, and the work envelope stopped being observed when its last binding resolved. Even a done that somehow reached Engine.report would name a resolved binding and be refused. Two independent reasons, the prompt and the structure, and only the structural one is load-bearing.

Why Not Just Reopen?

Some trackers reopen an issue automatically when someone comments on it. It feels helpful and it is quietly destructive. The status of an item stops meaning someone decided this is finished and starts meaning nobody has commented lately. Coop's lifecycle keeps those apart. A closed item changes state only when someone with authority makes a new decision, and the natural way to express new work is a new todo with its own contract, its own binding and its own report. A follow-up is how Dad talks to the worker. A todo is how he asks it to do something.

A conversation about finished work must never silently resurrect it. Let people ask, route the question to the one worker who knows, relay the answer, and leave the ledger exactly as it was. If the answer reveals more work, that is a new decision, and it deserves a new record.

Code

Control.reply, trimmed: record first, then reply review, follow-up, or recorded only·python
def reply(c):
    self.engine.row(c, KINDS[kind], item_id)
    message_id = domain.reply(**{kind+'_id':item_id}, body=body, sender=metadata['id'])
    binding = c.execute('SELECT * FROM engine_bindings WHERE kind=? AND item_id=? ORDER BY created DESC LIMIT 1', (kind,item_id)).fetchone()
    result = {'message_id':message_id,'kind':kind,'id':item_id,'delivery':'recorded_only'}
    if not binding:
        return result
    session = self.engine.row(c, 'engine_sessions', binding['session_id'])
    result['session_id'] = session['id']
    if session['mode']!='managed' or session['enrollment']!='active':
        result['detail'] = 'The recorded worker is not connected. No other session was selected.'
        return result
    if kind=='todo' and binding['state']=='active':
        c.execute('UPDATE engine_bindings SET work_revision=? WHERE id=?', (self.engine.revision(c,'todos',item_id), binding['id']))
        self.engine._queue_reply_review(c,item_id,reply_to_dad=True)
    else:
        self.engine._enqueue(c,session['id'],session['generation'],'followup',
            {'item':{'kind':kind,'id':item_id},'message_id':message_id,'text':body,
             'source':metadata['id'],'closed':binding['state']!='active'})
    return {**result,'delivery':'queued','native_id':session['native_id']}
A minimal sketch of the same routing as a pure function, with the five cases that matter·python
def route_reply(kind, binding, session):
    """Where a reply on one work item goes. Recording it in the thread always happens first."""
    if binding is None:
        return "recorded_only"                      # nobody ever worked on it through Coop
    if session["mode"] != "managed" or session["enrollment"] != "active":
        return "recorded_only"                      # the worker is gone; never pick another one
    if kind == "todo" and binding["state"] == "active":
        return "reply_review"                       # live work: the contract may have changed
    closed = binding["state"] != "active"
    return "followup (closed)" if closed else "followup"   # a conversation, never a reopen

live = {"mode": "managed", "enrollment": "active"}
gone = {"mode": "managed", "enrollment": "retired"}
cases = [
    ("todo", {"state": "active"}, live),
    ("todo", {"state": "resolved"}, live),
    ("request", {"state": "active"}, live),
    ("todo", {"state": "resolved"}, gone),
    ("todo", None, None),
]
for kind, binding, session in cases:
    print(kind, binding and binding["state"], session and session["enrollment"], "->", route_reply(kind, binding, session))

# todo active active -> reply_review
# todo resolved active -> followup (closed)
# request active active -> followup
# todo resolved retired -> recorded_only
# todo None None -> recorded_only

External links

Exercise

In a tracker or workflow tool you use, find out exactly what happens when someone comments on an item that is already closed. Does it reopen? Notify the last assignee? Do nothing? Write the rule you would want, in the same shape as Control.reply: which cases deliver, which record only, and which, if any, may ever change state.
Hint
Try it on a test item and watch the status and the notifications, not the documentation. Then ask whether anyone could tell, from the item's status alone, whether a human decided it was finished.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.