Skip to content

Customer Support

A local governed workflow over supplied support ticket snapshots. The support:triage read tool applies the consumer’s explicit structural rules to structured ticket attributes, assigns a queue and priority, produces a source-cited response DRAFT from a consumer-approved template and approved knowledge, and records why a human is required. It never sends a response, updates, closes or mutates a provider ticket, fetches a URL, or calls a model. Every response is an unsent DRAFT.

Rule matching is exact, typed equality over explicit attributes. There is no keyword, free-text or semantic matching: interpreting ticket prose is a host concern, and a host may later supply validated structured attributes. A ticket that no rule matches, or that lacks a required attribute, takes the safe default: the fallback queue, priority unassigned, no draft, and human escalation.

The ESM package exports triageTickets, SupportConfigSchema and SupportInputSchema; the triage subpath exposes the handler. The build bundles the handler and its dependencies (zod, MIT; see THIRD_PARTY_NOTICES.txt, which ships with the package and with the CLI bundled copy) into one artifact for the kernel sandbox, which exposes only the verified handler file. When a host dispatches the tool, the installed runtime supplies canonical pack_config; caller input, metadata and domain_data cannot set policy. No pack-specific CLI command is declared.

Delivery status. The pack is registered with the bundled host as trusted-host-adapter:customer-support, is bundled into the CLI tree with its zod notice, and has a public offering row and portfolio entries. A local build archive of the CLI is what proves bundled installation; that is evidence about the archive, not a registry release, and no registry release is asserted.

The pack ships bundled in the CLI; <pack-version> below is the version field of the installed CLI’s bundled packs/customer-support/package.json; read it there, because the pack version is not assumed to equal the CLI version. A local build archive of the CLI is what proves bundled installation, and no registry release is claimed here. Install the licensed CLI using the licence setup guide, initialize a workspace, then use the supported pack and configuration commands:

pnpm exec pack-install --help
pnpm exec pack-install --id customer-support --source builtin --version <pack-version>
pnpm exec config-set --help
pnpm exec config-set --key customer_support.allowed_queues --json-value '["billing"]'
pnpm exec config-get --key customer_support.allowed_queues
pnpm exec config-unset --key customer_support.allowed_queues

Run each command’s help before first use. Repository scripts expose the same commands as pnpm pack:install and pnpm config:set/get/unset. Always configure through these commands; the pack pin and consumer settings have separate owners.

Once the pack is registered with the host, a host binds a claimed task/run/session and dispatches support:triage through its governed tool interface, for example caller.executeToolWithReceipt('support:triage', input, selector). Enroll network posture off in the complete workspace security and selected lanes roots through config-set --json-value, preserving existing scopes and deny rules, and declare it on the persisted task. All four workspace/lane/task/tool scope sets must have a nonempty intersection; an empty set denies execution. The installed host resolves the pinned CLI bundle, freezes canonical workspace configuration, and passes it to the subprocess worker. Reports and execution receipts are separate evidence. See the tool reference and consumer configuration guide.

Configure customer_support with the supported config:set, config:get and config:unset commands once the pack is installed from a CLI build that bundles it (pack:install --id customer-support --source builtin --version <pack-version> is the command shape for that integrated build). Validation happens in two places:

  • At config:set, the shipped strict config.schema.json rejects structural problems: unknown keys, bad types and bounds, hostile names, and malformed rules, templates and knowledge entries.
  • At dispatch, the runtime SupportConfigSchema additionally checks cross-references and refuses the call with SUPPORT_CONFIG_INVALID and a field-path message such as routing_rules.0.queue: Queue "x" is not in allowed_queues.: queues must be in allowed_queues, template_id and knowledge_ids must exist, IDs must be unique, an attribute may be compared once per rule, and placeholders must be supported. JSON Schema cannot express these, so config:set alone does not catch them.
KeyDefaultMeaning
max_tickets100Tickets per request, 1 to 1000; checked before the input is walked
max_bytes1048576UTF-8 bytes of serialized input, 1 to 4194304; measured before shape validation
allowed_queues[]Queues a routing rule may assign (at most 100)
fallback_queuehuman-reviewQueue for unknown routing; always human-escalated
required_attributes[]Attributes every ticket must carry; a missing or null one means unknown routing
routing_rules[]Ordered rules (at most 200): rule_id, when, queue, priority, optional template_id
escalation_rules[]Rules (at most 200): rule_id, when, reason; a match requires a human
escalate_priorities["urgent"]Priorities (low, normal, high, urgent) that always require a human
templates[]Up to 50 templates: template_id, body (at most 2048 characters), knowledge_ids
approved_knowledge[]Up to 200 knowledge_id + revision entries with optional pinned sha256

A when list holds 1 to 8 predicates, all of which must match (AND). Each predicate is {attribute, equals} where equals is a string (at most 256 characters), a finite number or a boolean; the type must match exactly and null is never equal to anything. An attribute may be compared once per rule. Routing rules apply in array order and the first match wins; the report also counts how many rules matched. Rule IDs, template IDs and knowledge IDs are unique. Allowed queues, required attributes, priorities, templates and approved knowledge are sorted in the effective policy and its SHA-256 configuration digest; rule order is preserved because it is precedence.

