Replace absolute local filesystem markdown links with repository-relative targets and drop local :line suffixes so links resolve consistently across environments.
9.2 KiB
AGENT.md
This file is a restart brief for future agent sessions on this repository.
Project Identity
This repository supports a legacy, business-critical Redmine 3.4.4 installation. The real purpose is to make CRM/helpdesk communication data searchable and automatable without a risky near-term platform migration.
The LAN copy used for testing is:
http://192.168.50.170/
This is not a greenfield app. Treat it as a local-fork-and-integration project around an existing Redmine install and old RedmineUP plugins.
Tracked local plugin source lives in plugins/. The full redmine-copy/ tree
is ignored and should be treated as a working/reference copy, not the source of
truth for local plugin changes.
Original Motivation
The business stores most external communication inside Redmine through:
redmine_contactsredmine_contacts_helpdesk
The default UI/API is not good enough for:
- efficient contact search
- light contact maintenance
- helpdesk message search
- customer communication timeline lookup
- future semantic/vector search
The key realization from prior work:
Helpdesk-created issues may have Anonymous issue authors.
The real customer identity lives in helpdesk_tickets, journal_messages, and contacts.
Never design the search/index layer as if issues.author were enough.
High-Level Architecture Decision
The intended architecture is:
- Redmine writes local outbox rows.
- External worker reads those rows.
- Worker enriches from read-only MySQL joins.
- Worker builds ticket/message documents.
- External index stores/searches those documents.
Safety rule:
External failures must not break Redmine saves.
That is why the event boundary is local DB outbox, not request-time webhooks or embedding calls.
Redmine And Plugin Baseline
- Redmine version:
3.4.4 - Old RedmineUP plugin versions in use:
redmine_contacts4.1.2 PROredmine_contacts_helpdesk3.0.9 PRO
- Near-term upgrade to newer Redmine/RedmineUP is not the current goal.
- Treat these plugins as local legacy code when necessary.
Repository Landmarks
Top-level docs:
- README.md
- docs/event_outbox_spec.md
- docs/redmineup_local_fork_changelog.md
- docs/pre_existing_issues.md
Main scripts:
Local Redmine copy:
Important local plugin paths:
What Has Already Been Done
Contact CLI
redmine_contacts.py exists and supports:
- contact fetch to local cache
- fuzzy-ish contact search
- light contact updates
- helpdesk read API calls:
- ticket by issue
- issues by contact
- messages by issue
- contact timeline
Event Outbox Plugin
redmine_event_outbox exists and the known-good archive is:
dist/redmine_event_outbox-0.0.1-known-good-20260421T143957Z.tar.gz
Known-good tested LAN events:
issue.createdissue.updatedjournal.createdcontact.createdcontact.updated
Local code also adds optional helpdesk events:
helpdesk_ticket.createdhelpdesk_ticket.updatedjournal_message.createdjournal_message.updated
These helpdesk event paths are implemented locally but should still be treated as needing fuller end-to-end validation.
Helpdesk Read API In Local Plugin Fork
The local redmine_contacts_helpdesk fork includes:
helpdesk_search_controller.rb- routes under
/helpdesk_search/* - alias/usage routes to avoid noisy routing errors
This was deployed to the LAN copy and route-loaded successfully.
Helpdesk Export/Search CLI
redmine_helpdesk_search.py was created to prove the data model and export
path. It:
- SSHes to the LAN Redmine host
- reads remote MySQL credentials from
config/database.yml - runs read-only MySQL queries
- exports ticket/message docs to local JSONL cache
- provides rough local search/timeline/issues-by-contact commands
Important:
This script is a diagnostic/export tool, not the final search architecture.
Do not get dragged into treating local CLI search speed as the core mission.
External Outbox Worker Prototype
redmine_outbox_worker.py now exists as the first worker/indexer boundary.
It:
- reads and optionally claims pending
event_outbox_eventsover SSH/MySQL - supports
--dry-runfor non-mutating previews - uses
locked_at,locked_by,processed_at,attempts, andlast_error - enriches helpdesk ticket/message/contact events with read-only joins
- writes derived JSONL to
/tmp/redmine-outbox/derived_documents.jsonl - marks rows processed only after a successful local write
This is still a prototype output target, not the final vector index.
LAN Host Facts
These were verified previously:
- SSH host/user:
reddev@192.168.50.170 - SSH key used previously:
/tmp/reddev - remote Redmine path:
/usr/share/redmine - remote Ruby:
2.5.1 - remote Bundler:
1.17.3 - remote DB: MySQL 5.7
- remote plugin rollback archives were stored in:
/home/reddev/redmine-plugin-backups/
If using those exact credentials again, verify they still exist before relying on them.
Remote Plugin Changes Previously Deployed
Changes were copied to the LAN Redmine host for:
plugins/redmine_event_outbox/...plugins/redmine_contacts_helpdesk/...
Passenger was restarted with:
touch /usr/share/redmine/tmp/restart.txt
Routes were verified by remote rake routes.
Fork Hygiene Rules
Before editing RedmineUP plugin code:
- create a rollback archive in
dist/ - record the change in:
docs/redmineup_local_fork_changelog.md- plugin-local changelog if appropriate
- keep edits scoped
- prefer read-only APIs/endpoints first
- validate on the LAN copy before claiming the change is done
Existing rollback archives:
dist/redmine_contacts-4.1.2-local-before-helpdesk-search-20260421T215548Z.tar.gzdist/redmine_contacts_helpdesk-3.0.9-local-before-helpdesk-search-20260421T215548Z.tar.gz
Important Pre-Existing Issues
Read:
Especially remember:
- duplicate
id="avatar"is likely a real long-standing UI bug attachments/contacts_thumbnaildigest warning is probably log noiseacts_as_listwarning is an upgrade-compatibility note, not a current blocker
Do not confuse those with the main search deliverable unless they directly block the work in front of you.
Current Strategic State
The project is past the "can we get data out?" phase.
We now know:
- contact JSON access exists
- helpdesk/customer identity can be extracted
- outbox events can be recorded safely
- helpdesk read API routes can be added locally
- full ticket/message export is feasible
The most useful next step is:
Build the external worker/indexer pipeline.
Not:
- polishing a local CLI into a full search product
- building a big Redmine admin UI
- chasing unrelated legacy plugin cleanup
Recommended Next Steps For A Future Session
- Re-read:
README.mddocs/event_outbox_spec.mddocs/redmineup_local_fork_changelog.mddocs/pre_existing_issues.md
- Verify local and remote state:
- plugin files present
- LAN host reachable
- remote routes still load
- Validate
redmine_outbox_worker.pyend to end against the LAN copy - Refine read-only joins that enrich:
helpdesk_ticketsjournal_messagescontactsissuesjournals
- Confirm deterministic ticket/message/contact JSONL output
- Only after that, connect Qdrant and embeddings
Query/Data Model Notes
Authoritative helpdesk context lives in:
helpdesk_ticketsissue_idcontact_idfrom_addressto_addresscc_addressmessage_idticket_datesource
journal_messagesjournal_idcontact_idfrom_addressto_addresscc_addressbcc_addressmessage_idis_incomingmessage_date
contactsissuesjournals
The worker/indexer should prefer direct DB joins as the source of truth for this layer.
What To Be Careful About
- Do not assume Redmine core issue API data is enough for helpdesk search.
- Do not treat old RedmineUP code as untouchable vendor code.
- Do not claim a plugin change is validated just because
ruby -cpasses. - Do not burn time optimizing local CLI search if the real goal is external indexing.
- Do not revert unrelated repo changes; the Redmine tree may be dirty.
If You Need A One-Paragraph Summary
This project exists to safely extract and search real CRM/helpdesk communication history from a legacy Redmine 3.4.4 install. We already have a contact CLI, a local event outbox plugin, local helpdesk read API routes, and a rough helpdesk-export CLI. The next meaningful milestone is an external worker that consumes outbox rows, enriches from helpdesk/contact MySQL tables, and builds deterministic ticket/message documents for a future Qdrant/OpenAI-backed search index.