Skip to content

feat(cron): add optional agentId filter to cron.list#77191

Open
lonexreb wants to merge 4 commits intoopenclaw:mainfrom
lonexreb:feat/77118-cron-list-agentid-filter
Open

feat(cron): add optional agentId filter to cron.list#77191
lonexreb wants to merge 4 commits intoopenclaw:mainfrom
lonexreb:feat/77118-cron-list-agentid-filter

Conversation

@lonexreb
Copy link
Copy Markdown
Contributor

@lonexreb lonexreb commented May 4, 2026

Summary

Adds an additive optional agentId filter to cron.list so multi-agent installs can scope cron job listings to a single agent's jobs. Omitting agentId preserves the historical "all jobs across all agents" result, so every existing client keeps working.

This delivers the gateway-side foundation only. Tool-layer auto-fill from session context and a --agent CLI flag are sensible follow-ups but stay out of this PR to keep the change reviewable as one tight contract evolution.

Why

From the issue: in a 7-agent install with ~54 cron jobs, every agent's cron list returned all 54 jobs. Heartbeat prompts wasted tokens parsing irrelevant jobs, agents could see (and potentially modify via update/add) other agents' jobs, and prompt confusion led to incorrect status reports. The reporter mitigated locally by adding inline HEARTBEAT.md instructions to ignore other agents' jobs — a workaround pointing at a missing server-side capability.

Changes

  • src/cron/service/list-page-types.ts — Add agentId?: string to CronListPageOptions with a docstring linking the issue.
  • src/cron/service/ops.ts — In listPage, normalize the input (treat empty/whitespace as omitted), compute agentIdFilter once, and apply it inside the existing source.filter(...) predicate so it composes with enabledFilter and query.
  • src/gateway/protocol/schema/cron.ts — Add agentId: Type.Optional(Type.String()) to CronListParamsSchema. Schema is additionalProperties: false, so this is the canonical place for the new field.
  • src/gateway/server-methods/cron.ts — Thread agentId through the cron.list handler into context.cron.listPage(...).
  • src/cron/service.list-page-agent-filter.test.ts — 5 new regression tests:
    • matches only the target agent's jobs
    • omitted agentId returns every job (backward compatible)
    • empty/whitespace agentId is treated as omitted
    • no-match returns an empty page
    • composes with the existing query filter
  • CHANGELOG.md — Unreleased > Changes entry credited to me.

Boundary considerations

Per src/gateway/protocol/CLAUDE.md: schema changes are protocol changes, prefer additive evolution. This is purely additive — no existing field changes shape, no required field added, schema stays additionalProperties: false. Existing clients that omit agentId are unaffected.

Test plan

  • pnpm test src/cron/service.list-page-agent-filter.test.ts src/cron/service.list-page-sort-guards.test.ts — 7/7 pass (5 new + 2 existing)
  • pnpm test src/gateway/protocol/cron-validators.test.ts — 12/12 pass
  • pnpm exec oxfmt --check on touched files — clean

Fixes #77118.

Real behavior proof


@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime size: S labels May 4, 2026
Copy link
Copy Markdown

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

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: 610eac9dd9

ℹ️ 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/ops.ts Outdated
Comment on lines +291 to +292
if (agentIdFilter !== null && (job.agentId ?? "") !== agentIdFilter) {
return false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include default-agent jobs when filtering by agentId

Treating job.agentId as "" in the new filter drops valid default-agent jobs that intentionally omit agentId (main-session jobs are allowed to have no agentId), so cron.list with agentId set to the default agent can return incomplete results. This will surface as soon as callers auto-fill agentId from session context: legacy/default jobs without explicit agentId disappear from listings even though they belong to that agent, causing incorrect totals and missed jobs in heartbeats.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 4, 2026

Codex review: needs real behavior proof before merge.

Summary
The PR adds an optional agentId filter to cron list APIs, threads it through gateway protocol/server handling and Swift models, adds cron service regression tests, and updates the changelog.

Reproducibility: yes. by source inspection: current main lacks agentId in the cron list schema, handler, and service options, and the PR's remaining default-agent miss follows directly from the updated filter expression. I did not run a live gateway scenario because this review was read-only.

Real behavior proof
Needs real behavior proof before merge: The PR body's real behavior proof section is empty, so this external PR still needs after-fix terminal output, logs, screenshot, recording, or another real setup artifact before merge.

Next step before merge
Contributor action is needed for real behavior proof, and maintainers should decide whether this foundation-only PR should close the broader linked request after the P2 fallback bug is fixed.

Security
Cleared: The diff is an additive cron filter plus tests, generated Swift protocol models, and changelog text; it adds no dependencies, CI, secrets, permissions, downloads, or lifecycle hooks.

Review findings

  • [P2] Fallback missing default agent IDs to main — src/cron/service/ops.ts:293
Review details

Best possible solution:

Fix the filter to use the same default-agent fallback as other cron paths, then merge the gateway-contract slice with real behavior proof while tracking tool and CLI scoping separately.

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

Yes, by source inspection: current main lacks agentId in the cron list schema, handler, and service options, and the PR's remaining default-agent miss follows directly from the updated filter expression. I did not run a live gateway scenario because this review was read-only.

Is this the best way to solve the issue?

No for the current PR head: the additive protocol field and service predicate are the right narrow slice, but the default-agent fallback must match existing cron semantics before merge. Real behavior proof is also still required for this external PR.

Full review comments:

  • [P2] Fallback missing default agent IDs to main — src/cron/service/ops.ts:293
    When CronServiceState is constructed without deps.defaultAgentId, normalizeOptionalAgentId(state.deps.defaultAgentId) returns undefined; jobs with no agentId then get effectiveOwner === "" and are excluded from cron.list({ agentId: "main" }). Other cron paths treat a missing default as main, so embedded/non-gateway callers can still lose default-agent jobs. Use the same normalizeAgentId/DEFAULT_AGENT_ID fallback before comparing.
    Confidence: 0.91

Overall correctness: patch is incorrect
Overall confidence: 0.88

What I checked:

  • current-main cron.list has no agentId contract: Current main's CronListParamsSchema lists paging/query/enabled/sort fields and is additionalProperties: false, so agentId is not accepted today. (src/gateway/protocol/schema/cron.ts:337, a17d4371d101)
  • current-main handler does not pass agentId: The current cron.list server method narrows params without agentId and calls context.cron.listPage without an owner filter. (src/gateway/server-methods/cron.ts:182, a17d4371d101)
  • current-main service only filters enabled/query: listPage currently filters by enabled state and text query; job.agentId is only included in the query haystack, not as a scoped owner predicate. (src/cron/service/ops.ts:275, a17d4371d101)
  • remaining PR bug: missing defaultAgentId becomes empty owner: The PR computes defaultAgentId with normalizeOptionalAgentId(state.deps.defaultAgentId), so when that dependency is omitted, jobs with no agentId compare as "" and do not match agentId: "main". (src/cron/service/ops.ts:293, 363137161a7e)
  • other cron paths treat missing default as main: assertMainSessionAgentId uses normalizeAgentId(defaultAgentId), which falls back to main for missing input, and timer maintenance similarly falls back to DEFAULT_AGENT_ID. (src/cron/service/jobs.ts:177, a17d4371d101)
  • real behavior proof is missing: The PR body's Real behavior proof section is an empty fenced block; the listed validation is unit/protocol tests and formatter proof only. (363137161a7e)

Likely related people:

  • @steipete: Current-main blame for the central cron list, gateway schema, and server-method sections points to Peter Steinberger, and nearby history shows repeated release/protocol maintenance on these files. (role: recent maintainer and protocol/runtime owner; confidence: high; commits: e0fafdcc1d1d; files: src/cron/service/ops.ts, src/gateway/protocol/schema/cron.ts, src/gateway/server-methods/cron.ts)
  • @Takhoffman: Tak Hoffman carried recent cron UI and gateway list/run-history work touching the same cron listing surface and related user-facing behavior. (role: adjacent cron UI/gateway contributor; confidence: medium; commits: 77c3b142a966; files: src/cron/service/ops.ts, src/gateway/server-methods/cron.ts, src/cli/cron-cli/shared.ts)
  • @liaosvcaf: C. Liao's prior cron change established the default-agent/main-session ownership guard that this filter needs to mirror for jobs with implicit default ownership. (role: adjacent default-agent cron behavior contributor; confidence: medium; commits: 313a655d13fa; files: src/cron/service/jobs.ts)
  • Tyler Yust: Recent cron service history includes multiple scheduler and delivery refactors by Tyler Yust, making them a likely routing candidate for cron service semantics. (role: cron service maintainer; confidence: medium; commits: 38543d819690; files: src/cron/service/ops.ts, src/cron/service/jobs.ts)

Remaining risk / open question:

  • Filtered cron.list({ agentId: "main" }) can omit implicit default-agent jobs when CronServiceState.deps.defaultAgentId is unset.
  • The contributor has not provided after-fix real behavior proof for the changed gateway behavior.
  • The PR is intentionally foundation-only, so the linked user problem still needs explicit follow-up for tool auto-fill and CLI scoping if maintainers want the full workflow closed.

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

@lonexreb lonexreb force-pushed the feat/77118-cron-list-agentid-filter branch from 610eac9 to cf286f8 Compare May 4, 2026 09:35
In multi-agent installs, every agent calling cron.list received every
agent's jobs (often 50+ across 7 agents in the reporter's setup),
creating prompt noise during heartbeat checks and accidental
cross-agent modification risk.

Add an optional agentId field to:
  - CronListPageOptions (cron service)
  - CronListParamsSchema (gateway protocol)
  - cron.list server-method handler

The listPage op filters jobs by exact agentId match (treating empty/
whitespace as omitted) and composes with the existing query/enabled
filters. Omitting agentId preserves the historical 'all jobs' result.

Tool-layer auto-fill from session context and CLI --agent flag are
follow-ups; this PR delivers the gateway-side foundation.

Tests: 5 new cron listPage cases covering match, omit/empty,
no-match, and composition with the query filter. All 7 listPage tests
plus 12 cron-validators tests pass.
@lonexreb lonexreb force-pushed the feat/77118-cron-list-agentid-filter branch from cf286f8 to 98872f0 Compare May 4, 2026 19:25
@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 4, 2026

Addressed [P2] from codex review (src/cron/service/ops.ts:292). The current PR head already implements the suggested fallback: when job.agentId is missing, the filter resolves the effective owner to state.deps.defaultAgentId so default-agent jobs that omit agentId are correctly matched when callers pass agentId: defaultAgentId. Codex was reviewing an older revision (610eac9dd9); the current head carries the fix at line 297 (const effectiveOwner = (job.agentId ?? "").trim() || defaultAgentId || "") along with regression coverage at src/cron/service.list-page-agent-filter.test.ts:92 ("treats jobs missing agentId as default-agent jobs"). Also rebased onto current origin/main.

Copy link
Copy Markdown

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

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: 98872f0304

ℹ️ 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/ops.ts Outdated
Comment on lines +280 to +281
const agentIdFilter =
typeof opts?.agentId === "string" && opts.agentId.trim() ? opts.agentId.trim() : null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize agentId filter before matching jobs

The new agentId filter compares raw trimmed strings, but cron job ownership is normalized elsewhere (normalizeOptionalAgentId/normalizeAgentId) and can be case-normalized independently of caller input. As a result, requests like cron.list({ agentId: "MAIN" }) can miss jobs stored as "main", and implicit default-agent jobs can also be dropped when casing differs, even though the same IDs are treated as equivalent in create/update paths. Normalize the filter (and effective owner) with the same agent-id normalizer before comparing.

Useful? React with 👍 / 👎.

…param

The new optional agentId parameter on CronListParams added in this PR
needs the generated Swift protocol files to include the new field; CI
fast-protocol drift check flagged the missing regeneration.
@openclaw-barnacle openclaw-barnacle Bot added the app: macos App: macos label May 5, 2026
lonexreb added 2 commits May 4, 2026 20:42
Lint check (oxlint) flagged two .sort() calls on the new test file as
'Use Array#toSorted() instead of Array#sort()' — toSorted() is the
non-mutating variant that the repo lint rule prefers.
Codex follow-up on PR openclaw#77191 noted: cron job ownership is normalized via
normalizeOptionalAgentId/normalizeAgentId on create/update paths, but the
new agentId filter compared raw trimmed strings. As a result, requests
like cron.list({ agentId: 'MAIN' }) missed jobs stored as 'main', and
implicit default-agent jobs could also be dropped when the configured
default-agent id casing differed from caller input — even though the same
IDs are treated as equivalent in create/update paths.

Route both the requested filter and the resolved default-agent id through
normalizeOptionalAgentId, and normalize each job's agentId the same way
when computing the effective owner. Add a regression test covering the
'MAIN' vs 'main' casing scenario and a whitespace+case variant.
@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 5, 2026

Addressed [P2] from codex review (src/cron/service/ops.ts:281 — normalize agentId filter). Routed both the requested filter and the resolved default-agent id through normalizeOptionalAgentId (the same wrapper around normalizeAgentId used by job create/update paths), and normalize each job's agentId the same way when computing the effective owner. So cron.list({ agentId: "MAIN" }) against jobs stored as "main" now matches, and an uppercase defaultAgentId matches implicit default-agent jobs symmetrically. Added a regression test covering the casing scenario plus a whitespace+case variant. 7 tests pass.

Copy link
Copy Markdown

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

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: 363137161a

ℹ️ 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/ops.ts
// Mirror that here so `cron.list({ agentId: defaultAgentId })` returns
// default-agent jobs that omit the field, not an empty page. Normalize the
// default-agent id too so the comparison is symmetric. See #77118.
const defaultAgentId = normalizeOptionalAgentId(state.deps.defaultAgentId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize default agent id before owner fallback

listPage now maps jobs without agentId to defaultAgentId, but this uses normalizeOptionalAgentId(state.deps.defaultAgentId), which returns undefined when the dependency is omitted. In that common fallback configuration, implicit default-agent jobs get effectiveOwner === "" and are excluded by cron.list({ agentId: "main" }), even though other cron paths treat missing default agent as main via normalizeAgentId. This makes filtered results inconsistent for embedded/non-gateway cron service users that rely on the default main agent behavior.

Useful? React with 👍 / 👎.

@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 5, 2026
@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 5, 2026

Heads up — origin/main now contains the agentId filter and normalizeOptionalAgentId plumbing in src/cron/service/ops.ts (lines 279, 292). The behavior this PR proposed appears to have landed via a different commit. I'll let the maintainer decide whether to close this PR as superseded or keep it open for the test-coverage delta. I won't keep rebasing it until that's resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: macos App: macos app: web-ui App: web-ui gateway Gateway runtime size: S 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.

Feature request: cron list should support agentId filtering in multi-agent setups

1 participant