212 lines
7.7 KiB
Markdown
212 lines
7.7 KiB
Markdown
# Contact Triage Loop Workflow (Vision + Operating Pattern)
|
|
|
|
## Why this exists
|
|
|
|
This document captures Jason's intended **repeatable contact-review loop** so work can proceed in deterministic batches without reprocessing the same contacts immediately.
|
|
|
|
Captured from uploaded project note on **2026-07-04** (saved into repo context to avoid losing the design intent).
|
|
|
|
Raw source capture:
|
|
- `/home/jason/projects/redmine/docs/contact_triage_loop_raw_notes_2026-07-04.md`
|
|
|
|
---
|
|
|
|
## Core loop pattern
|
|
|
|
This is a **multi-pass routing system**, not a final one-shot classifier.
|
|
|
|
1. Select a small batch (10-20 contacts) where `updated_on` is old enough and optional tag constraints match.
|
|
2. Sort **newest stale first** (recently stale / last-updated descending), so the loop works through the freshest cooled-down contacts first.
|
|
3. For each contact:
|
|
- quick contact-only triage
|
|
- inspect recent helpdesk tickets/messages when the current pass requires it
|
|
- route by adding/removing tags that represent either classifications or workflow state
|
|
4. Apply tag updates:
|
|
- if the pass identifies a route, add that route/classification tag
|
|
- otherwise add a status marker like `ai-checked`
|
|
5. Because contact metadata was updated, `updated_on` advances.
|
|
6. On future runs, recently processed contacts fall out of the immediate stale batch; later passes can pick them up again when stale or when a designated tag routes them to another worker.
|
|
7. Later filters/workers may deliberately remove status tags such as `ai-checked` after consuming them, allowing the contact to re-enter another pass of the conveyor belt.
|
|
|
|
This forms a self-advancing conveyor belt for incremental cleanup/enrichment. Tags are not all semantic classifications: some are routing/status markers. For example, `ai-checked` means this pass touched the contact; it does not mean the contact's final identity is "AI checked." Likewise, `spam`/`promotions` can be step 1 in a longer handling pipeline, not the final disposition.
|
|
|
|
Operational objective: reducing clutter in the customer-service/sales channel is valid even when a sender is technically a vendor, billing system, automailer, or promotional sender, because Redmine customer-service is not the only channel available to them.
|
|
|
|
---
|
|
|
|
## Existing tooling alignment (current)
|
|
|
|
`redmine_contacts.py` already supports key building blocks:
|
|
|
|
- batch candidate selection:
|
|
- `batch-update --max-contacts`
|
|
- date/tag filters (`--updated-before`, `--with-tag`, `--without-tag`, `--no-tags`)
|
|
- deterministic ordering (`--order newest-updated-first` etc)
|
|
- safe mutation workflow:
|
|
- dry-run by default
|
|
- write only with `--apply`
|
|
|
|
What is still missing for full loop automation:
|
|
|
|
- richer FILTER-2 workers that classify contacts and mutate tickets/contacts when needed
|
|
- scoring engines that map evidence to tags with thresholds
|
|
- downstream queue workers for enrichment/NMI/company/person linking
|
|
|
|
Implemented tracer pieces:
|
|
|
|
- `redmine_contacts.py loop-run`
|
|
- profile contract in `docs/contact_loop_filter_contract.md`
|
|
- sample tracer profile: `profiles/contact-loop/noop-ai-checked.json`
|
|
- sample no-op FILTER-2 worker: `profiles/contact-loop/filters/noop_filter2.py`
|
|
|
|
The intended architecture is now: **runner owns conveyor-belt mechanics; FILTER-2 owns domain-specific ticket/contact mutation logic.**
|
|
|
|
---
|
|
|
|
## Suggested scoring framework (v0)
|
|
|
|
### customer
|
|
Signals:
|
|
- internal/team response exists (`+1`)
|
|
- discussion indicates warehouse packages/items (`+1`)
|
|
|
|
### promo + spam
|
|
Signals:
|
|
- sender hints (`no-reply`, `noreply`, `deals`, `promotions`) (`+1`)
|
|
- message mentions discount/coupon/deal language (`+1`)
|
|
|
|
Threshold model (initial):
|
|
- tag set applied when score >= `2`
|
|
- if no tag set crosses threshold: add `ai-checked`
|
|
|
|
---
|
|
|
|
## Diagrams
|
|
|
|
### Diagram 1 — Conveyor-belt loop
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[Candidate pool\nRedmine contacts] --> B[Select stale contacts\nupdated_before + tag filters\nmax 10-20]
|
|
B --> C[Sort newest stale first\nupdated_on descending]
|
|
C --> D{For each contact}
|
|
D --> E[Run current pass\nFILTER-1 / worker gate]
|
|
E -->|pass can route immediately| F[Add route/status/classification tags]
|
|
E -->|needs evidence| G[Fetch recent tickets/messages]
|
|
G --> H[Manual review or FILTER-2 worker]
|
|
H --> I{Route decided?}
|
|
I -->|yes| F
|
|
I -->|no| J[Add status marker\nai-checked]
|
|
F --> K[Update contact/tickets as allowed]
|
|
J --> K
|
|
K --> L[updated_on advances\ncontact leaves immediate batch]
|
|
L --> M[Later passes use staleness + tags\nto route next work]
|
|
M --> N[Later worker may remove status tag\ne.g. ai-checked]
|
|
N --> B
|
|
```
|
|
|
|
### Diagram 2 — Tag/status re-entry lifecycle
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> Eligible: updated_on older than cutoff\nand tag filters match
|
|
Eligible --> InCurrentPass: newest-stale-first selection
|
|
InCurrentPass --> Routed: worker adds route tag\nspam/promotions/etc.
|
|
InCurrentPass --> Checked: no route yet\nadd ai-checked
|
|
Routed --> CoolingDown: tag update bumps updated_on
|
|
Checked --> CoolingDown: status tag update bumps updated_on
|
|
CoolingDown --> HeldOut: too fresh for current cutoff
|
|
HeldOut --> Eligible: time passes / becomes stale again
|
|
Checked --> DownstreamQueued: later worker selects ai-checked\nor another status tag
|
|
DownstreamQueued --> ReEntered: worker removes ai-checked\nafter consuming status marker
|
|
ReEntered --> Eligible: tag filters match next pass
|
|
```
|
|
|
|
---
|
|
|
|
## Future queue/tag orchestration pattern
|
|
|
|
The long-term architecture should treat tags as explicit work queues, for example:
|
|
|
|
- `needs-summary` -> summary worker processes recent communications, then removes tag
|
|
- `needs-enrichment:*` -> enrichment workers (social/entity/profile data)
|
|
- `needs-nmi-mapping` -> account-linkage worker for NMI mapping tasks
|
|
- `special-handling:*` -> SLA/VIP/high-risk handling workflows
|
|
|
|
This lets one loop classify and route, while specialized workers execute downstream tasks.
|
|
|
|
---
|
|
|
|
## Proposed implementation phases
|
|
|
|
### Phase 1 (implemented tracer)
|
|
- Add `loop-run` command that performs a **per-contact review loop** in dry-run mode:
|
|
- select candidates
|
|
- run FILTER-1
|
|
- fetch recent tickets/messages
|
|
- call a profile-defined FILTER-2 command
|
|
- print proposed runner-owned tag updates
|
|
|
|
### Phase 2
|
|
- Replace no-op FILTER-2 with a classifier/mutator worker.
|
|
- Use FILTER-2 to update contacts/tickets directly through helper CLI calls.
|
|
- Keep runner-owned fallback `ai-checked` and RTAG removal semantics.
|
|
|
|
### Phase 3
|
|
- Add queue tags + worker handoff contracts (`needs-summary`, `needs-nmi-mapping`, etc.).
|
|
|
|
### Phase 4
|
|
- Integrate external systems (NMI mapping tables, obsidian profile updates, comm summaries, etc.) with strict scoped guards.
|
|
|
|
---
|
|
|
|
## Practical run pattern (current CLI)
|
|
|
|
Dry-run stale-first loop tracer:
|
|
|
|
```bash
|
|
./redmine_contacts.py \
|
|
--base-url http://192.168.50.170 \
|
|
--project customer-service \
|
|
loop-run \
|
|
--profile profiles/contact-loop/noop-ai-checked.json \
|
|
--source cache \
|
|
--updated-before 2026-06-01 \
|
|
--without-tag ai-checked \
|
|
--max-contacts 10 \
|
|
--order newest-updated-first
|
|
```
|
|
|
|
Apply runner-owned fallback/default tags after reviewing output:
|
|
|
|
```bash
|
|
./redmine_contacts.py \
|
|
--base-url http://192.168.50.170 \
|
|
--project customer-service \
|
|
loop-run \
|
|
--profile profiles/contact-loop/noop-ai-checked.json \
|
|
--source cache \
|
|
--updated-before 2026-06-01 \
|
|
--without-tag ai-checked \
|
|
--max-contacts 10 \
|
|
--order newest-updated-first \
|
|
--apply
|
|
```
|
|
|
|
Older dry-run batch-update skeleton:
|
|
|
|
```bash
|
|
./redmine_contacts.py \
|
|
--base-url http://192.168.50.170 \
|
|
--project customer-service \
|
|
batch-update \
|
|
--source cache \
|
|
--updated-before 2026-06-01 \
|
|
--with-tag customer \
|
|
--max-contacts 20 \
|
|
--order newest-updated-first \
|
|
--set background=ai-checked
|
|
```
|
|
|
|
> Replace payload fields/tags only after reviewing dry-run output.
|