feat: let Acme proactively close stale "Waiting for you" pings - #86
Open
sandydasari wants to merge 3 commits into
Open
feat: let Acme proactively close stale "Waiting for you" pings#86sandydasari wants to merge 3 commits into
sandydasari wants to merge 3 commits into
Conversation
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>
Deploying openacme-ai with
|
| 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 |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A
ping_usercould 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)ping_resolvedevent (mirrored into the webEVENT_KINDS).unresolvedPingsBySessionnow retires a ping on either a later user message or a laterping_resolved— symmetric to the existing rule, no column migration. The home list drops the row on its next build.AGENT.md, likereload_config):ping_listshows every outstanding request with its age;resolve_ping(sessionId, reason)withdraws one, recordingresolvedBy+ reason for the audit trail. The close event isactor-tagged to the waiting agent so withdrawing wakes no one.openacme-platformskill document the conservative sweep (close only what's genuinely moot; always record a reason; when unsure, leave it).Chat surface (
d2acc24d)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 SSEtask_eventstream only replays forward, so reload needs the fetch.BellOfficon, 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-unusedonTaskEventcallback.Testing
pnpm check-types19/19,pnpm test30/30 packages green (incl. new db query tests,ping-admintool tests, and the/eventsendpoint tests).Follow-ups (not in this PR)
AGENT.mdthen needsping_list+resolve_pingadded (the catalog template already has them, so fresh installs get them).ping_resolvedtimestamps; 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
withdraw_ping(reason)— scoped to the caller's own outstanding ping on the current session (agent + session fromToolCallContext), reusing theping_resolvedevent. No cross-agent authority: a sharedresolvePingForSessionhelper'sownerMustBeguard refuses to clear another agent's ping.resolve_ping(Acme) andwithdraw_ping(any agent) both route through that one helper.ping_userguidance now tells the agent to self-withdraw a now-moot request when it's woken.openacme-platformskill reframed: asking agent is first line of defense, Acme is the backstop.packages/tools/test/ping.test.ts(toolset, binding-passthrough, no-op, uninitialized, missing-context).pnpm check-typesgreen;pnpm testgreen (new ping tests included).