Skip to content

feat(cli): add agent-id and session-key to system event wake#57199

Closed
janicduplessis wants to merge 1 commit into
openclaw:mainfrom
janicduplessis:feat/system-event-session-key
Closed

feat(cli): add agent-id and session-key to system event wake#57199
janicduplessis wants to merge 1 commit into
openclaw:mainfrom
janicduplessis:feat/system-event-session-key

Conversation

@janicduplessis

Copy link
Copy Markdown

Summary

Adds --agent-id <agentId> and --session-key <sessionKey> to openclaw system event, threading both through the wake request validation and cron wake handling so a system event can target a specific agent and session.

Why

On multi-agent setups, openclaw system event always targets the default agent's main session. This makes it impossible to:

  • Wake a specific non-default agent (e.g. coding instead of main)
  • Target a specific conversation session (e.g. a Discord DM session instead of the heartbeat session)

With both params, commands like:

# Target an agent (uses its main/heartbeat session)
openclaw system event --text "Task done" --agent-id coding --mode now

# Target a specific conversation session
openclaw system event --text "Task done" \
  --agent-id coding \
  --session-key "agent:coding:discord:direct:123456" \
  --mode now

route the event and heartbeat wake to the correct agent and session.

Changes

  • CLI: add --agent-id <agentId> and --session-key <sessionKey> options with whitespace normalization
  • Schema: WakeParamsSchema gains optional agentId (NonEmptyString) and sessionKey (String)
  • Cron handler: extracts both from validated params and forwards to cron.wake()
  • Timer: passes agentId/sessionKey to both enqueueSystemEvent and requestHeartbeatNow
  • Tests: coverage for both --agent-id and --session-key forwarding

How it works

  • agentId determines which agent handles the event (workspace, model, heartbeat config)
  • sessionKey determines which conversation session the event lands in
  • Both are optional and work independently:
    • --agent-id alone → agent's default main session
    • --session-key alone → agent inferred from the key prefix
    • Both → explicit targeting

Testing

  • pnpm vitest run src/cli/system-cli.test.ts
  • pnpm vitest run src/cron/
  • Manual end-to-end on a local multi-agent install: confirmed events route to the correct agent DM session with full conversation context

Adds --agent-id and --session-key to openclaw system event, threading
both through the wake request validation and cron wake handling so a
system event can target a specific agent session.

- CLI: --agent-id <agentId> and --session-key <sessionKey> options
- Schema: WakeParamsSchema gains optional agentId + sessionKey
- Cron handler: extracts and forwards both to cron.wake()
- Timer: passes agentId/sessionKey to enqueueSystemEvent + requestHeartbeatNow
- Tests: coverage for both parameters
@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime cli CLI command changes size: S labels Mar 29, 2026
@greptile-apps

greptile-apps Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends openclaw system event with --agent-id and --session-key flags, threading both values through the CLI → gateway schema validation → cron handler → CronService.wake()timer.wake()enqueueSystemEvent / requestHeartbeatNow. The change is clean and well-structured: the existing CronServiceDeps interface already accepted optional agentId and sessionKey on both dependency calls, so the wiring is straightforward.