A template body is literal text with two placeholders: {{ticket_id}} and {{attribute:<name>}}. Any other {{...}} is refused when the policy is read, and a body holds at most 20 placeholders. A template cites at most 100 knowledge_ids, and an escalation reason is 1 to 128 characters. Substitution is one pass, so supplied values are inserted literally and never re-expanded. A missing or null attribute withholds the draft and requires a human.

Character limits (values, reasons, template bodies, knowledge text) count Unicode code points at runtime, exactly as JSON Schema maxLength does, independent of the zod version. The config:set validator is built on zod and, on versions that count UTF-16 units, can be stricter for astral characters (such as emoji) in a policy; it is never looser on character-length checks, so a policy that passes config:set never fails a runtime length check. It can still be refused at dispatch with SUPPORT_CONFIG_INVALID by the runtime cross-reference checks (SupportConfigSchema superRefine) described above. IDs and attribute names are ASCII only, where every measure agrees.

{
  "tickets": [
    { "ticket_id": "T-1", "attributes": { "category": "billing", "topic": "refund", "vip": false } }
  ],
  "knowledge": [{ "knowledge_id": "kb-refunds", "revision": "3", "text": "Refunds take 5 days." }]
}

Every object is strict. IDs are 1 to 128 characters, start with an ASCII letter or digit and then contain letters, digits, dot, underscore, colon or hyphen. A request holds 1 or more tickets (up to max_tickets, at most 1000) and up to 200 knowledge entries (text 1 to 8192 characters). attributes is a flat object of at most 32 entries. Names start with an ASCII letter (up to 64 characters of letters, digits, underscore, dot or hyphen), so __proto__ cannot occur, and constructor and prototype are refused explicitly. Values are strings of at most 256 characters, finite numbers, booleans or null. Ticket and knowledge IDs must be unique. Unknown root keys, including pack_config, metadata, domain_data, __proto__, constructor and prototype, are refused.

Supplied knowledge is only usable if the consumer approved it: its ID must be in approved_knowledge, at the approved revision, and its text must match a pinned sha256 when one is configured. Otherwise the request is refused with SUPPORT_KNOWLEDGE_UNAPPROVED. A template that cites knowledge which was not supplied withholds its draft and requires a human.

The report carries status: "DRAFT", the input byte count, a SHA-256 input_digest of the serialized request, per-ticket digests of each supplied ticket, the approved knowledge citations (knowledge_id, revision, sha256), the configuration_digest of the effective policy, a bounded policy_summary and, when it is small (at most 65536 bytes encoded), the full effective_policy (policy_echo is full; otherwise summary and effective_policy is null). Each ticket has a route (routed or unknown, queue, priority, rule_id, matched_rule_count), human_required, escalation_reasons, matched_escalations (the rule_id and configured reason of every matched escalation rule) and a draft (or null) with its citations and citation_count. Reasons are missing_attribute:<name>, unknown_routing, escalation_rule:<rule_id>, priority_requires_review:<priority> and draft_unavailable:<cause>. The actions receipt, created fresh per call, always reports zero responses sent, zero ticket mutations and zero network calls. Output is deterministic: the same input and policy yield byte-identical reports, in input ticket order.

The whole encoded report stays under 900000 bytes, well inside the 1 MiB subprocess output cap. When it would not fit, evidence is shed in a fixed, disclosed order and the first level whose report fits is used: detail_level is full, then citation_ids (each draft lists cited knowledge IDs instead of full citations), citation_counts (only citation_count; every full citation stays once in the report-level knowledge), escalation_capped (at most 3 reasons and matched escalations per ticket) and escalation_counts (counts only); citations_truncated and escalation_detail_truncated mark what was cut. At the chosen level, draft text is kept for a large deterministic prefix of drafts that fits (found by binary search, so it is approximate, never exceeds the ceiling and is identical for identical input); the rest have text null, text_omitted true and a text_digest, and draft_text_limit states how many kept their text. Routing outcome, digests and all counts are always complete. If the report cannot fit even at the last level (for example 1000 tickets whose ticket, queue, rule and template IDs are all very long, so their identifiers alone approach the budget), the call fails with the distinct code SUPPORT_REPORT_TOO_LARGE. The exact boundary depends on the policy and tickets, so it is not a fixed identifier length, and any specific length that overflowed in a test is a result for those test fixtures, not a bound for every policy or input.

The manifest rejects malformed outer envelopes and unknown root fields before dispatch with INVALID_INPUT, leaving nested items unprojected for the handler. SUPPORT_INPUT_INVALID covers malformed tickets or knowledge and duplicate IDs, SUPPORT_INPUT_LIMIT covers ticket-count and input-byte overflow, SUPPORT_REPORT_TOO_LARGE covers a result that cannot fit the report budget, SUPPORT_KNOWLEDGE_UNAPPROVED covers unapproved knowledge, and SUPPORT_CONFIG_INVALID covers an invalid canonical policy (including the cross-reference checks above). The handler receives already-parsed JSON, so input bytes are measured on its compact JSON.stringify serialization in original key order (UTF-8 bytes; not the kernel’s canonical, key-sorted serialization) after the ticket-count check and before the strict shape validation walks it; an over-budget input is therefore always SUPPORT_INPUT_LIMIT, never a shape error. JSON parsers collapse duplicate keys inside one object before the handler sees them, so the pack detects duplicate identifiers across array entries rather than duplicate object keys.

Billing and marketplace charging remain future, unvalidated capabilities.