Build

My Tasco integration

Engram as the AI knowledge layer inside My Tasco: a COP-envelope facade the Flutter app can call through a thin adapter, and a connector that syncs the org tree, staff roster, and news feed straight from the COP gateway. Same access algebra, Tasco's wire format.

The facade endpoints

Three routes under /api/mytasco/v1 speak the COP contract: payloads under body, status: "success", the eight-code ErrorResponse on failure, and requestId echoing your X-Request-Id. X-App-Code/X-Locale/X-Timezone are accepted; auth is the Bearer token.

EndpointWhat it does
GET /healthLiveness, no auth
POST /assistant/askGrounded AI answer + citations, trimmed to the staff member's access
POST /knowledge/searchPermission-aware hybrid search in the COP list shape (result: […])
POST /api/mytasco/v1/assistant/ask
Authorization: Bearer egm_live_...
X-App-Code: MYTASCO
X-Request-Id: 3b1d0fb1-7a45-4d2c-9f57-9c0fd85a9b9d

{ "question": "Chính sách nghỉ phép năm là gì?", "staffCode": "TS0002" }

→ {
  "status": "success", "message": "SUCCESS",
  "body": {
    "answer": "Nhân viên chính thức được 12 ngày phép năm…",
    "confidence": "grounded",
    "citations": [
      { "title": "Chính sách nghỉ phép năm 2026", "path": "/tasco/news", "status": "approved", "snippet": "…" }
    ]
  },
  "requestId": "3b1d0fb1-7a45-4d2c-9f57-9c0fd85a9b9d"
}
Error envelope
→ 401 {
  "status": "error",
  "code": "unauthorized",
  "message": "invalid or revoked token",
  "requestId": "3b1d0fb1-7a45-4d2c-9f57-9c0fd85a9b9d"
}

Per-staff identity — staffCode is enough

The token is an Engram integration token with on-behalf-of enabled, held by the My Tasco backend. Who is asking arrives three ways, in priority order:

  • subject — the raw on-behalf-of shape (externalUserId + roles), identical to the /v1 API.
  • staffCode + roles — roles passed verbatim from your session.
  • staffCode alone — roles come from the roster the My Tasco connector synced, so the app never has to model permissions itself. Unknown or inactive staffCodes are refused (forbidden).

Either way the subject's access is intersected with the token's own scopes — a leaked token can never grant more than it holds.

The My Tasco connector

Settings → Integrations → My Tasco (COP). One pasted COP access token (refresh token optional — expiry is refreshed and re-encrypted automatically) syncs three things on every run:

  • Organization tree (hrm/organization/tree) → department folders under /departments/<org-unit-code> plus dept:* role mappings.
  • Staff directory (sys/staff/search) → external identities keyed by staffCode, with dept:* roles from their org units. These render as people in the org tree and power staffCode-only facade calls.
  • Published news (hrm/news-article/search) → memories, incremental on a publishedAt watermark. Vietnamese text and diacritics preserved.
No gateway credentials handy? Paste mock as the access token — a deterministic built-in fixture set (org tree, staff, news) syncs instead, so demos and permission tests are reproducible.

Ranking & latency

  • Ranking: hybrid retrieval — vector similarity + full-text — fused into one score per hit. mode: "text_only" signals degraded (no-embedding) operation so clients can tell the difference.
  • Latency: /knowledge/search is one embedding + one pgvector query (sub-second). /assistant/ask adds a single LLM generation (~2–6 s). Use search for typeahead, ask for Q&A.
  • Paging: retrieval is top-k — pageSize bounds the result count; there is no offset paging.

Fallback & provenance

  • confidence: "no_answer" is an explicit refusal — when the permitted corpus can't support an answer, the assistant says so rather than invent one. partial flags weak grounding; render citations and gate on confidence.
  • Every answer carries citations (node id, folder path, approval status, snippet) — the provenance trail back to the governed document. approvedOnly: true restricts grounding to human-approved memories.
  • Access failures are clean COP codes: expired token → unauthorized, out-of-scope staff → forbidden; gateway-side timeouts map to timeout.

Dart SDK & OpenAPI

The machine-readable contract lives at /mytasco/openapi.yaml (OpenAPI 3.1). A pure-Dart adapter — configurable base URL, async bearer-token provider, X-App-Code header, DTO classes, UTF-8-safe decoding — ships in the repo at clients/dart/engram_mytasco, ready to drop behind a My Tasco DioClient-style boundary.

Dart
final engram = EngramMyTascoClient(
  baseUrl: 'https://your-engram.app/api/mytasco/v1',
  tokenProvider: () async => await vault.readEngramToken(),
);

final res = await engram.ask(
  'Quy trình xin cấp môi trường dev mới?',
  staffCode: session.staffCode,
);
if (res.confidence != 'no_answer') showAnswer(res.answer, res.citations);