210 lines
6.3 KiB
Markdown
210 lines
6.3 KiB
Markdown
# Phase-6 Anti-Silent Lessons Tuning (Closed-loop)
|
|
|
|
## Goal
|
|
Tune anti-silent ACK template selection using evidence from gateway events (`reason + chat_type + template_id + user_signal`) without unsafe auto-mutation.
|
|
|
|
## Safety Invariants
|
|
- No automatic global policy rewrite.
|
|
- Gateway applies tuning only when `ANTI_SILENT_TUNING_ENABLED=true`.
|
|
- Learner emits tuning lessons only with thresholds (`MIN_EVIDENCE`, `MIN_SCORE`).
|
|
- Lessons have TTL (`expires_at`) for rollback-by-expiry.
|
|
|
|
## Components
|
|
- `services/experience-learner/main.py`
|
|
- emits `lesson_type=anti_silent_tuning`
|
|
- computes score: `1 - (w_retry*retry_rate + w_negative*negative_rate + w_suppressed*suppressed_rate)`
|
|
- `gateway-bot/http_api.py`
|
|
- applies tuning in anti-silent template resolver under feature flag
|
|
- fail-open on DB/lookup errors
|
|
- `gateway-bot/gateway_experience_bus.py`
|
|
- DB lookup of active tuning lesson by trigger (`reason=<...>;chat_type=<...>`)
|
|
|
|
## Environment
|
|
|
|
### Learner
|
|
- `ANTI_SILENT_TUNING_ENABLED=true`
|
|
- `ANTI_SILENT_TUNING_WINDOW_DAYS=7`
|
|
- `ANTI_SILENT_TUNING_MIN_EVIDENCE=20`
|
|
- `ANTI_SILENT_TUNING_MIN_SCORE=0.75`
|
|
- `ANTI_SILENT_TUNING_WEIGHT_RETRY=0.6`
|
|
- `ANTI_SILENT_TUNING_WEIGHT_NEGATIVE=0.3`
|
|
- `ANTI_SILENT_TUNING_WEIGHT_SUPPRESSED=0.1`
|
|
- `ANTI_SILENT_TUNING_TTL_DAYS=7`
|
|
|
|
### Gateway
|
|
- `ANTI_SILENT_TUNING_ENABLED=false` (default; turn on only after smoke)
|
|
- `ANTI_SILENT_TUNING_DB_TIMEOUT_MS=40`
|
|
- `ANTI_SILENT_TUNING_CACHE_TTL_SECONDS=60`
|
|
|
|
## Deploy
|
|
```bash
|
|
cd /opt/microdao-daarion
|
|
docker compose -f docker-compose.node1.yml up -d --no-deps --build --force-recreate experience-learner gateway
|
|
```
|
|
|
|
## Single-Command Smoke (Phase-6.1)
|
|
```bash
|
|
make phase6-smoke
|
|
```
|
|
|
|
The command runs:
|
|
1. deterministic seed events
|
|
2. learner lesson generation assertion
|
|
3. gateway apply assertion (flag ON)
|
|
4. gateway fallback assertion (flag OFF)
|
|
5. seed cleanup (events + lessons)
|
|
|
|
Use `PHASE6_CLEANUP=0 make phase6-smoke` to keep artifacts for debugging.
|
|
|
|
## CI Integration
|
|
- Workflow: `.github/workflows/phase6-smoke.yml`
|
|
- Modes:
|
|
- `workflow_dispatch` (manual)
|
|
- `workflow_run` after successful deploy workflow
|
|
- Operations guide: `docs/ops/ci_smoke.md`
|
|
|
|
## Fixed Smoke (Deterministic)
|
|
|
|
### 1) Temporary smoke thresholds
|
|
Use low thresholds only for smoke:
|
|
- `ANTI_SILENT_TUNING_MIN_EVIDENCE=3`
|
|
- `ANTI_SILENT_TUNING_MIN_SCORE=0.5`
|
|
- `ANTI_SILENT_TUNING_WINDOW_DAYS=1`
|
|
|
|
### 2) Seed synthetic gateway events
|
|
```bash
|
|
export PG_CONTAINER='dagi-postgres'
|
|
|
|
docker exec "$PG_CONTAINER" psql -U daarion -d daarion_memory -c "
|
|
WITH seed AS (
|
|
SELECT
|
|
(
|
|
substr(md5(random()::text || clock_timestamp()::text), 1, 8) || '-' ||
|
|
substr(md5(random()::text || clock_timestamp()::text), 9, 4) || '-' ||
|
|
substr(md5(random()::text || clock_timestamp()::text), 13, 4) || '-' ||
|
|
substr(md5(random()::text || clock_timestamp()::text), 17, 4) || '-' ||
|
|
substr(md5(random()::text || clock_timestamp()::text), 21, 12)
|
|
)::uuid AS event_id,
|
|
now() - (g * interval '1 minute') AS ts,
|
|
CASE WHEN g <= 3 THEN 'UNSUPPORTED_INPUT' ELSE 'SILENT_POLICY' END AS template_id,
|
|
CASE WHEN g <= 3 THEN 'retry' ELSE 'none' END AS user_signal
|
|
FROM generate_series(1,6) g
|
|
)
|
|
INSERT INTO agent_experience_events (
|
|
event_id, ts, node_id, source, agent_id, task_type, request_id,
|
|
channel, inputs_hash, provider, model, profile, latency_ms,
|
|
tokens_in, tokens_out, ok, error_class, error_msg_redacted,
|
|
http_status, raw
|
|
)
|
|
SELECT
|
|
event_id,
|
|
ts,
|
|
'NODA1',
|
|
'gateway',
|
|
'agromatrix',
|
|
'webhook',
|
|
'phase6-seed-' || event_id::text,
|
|
'telegram',
|
|
md5(event_id::text),
|
|
'gateway',
|
|
'gateway',
|
|
NULL,
|
|
25,
|
|
NULL,
|
|
NULL,
|
|
true,
|
|
NULL,
|
|
NULL,
|
|
200,
|
|
jsonb_build_object(
|
|
'event_id', event_id::text,
|
|
'ts', to_char(ts, 'YYYY-MM-DD"T"HH24:MI:SS"Z"'),
|
|
'source', 'gateway',
|
|
'agent_id', 'agromatrix',
|
|
'chat_type', 'group',
|
|
'anti_silent_action', 'ACK_EMITTED',
|
|
'anti_silent_template', template_id,
|
|
'policy', jsonb_build_object('sowa_decision', 'SILENT', 'reason', 'unsupported_no_message'),
|
|
'feedback', jsonb_build_object('user_signal', user_signal),
|
|
'result', jsonb_build_object('ok', true, 'http_status', 200)
|
|
)
|
|
FROM seed;
|
|
"
|
|
```
|
|
|
|
### 3) Trigger learner evaluation with one real event
|
|
```bash
|
|
export GATEWAY_WEBHOOK_URL='http://127.0.0.1:9300/agromatrix/telegram/webhook'
|
|
|
|
curl -sS -X POST "$GATEWAY_WEBHOOK_URL" \
|
|
-H 'content-type: application/json' \
|
|
-d @docs/ops/payloads/phase5_payload_group_unsupported_no_message.json
|
|
```
|
|
|
|
### 4) Verify tuning lesson exists
|
|
```bash
|
|
docker exec "$PG_CONTAINER" psql -U daarion -d daarion_memory -P pager=off -c "
|
|
SELECT ts,
|
|
trigger,
|
|
action,
|
|
raw->>'lesson_type' AS lesson_type,
|
|
raw->>'expires_at' AS expires_at,
|
|
evidence
|
|
FROM agent_lessons
|
|
WHERE raw->>'lesson_type'='anti_silent_tuning'
|
|
ORDER BY ts DESC
|
|
LIMIT 5;
|
|
"
|
|
```
|
|
Expected: lesson with
|
|
- `trigger=reason=unsupported_no_message;chat_type=group`
|
|
- `action=prefer_template=SILENT_POLICY`
|
|
|
|
### 5) Enable gateway tuning and verify apply
|
|
```bash
|
|
# set ANTI_SILENT_TUNING_ENABLED=true for gateway and restart container
|
|
# then replay unsupported payload:
|
|
|
|
curl -sS -X POST "$GATEWAY_WEBHOOK_URL" \
|
|
-H 'content-type: application/json' \
|
|
-d @docs/ops/payloads/phase5_payload_group_unsupported_no_message.json
|
|
|
|
# verify latest gateway events
|
|
|
|
docker exec "$PG_CONTAINER" psql -U daarion -d daarion_memory -P pager=off -c "
|
|
SELECT ts,
|
|
raw->'policy'->>'reason' AS reason,
|
|
raw->>'chat_type' AS chat_type,
|
|
raw->>'anti_silent_template' AS template_id,
|
|
raw->>'anti_silent_tuning_applied' AS tuning_applied,
|
|
raw->>'anti_silent_action' AS anti_action
|
|
FROM agent_experience_events
|
|
WHERE source='gateway'
|
|
AND raw->'policy'->>'reason'='unsupported_no_message'
|
|
ORDER BY ts DESC
|
|
LIMIT 10;
|
|
"
|
|
```
|
|
Expected: newest rows have
|
|
- `template_id=SILENT_POLICY`
|
|
- `tuning_applied=true`
|
|
|
|
## PASS
|
|
- Tuning lesson created only when evidence/score thresholds pass.
|
|
- Gateway does not change template when feature flag is off.
|
|
- With feature flag on, gateway applies active non-expired tuning lesson.
|
|
- Expired lessons are ignored.
|
|
|
|
## FAIL
|
|
- Tuning lesson appears below evidence/score threshold.
|
|
- Gateway changes template while feature flag is off.
|
|
- Gateway applies expired lesson.
|
|
- Webhook path fails when tuning lookup fails (must stay fail-open).
|
|
|
|
## Manual Cleanup Query
|
|
```sql
|
|
DELETE FROM agent_lessons
|
|
WHERE raw->>'lesson_type'='anti_silent_tuning'
|
|
AND raw->>'seed_test'='true';
|
|
```
|