Skip to content

feat: let Acme proactively close stale "Waiting for you" pings - #86

Open
sandydasari wants to merge 3 commits into
mainfrom
worktree-proactive-ping-resolve
Open

feat: let Acme proactively close stale "Waiting for you" pings#86
sandydasari wants to merge 3 commits into
mainfrom
worktree-proactive-ping-resolve

Conversation

@sandydasari

@sandydasari sandydasari commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Problem

A ping_user could only ever be cleared by the user replying in its session. So a request that went stale — task canceled, situation moved on, answer now obvious, or the approach was abandoned — sat in the home "Waiting for you" list forever, demanding attention it no longer needed. The only way to clear it was to type into that session.

What this does

Adds a proactive-close path scoped to the platform overseer (Acme), built on two product decisions: Acme-only / cross-session and judgment-based with visibility (nothing auto-expires on a timer, so a still-relevant question is never silently dropped).

Backend (0f2447df)

  • New append-only ping_resolved event (mirrored into the web EVENT_KINDS). unresolvedPingsBySession now retires a ping on either a later user message or a later ping_resolved — symmetric to the existing rule, no column migration. The home list drops the row on its next build.
  • Two Acme-only platform tools (gated via Acme's AGENT.md, like reload_config): ping_list shows every outstanding request with its age; resolve_ping(sessionId, reason) withdraws one, recording resolvedBy + reason for the audit trail. The close event is actor-tagged to the waiting agent so withdrawing wakes no one.
  • Acme persona + openacme-platform skill document the conservative sweep (close only what's genuinely moot; always record a reason; when unsure, leave it).

Chat surface (d2acc24d)

  • New GET /api/sessions/:id/events (session-scoped event log, lazy on unknown ids like the SSE stream) so the chat page can learn which pings were closed on load — the live SSE task_event stream only replays forward, so reload needs the fetch.
  • The waiting agent's transcript renders a resolved ping as a third "Closed by Acme" state (muted, BellOff icon, with the close reason) instead of a perpetual red "Needs your input". Seeded from the events endpoint on load and updated live via the previously-unused onTaskEvent callback.

Testing

  • pnpm check-types 19/19, pnpm test 30/30 packages green (incl. new db query tests, ping-admin tool tests, and the /events endpoint tests).
  • Verified the badge in a real browser against a seeded scenario: withdrawn ping → muted "CLOSED BY ACME" + reason; control ping → unchanged red "NEEDS YOUR INPUT".

Follow-ups (not in this PR)

  • A running daemon must be rebuilt before its Acme can use the tools; the live AGENT.md then needs ping_list + resolve_ping added (the catalog template already has them, so fresh installs get them).
  • In-transcript badge keys off message vs ping_resolved timestamps; in the rare multi-unanswered-ping-per-session case, more than one bubble may show "Closed". The home list is exact.

🤖 Generated with Claude Code


Update — layered: asking agent self-heals first, Acme is the backstop

The first revision made Acme the only way a stale ping gets closed. That solves the bug but cleans up at the least-informed layer: Acme judges staleness from outside the session and needs cross-session authority over every other agent's pings, which cuts against the platform's "scope by agent_id" lens.

This adds the better-placed primary layer — the asking agent withdraws its own request — and demotes Acme's sweep to a backstop.

Why the asking agent first. The agent that fired the ping has the most context on whether its own question still matters, and the platform already wakes it when the task it pinged about changes (inbox fan-out to assignee + creator). For a same-session wake the agent's own transcript already carries the ask — no extra surfacing needed. So most stale pings self-heal at the source.

Why keep Acme. Not every ping is task-linked, and some go stale with no event to wake anyone (answer became obvious, user lost interest). That long tail is exactly what a conservative cross-session sweep is for. Acme stays — reframed as the janitor for orphans, not the primary cleaner. (The team-manager-polices-members variant was considered and dropped: the team manager is deliberately a thin routing role with no authority/tools, and this pair already covers the cases.)

What this commit adds

  • New all-agents system tool withdraw_ping(reason) — scoped to the caller's own outstanding ping on the current session (agent + session from ToolCallContext), reusing the ping_resolved event. No cross-agent authority: a shared resolvePingForSession helper's ownerMustBe guard refuses to clear another agent's ping. resolve_ping (Acme) and withdraw_ping (any agent) both route through that one helper.
  • ping_user guidance now tells the agent to self-withdraw a now-moot request when it's woken.
  • Acme persona + openacme-platform skill reframed: asking agent is first line of defense, Acme is the backstop.
  • New packages/tools/test/ping.test.ts (toolset, binding-passthrough, no-op, uninitialized, missing-context).

pnpm check-types green; pnpm test green (new ping tests included).

sandydasari and others added 2 commits June 22, 2026 00:57
A ping_user could only ever be cleared by the user replying in its
session, so a request that went stale (task canceled, situation moved
on, answer now obvious) sat in the home "Waiting for you" list forever,
demanding attention it no longer needed.

Add the proactive-close path, scoped to the platform overseer:

- New append-only `ping_resolved` event (mirrored into the web kinds
  list). unresolvedPingsBySession now retires a ping on either a later
  user message OR a later ping_resolved (symmetric to the existing rule,
  no column migration). The home list drops the row on its next build.
- Two Acme-only platform tools (gated via Acme's AGENT.md, like
  reload_config): `ping_list` shows every outstanding request with its
  age; `resolve_ping(sessionId, reason)` withdraws one, recording
  resolvedBy + reason for the audit trail. The close event is actor-tagged
  to the waiting agent so withdrawing wakes no one.
- Acme persona + openacme-platform skill document the conservative sweep
  (close only what's genuinely moot; record a reason; when unsure, leave).

Judgment-based, not time-based: nothing auto-expires, so a still-relevant
question is never silently dropped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The home "Waiting for you" list drops a resolved ping automatically, but
the waiting agent's own transcript still rendered the ping bubble red
("Needs your input") forever, since that state was derived only from
later user messages — a ping_resolved event is not a message.

- Add GET /api/sessions/:id/events (session-scoped event log, lazy on
  unknown ids like the SSE stream) so the chat page can learn which pings
  were closed on load — the live SSE task_event stream only replays from
  the connection point onward, so a reload needs the fetch.
- The chat page seeds ping_resolved timestamps from that endpoint and
  appends live ones via the existing (previously-unused) onTaskEvent
  callback. A ping the user never answered, whose assistant message
  predates a ping_resolved, renders as a third "Closed by Acme" state
  (muted, BellOff icon, with the close reason) instead of red.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 21, 2026

Copy link
Copy Markdown

Deploying openacme-ai with  Cloudflare Pages  Cloudflare Pages

Latest commit: d256c4a
Status: ✅  Deploy successful!
Preview URL: https://085318ae.openacme-ai.pages.dev
Branch Preview URL: https://worktree-proactive-ping-reso.openacme-ai.pages.dev

View logs

The proactive-close path was Acme-only: the platform overseer swept the
"Waiting for you" list and judged staleness from the outside, the least-
informed position, holding cross-session authority over every other
agent's pings. Add the first, better-placed layer: the asking agent
withdraws its own request when it's woken (the task it pinged about was
canceled, the situation moved on, or it worked out the answer itself) and
the question has gone moot. That agent has the most context, and its own
session transcript already carries the ask — for same-session wakes no
extra surfacing is needed.

- New all-agents system tool `withdraw_ping(reason)` — scoped to the
  caller's own outstanding ping on the current session via the
  ToolCallContext agent+session, reusing the `ping_resolved` event. No
  cross-agent authority: `resolvePingForSession`'s `ownerMustBe` guard
  refuses to clear another agent's ping.
- agent-manager extracts the resolve logic into one helper; the unscoped
  platform `resolve_ping` and the agent-scoped `withdraw_ping` both go
  through it.
- ping_user guidance tells the agent to self-withdraw a now-moot request.
- Acme persona + openacme-platform skill reframe Acme as the BACKSTOP for
  the long tail (pings whose asking agent never wakes to revisit them),
  not the primary cleaner.

Layered, not either/or: agents self-heal at the source; Acme catches the
orphans nothing woke anyone for.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant