Skip to content

Add scoped agent research, memory, and RBAC - #396

Open
michaelmwu wants to merge 13 commits into
mainfrom
permission-tiers-smarter-bot
Open

Add scoped agent research, memory, and RBAC#396
michaelmwu wants to merge 13 commits into
mainfrom
permission-tiers-smarter-bot

Conversation

@michaelmwu

@michaelmwu michaelmwu commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

  • add production-safe Discord role-ID and guild-bound agent authorization
  • add SearXNG, Brave, and Firecrawl public-web research with bounded planning
  • add durable PostgreSQL memory and tenant-bound, read-only ERP/Billing tools
  • add deployment configuration, audit redaction, and confirmation hardening

Validation

  • uv run pytest -q focused agent suite: 342 passed
  • Ruff, Ruff format, Pyrefly: passed
  • Alembic migration head: 20260728_0100

Notes

  • The broader FastAPI TestClient agent subset stalls in this sandbox's async executor; targeted API assertions pass.
  • Deploy against one Discord guild until role mappings are represented per guild for multi-guild use.

Note

High Risk
Large changes to agent authorization, scheduled execution of read tools against CRM/ERP/billing, and Discord delivery—security- and data-handling critical paths with many failure modes around scope revocation and ambiguous delivery.

Overview
Extends the agent platform with durable recurring reports, operator-facing dashboard tools, and tighter production guardrails around Discord identity, ERP access, and audit privacy.

Recurring agent schedules persist cron-based jobs with a fixed read-only tool envelope (bounded agent_loop or legacy frozen GitHub actions). A background dispatcher creates worker runs; execution refreshes the owner’s Discord roles from the bot, re-checks saved scopes, runs tools with aggregate-only observations for CRM/billing/ERP/onboarding, and posts bounded reports to a configured channel with delivery idempotency handling. Dashboard Agent schedules and bot/API routes support create, pause/resume/archive, and manual run; confirmed chat plans can route agent_schedule.create through the same API path.

Discord diagnostics adds an admin dashboard view backed by GET /dashboard/api/discord-diagnostics, surfacing guild role catalogs, AGENT_DISCORD_* binding health, and copyable config—without exposing secrets.

Configuration and runtime document new env vars: per-bundle Discord role IDs, AGENT_ERP_ORGANIZATION_ID, public-web search providers, planning/response budgets, memory cleanup scheduling, and longer bot AGENT_API_TIMEOUT_SECONDS. Sync agent requests gain a response budget, planner capacity bulkhead, redacted audit metadata, and stricter pending-plan context (no retained thread snippets).

Reviewed by Cursor Bugbot for commit 1bca150. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added recurring agent reports with scheduling, pause/resume/archive controls, manual runs, and Discord delivery.
    • Added Discord diagnostics for role mappings, server health, searchable role catalogs, and configuration export.
    • Added bounded public web research plus read-only billing, ERP, and onboarding summaries.
    • Added clearer capability-based agent access and confirmation safeguards.
  • Bug Fixes

    • Improved privacy protections, tenant isolation, timeout handling, and fail-closed authorization.
    • Added durable agent memory cleanup and safer schedule execution recovery.
  • Documentation

    • Expanded configuration, permissions, scheduling, diagnostics, and agent capability guidance.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds role-ID Discord authorization, bounded public-web and ERP/Billing reads, tenant-scoped durable memory, recurring agent schedules, Discord diagnostics, worker dispatch, and dashboard management surfaces with associated migrations, tests, configuration, and documentation.

Changes

Agent authorization and safety

Layer / File(s) Summary
Role-bound authorization and request identity
packages/shared/src/five08/agent/*, packages/shared/src/five08/settings.py, apps/discord_bot/src/five08/discord_bot/cogs/agent.py, tests/unit/test_agent_role_bindings.py
Agent identities now carry validated Discord role IDs and guild context; policy scopes resolve from configured role-ID bundles, fail closed across guild or tenant mismatches, and restrict role-name fallback to local/test environments.
Bounded planning and external reads
packages/shared/src/five08/agent/orchestrator.py, packages/shared/src/five08/agent/planner.py, packages/shared/src/five08/agent/tools.py, packages/shared/src/five08/agent/web.py, tests/unit/test_agent_web.py, tests/unit/test_agent_erp_tools.py
Planner and tool execution add bounded public-web research, read-only ERP/Billing access, provider fallback, deadline propagation, SSRF validation, sanitized outputs, and schedule-safe tool metadata.
Tenant-scoped agent memory
packages/shared/src/five08/agent/memory.py, packages/shared/src/five08/agent/postgres_memory.py, packages/shared/src/five08/agent/models.py, apps/worker/src/five08/worker/migrations/versions/20260728_0100_create_agent_memory_facts.py
Memory values are bounded and screened for sensitive data; reads, writes, deletion, and cleanup require organization scoping, with PostgreSQL persistence and lifecycle indexes added.

Durable schedules and operational surfaces

Layer / File(s) Summary
Durable schedule state and execution
packages/shared/src/five08/agent/schedules.py, apps/api/src/five08/backend/api.py, apps/worker/src/five08/worker/jobs.py, apps/worker/src/five08/worker/migrations/versions/*agent_schedule*
Recurring schedules and runs receive validated definitions, cron/timezone handling, lifecycle transitions, leases, queue reconciliation, worker handoff, bounded execution, and durable Discord delivery idempotency.
Discord schedule commands and internal delivery
apps/discord_bot/src/five08/discord_bot/cogs/schedules.py, apps/discord_bot/src/five08/discord_bot/utils/internal_api.py, apps/api/src/five08/backend/routes.py, apps/api/src/five08/backend/schemas.py
Discord commands create, list, control, and run schedules; authenticated internal endpoints refresh member roles and post bounded reports; backend routes and request schemas expose the schedule API.
Dashboard schedules and diagnostics
apps/admin_dashboard/src/main.tsx, apps/admin_dashboard/src/views/agent-schedules-view.tsx, apps/admin_dashboard/src/views/discord-diagnostics-view.tsx, apps/discord_bot/src/five08/discord_bot/cogs/diagnostics.py
The dashboard adds permission-gated schedule and diagnostics views, while the bot provides read-only role-binding snapshots, exports, refresh behavior, pagination, and permission checks.

Runtime delivery and validation

Layer / File(s) Summary
API hardening and lifecycle wiring
apps/api/src/five08/backend/api.py, tests/unit/test_backend_api.py
Agent requests use shared-secret authentication, bounded planning bulkheads and response budgets, sanitized audit metadata, cleared pending context snippets, confirmation reauthorization, and conditional schedule/memory cleanup tasks.
Configuration, documentation, and service wiring
.env.example, compose.yaml, docs/configuration.md, apps/api/README.md, apps/discord_bot/README.md, packages/shared/pyproject.toml, packages/shared/src/five08/agent/__init__.py
Environment settings, provider and worker wiring, API and bot behavior documentation, public exports, and the cron dependency are updated.
Regression and generated dashboard assets
tests/unit/*, tests/evals/discord-agent/fixtures/v1/*, apps/api/src/five08/backend/static/dashboard/*
Unit, integration-style, evaluation, dashboard component, memory, web, schedule, authorization, diagnostics, queue, and worker tests are added or updated; hashed dashboard assets are refreshed.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • 508-dev/508-workflows#29: Extends the shared queue behavior that this PR updates for re-delivery of existing queued jobs.
  • 508-dev/508-workflows#264: Introduced the Discord agent gateway that this PR extends with stricter authorization, additional tools, scheduling, and diagnostics.
  • 508-dev/508-workflows#372: Refactored backend route registration that this PR extends with diagnostics and scheduling endpoints.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main themes of scoped research, memory, and role-based access changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch permission-tiers-smarter-bot

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@michaelmwu
michaelmwu marked this pull request as ready for review July 28, 2026 01:28
@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_3d03ab4a-354b-4e19-bcf9-27ef840415df)

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_4aafade4-4897-41d9-97c1-0a117182683b)

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_1958e8cd-7792-43a0-8528-468812d96e23)

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🧹 Nitpick comments (13)
tests/unit/test_agent_postgres_memory.py (2)

211-241: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test PAN is intentional; the scanner hit is a false positive.

4111 1111 1111 1111 is the canonical non-issued test number used to exercise the Luhn detector. No change needed, though a # nosemgrep/inline suppression would keep the PII scan clean in CI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_agent_postgres_memory.py` around lines 211 - 241, Suppress
the intentional PII scanner finding on the PAN test value in
test_remember_fact_rejects_sensitive_value_before_database_write, using the
repository’s supported inline suppression syntax (such as nosemgrep) without
changing the test data or behavior.

Source: Linters/SAST tools


334-349: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the expected_organization_id postcondition failure.

_memory_fact_from_row raises RuntimeError("Memory fact row violated its organization boundary") on remember_fact/forget_fact, but no test drives that path. A fake returning a row for the wrong tenant would lock in that last-line tenant guard.

Also applies to: 369-390

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_agent_postgres_memory.py` around lines 334 - 349, The tests
cover query filtering but not the tenant-boundary postcondition in
_memory_fact_from_row. Add coverage for remember_fact or forget_fact using a
fake cursor row whose organization_id differs from the expected organization,
and assert that RuntimeError with “Memory fact row violated its organization
boundary” is raised.
tests/unit/test_agent_memory.py (2)

96-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add negative cases so the detector's false-positive surface is pinned down.

Every parametrized case asserts rejection; nothing asserts that benign text is accepted. Given how loose _INLINE_SECRET_RE is (see packages/shared/src/five08/agent/memory.py Line 38), a companion parametrized test over phrases like "password reset instructions", "token bucket limit", and "the secret sauce doc" would make the regex tightening safe to land and prevent regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_agent_memory.py` around lines 96 - 131, Add a companion
parametrized test near test_in_memory_rejects_sensitive_values_before_storing
covering benign phrases such as “password reset instructions,” “token bucket
limit,” and “the secret sauce doc.” Assert these values are accepted and can be
stored by InMemoryMemoryStore, preserving the existing rejection test for
genuinely sensitive values.

173-193: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert through the public API rather than store._facts.

Both tests reach into the private dict. purge_expired already returns a count and list_facts exposes remaining rows per tenant, so the same assertions can be made without coupling to internals.

Also applies to: 196-211

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_agent_memory.py` around lines 173 - 193, Update the tests
around test_in_memory_purge_is_tenant_scoped_and_removes_expired_or_deleted_rows
and the additionally referenced test to stop asserting against the private
store._facts dictionary. Use purge_expired’s returned count and list_facts with
the appropriate organization_id to verify remaining rows through the public API,
preserving the tenant-scoped expectations.
packages/shared/src/five08/agent/context.py (1)

91-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the hard character cap from the model instead of hardcoding 2048.

AgentContextSnippet.text already declares max_length=2048 (packages/shared/src/five08/agent/models.py Line 117). Duplicating the literal here means a model change silently desyncs the bounding logic. Also, estimate_context_tokens returns max(1, ...), so the token_count <= 0 branch is unreachable.

♻️ Proposed refactor
-        truncated_text = snippet.text[:2048]
+        truncated_text = snippet.text[:MAX_CONTEXT_SNIPPET_CHARS]

Define the constant once (in models.py) and reuse it for the Field(max_length=...) declaration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/agent/context.py` around lines 91 - 99, Define a
shared maximum-length constant in the module containing AgentContextSnippet, use
it for the model field’s max_length, and import/reuse it when truncating
snippet.text in the context-building logic instead of hardcoding 2048. Remove
the unreachable token_count <= 0 check, while preserving the existing
remaining-token filtering.
packages/shared/src/five08/agent/memory.py (1)

274-283: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

_purge_expired_locked runs a full-store scan on every read and write.

remember_fact, list_facts, and forget_fact each walk all facts across all tenants under the lock. At MVP scale this is fine, but it makes every memory operation O(total facts) and serializes them. If this store is used beyond tests, consider purging opportunistically (time-gated) rather than on every call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/agent/memory.py` around lines 274 - 283, The
_purge_expired_locked flow currently scans the entire store for every memory
operation. Add a time-gated purge policy so remember_fact, list_facts, and
forget_fact reuse the existing purge logic only when the configured interval has
elapsed, while preserving deletion of expired and deleted facts when purging
runs and keeping access under the lock.
tests/unit/test_diagnostics_cog.py (1)

145-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fallback test only exercises the generic except Exception branch.

RuntimeError skips the discord.Forbidden and discord.HTTPException handlers, which produce a different refresh_error message. Adding a discord.Forbidden case would pin the user-visible copy for the most likely real failure (missing permissions).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_diagnostics_cog.py` around lines 145 - 157, Add a dedicated
fallback test alongside test_cog_refresh_falls_back_to_gateway_cache using
discord.Forbidden from guild.fetch_roles, then assert the 200 response,
gateway_cache source, and the permission-specific refresh_error text. Keep the
existing RuntimeError test to cover the generic exception branch.
apps/api/src/five08/backend/api.py (1)

339-346: 🩺 Stability & Availability | 🔵 Trivial

Bulkhead bounds agent planner threads, but the shared default executor is still the real limit.

asyncio.to_thread uses the loop's default ThreadPoolExecutor, which other to_thread callers in this module share. Timed-out planner threads hold both a bulkhead permit and an executor thread until the sync call returns, so under sustained timeouts unrelated to_thread work can still starve. Consider a dedicated executor for agent planning (or a metric on _AGENT_REQUEST_PLAN_BULKHEAD saturation) so the isolation is real and observable.

Also applies to: 378-392

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/five08/backend/api.py` around lines 339 - 346, Update the agent
planning flow that uses asyncio.to_thread to run synchronous planner work on a
dedicated executor, isolating it from the event loop’s shared default executor
while preserving the _AGENT_REQUEST_PLAN_BULKHEAD limit. Ensure the executor
lifecycle is managed by the surrounding API application, and expose saturation
through the existing AgentRequestPlanCapacityError path or an appropriate
metric.
apps/admin_dashboard/src/discord-diagnostics-view.test.tsx (1)

78-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider covering the clipboard and empty-state branches.

copyToClipboard has three outcomes (unsupported clipboard, success, failure) that all drive onNotice, and the diagnostics === null empty state has its own render path; neither is exercised here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin_dashboard/src/discord-diagnostics-view.test.tsx` around lines 78 -
98, Extend the DiscordDiagnosticsView tests to cover all copyToClipboard
outcomes—unsupported clipboard, successful copy, and rejected copy—and assert
each corresponding onNotice result. Add a separate test rendering
diagnostics={null} and verify the empty-state UI, while preserving the existing
role filtering assertions.
packages/shared/src/five08/agent/orchestrator.py (1)

411-441: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the unused _plan_with_model parameter.

plan() is the only caller and it does not pass explicit_public_web_action, while the public-web branch returns before that call. This leaves the check in this block and the pass-through to _response_for_planner_result unreachable; remove them or wire the flow explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/agent/orchestrator.py` around lines 411 - 441,
Update the planner flow around _plan_with_model and its sole caller plan():
remove the unused explicit_public_web_action parameter and eliminate the
unreachable public-web validation/pass-through logic in that block. Preserve the
existing deterministic explicit-public-web handling and ensure
_response_for_planner_result receives only values that remain reachable and
necessary.
packages/shared/src/five08/settings.py (2)

93-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider enforcing agent_public_web_deadline_seconds < agent_request_response_budget_seconds.

Both bounds are independently configurable (e.g. budget 10, deadline 50), so a misconfiguration silently converts public-web research into 504s. The docs state the invariant; the existing model_validator could assert it.

♻️ Sketch
     `@model_validator`(mode="after")
     def _reject_everyone_agent_role_bindings(self) -> "SharedSettings":
+        # (or a separate validator)
+        if self.agent_public_web_deadline_seconds >= (
+            self.agent_request_response_budget_seconds
+        ):
+            raise ValueError(
+                "AGENT_PUBLIC_WEB_DEADLINE_SECONDS must stay below "
+                "AGENT_REQUEST_RESPONSE_BUDGET_SECONDS"
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/settings.py` around lines 93 - 107, Update the
settings model’s existing model_validator to enforce that
agent_public_web_deadline_seconds is strictly less than
agent_request_response_budget_seconds, rejecting configurations such as budget
10 with deadline 50 while preserving valid configurations and existing field
bounds.

422-430: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the local-environment set with PolicyEngine.from_settings.

The same {"local", "development", "dev", "test", "testing"} literal is duplicated in packages/shared/src/five08/agent/policy.py (line 216) where it decides require_guild_binding. Divergence between the two copies would silently change the fail-closed boundary; export one constant from settings.py and reuse it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/settings.py` around lines 422 - 430, Define and
export a single shared local-environment set in settings.py, then update
agent_role_name_fallback_enabled and PolicyEngine.from_settings to reuse it
instead of maintaining duplicate literals. Preserve the existing normalized
environment matching and require_guild_binding behavior.
tests/unit/test_agent_web.py (1)

345-406: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the deadline_monotonic path.

The suite exercises provider fallback and typed errors thoroughly, but nothing asserts _effective_request_timeout behavior: that a supplied deadline clamps the per-request timeout, and that an already-expired deadline raises WebResearchTransportError before any HTTP call. That is the control bounding agent latency for public web reads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_agent_web.py` around lines 345 - 406, Add unit tests covering
WebResearchClient’s deadline_monotonic path: verify a supplied deadline clamps
the effective per-request timeout, and verify an already-expired deadline raises
WebResearchTransportError before any provider or HTTP call occurs. Reuse the
existing provider test patterns and assert the transport error and zero
provider-call behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/shared/src/five08/agent/context.py`:
- Around line 17-24: Update _contains_private_context_text and its
snippet-processing flow to avoid dropping an entire request-context snippet
merely because snippet.text contains a UUID; narrowly classify only genuinely
sensitive UUIDs or redact matched tokens while preserving surrounding context.
Add a production counter or log for snippets that are dropped or become blank,
using the existing context-handling symbols and observability mechanisms.

In `@packages/shared/src/five08/agent/memory.py`:
- Around line 38-47: N/A
- Line 64: Replace the shape-only pattern in _IBAN_RE with IBAN validation that
accepts lowercase input case-insensitively, restricts matches to a real IBAN
country-code set, and verifies the rearranged IBAN mod-97 checksum. Preserve
matching only valid IBANs so unrelated uppercase identifiers such as
SKU12ABCDEFGHIJKL are rejected.

In `@packages/shared/src/five08/agent/postgres_memory.py`:
- Around line 19-26: Make the shared helper contract explicit by renaming
_assert_visible_org_matches_tenant and _normalized_time in five08.agent.memory
to public names, then update postgres_memory.py and all other call sites to use
those names consistently. Alternatively, move both helpers into a shared
_memory_common module and import them from there, removing cross-module imports
of the underscore-prefixed symbols.
- Around line 231-232: Remove the _purge_expired_with_cursor call from the
list_facts read path so reads perform no deletion or locking; rely on the
existing expires_at > %s predicate to hide expired rows. Move expiry cleanup to
a scheduled worker purge_expired invocation, and update include_deleted handling
consistently—either preserve its soft-delete filtering for rows that can exist
or remove the unused parameter and related conditions, noting that forget_fact
performs physical deletion.
- Around line 47-63: Update PostgresMemoryStore.__init__ and its database-access
path to enforce bounded psycopg connection timeouts and a driver-supported
statement timeout for every operation. Reuse a connection pool for normal agent
traffic so memory operations do not create a fresh TCP/TLS/auth connection each
time, while preserving the injectable ConnectionFactory behavior for tests and
callers that provide one.

---

Nitpick comments:
In `@apps/admin_dashboard/src/discord-diagnostics-view.test.tsx`:
- Around line 78-98: Extend the DiscordDiagnosticsView tests to cover all
copyToClipboard outcomes—unsupported clipboard, successful copy, and rejected
copy—and assert each corresponding onNotice result. Add a separate test
rendering diagnostics={null} and verify the empty-state UI, while preserving the
existing role filtering assertions.

In `@apps/api/src/five08/backend/api.py`:
- Around line 339-346: Update the agent planning flow that uses
asyncio.to_thread to run synchronous planner work on a dedicated executor,
isolating it from the event loop’s shared default executor while preserving the
_AGENT_REQUEST_PLAN_BULKHEAD limit. Ensure the executor lifecycle is managed by
the surrounding API application, and expose saturation through the existing
AgentRequestPlanCapacityError path or an appropriate metric.

In `@packages/shared/src/five08/agent/context.py`:
- Around line 91-99: Define a shared maximum-length constant in the module
containing AgentContextSnippet, use it for the model field’s max_length, and
import/reuse it when truncating snippet.text in the context-building logic
instead of hardcoding 2048. Remove the unreachable token_count <= 0 check, while
preserving the existing remaining-token filtering.

In `@packages/shared/src/five08/agent/memory.py`:
- Around line 274-283: The _purge_expired_locked flow currently scans the entire
store for every memory operation. Add a time-gated purge policy so
remember_fact, list_facts, and forget_fact reuse the existing purge logic only
when the configured interval has elapsed, while preserving deletion of expired
and deleted facts when purging runs and keeping access under the lock.

In `@packages/shared/src/five08/agent/orchestrator.py`:
- Around line 411-441: Update the planner flow around _plan_with_model and its
sole caller plan(): remove the unused explicit_public_web_action parameter and
eliminate the unreachable public-web validation/pass-through logic in that
block. Preserve the existing deterministic explicit-public-web handling and
ensure _response_for_planner_result receives only values that remain reachable
and necessary.

In `@packages/shared/src/five08/settings.py`:
- Around line 93-107: Update the settings model’s existing model_validator to
enforce that agent_public_web_deadline_seconds is strictly less than
agent_request_response_budget_seconds, rejecting configurations such as budget
10 with deadline 50 while preserving valid configurations and existing field
bounds.
- Around line 422-430: Define and export a single shared local-environment set
in settings.py, then update agent_role_name_fallback_enabled and
PolicyEngine.from_settings to reuse it instead of maintaining duplicate
literals. Preserve the existing normalized environment matching and
require_guild_binding behavior.

In `@tests/unit/test_agent_memory.py`:
- Around line 96-131: Add a companion parametrized test near
test_in_memory_rejects_sensitive_values_before_storing covering benign phrases
such as “password reset instructions,” “token bucket limit,” and “the secret
sauce doc.” Assert these values are accepted and can be stored by
InMemoryMemoryStore, preserving the existing rejection test for genuinely
sensitive values.
- Around line 173-193: Update the tests around
test_in_memory_purge_is_tenant_scoped_and_removes_expired_or_deleted_rows and
the additionally referenced test to stop asserting against the private
store._facts dictionary. Use purge_expired’s returned count and list_facts with
the appropriate organization_id to verify remaining rows through the public API,
preserving the tenant-scoped expectations.

In `@tests/unit/test_agent_postgres_memory.py`:
- Around line 211-241: Suppress the intentional PII scanner finding on the PAN
test value in test_remember_fact_rejects_sensitive_value_before_database_write,
using the repository’s supported inline suppression syntax (such as nosemgrep)
without changing the test data or behavior.
- Around line 334-349: The tests cover query filtering but not the
tenant-boundary postcondition in _memory_fact_from_row. Add coverage for
remember_fact or forget_fact using a fake cursor row whose organization_id
differs from the expected organization, and assert that RuntimeError with
“Memory fact row violated its organization boundary” is raised.

In `@tests/unit/test_agent_web.py`:
- Around line 345-406: Add unit tests covering WebResearchClient’s
deadline_monotonic path: verify a supplied deadline clamps the effective
per-request timeout, and verify an already-expired deadline raises
WebResearchTransportError before any provider or HTTP call occurs. Reuse the
existing provider test patterns and assert the transport error and zero
provider-call behavior.

In `@tests/unit/test_diagnostics_cog.py`:
- Around line 145-157: Add a dedicated fallback test alongside
test_cog_refresh_falls_back_to_gateway_cache using discord.Forbidden from
guild.fetch_roles, then assert the 200 response, gateway_cache source, and the
permission-specific refresh_error text. Keep the existing RuntimeError test to
cover the generic exception branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 430dd4e2-8c33-4d2a-a15d-9faa92cea943

📥 Commits

Reviewing files that changed from the base of the PR and between ea1e294 and 3a4901f.

📒 Files selected for processing (57)
  • .env.example
  • apps/admin_dashboard/src/discord-diagnostics-view.test.tsx
  • apps/admin_dashboard/src/main.tsx
  • apps/admin_dashboard/src/views/discord-diagnostics-view.tsx
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/routes.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-2UypYUrJ.css
  • apps/api/src/five08/backend/static/dashboard/assets/index-CjyViga_.js
  • apps/api/src/five08/backend/static/dashboard/assets/index-DVOtKdpK.js
  • apps/api/src/five08/backend/static/dashboard/assets/index-Dkp5BMUm.css
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/agent.py
  • apps/discord_bot/src/five08/discord_bot/cogs/diagnostics.py
  • apps/discord_bot/src/five08/discord_bot/config.py
  • apps/discord_bot/src/five08/discord_bot/utils/internal_api.py
  • apps/worker/src/five08/worker/config.py
  • apps/worker/src/five08/worker/migrations/versions/20260728_0100_create_agent_memory_facts.py
  • compose.yaml
  • docs/configuration.md
  • packages/shared/src/five08/agent/__init__.py
  • packages/shared/src/five08/agent/context.py
  • packages/shared/src/five08/agent/evals.py
  • packages/shared/src/five08/agent/memory.py
  • packages/shared/src/five08/agent/models.py
  • packages/shared/src/five08/agent/orchestrator.py
  • packages/shared/src/five08/agent/planner.py
  • packages/shared/src/five08/agent/policy.py
  • packages/shared/src/five08/agent/postgres_memory.py
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/agent/web.py
  • packages/shared/src/five08/settings.py
  • tests/evals/discord-agent/fixtures/v1/context_prompt_injection_ignored_001.json
  • tests/evals/discord-agent/fixtures/v1/create_task_confirmation_001.json
  • tests/evals/discord-agent/fixtures/v1/github_issue_member_denied_001.json
  • tests/evals/discord-agent/fixtures/v1/memory_read_self_001.json
  • tests/evals/discord-agent/fixtures/v1/memory_remember_confirmation_001.json
  • tests/evals/discord-agent/fixtures/v1/missing_project_clarification_001.json
  • tests/evals/discord-agent/fixtures/v1/search_project_tasks_001.json
  • tests/evals/discord-agent/fixtures/v1/task_assign_member_denied_001.json
  • tests/evals/discord-agent/fixtures/v1/task_complete_confirmation_001.json
  • tests/evals/discord-agent/fixtures/v1/task_create_mentions_github_issue_001.json
  • tests/evals/discord-agent/fixtures/v1/task_project_prefixed_search_001.json
  • tests/evals/discord-agent/fixtures/v1/thread_followup_latest_message_001.json
  • tests/unit/test_agent_cog.py
  • tests/unit/test_agent_erp_tools.py
  • tests/unit/test_agent_gateway.py
  • tests/unit/test_agent_memory.py
  • tests/unit/test_agent_postgres_memory.py
  • tests/unit/test_agent_role_bindings.py
  • tests/unit/test_agent_web.py
  • tests/unit/test_backend_api.py
  • tests/unit/test_diagnostics_cog.py
  • tests/unit/test_internal_api.py
  • tests/unit/test_shared_settings.py
💤 Files with no reviewable changes (2)
  • apps/api/src/five08/backend/static/dashboard/assets/index-2UypYUrJ.css
  • apps/worker/src/five08/worker/config.py

Comment thread packages/shared/src/five08/agent/context.py
Comment thread packages/shared/src/five08/agent/memory.py Outdated
Comment thread packages/shared/src/five08/agent/memory.py Outdated
Comment thread packages/shared/src/five08/agent/postgres_memory.py
Comment thread packages/shared/src/five08/agent/postgres_memory.py
Comment thread packages/shared/src/five08/agent/postgres_memory.py
@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_2bd98a6f-056d-4975-8a27-368fd43a25db)

@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: 8375bcab77

ℹ️ 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 apps/api/src/five08/backend/api.py Outdated
Comment on lines +8923 to +8926
orchestrator.registry.validate_planner_action(
configured_action.tool_name,
configured_action.arguments,
)

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 Reject disallowed repositories when creating schedules

When an admin supplies a syntactically valid repository that is absent from GITHUB_DEFAULT_REPO/GITHUB_ALLOWED_REPOS, this validation succeeds because validate_planner_action checks only argument shape. The schedule is persisted as active, but every occurrence later fails in ToolRegistry._resolve_repository, producing repeated failed worker runs instead of rejecting the unusable schedule during creation. Validate the frozen repository against the configured allowlist here before persisting it.

Useful? React with 👍 / 👎.

prompt: str = Field(min_length=1, max_length=4_000)
repository: str = Field(min_length=3, max_length=256)
query: str = Field(default="", max_length=512)
state: Literal["open", "closed", "all"] = "open"

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 Omit the GitHub state qualifier when requesting all states

When an API caller creates a schedule with state: "all", that value is frozen and later passed to GitHubClient.search_issues, which emits the query qualifier state:all. GitHub issue search uses state:open or state:closed; searching both states requires omitting the qualifier, so the advertised all option produces an incorrect or empty recurring report. Normalize all to no state qualifier, or remove it from the accepted schema.

Useful? React with 👍 / 👎.

Comment on lines +9528 to +9532
delivery, delivery_status = await _post_agent_schedule_report_to_bot(
request,
schedule=schedule,
run=run,
content=report_content,

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 Make Discord report delivery idempotent by run ID

If Discord accepts the message but the API process crashes, the worker-to-API connection times out, or the subsequent database completion write fails, the run remains running/failed even though the report was posted. Once the lease expires, the worker retry reclaims the run and calls this delivery endpoint again, and the bot currently sends every request without deduplicating run_id, so one occurrence can post duplicate reports. Persist and check a delivery idempotency record keyed by the run before retryable execution.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_fdf16fc5-a727-49bc-9554-809cd0eee2b5)

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (11)
apps/worker/src/five08/worker/migrations/versions/20260728_0200_create_agent_schedules.py (1)

165-197: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

idx_agent_schedule_runs_schedule_occurrence duplicates the unique constraint's index.

uq_agent_schedule_runs_schedule_occurrence already creates a btree on (schedule_id, occurrence_at), which serves list_agent_schedule_runs. Dropping the extra index removes write amplification with no read cost.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/worker/src/five08/worker/migrations/versions/20260728_0200_create_agent_schedules.py`
around lines 165 - 197, Remove the redundant
idx_agent_schedule_runs_schedule_occurrence index creation from the migration,
while retaining the uq_agent_schedule_runs_schedule_occurrence unique constraint
to provide the required (schedule_id, occurrence_at) index.
tests/unit/test_worker_agent_schedules.py (1)

13-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the retryable branch too.

Only the 403 non-retryable path is asserted. A 500 response should raise plain RuntimeError (retryable); pinning that distinction protects the retry policy from an accidental widening of the non-retryable status set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_worker_agent_schedules.py` around lines 13 - 59, The tests
for run_agent_schedule_job cover only the 403 non-retryable response; add a test
for a 500 response that asserts plain RuntimeError is raised, preserving the
distinction between retryable server errors and
AgentScheduleRunNonRetryableError policy rejections.
packages/shared/src/five08/agent/web.py (1)

698-761: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Streamed reads can outlive the per-call deadline.

timeout on requests is a per-read (socket) timeout, not a wall-clock budget. With stream=True, a drip-feeding upstream can keep the body read alive for up to ceil(MAX_WEB_RESPONSE_BYTES / WEB_RESPONSE_CHUNK_BYTES) × timeout, bypassing the monotonic deadline that _effective_request_timeout computes. Consider re-checking the deadline inside the iter_content loop and raising WebResearchTransportError when it has passed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/agent/web.py` around lines 698 - 761, Update
_read_bounded_json_response to enforce the wall-clock deadline during streamed
reads by re-checking the request deadline inside the response.iter_content loop.
When the deadline has passed, raise WebResearchTransportError and preserve the
existing response-closing behavior in _response_json; reuse the deadline
computed by _effective_request_timeout rather than introducing a separate
timeout budget.
apps/admin_dashboard/src/agent-schedules-view.test.tsx (1)

32-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a canWrite={false} case.

The lifecycle controls are permission-gated, but only the fully-privileged render is asserted. A case verifying Pause/Run are absent (or disabled) for read-only viewers would guard the RBAC surface this PR is built around.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin_dashboard/src/agent-schedules-view.test.tsx` around lines 32 - 57,
Add a separate read-only test for AgentSchedulesView with canWrite={false},
while keeping the existing schedule setup and lifecycle callbacks. Assert that
the Pause and Run controls are absent or disabled, covering the permission-gated
RBAC behavior without changing the privileged test.
apps/discord_bot/src/five08/discord_bot/cogs/schedules.py (2)

230-254: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Role-snapshot normalization is duplicated in apps/discord_bot/src/five08/discord_bot/utils/internal_api.py (Lines 604-612).

Same decimal/dedupe loop for role_ids/roles exists in both places. Consider extracting a shared helper in the bot package so the two stay in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/discord_bot/src/five08/discord_bot/cogs/schedules.py` around lines 230 -
254, Extract the duplicated role snapshot normalization loop from `_context` and
`internal_api.py` into a shared helper in the bot package. Update both call
sites to use it, preserving positive decimal role ID filtering, deduplication,
and role-name collection behavior.

174-177: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Percent-encode schedule_id before interpolating it into the backend path.

schedule_id comes straight from user slash-command input. Characters like /, .., or whitespace are pasted unescaped into the request URL, letting a caller reshape which backend path is hit. Wrap it with urllib.parse.quote(schedule_id, safe="").

🔒 Proposed fix
+from urllib.parse import quote
@@
         response = await self._post_backend(
-            f"/agent/schedules/{schedule_id}/control",
+            f"/agent/schedules/{quote(schedule_id, safe='')}/control",
             {"context": context, "action": action.value},
         )
@@
         response = await self._post_backend(
-            f"/agent/schedules/{schedule_id}/run",
+            f"/agent/schedules/{quote(schedule_id, safe='')}/run",
             {"context": context},
         )

Also applies to: 213-216

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/discord_bot/src/five08/discord_bot/cogs/schedules.py` around lines 174 -
177, Update the schedule control request in the relevant handler to
percent-encode the user-provided schedule_id with
urllib.parse.quote(schedule_id, safe="") before interpolating it into the
/agent/schedules/{schedule_id}/control path, and apply the same change to the
additional request path noted in the comment.
tests/unit/test_backend_agent_schedules.py (1)

197-258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test name promises "only" but never exercises the non-stale case.

Only the reclaimable path is asserted. Add a companion case where started_at is recent and assert _execute_agent_schedule_run returns 409 schedule_run_already_running with claim_agent_schedule_run not called — that's the boundary that actually protects against double execution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_backend_agent_schedules.py` around lines 197 - 258, Add a
companion test for _execute_agent_schedule_run using a RUNNING run with a recent
started_at value; assert it returns HTTP 409 with “schedule_run_already_running”
and verify claim_agent_schedule_run is not called. Keep the existing stale-run
reclaim assertions unchanged.
apps/admin_dashboard/src/views/agent-schedules-view.tsx (1)

186-304: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider wrapping the create fields in a <form> with onSubmit.

Currently there's no form element, so Enter in any input does nothing and screen-reader users get no form semantics. A <form onSubmit={(e) => { e.preventDefault(); void submit() }}> with a type="submit" button keeps the same behavior and restores keyboard submission.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin_dashboard/src/views/agent-schedules-view.tsx` around lines 186 -
304, Wrap the schedule creation fields and create button in a form with an
onSubmit handler that prevents the default action and invokes submit(). Change
the Create schedule Button to type="submit", preserving the existing canSubmit
disabled state and field behavior while enabling Enter-key submission and form
semantics.
apps/api/src/five08/backend/api.py (3)

10239-10248: 🚀 Performance & Scalability | 🔵 Trivial

Note: a planner timeout does not free the thread or the bulkhead permit immediately.

asyncio.wait_for cancels the await, but the to_thread worker keeps running until orchestrator.plan returns, so the permit is released only then. Under sustained overload, callers will see 503 (capacity) rather than 504 (timeout). That is the intended bulkhead behavior; just make sure _AGENT_REQUEST_PLAN_BULKHEAD's bound is not larger than the default asyncio.to_thread executor size, otherwise the executor saturates before the bulkhead rejects.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/five08/backend/api.py` around lines 10239 - 10248, Ensure the
configured bound for _AGENT_REQUEST_PLAN_BULKHEAD does not exceed the default
asyncio.to_thread executor capacity, so the bulkhead rejects excess work before
the executor saturates. Preserve the existing asyncio.wait_for timeout and
worker/permit behavior.

9648-9667: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider auditing denied schedule-management attempts.

_agent_schedule_manager_context returns a 403 payload without emitting an audit event, so failed attempts to pause/resume/archive/run a schedule leave no trace, while successes do. A denied agent.schedule.* audit event here would make privilege-revocation and probing visible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/five08/backend/api.py` around lines 9648 - 9667, Update
_agent_schedule_manager_context to emit an audit event whenever
_agent_schedule_manager_error returns a denial, before returning the 403
payload. Record the denied agent.schedule.* management attempt with the relevant
request/context details, while preserving the existing authorization response
and success behavior.

7906-7928: 🚀 Performance & Scalability | 🔵 Trivial

Consider a server-side cooldown for ?refresh=true.

Any configuration:read session can repeatedly force a live Discord fetch through this proxy. If the diagnostics cog does not already throttle refreshes, a short cooldown (or cached-snapshot reuse) would protect the bot's Discord rate-limit budget.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/five08/backend/api.py` around lines 7906 - 7928, Update
dashboard_discord_diagnostics_handler and the associated refresh path to enforce
a short server-side cooldown for refresh=true, reusing the most recent
diagnostics snapshot while the cooldown is active. Preserve normal cached reads,
permission checks, no-store responses, and the existing 503 behavior when no
snapshot is available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 9527-9560: The agent schedule delivery flow around
_post_agent_schedule_report_to_bot must become idempotent for retries: establish
a durable pre-delivery marker or bot-side deduplication keyed by (schedule.id,
run.id) before channel.send, and have retries detect the existing delivery and
reuse it instead of posting again. Preserve the existing failure completion and
response handling for genuine delivery errors.

In `@packages/shared/src/five08/agent/schedules.py`:
- Around line 369-382: Normalize incoming schedule_id and run_id values as UUIDs
before executing database queries, treating parse failures as not found/no-op
results rather than allowing database errors. Apply this consistently in
get_agent_schedule, pause_agent_schedule, resume_agent_schedule,
archive_agent_schedule, create_manual_agent_schedule_run,
claim_agent_schedule_run, complete_agent_schedule_run,
set_agent_schedule_run_job_id, and list_agent_schedule_runs, while preserving
existing behavior for valid identifiers.
- Around line 287-291: Update the cadence validation error in the schedule
normalization flow to report the same effective minimum used by the check:
max(60, minimum_interval_seconds). Keep the enforcement condition and successful
return values unchanged.

---

Nitpick comments:
In `@apps/admin_dashboard/src/agent-schedules-view.test.tsx`:
- Around line 32-57: Add a separate read-only test for AgentSchedulesView with
canWrite={false}, while keeping the existing schedule setup and lifecycle
callbacks. Assert that the Pause and Run controls are absent or disabled,
covering the permission-gated RBAC behavior without changing the privileged
test.

In `@apps/admin_dashboard/src/views/agent-schedules-view.tsx`:
- Around line 186-304: Wrap the schedule creation fields and create button in a
form with an onSubmit handler that prevents the default action and invokes
submit(). Change the Create schedule Button to type="submit", preserving the
existing canSubmit disabled state and field behavior while enabling Enter-key
submission and form semantics.

In `@apps/api/src/five08/backend/api.py`:
- Around line 10239-10248: Ensure the configured bound for
_AGENT_REQUEST_PLAN_BULKHEAD does not exceed the default asyncio.to_thread
executor capacity, so the bulkhead rejects excess work before the executor
saturates. Preserve the existing asyncio.wait_for timeout and worker/permit
behavior.
- Around line 9648-9667: Update _agent_schedule_manager_context to emit an audit
event whenever _agent_schedule_manager_error returns a denial, before returning
the 403 payload. Record the denied agent.schedule.* management attempt with the
relevant request/context details, while preserving the existing authorization
response and success behavior.
- Around line 7906-7928: Update dashboard_discord_diagnostics_handler and the
associated refresh path to enforce a short server-side cooldown for
refresh=true, reusing the most recent diagnostics snapshot while the cooldown is
active. Preserve normal cached reads, permission checks, no-store responses, and
the existing 503 behavior when no snapshot is available.

In `@apps/discord_bot/src/five08/discord_bot/cogs/schedules.py`:
- Around line 230-254: Extract the duplicated role snapshot normalization loop
from `_context` and `internal_api.py` into a shared helper in the bot package.
Update both call sites to use it, preserving positive decimal role ID filtering,
deduplication, and role-name collection behavior.
- Around line 174-177: Update the schedule control request in the relevant
handler to percent-encode the user-provided schedule_id with
urllib.parse.quote(schedule_id, safe="") before interpolating it into the
/agent/schedules/{schedule_id}/control path, and apply the same change to the
additional request path noted in the comment.

In
`@apps/worker/src/five08/worker/migrations/versions/20260728_0200_create_agent_schedules.py`:
- Around line 165-197: Remove the redundant
idx_agent_schedule_runs_schedule_occurrence index creation from the migration,
while retaining the uq_agent_schedule_runs_schedule_occurrence unique constraint
to provide the required (schedule_id, occurrence_at) index.

In `@packages/shared/src/five08/agent/web.py`:
- Around line 698-761: Update _read_bounded_json_response to enforce the
wall-clock deadline during streamed reads by re-checking the request deadline
inside the response.iter_content loop. When the deadline has passed, raise
WebResearchTransportError and preserve the existing response-closing behavior in
_response_json; reuse the deadline computed by _effective_request_timeout rather
than introducing a separate timeout budget.

In `@tests/unit/test_backend_agent_schedules.py`:
- Around line 197-258: Add a companion test for _execute_agent_schedule_run
using a RUNNING run with a recent started_at value; assert it returns HTTP 409
with “schedule_run_already_running” and verify claim_agent_schedule_run is not
called. Keep the existing stale-run reclaim assertions unchanged.

In `@tests/unit/test_worker_agent_schedules.py`:
- Around line 13-59: The tests for run_agent_schedule_job cover only the 403
non-retryable response; add a test for a 500 response that asserts plain
RuntimeError is raised, preserving the distinction between retryable server
errors and AgentScheduleRunNonRetryableError policy rejections.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 32e20c24-4c1b-4153-ae9b-1cd450da85a6

📥 Commits

Reviewing files that changed from the base of the PR and between 3a4901f and 8375bca.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (38)
  • .env.example
  • apps/admin_dashboard/src/agent-schedules-view.test.tsx
  • apps/admin_dashboard/src/main.tsx
  • apps/admin_dashboard/src/views/agent-schedules-view.tsx
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/routes.py
  • apps/api/src/five08/backend/schemas.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-C2hkz1-S.css
  • apps/api/src/five08/backend/static/dashboard/assets/index-DvWm4SlD.js
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/schedules.py
  • apps/discord_bot/src/five08/discord_bot/utils/internal_api.py
  • apps/worker/src/five08/worker/actors.py
  • apps/worker/src/five08/worker/config.py
  • apps/worker/src/five08/worker/jobs.py
  • apps/worker/src/five08/worker/migrations/versions/20260728_0200_create_agent_schedules.py
  • compose.yaml
  • docs/configuration.md
  • packages/shared/pyproject.toml
  • packages/shared/src/five08/agent/__init__.py
  • packages/shared/src/five08/agent/policy.py
  • packages/shared/src/five08/agent/postgres_memory.py
  • packages/shared/src/five08/agent/schedules.py
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/agent/web.py
  • packages/shared/src/five08/settings.py
  • tests/unit/test_agent_postgres_memory.py
  • tests/unit/test_agent_role_bindings.py
  • tests/unit/test_agent_schedules.py
  • tests/unit/test_agent_web.py
  • tests/unit/test_backend_agent_schedules.py
  • tests/unit/test_backend_api.py
  • tests/unit/test_internal_api.py
  • tests/unit/test_shared_settings.py
  • tests/unit/test_worker_agent_schedules.py
🚧 Files skipped from review as they are similar to previous changes (11)
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • tests/unit/test_shared_settings.py
  • apps/discord_bot/README.md
  • docs/configuration.md
  • packages/shared/src/five08/agent/postgres_memory.py
  • tests/unit/test_agent_role_bindings.py
  • tests/unit/test_agent_postgres_memory.py
  • packages/shared/src/five08/agent/policy.py
  • packages/shared/src/five08/settings.py
  • packages/shared/src/five08/agent/tools.py
  • tests/unit/test_backend_api.py

Comment thread apps/api/src/five08/backend/api.py Outdated
Comment on lines +287 to +291
if (second - first).total_seconds() < max(60, minimum_interval_seconds):
raise ValueError(
f"schedule must run no more often than every {minimum_interval_seconds} seconds"
)
return normalized_cron, normalized_timezone, first

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cadence error message can misstate the enforced floor.

The check uses max(60, minimum_interval_seconds) but the message reports minimum_interval_seconds. With a configured value below 60, a rejected * * * * * schedule reports "no more often than every 30 seconds" while 60s is what was actually enforced.

🔧 Report the effective floor
-    if (second - first).total_seconds() < max(60, minimum_interval_seconds):
+    effective_minimum_seconds = max(60, minimum_interval_seconds)
+    if (second - first).total_seconds() < effective_minimum_seconds:
         raise ValueError(
-            f"schedule must run no more often than every {minimum_interval_seconds} seconds"
+            "schedule must run no more often than every "
+            f"{effective_minimum_seconds} seconds"
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (second - first).total_seconds() < max(60, minimum_interval_seconds):
raise ValueError(
f"schedule must run no more often than every {minimum_interval_seconds} seconds"
)
return normalized_cron, normalized_timezone, first
effective_minimum_seconds = max(60, minimum_interval_seconds)
if (second - first).total_seconds() < effective_minimum_seconds:
raise ValueError(
"schedule must run no more often than every "
f"{effective_minimum_seconds} seconds"
)
return normalized_cron, normalized_timezone, first
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/agent/schedules.py` around lines 287 - 291, Update
the cadence validation error in the schedule normalization flow to report the
same effective minimum used by the check: max(60, minimum_interval_seconds).
Keep the enforcement condition and successful return values unchanged.

Comment on lines +369 to +382
def get_agent_schedule(
settings: SharedSettings,
*,
schedule_id: str,
) -> AgentScheduleRecord | None:
"""Load one persisted schedule without changing its lifecycle."""

with get_postgres_connection(settings) as conn:
with conn.cursor(row_factory=dict_row) as cursor:
cursor.execute(
"SELECT * FROM agent_schedules WHERE id = %s", (schedule_id,)
)
row = cursor.fetchone()
return _as_schedule_record(row) if row is not None else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Malformed schedule_id/run_id surfaces as a database error instead of "not found".

agent_schedules.id and agent_schedule_runs.id are uuid columns (see apps/worker/src/five08/worker/migrations/versions/20260728_0200_create_agent_schedules.py), so a non-UUID string reaching these lookups raises psycopg.errors.InvalidTextRepresentation rather than returning None. Since these ids flow in from Discord commands and dashboard/API paths, parse them first and treat a parse failure as a miss.

🛡️ Normalize identifiers before querying
+def _normalized_uuid(value: str) -> str | None:
+    try:
+        return str(UUID(str(value or "").strip()))
+    except (AttributeError, TypeError, ValueError):
+        return None
+
+
 def get_agent_schedule(
     settings: SharedSettings,
     *,
     schedule_id: str,
 ) -> AgentScheduleRecord | None:
     """Load one persisted schedule without changing its lifecycle."""
 
+    normalized_schedule_id = _normalized_uuid(schedule_id)
+    if normalized_schedule_id is None:
+        return None
     with get_postgres_connection(settings) as conn:
         with conn.cursor(row_factory=dict_row) as cursor:
             cursor.execute(
-                "SELECT * FROM agent_schedules WHERE id = %s", (schedule_id,)
+                "SELECT * FROM agent_schedules WHERE id = %s",
+                (normalized_schedule_id,),
             )

The same normalization applies to pause_agent_schedule, resume_agent_schedule, archive_agent_schedule, create_manual_agent_schedule_run, claim_agent_schedule_run, complete_agent_schedule_run, set_agent_schedule_run_job_id, and list_agent_schedule_runs.

Also applies to: 684-695

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/agent/schedules.py` around lines 369 - 382,
Normalize incoming schedule_id and run_id values as UUIDs before executing
database queries, treating parse failures as not found/no-op results rather than
allowing database errors. Apply this consistently in get_agent_schedule,
pause_agent_schedule, resume_agent_schedule, archive_agent_schedule,
create_manual_agent_schedule_run, claim_agent_schedule_run,
complete_agent_schedule_run, set_agent_schedule_run_job_id, and
list_agent_schedule_runs, while preserving existing behavior for valid
identifiers.

@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: 91e99d2fbf

ℹ️ 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".

timezone: str = Field(default="UTC", min_length=1, max_length=128)
prompt: str = Field(min_length=1, max_length=4_000)
repository: str = Field(min_length=3, max_length=256)
query: str = Field(default="", max_length=512)

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 Reject repository qualifiers in scheduled queries

When a schedule query contains Boolean repository qualifiers such as OR repo:other/private-repo, GitHubClient.search_issues appends it verbatim after the approved repo:<repository> qualifier. This can widen the search beyond the frozen repository and expose metadata from any additional repository accessible to the API token, potentially including it in Discord delivery or a public-data model summary. Reject scope-changing qualifiers/operators or construct the query so the configured repository constraint applies to the entire expression.

Useful? React with 👍 / 👎.

query: str = Field(default="", max_length=512)
state: Literal["open", "closed", "all"] = "open"
limit: int = Field(default=10, ge=1, le=20)
channel_id: str = Field(min_length=1, max_length=32)

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 Validate dashboard delivery channels before scheduling

In the dashboard creation flow, channel_id is accepted as arbitrary text and converted only to a positive snowflake; unlike the Discord command's typed TextChannel, nothing verifies that it exists in the configured guild or that the bot can post there. A mistyped, deleted, or non-messageable channel therefore creates an active schedule whose every occurrence reaches _post_agent_schedule_report_to_bot and fails, so validate the destination through the bot before persisting the schedule.

Useful? React with 👍 / 👎.

Comment on lines +919 to +924
return self._response_for_actions(
actions=next_actions,
context=context,
planning_text=planning_text,
planner="live_model",
model=result.model,

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 Route follow-up web actions deterministically

After a public search, the result snippets are untrusted web content, but the planner's next drafted action is passed directly back into _response_for_actions; the subsequent checks constrain tool shape and scope but do not establish that the user selected the new query or result. A prompt-injected search result can therefore steer an additional outbound search or extraction request. Select follow-up actions in code or require the user to choose the next result instead of using the LLM for this routing decision.

AGENTS.md reference: AGENTS.md:L130-L130

Useful? React with 👍 / 👎.

Comment on lines +526 to +527
occurrence_at = _utc_datetime(now or datetime.now(tz=timezone.utc))
run_id = str(uuid4())

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 Make manual schedule runs idempotent

Every invocation generates a new run ID and timestamp, so the database uniqueness constraint does not deduplicate retries of the same Run now request. If the dashboard or bot loses the response after this insert, or an administrator submits twice, the retry creates a second worker job and a second Discord report; it also bypasses the configured minimum schedule interval and can multiply provider cost. Carry an interaction/request idempotency key into this insert or enforce a per-schedule manual-run cooldown.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_275d0a7c-ee00-4983-a527-8af4a4fa505c)

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/schedules.py (1)

27-172: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting shared boilerplate between /schedule-github-issues and /schedule-agent.

Both commands repeat the same defer → _context guild check → channel-guild check → backend post → status/shape error handling sequence almost verbatim. With a second command now following this pattern, extracting a shared helper (e.g. _guard_and_post(interaction, payload, path, success_message_fn)) would reduce duplication and keep future schedule commands consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/discord_bot/src/five08/discord_bot/cogs/schedules.py` around lines 27 -
172, Extract the duplicated defer, context validation, channel-guild validation,
backend posting, response-status handling, and schedule-shape validation from
schedule_github_issues_command and schedule_agent_command into a shared helper
such as _guard_and_post. Have each command provide its payload and
success-message construction while preserving the existing user-facing messages
and response behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/schedules.py`:
- Around line 27-172: Extract the duplicated defer, context validation,
channel-guild validation, backend posting, response-status handling, and
schedule-shape validation from schedule_github_issues_command and
schedule_agent_command into a shared helper such as _guard_and_post. Have each
command provide its payload and success-message construction while preserving
the existing user-facing messages and response behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f349518c-13d4-4eec-8f8d-9823ae335975

📥 Commits

Reviewing files that changed from the base of the PR and between 8375bca and 6890a6f.

📒 Files selected for processing (27)
  • .env.example
  • apps/admin_dashboard/src/agent-schedules-view.test.tsx
  • apps/admin_dashboard/src/views/agent-schedules-view.tsx
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/schemas.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-BZXfv05L.css
  • apps/api/src/five08/backend/static/dashboard/assets/index-Buji_yBE.js
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/schedules.py
  • docs/configuration.md
  • packages/shared/src/five08/agent/__init__.py
  • packages/shared/src/five08/agent/context.py
  • packages/shared/src/five08/agent/memory.py
  • packages/shared/src/five08/agent/planner.py
  • packages/shared/src/five08/agent/postgres_memory.py
  • packages/shared/src/five08/agent/schedules.py
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/settings.py
  • tests/unit/test_agent_erp_tools.py
  • tests/unit/test_agent_gateway.py
  • tests/unit/test_agent_memory.py
  • tests/unit/test_agent_postgres_memory.py
  • tests/unit/test_agent_schedules.py
  • tests/unit/test_backend_agent_schedules.py
🚧 Files skipped from review as they are similar to previous changes (13)
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/api/README.md
  • apps/discord_bot/README.md
  • tests/unit/test_agent_schedules.py
  • .env.example
  • packages/shared/src/five08/settings.py
  • packages/shared/src/five08/agent/planner.py
  • docs/configuration.md
  • apps/api/src/five08/backend/schemas.py
  • tests/unit/test_agent_postgres_memory.py
  • tests/unit/test_agent_gateway.py
  • packages/shared/src/five08/agent/memory.py
  • packages/shared/src/five08/agent/tools.py

@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: 6890a6f2aa

ℹ️ 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".

normalized_timezone,
after=first,
)
if (second - first).total_seconds() < max(60, minimum_interval_seconds):

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 Validate minimum spacing across the full cron cycle

When AGENT_SCHEDULE_MIN_INTERVAL_SECONDS exceeds one hour, checking only the first two occurrences can accept schedules that later violate the configured minimum. For example, with a 7,200-second minimum, creating 0 0,1 * * * at 00:30 compares 01:00 to the following midnight and passes, but that midnight run is followed by another at 01:00, only one hour later. Validate enough successive occurrences to cover the cron cycle so the configured cost/rate bound is actually enforced.

Useful? React with 👍 / 👎.

SELECT *
FROM agent_schedule_runs
WHERE status = 'queued'
AND job_id IS 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.

P1 Badge Recover runs attached to undelivered or dead jobs

Filtering out every run with a job_id permanently strands occurrences whose job record exists but cannot execute. If enqueue_job commits its Postgres row and Redis delivery then raises, the next dispatcher pass reuses the idempotent job, does not call queue.enqueue because it was not newly created, attaches that job ID, and excludes the run forever; the same stuck state occurs when a successfully delivered job exhausts retries before reaching the API, because the job becomes dead while the schedule run remains queued. Reconcile attached job status or explicitly redeliver reusable queued jobs instead of treating any non-null ID as proof of live delivery.

AGENTS.md reference: AGENTS.md:L95-L96

Useful? React with 👍 / 👎.

Comment on lines +9363 to +9365
if status == "answer":
answer = str(getattr(draft, "answer", "") or "").strip()
return _AgentScheduleLoopOutcome(results=results, answer=answer or None)

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 Reject unsourced answers before any scheduled tool runs

On the first agent-loop step, the planner has received only the saved prompt and no tool observations, yet a schema-valid status: "answer" is accepted immediately and later delivered as a successful recurring report. If the model answers instead of selecting a tool, it can therefore publish a fabricated onboarding, CRM, ERP, or billing report without reading any source. Accept an answer only after at least one successful scheduled observation, and otherwise reject or require a tool plan in code.

AGENTS.md reference: AGENTS.md:L130-L130

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_a480f08e-440d-4a1b-ae26-4374143220f3)

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
tests/unit/test_agent_cog.py (1)

33-42: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Avoid enabling name-based authorization for every test in this module.

The autouse fixture enables the local role-name fallback globally, and the new admin test passes role_ids=[]. This verifies "Admin" name fallback rather than the production role-ID plus guild-bound path. Scope the fallback to only legacy tests and add the admin assertion with a configured role ID; otherwise role-ID authorization regressions can remain green.

Also applies to: 152-161

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_agent_cog.py` around lines 33 - 42, Remove the autouse
behavior from _enable_explicit_local_role_name_fallback so name-based
authorization is not enabled for every test in the module. Apply the fallback
fixture only to legacy role-name tests, and update the admin authorization test
to configure and assert against a non-empty role ID while preserving the
guild-bound authorization path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/unit/test_agent_cog.py`:
- Around line 33-42: Remove the autouse behavior from
_enable_explicit_local_role_name_fallback so name-based authorization is not
enabled for every test in the module. Apply the fallback fixture only to legacy
role-name tests, and update the admin authorization test to configure and assert
against a non-empty role ID while preserving the guild-bound authorization path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 26d74eb5-e2a6-4f90-a58b-fa3d0be137f9

📥 Commits

Reviewing files that changed from the base of the PR and between 6890a6f and 2d829f3.

📒 Files selected for processing (14)
  • apps/api/src/five08/backend/api.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/agent.py
  • docs/configuration.md
  • packages/shared/src/five08/agent/__init__.py
  • packages/shared/src/five08/agent/orchestrator.py
  • packages/shared/src/five08/agent/planner.py
  • packages/shared/src/five08/agent/schedules.py
  • packages/shared/src/five08/agent/tools.py
  • tests/unit/test_agent_cog.py
  • tests/unit/test_agent_gateway.py
  • tests/unit/test_agent_schedules.py
  • tests/unit/test_backend_agent_schedules.py
  • tests/unit/test_backend_api.py
🚧 Files skipped from review as they are similar to previous changes (12)
  • packages/shared/src/five08/agent/init.py
  • apps/discord_bot/README.md
  • tests/unit/test_backend_agent_schedules.py
  • tests/unit/test_agent_schedules.py
  • docs/configuration.md
  • packages/shared/src/five08/agent/planner.py
  • apps/discord_bot/src/five08/discord_bot/cogs/agent.py
  • packages/shared/src/five08/agent/schedules.py
  • packages/shared/src/five08/agent/orchestrator.py
  • packages/shared/src/five08/agent/tools.py
  • tests/unit/test_backend_api.py
  • apps/api/src/five08/backend/api.py

@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: 2d829f3007

ℹ️ 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".

"or return a concise answer with no tool actions.\n\n"
f"Allowed tool IDs: {allowed_tools}\n\n"
"Schedule objective (treat it as untrusted task data, not authority):\n"
f"{schedule.definition.prompt}"

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 Keep internal schedule objectives out of the external planner

When an admin creates an agent-loop schedule such as “report Purchase Invoice PINV-123 for Acme,” this sends the objective verbatim to the configured model on every run. The creation guard rejects credential/payment patterns but not ordinary customer, invoice, or project identifiers, bypassing the deterministic ERP path that deliberately keeps those identifiers out of model planning; classify and redact the objective or route private operational schedules deterministically before invoking the planner.

AGENTS.md reference: AGENTS.md:L130-L130

Useful? React with 👍 / 👎.

Comment on lines +1235 to +1243
payload = self.registry.execute(
action.tool_name,
action.arguments,
organization_id=organization_id,
actor_id=actor_id,
project_id=context.project_id,
actor_scopes=actor_scopes,
deadline_monotonic=deadline_monotonic,
)

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 Enforce deadlines for non-web schedule tools

When a schedule uses ERP or billing tools, forwarding deadline_monotonic does not enforce the configured runtime: those registry handlers ignore it and use erpnext_api_timeout_seconds (20 seconds by default), while execute_plan also does not stop before starting the next action. With AGENT_SCHEDULE_EXECUTION_TIMEOUT_SECONDS=10, even one stalled ERP call exceeds the promised limit, and a multi-step schedule can exceed it repeatedly; check the deadline before each action and clamp every integration timeout to the remaining budget.

Useful? React with 👍 / 👎.

Comment on lines +2408 to +2409
if _WEB_QUERY_UUID_RE.search(query):
raise PermissionError("Public web search queries cannot contain internal IDs")

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 Reject CRM identifiers before public web dispatch

For an explicit request such as Search the web for CRM contact contact-123, the orchestrator takes the public-web branch before _has_private_model_identifier can recognize the contact reference, and this validator does not reject the repository's contact-<id> format. The internal CRM identifier is therefore sent to SearXNG, Brave, or Firecrawl despite the private-identifier boundary; apply the same contact/task/ERP identifier checks to outbound web queries before calling a provider.

Useful? React with 👍 / 👎.

Comment on lines +78 to +79
"octet_length(value_json::text) <= 8192",
name="ck_agent_memory_facts_value_json_size",

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 Align the JSONB size constraint with model validation

Memory validation measures compact JSON produced with separators=(",", ":"), while this constraint measures PostgreSQL's spaced jsonb::text representation. A valid four-field object with four 2,040-character strings is 8,189 bytes under the model check but 8,196 bytes as JSONB text, so a confirmed memory write accepted by Pydantic fails at insertion; measure the same representation on both sides or leave enough database headroom for JSONB formatting.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_67d64d9c-f2b7-4ddc-8bf4-ea6aa6544dc2)

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/shared/src/five08/agent/schedules.py (1)

882-915: 🩺 Stability & Availability | 🔵 Trivial

Delivery can get permanently stuck in claimed with no recovery path.

claim_agent_schedule_run_delivery intentionally never auto-retries once claimed The claim is intentionally durable and is never retried automatically once an outcome becomes ambiguous. But if the process crashes right after the claim succeeds and before the Discord call is even attempted (not merely "ambiguous"), there is no equivalent of the run-status reclaim_running_before lease mechanism for delivery_status. That run's report can never be posted, and no other code path here resolves claimed to posted/unknown.

Consider whether the dispatcher/diagnostics surfaces should expose "stuck claimed for > N minutes" so an operator can manually resolve it (e.g., via mark_agent_schedule_run_delivery_unknown).

Also applies to: 951-977

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/agent/schedules.py` around lines 882 - 915, Update
the delivery diagnostics/dispatcher flow around
claim_agent_schedule_run_delivery to detect delivery_status CLAIMED entries
older than the configured threshold. Expose these stuck claims for operator
inspection and provide the existing mark_agent_schedule_run_delivery_unknown
action to resolve them manually, preserving the current no-automatic-retry
behavior and at-most-once delivery contract.
apps/api/src/five08/backend/static/dashboard/assets/index-Cp42DTyJ.js (1)

1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Avoid committing generated dashboard build artifacts. Both files are minified, generated Vite/Tailwind build output rather than authored source; the static-analysis findings inside them originate from vendored library/Tailwind preflight internals, not first-party logic.

  • apps/api/src/five08/backend/static/dashboard/assets/index-Cp42DTyJ.js: generated React/app JS bundle — build this in CI/CD at deploy time instead of committing it.
  • apps/api/src/five08/backend/static/dashboard/assets/index-DhXOZXBT.css: generated Tailwind CSS bundle — same recommendation, build via CI/CD rather than checking in.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/five08/backend/static/dashboard/assets/index-Cp42DTyJ.js` around
lines 1 - 11, Remove the generated dashboard build artifacts from version
control and configure deployment CI/CD to build them instead:
apps/api/src/five08/backend/static/dashboard/assets/index-Cp42DTyJ.js (lines
1-11) and apps/api/src/five08/backend/static/dashboard/assets/index-DhXOZXBT.css
(lines 1-2). Do not modify authored React or Tailwind source; ensure the
deployment process produces these assets when needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/shared/src/five08/queue.py`:
- Around line 417-424: Make replay handling idempotent by job ID across get_job,
queue.enqueue, and DramatiqQueueClient.enqueue, ensuring a QUEUED replay cannot
create a duplicate delivery or worker execution after successful initial
delivery. Prefer an atomic database/outbox transition, or enforce an idempotent
worker claim before execution in _run_job, while preserving terminal-job
behavior. Add a regression test covering a second retry after the initial
delivery succeeds.

---

Nitpick comments:
In `@apps/api/src/five08/backend/static/dashboard/assets/index-Cp42DTyJ.js`:
- Around line 1-11: Remove the generated dashboard build artifacts from version
control and configure deployment CI/CD to build them instead:
apps/api/src/five08/backend/static/dashboard/assets/index-Cp42DTyJ.js (lines
1-11) and apps/api/src/five08/backend/static/dashboard/assets/index-DhXOZXBT.css
(lines 1-2). Do not modify authored React or Tailwind source; ensure the
deployment process produces these assets when needed.

In `@packages/shared/src/five08/agent/schedules.py`:
- Around line 882-915: Update the delivery diagnostics/dispatcher flow around
claim_agent_schedule_run_delivery to detect delivery_status CLAIMED entries
older than the configured threshold. Expose these stuck claims for operator
inspection and provide the existing mark_agent_schedule_run_delivery_unknown
action to resolve them manually, preserving the current no-automatic-retry
behavior and at-most-once delivery contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 715002cd-b8c0-43aa-bbd5-df62935809c1

📥 Commits

Reviewing files that changed from the base of the PR and between 2d829f3 and 05a6d44.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • .env.example
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-Cp42DTyJ.js
  • apps/api/src/five08/backend/static/dashboard/assets/index-DhXOZXBT.css
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/agent.py
  • apps/worker/src/five08/worker/migrations/versions/20260729_0100_add_agent_schedule_delivery_idempotency.py
  • compose.yaml
  • docs/configuration.md
  • docs/discord-github-todos.md
  • packages/shared/pyproject.toml
  • packages/shared/src/five08/agent/__init__.py
  • packages/shared/src/five08/agent/evals.py
  • packages/shared/src/five08/agent/orchestrator.py
  • packages/shared/src/five08/agent/planner.py
  • packages/shared/src/five08/agent/policy.py
  • packages/shared/src/five08/agent/schedules.py
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/queue.py
  • packages/shared/src/five08/settings.py
  • tests/evals/discord-agent/fixtures/v1/github_todo_member_confirmation_001.json
  • tests/unit/test_agent_gateway.py
  • tests/unit/test_backend_agent_schedules.py
  • tests/unit/test_shared_queue.py
  • tests/unit/test_shared_settings.py
🚧 Files skipped from review as they are similar to previous changes (16)
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • packages/shared/pyproject.toml
  • packages/shared/src/five08/agent/init.py
  • compose.yaml
  • tests/unit/test_shared_settings.py
  • apps/discord_bot/README.md
  • packages/shared/src/five08/agent/planner.py
  • docs/configuration.md
  • packages/shared/src/five08/settings.py
  • packages/shared/src/five08/agent/policy.py
  • apps/discord_bot/src/five08/discord_bot/cogs/agent.py
  • packages/shared/src/five08/agent/orchestrator.py
  • apps/api/src/five08/backend/api.py
  • packages/shared/src/five08/agent/tools.py
  • tests/unit/test_agent_gateway.py

Comment thread packages/shared/src/five08/queue.py

@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: 05a6d44e2c

ℹ️ 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 on lines +9282 to +9285
if tool_name == "web_read.extract" and not _schedule_extract_from_prior_search(
arguments,
prior_results,
):

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 Keep scheduled follow-up searches deterministic

When an agent-loop schedule first executes web_read.search, its untrusted result titles and snippets are passed to plan_with_observations, but this validation constrains only web_read.extract; a model-proposed web_read.search with an arbitrary new query is accepted. A prompt-injected result can therefore steer another outbound search that the saved objective never selected. Fresh evidence is the schedule-loop validator here, which remains permissive even though the interactive web loop now rejects model-routed follow-ups; stop after the initial search or select any follow-up query deterministically.

AGENTS.md reference: AGENTS.md:L130-L130

Useful? React with 👍 / 👎.

Comment on lines +422 to +424
existing = get_job(settings, job_id)
if existing is not None and existing.status is JobStatus.QUEUED:
queue.enqueue(job_id, run_at=existing.run_after)

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 Avoid redelivering already queued idempotent jobs

Whenever the same idempotency key is submitted while its first job is still queued—for example, two API replicas running the same scheduler or a webhook retry before a worker claims the job—this path puts the same job ID onto Redis again. The worker's read-then-mark_job_running sequence is not an atomic conditional claim, and every worker uses the same default worker_name, so two deliveries can both enter a side-effectful handler and defeat the database idempotency record. Track broker delivery durably or atomically claim the Postgres row rather than interpreting queued as proof that delivery was lost.

AGENTS.md reference: AGENTS.md:L95-L96

Useful? React with 👍 / 👎.

Comment on lines +10346 to +10350
if delivery_status >= 400:
# A failed bot response can arrive after Discord accepted the message.
# Do not retry this run's external side effect without a durable bot
# idempotency key.
await asyncio.to_thread(

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 Retry bot failures that occur before message delivery

All bot error responses are treated as ambiguous delivery and converted into a successful HTTP response to the worker. For errors such as channel_lookup_failed or missing_channel_send_permission, the bot returns before calling channel.send, so delivery is definitively absent; nevertheless the run becomes permanently failed, the worker marks its job successful, and that scheduled occurrence is never retried after the transient lookup or permission problem is repaired. Classify pre-send failures separately and leave them retryable, reserving unknown for failures whose send outcome is genuinely ambiguous.

Useful? React with 👍 / 👎.

Comment on lines +738 to +748
for chunk in response.iter_content(chunk_size=WEB_RESPONSE_CHUNK_BYTES):
if not chunk:
continue
if not isinstance(chunk, bytes):
raise WebResearchResponseError(
f"{provider} response payload contained non-binary data."
)
total_bytes += len(chunk)
if total_bytes > MAX_WEB_RESPONSE_BYTES:
raise WebResearchResponseError(
f"{provider} response payload is too large."

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 Enforce the public-web deadline while streaming

When a web provider begins its response in time but trickles data with each socket read arriving before the Requests read timeout, this loop can continue past deadline_monotonic because the deadline is checked only when constructing the initial request timeout. The response may take arbitrarily long to reach the 1 MiB cap, leaving timed-out API work running in a bulkhead thread and allowing scheduled web tools to exceed their execution budget. Recheck the monotonic deadline during iteration and close the response once it expires.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_33a6af01-5731-4f0e-ab8e-21842a4e1299)

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_72a02e73-9afc-451e-899d-74cadd20dd67)

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e0b58cbb-3b46-459a-9673-0f754c25fedf)

@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: 1bca150aa6

ℹ️ 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".

last_error = NULL,
updated_at = NOW()
WHERE id = %s
AND status IN (%s, %s)

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 Reclaim jobs left running after worker loss

When a worker terminates after claim_job_for_execution commits but before _run_job writes a retry or terminal status, the durable row remains running. Any Dramatiq redelivery calls this update again, but running is excluded, so the actor treats the job as no longer claimable; there is no stale-running reconciler, and job_timeout_seconds is not used here. This permanently strands the job, so reclaim expired running leases with ownership fencing or a heartbeat rather than accepting only queued and failed rows.

AGENTS.md reference: AGENTS.md:L95-L96

Useful? React with 👍 / 👎.

AGENT_SCHEDULE_DEFINITION_VERSION = 1
AGENT_SCHEDULE_ALLOWED_TOOL_NAMES = frozenset(
{
"github_issue.search_issues",

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 Opt GitHub search into agent-loop schedules

When /schedule-agent, the dashboard form, or a confirmed schedule proposal omits tool_allowlist, _agent_schedule_definition_from_fields expands it to this entire set. This entry then reaches _validate_agent_schedule_envelope, which requires every agent-loop manifest to have schedule_safe=True, but github_issue.search_issues retains the default False. Consequently every default generic schedule is rejected as scheduled_actions_must_be_read_only before persistence, and explicitly GitHub-enabled loop schedules fail too; mark the manifest schedule-safe or derive the default catalog from the registry's opted-in tools.

Useful? React with 👍 / 👎.

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