Skip to content

feat(plugins): PC-2 — a hook bus and the core scheduler - #544

Open
jwvanderstam wants to merge 1 commit into
mainfrom
feat/pc2-hook-bus
Open

jwvanderstam wants to merge 1 commit into
mainfrom
feat/pc2-hook-bus

Conversation

@jwvanderstam

Copy link
Copy Markdown
Owner

PC-2 of the plugin contract (.claude/rules/plugins.md, ROADMAP sprint 10). The core announces three events; a plugin names a hook in PLUGIN_HOOKS and defines a function of that name.

Hook Emitted from Payload
on_document_ingested DocumentProcessor.ingest_document() document_id, filename, workspace_id, chunks, replaced, source_id
on_scheduler_tick new core Scheduler, every SCHEDULER_TICK_SECONDS (3600) tick, at
on_tool_invocation the tool loop, src/tools/executor.py, permitted tools only tool, source

Decided by the maintainer (2026-11-13)

  • emit() is synchronous; the tool loop calls it through asyncio.to_thread.
  • on_route_mount is deferred: plugins load in bootstrap_app(), after create_app() has mounted the routers.
  • One daemon scheduler thread; the weekly reranker fine-tune is its first subscriber and replaces the threading.Timer (monotonic clock, first tick a week after the last attempt).

Departures from the spec, recorded in ROADMAP

  • Ingest is emitted in the processor only: SyncWorker ingests through it, so emitting there too would announce one document twice.
  • Tool invocation comes from the executor, not src/agent/tool_router.py, which dispatches the aggregator's retrieval rather than tool calls. Arguments are left out: they carry the user's question.

Behaviour

  • A raising subscriber is logged and skipped; each subscriber gets its own payload copy.
  • The loader subscribes after setup() succeeds, unsubscribes on unload, and leaves one subscription across a reload.
  • Upgrading: a manifest naming on_route_mount, or a hook without its function, now disables that plugin.

Verification

  • Locally: ruff, mypy (src.plugins.hooks in the strict list) and lint-imports are clean. tests/unit/test_hook_bus.py and the rewritten reranker scheduler tests pass, along with the docs, config and import-cycle guard tests.
  • Local full fast suite (Python 3.14, -x): 3078 passed and 1 failed, test_request_and_ooxml_limits.py::...test_the_application_mounts_it_at_max_content_length_exempting_only_upload, with SystemExit from validate_secrets(). It passes when run alone, so it looks order-dependent. This CI run on 3.12 is meant to settle whether it reproduces here.
  • unit-tests will fail pip-audit on dnspython until fix(deps): dnspython 2.9.2 and opentelemetry-api 1.45.2 — two CVEs #543 is on main; that failure is not caused by this change.

Docs updated: plugins rule, plugins/README.md (Hooks section), CLAUDE.md, ROADMAP, file-map, CONFIGURATION, CHANGELOG.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings November 14, 2026 08:18

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

The core now announces three events a plugin can react to. A plugin names a hook
in PLUGIN_HOOKS and defines a function of that name; the loader subscribes it
after setup succeeds and unsubscribes it on unload or reload.

- on_document_ingested from DocumentProcessor.ingest_document(), the one path
  uploads and connectors share (not also in SyncWorker, which would announce a
  document twice).
- on_scheduler_tick from one daemon thread, every SCHEDULER_TICK_SECONDS.
- on_tool_invocation from the tool loop for a permitted tool: name and source,
  never its arguments; emitted through asyncio.to_thread.

emit() is synchronous; a subscriber that raises is logged and skipped, and each
gets its own copy of the payload. on_route_mount is deferred: plugins load after
create_app() has assembled the routers. A hook named without its function is a
malformed manifest.

The weekly reranker fine-tune is the scheduler's first subscriber and replaces
its threading.Timer: it runs on the first tick a week after its last attempt,
on a monotonic clock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 14, 2026 10:47

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@sonarqubecloud

Copy link
Copy Markdown

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.

2 participants