feat(triage): per-channel serial queue + HF on_call gate + observer skip #3

Merged
hzhang merged 2 commits from feat/triage-on-call-gate-and-queue into main 2026-05-22 21:59:24 +00:00
Contributor

Three changes to plugin inbound for the new triage flow + one fallback fix discovered during sim test:

  1. Per-channel serial queue — replaces fire-and-forget void this.dispatch(...). Map<channelId, Promise> chain so consecutive messages on the same channel are processed strictly in order, no concurrent model turns.

  2. HF on_call gate (triage + wake=true only) — before dispatching, GET /calendar/agent/status?agent_id=.... If status != on_call, message goes into per-agent gated queue. Status cached 5s. On next triage arrival with status now=on_call, drain gated FIFO then dispatch.

  3. Triage observer skip — triage wake=false (admin observer) MUST NOT enter the agent's session history. Skipped entirely. Non-triage channels keep legacy record-as-history.

  4. claw_identifier fallback — reads plugins.entries.harbor-forge.config.identifier from openclaw config when HF_CLAW_IDENTIFIER env isn't set (the HF plugin uses this same value; os.hostname() was the wrong fallback because sim container hostname server.t2 ≠ HF agent row sim-t2).

Sim-verified all 8 cases (A-H).

Pairs with Fabric.Backend.Guild PR and HarborForge.Backend PR.

🤖 Generated with Claude Code

Three changes to plugin inbound for the new triage flow + one fallback fix discovered during sim test: 1. **Per-channel serial queue** — replaces fire-and-forget `void this.dispatch(...)`. Map<channelId, Promise> chain so consecutive messages on the same channel are processed strictly in order, no concurrent model turns. 2. **HF on_call gate** (triage + wake=true only) — before dispatching, GET `/calendar/agent/status?agent_id=...`. If status != on_call, message goes into per-agent gated queue. Status cached 5s. On next triage arrival with status now=on_call, drain gated FIFO then dispatch. 3. **Triage observer skip** — triage wake=false (admin observer) MUST NOT enter the agent's session history. Skipped entirely. Non-triage channels keep legacy record-as-history. 4. **claw_identifier fallback** — reads `plugins.entries.harbor-forge.config.identifier` from openclaw config when `HF_CLAW_IDENTIFIER` env isn't set (the HF plugin uses this same value; `os.hostname()` was the wrong fallback because sim container hostname `server.t2` ≠ HF agent row `sim-t2`). Sim-verified all 8 cases (A-H). Pairs with Fabric.Backend.Guild PR and HarborForge.Backend PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hzhang added 2 commits 2026-05-22 21:59:20 +00:00
Three behavioral changes to inbound message handling to support the
new triage flow:

## 1. Per-channel serial queue

Replaces `void this.dispatch(...)` (fire-and-forget) with a per-channel
chain so consecutive messages on the same channel are processed strictly
in order — no concurrent model turns for the same channel. Other
channels remain independent (parallelism preserved across channels).

Implementation: `Map<channelId, Promise>` where each new task awaits
the previous. The map entry self-cleans when the chain settles AND
no newer task has overwritten it.

## 2. HF on_call gate (triage + wake=true only)

Before dispatching a triage wake to the on-duty agent, hit HF
`GET /calendar/agent/status?agent_id=...`. If the agent isn't
currently on_call, the message is pushed to a per-agent gated queue
instead of dispatched — no model turn fires.

Status check is cached for 5s to amortise across rapid triage bursts.

When a subsequent triage message arrives and the agent IS on_call by
that point, the gated queue drains FIFO (re-enqueued through the same
per-channel chain so order is kept) before the new message dispatches.

Drained queue is in-memory only; on gateway restart the underlying
Fabric messages get re-fetched via the connect-time history sweep.

## 3. Triage observer skip (wake=false)

Triage messages that arrive with wakeup=false are admin observers — by
spec they MUST NOT enter the agent's session history. Skipped entirely
(no recordInboundSession call). The next time this agent legitimately
wakes for triage, their context contains only past wakeups + their own
outgoing messages — no observer-side chatter from other agents.

For NON-triage channels the legacy "record-as-history" stays — those
keep their full channel conversation available for later wakes.

## Env

- HF_API_BASE_URL  — defaults `https://monitor.hangman-lab.top`
- HF_CLAW_IDENTIFIER — defaults to `os.hostname()`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
os.hostname() fallback is wrong in sim where container hostname (server.t2)
doesn't match the HF agent row's claw_identifier (sim-t2). Add intermediate
fallback that reads openclaw config plugins.harbor-forge.identifier — the
same value the HF plugin uses for its outbound HF calls — keeping plugin
and HF agent state aligned without a per-service-unit HF_CLAW_IDENTIFIER
env override.

Priority:
  1. HF_CLAW_IDENTIFIER env (operator override)
  2. openclaw config plugins.harbor-forge.identifier (NEW)
  3. os.hostname() last-resort

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hzhang merged commit 73bfbd7594 into main 2026-05-22 21:59:24 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: nav/Fabric.OpenclawPlugin#3