Key observations:

  • The CronServiceDeps.enqueueSystemEvent wrapper already accepts both optional fields, so the in-process call is type-safe and correct.
  • requestHeartbeatNow correctly receives both fields for mode: \"now\" path.
  • CLI whitespace normalization (trim() || undefined) is applied consistently before sending to the gateway.
  • One minor schema inconsistency: agentId uses NonEmptyString (enforces non-empty) while sessionKey uses plain Type.String() (allows \"\"). An empty sessionKey passes validation but is silently discarded via the falsy check in timer.ts. No functional impact through the CLI (which already normalizes to undefined), but direct API callers could be misled.

Confidence Score: 5/5

Safe to merge; only a minor schema consistency suggestion remains.

The change is narrowly scoped and all new parameters are optional — no existing behavior is broken. The only finding is a P2 style suggestion to use NonEmptyString for sessionKey in the schema to match agentId and prevent silent no-ops for direct API callers. No logic errors, data loss, or security concerns were identified.

src/gateway/protocol/schema/agent.ts — minor schema type inconsistency for sessionKey.

Important Files Changed

Filename Overview
src/cli/system-cli.ts Adds --agent-id and --session-key options with whitespace normalization; correct camelCase mapping from Commander.js.
src/gateway/protocol/schema/agent.ts WakeParamsSchema gains optional agentId (NonEmptyString) and sessionKey (plain String); minor inconsistency in that sessionKey allows empty strings.
src/gateway/server-methods/cron.ts Cron wake handler extracts and forwards agentId/sessionKey from validated params correctly.
src/cron/service/timer.ts wake() passes agentId/sessionKey to both enqueueSystemEvent and requestHeartbeatNow via falsy guards.
src/cron/service.ts CronService.wake() signature extended with optional agentId/sessionKey, forwarded to ops.wakeNow.
src/cron/service/ops.ts wakeNow() signature extended to accept and pass through agentId/sessionKey.
src/cli/system-cli.test.ts Tests cover --agent-id alone and --agent-id + --session-key together; CLI layer forwarding verified.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/protocol/schema/agent.ts
Line: 138

Comment:
**Schema inconsistency: `sessionKey` accepts empty strings**

`agentId` correctly uses `NonEmptyString` to reject blank values at the schema layer, but `sessionKey` uses plain `Type.String()` which allows `""`. An empty string passes validation and then reaches `timer.ts`, where it is silently discarded by the falsy guard (`opts.sessionKey ? ...`). For direct API callers who bypass the CLI this means the parameter is accepted without error but has no effect.

For consistency, consider using `NonEmptyString` here as well, the same way `agentId` is declared on the line above.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(cli): add agent-id and session-key ..." | Re-trigger Greptile

mode: Type.Union([Type.Literal("now"), Type.Literal("next-heartbeat")]),
text: NonEmptyString,
agentId: Type.Optional(NonEmptyString),
sessionKey: Type.Optional(Type.String()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Schema inconsistency: sessionKey accepts empty strings

agentId correctly uses NonEmptyString to reject blank values at the schema layer, but sessionKey uses plain Type.String() which allows "". An empty string passes validation and then reaches timer.ts, where it is silently discarded by the falsy guard (opts.sessionKey ? ...). For direct API callers who bypass the CLI this means the parameter is accepted without error but has no effect.

For consistency, consider using NonEmptyString here as well, the same way agentId is declared on the line above.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/protocol/schema/agent.ts
Line: 138

Comment:
**Schema inconsistency: `sessionKey` accepts empty strings**

`agentId` correctly uses `NonEmptyString` to reject blank values at the schema layer, but `sessionKey` uses plain `Type.String()` which allows `""`. An empty string passes validation and then reaches `timer.ts`, where it is silently discarded by the falsy guard (`opts.sessionKey ? ...`). For direct API callers who bypass the CLI this means the parameter is accepted without error but has no effect.

For consistency, consider using `NonEmptyString` here as well, the same way `agentId` is declared on the line above.

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa9a68af7b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cron/service/timer.ts
Comment on lines +1243 to +1245
state.deps.enqueueSystemEvent(text, {
...(opts.agentId ? { agentId: opts.agentId } : {}),
...(opts.sessionKey ? { sessionKey: opts.sessionKey } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Derive agent from session key before enqueueing wake event

Forwarding sessionKey to enqueueSystemEvent without an agentId breaks the new --session-key-only flow: in buildGatewayCronService (src/gateway/server-cron.ts), enqueue resolution uses resolveCronAgent(opts?.agentId) and defaults to the main agent when agentId is missing, so a call like openclaw system event --session-key agent:coding:... queues the event into agent:main:* while requestHeartbeatNow may wake coding. This can route wake/event to different sessions and lose the intended target unless --agent-id is always provided.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Keep open. The requested CLI agentId wake targeting is still missing on current main, but this branch is stale/conflicting, lacks attached real behavior proof, and needs refresh to preserve current sessionKey wake semantics while coordinating with #83738.

Canonical path: Close this PR as superseded by #83738.

So I’m closing this here and keeping the remaining discussion on #83738.

Review details

Best possible solution:

Close this PR as superseded by #83738.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level. Current main exposes --session-key but no --agent-id, and the validated Gateway/CronService/timer wake path can carry only sessionKey, not an explicit agentId; I did not run a live multi-agent route in this read-only review.

Is this the best way to solve the issue?

No. The direction is useful, but this exact branch is not the best merge shape because it is stale against current sessionKey semantics, misses current contract follow-through, overlaps newer cron-origin work, and lacks real behavior proof.

Security review:

Security review cleared: The diff changes CLI parsing, wake protocol plumbing, cron wake forwarding, and tests only; it does not add dependencies, workflows, install scripts, release paths, or secret handling.

What I checked:

  • linked superseding PR: fix(cron): capture originating session/agent on the cron wake tool call #83738 (fix(cron): capture originating session/agent on the cron wake tool call) is still open as the canonical replacement.
  • cluster evidence: the durable review links that PR in the work cluster or recommended risk path.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • Kaspre: Authored the merged typed sessionKey wake protocol, system event CLI, targeted wake proof, and subagent wake guard work that this PR must preserve while adding agentId. (role: wake protocol contributor; confidence: high; commits: 4ddd942f5f89, 5971f74bf133, 528ab7ed4da5; files: src/cli/system-cli.ts, src/gateway/protocol/schema/agent.ts, src/gateway/server-methods/cron.ts)
  • steipete: Recent commits centralize wake session target resolution and touch adjacent cron/session routing surfaces involved in this PR. (role: recent cron wake target contributor; confidence: high; commits: 15fa1e546f2b, c0fe7ab34ab8, 02182d5a3031; files: src/gateway/server-cron.ts, src/cron/service/timer.ts, src/gateway/protocol/schema/agent.ts)
  • vincentkoc: Split and aligned the Gateway cron service contract that must be widened if agentId becomes part of wake. (role: adjacent cron contract contributor; confidence: medium; commits: 3e96fdea9fc7, 7204d490aa93; files: src/cron/service-contract.ts, src/cron/service.ts)
  • galiniliev: Authored recent cron timer work isolating main-session cron wake lanes in the same timer and session-routing area. (role: recent adjacent contributor; confidence: medium; commits: 9eee202a694b; files: src/cron/service/timer.ts, src/gateway/server-cron.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 7bc4a333aa09.

anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
The cron `wake` MCP tool currently forwards only `{mode, text}` to the
gateway. Every wake then enqueues a system event with no sessionKey /
agentId, so the cron service falls back to the heartbeat / main
default. Wakes scheduled from a non-main session (Telegram thread,
Discord channel, multi-agent setup) silently route to the wrong
conversation lane — and on CLI runtimes the woken session burns
tokens generating output that no caller can route back.

Origin capture (closes the upstream half of openclaw#46886 and openclaw#64556):

  - `src/agents/tools/cron-tool.ts` — the `wake` case now resolves
    `opts.agentSessionKey` through `resolveInternalSessionKey` and
    `resolveSessionAgentId`, matching the existing `add` action at
    L569-583. Explicit `sessionKey` / `agentId` params on the tool
    call take precedence over the inferred values so cross-session
    wakes remain expressible.
  - `src/gateway/protocol/schema/agent.ts` — `WakeParamsSchema` now
    declares optional `sessionKey` and `agentId` (NonEmptyString)
    so the gateway-level validator types them explicitly. The
    schema's `additionalProperties: true` continues to accept
    forward-compat metadata unchanged.
  - `src/gateway/server-methods/cron.ts` — the wake handler reads
    both fields, trims them, and forwards to `context.cron.wake`.
  - `src/cron/service.ts` + `src/cron/service/ops.ts` +
    `src/cron/service/timer.ts` — `wake()` accepts optional
    `sessionKey`/`agentId` and threads them into
    `enqueueSystemEvent` and `requestHeartbeatNow`. Missing /
    whitespace-only fields fall through to the dep's default so
    pre-existing call sites with no origin keep behaving the same
    way (backwards compatible).

Tests:

  - `src/cron/service/wake-origin.test.ts` (new, 6 tests) —
    direct seams on `wake()`: origin forwarded on `mode: "now"`,
    queued on `mode: "next-heartbeat"`, default fallback when
    origin omitted, whitespace-only origin treated as omitted,
    empty text still rejected.
  - `src/gateway/protocol/index.test.ts` — extends
    `validateWakeParams` coverage with the new optional fields
    accepted + empty-string rejected.

Out of scope (deliberate split):

  - Channel/thread/topic capture on the job's `delivery` block —
    follow-up PR once this contract lands. The minimum-viable fix
    here is session routing, which unblocks the CLI runtime's
    `--resume <session>` path and the embedded session-resolution
    path without a schema rewrite of the delivery contract.
  - The 5 stalled wake-related PRs (openclaw#70268, openclaw#57199, openclaw#82767,
    openclaw#79869, openclaw#63096) each fix downstream specifics. This PR fixes
    the upstream origin-capture they all silently assume.
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 19, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 19, 2026
anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
The cron `wake` MCP tool currently forwards only `{mode, text}` to the
gateway. Every wake then enqueues a system event with no sessionKey /
agentId, so the cron service falls back to the heartbeat / main
default. Wakes scheduled from a non-main session (Telegram thread,
Discord channel, multi-agent setup) silently route to the wrong
conversation lane — and on CLI runtimes the woken session burns
tokens generating output that no caller can route back.

Origin capture (closes the upstream half of openclaw#46886 and openclaw#64556):

  - `src/agents/tools/cron-tool.ts` — the `wake` case now resolves
    `opts.agentSessionKey` through `resolveInternalSessionKey` and
    `resolveSessionAgentId`, matching the existing `add` action at
    L569-583. Explicit `sessionKey` / `agentId` params on the tool
    call take precedence over the inferred values so cross-session
    wakes remain expressible.
  - `src/gateway/protocol/schema/agent.ts` — `WakeParamsSchema` now
    declares optional `sessionKey` and `agentId` (NonEmptyString)
    so the gateway-level validator types them explicitly. The
    schema's `additionalProperties: true` continues to accept
    forward-compat metadata unchanged.
  - `src/gateway/server-methods/cron.ts` — the wake handler reads
    both fields, trims them, and forwards to `context.cron.wake`.
  - `src/cron/service.ts` + `src/cron/service/ops.ts` +
    `src/cron/service/timer.ts` — `wake()` accepts optional
    `sessionKey`/`agentId` and threads them into
    `enqueueSystemEvent` and `requestHeartbeatNow`. Missing /
    whitespace-only fields fall through to the dep's default so
    pre-existing call sites with no origin keep behaving the same
    way (backwards compatible).

Tests:

  - `src/cron/service/wake-origin.test.ts` (new, 6 tests) —
    direct seams on `wake()`: origin forwarded on `mode: "now"`,
    queued on `mode: "next-heartbeat"`, default fallback when
    origin omitted, whitespace-only origin treated as omitted,
    empty text still rejected.
  - `src/gateway/protocol/index.test.ts` — extends
    `validateWakeParams` coverage with the new optional fields
    accepted + empty-string rejected.

Out of scope (deliberate split):

  - Channel/thread/topic capture on the job's `delivery` block —
    follow-up PR once this contract lands. The minimum-viable fix
    here is session routing, which unblocks the CLI runtime's
    `--resume <session>` path and the embedded session-resolution
    path without a schema rewrite of the delivery contract.
  - The 5 stalled wake-related PRs (openclaw#70268, openclaw#57199, openclaw#82767,
    openclaw#79869, openclaw#63096) each fix downstream specifics. This PR fixes
    the upstream origin-capture they all silently assume.
anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
The cron `wake` MCP tool currently forwards only `{mode, text}` to the
gateway. Every wake then enqueues a system event with no sessionKey /
agentId, so the cron service falls back to the heartbeat / main
default. Wakes scheduled from a non-main session (Telegram thread,
Discord channel, multi-agent setup) silently route to the wrong
conversation lane — and on CLI runtimes the woken session burns
tokens generating output that no caller can route back.

Origin capture (closes the upstream half of openclaw#46886 and openclaw#64556):

  - `src/agents/tools/cron-tool.ts` — the `wake` case now resolves
    `opts.agentSessionKey` through `resolveInternalSessionKey` and
    `resolveSessionAgentId`, matching the existing `add` action at
    L569-583. Explicit `sessionKey` / `agentId` params on the tool
    call take precedence over the inferred values so cross-session
    wakes remain expressible.
  - `src/gateway/protocol/schema/agent.ts` — `WakeParamsSchema` now
    declares optional `sessionKey` and `agentId` (NonEmptyString)
    so the gateway-level validator types them explicitly. The
    schema's `additionalProperties: true` continues to accept
    forward-compat metadata unchanged.
  - `src/gateway/server-methods/cron.ts` — the wake handler reads
    both fields, trims them, and forwards to `context.cron.wake`.
  - `src/cron/service.ts` + `src/cron/service/ops.ts` +
    `src/cron/service/timer.ts` — `wake()` accepts optional
    `sessionKey`/`agentId` and threads them into
    `enqueueSystemEvent` and `requestHeartbeatNow`. Missing /
    whitespace-only fields fall through to the dep's default so
    pre-existing call sites with no origin keep behaving the same
    way (backwards compatible).

Tests:

  - `src/cron/service/wake-origin.test.ts` (new, 6 tests) —
    direct seams on `wake()`: origin forwarded on `mode: "now"`,
    queued on `mode: "next-heartbeat"`, default fallback when
    origin omitted, whitespace-only origin treated as omitted,
    empty text still rejected.
  - `src/gateway/protocol/index.test.ts` — extends
    `validateWakeParams` coverage with the new optional fields
    accepted + empty-string rejected.

Out of scope (deliberate split):

  - Channel/thread/topic capture on the job's `delivery` block —
    follow-up PR once this contract lands. The minimum-viable fix
    here is session routing, which unblocks the CLI runtime's
    `--resume <session>` path and the embedded session-resolution
    path without a schema rewrite of the delivery contract.
  - The 5 stalled wake-related PRs (openclaw#70268, openclaw#57199, openclaw#82767,
    openclaw#79869, openclaw#63096) each fix downstream specifics. This PR fixes
    the upstream origin-capture they all silently assume.
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@clawsweeper

clawsweeper Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui cli CLI command changes gateway Gateway runtime merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant