# Liyagent > Liyagent builds governed AI agents for enterprise work: IT service (TicketIQ), HR, SAP, Salesforce and finance, all generally available. Agents triage and answer requests inside the systems a company already runs, hold risky steps for a person to approve, and record every step in a tamper-evident audit log. Liyagent is self-hosted in the customer's own cloud or data centre and works with the AI models the customer chooses. Key facts: - **TicketIQ** is the IT service desk product: it takes tickets in from Microsoft Teams, email, Jira and ServiceNow, classifies, prioritises and routes them, folds duplicates into one known issue, and drafts answers grounded in the customer's own knowledge base, each with a confidence band. The assistant users talk to is called Liya. - **HR, SAP, Salesforce and Finance agents** are generally available: HR answers from the HR record (Workday, BambooHR, SAP SuccessFactors); SAP S/4HANA plant maintenance (with predictive maintenance), purchasing, sales, stock, supplier invoices and master data, SAP Field Service Management dispatch, and the published APIs of other SAP products — directly over SAP's APIs with a person confirming each agent call, or through the customer's own SAP Integration Suite MCP gateway; Liya for Salesforce, which answers from Salesforce records as the signed-in person and runs Flows, invocable actions and Prompt Builder templates, with every write held for approval by default; and Liya Finance, which matches supplier invoices to purchase orders (NetSuite, Dynamics 365), flags variances beyond a tolerance, holds a mismatched invoice for AP approval and answers spend questions from Ramp. Liyagent is not affiliated with SAP or Salesforce. - **Self-hosted**: runs as Docker containers, on Kubernetes or on a single VM, with PostgreSQL as its system of record, inside the customer's network. - **Bring your own model**: Anthropic, OpenAI, Google Gemini, AWS Bedrock, or any OpenAI-compatible endpoint (Ollama, vLLM, OpenRouter, Groq, Cerebras, models on your own GPUs), with per-agent model chains, fallbacks and spend budgets. - **Governance**: Cedar policies decide which person or agent may do what; guardrails check every model input and output for prompt injection, PII and denied topics; kill switches stop one agent, a channel or the whole fleet; budgets cap spend per agent, team and tenant; human approval where you want it; built-in red-team runs; and a reply cannot claim an action no tool call made. - **Audit**: every call, cost and decision is written to a hash-chained audit log that can be verified and exported. - **Identity**: OIDC single sign-on with Microsoft Entra ID, Okta, Google or any OIDC provider, and SCIM 2.0 provisioning from Entra ID, Okta or any SCIM directory. - **Open protocols**: agents call tools over MCP (Model Context Protocol) and talk agent-to-agent (A2A); the full HTTP API is described by an OpenAPI schema. - **Engagement**: a demo, a 4–6 week pilot in the customer's environment, then production. Contact: sales@liyagent.com. - **Try it live**: https://liyagent.com/try lets anyone, without signing up, ask Liya to work in a sample CRM of invented companies: changes wait for the visitor's approval and every step lands in a hash-chained trail. - **Agent finder**: https://liyagent.com/start asks a few questions about the work, the systems and the approvals needed, and drafts a blueprint of the agent that fits. - **Company**: Liyagent, also known as Agent Liya and AgentLiya (agentliya.com). Founded in 2026 in Bengaluru, India, by Pavan Kumar Guthikonda, for customers in India and the United Kingdom; the company is being incorporated in India and in the United Kingdom. About: https://liyagent.com/about - **Services**: the Liyagent platform hosted by Liyagent or licensed to self-host; ready-made agents; custom agents for a customer's own workflows; integration and implementation with Jira, ServiceNow, SAP, Salesforce, Dynamics 365 and in-house systems; AI governance setup; and a 4–6 week pilot. https://liyagent.com/services - **Catalogues**: every pre-built agent at https://liyagent.com/agents and the 50+ ready-made connectors at https://liyagent.com/connectors. Book a demo at https://liyagent.com/demo. --- Source: https://liyagent.com/docs # Liyagent documentation Build, connect and govern service-desk agents with Liyagent, the agent platform.
TICKETIQ DOCUMENTATION

Service-desk agents you can run, trace and govern.

Liyagent takes tickets in from Teams, email, Jira and ServiceNow, triages and routes them with the Liya agents, answers repeat questions from knowledge you trust, and puts every model call behind one governed gateway.

Try:

Choose where to start

Path 1

Build a new agent

Run Liyagent, sign in, create a managed agent with instructions and a model, and try it in the playground.

Get started →
Path 2

Govern existing agents

Point agents you already run at the gateway and put budgets, guardrails, policies and the audit log around them.

Set up your agent →

Popular across Liyagent

Get started

Quickstart

From a clean checkout to a triaged ticket on your laptop in about ten minutes.

AI Gateway

Create a guardrail

Mask PII, block prompt attacks and deny topics on the way in and out of a model.

Connect

Microsoft Teams app

Build the Liya app package and put the agent in front of your users in Teams.

Service desk

Known issues and duplicates

Fold repeat reports into one ticket and tell every reporter when it is fixed.

AI Gateway

Model chain and fallback

Choose which failures move a call down the chain and which never should.

Monitor

Audit log

Query who did what, verify the hash chain and export an evidence bundle.

New here? Read What is Liyagent? for the ten-minute tour of agents, the gateway and the service-desk pipeline, then try a lab. Building an integration? The OpenAPI schema is served at /openapi.json and summarised in the API overview.

Need a hand?

Talk to the team about a rollout, or ask other builders in the community.

Contact salesCommunity
--- Source: https://liyagent.com/docs/overview # What is Liyagent? Liyagent runs governed AI agents for any system, one server that runs the Liya agents and the ones you build, governs every model call and keeps the evidence. Liyagent is a single FastAPI server (`ticketiq/api.py`) with a web console. Out of the box it runs a fleet of built-in agents, the **Liya** family, that turn inbound messages into tickets, classify and route them, spot duplicates, suggest fixes from your knowledge base and answer people in Microsoft Teams, web chat or email. Around those agents sits a gateway that every model call goes through, so cost, guardrails, access policies and the audit trail apply the same way to built-in agents, agents you create in the console and agents you run on your own infrastructure. The built-in agents are starting points, not the limit. Anyone with the right role can build and onboard a new agent for any system in the console, in minutes: [create it](/docs/agents/create) with its own instructions, model, context window and tools from any MCP server (the catalogue has ready-made ones, and any custom [MCP server](/docs/connect/mcp-servers), OpenAPI operations or your organisation's own code can be added), give it [sub-agents](/docs/agents/sub-agents), guardrails, a budget and an audit trail, then connect it to Microsoft Teams, Google Chat, web chat, the [employee workspace](/docs/agents/workspace), the mobile app or any other channel through [triggers](/docs/agents/triggers) and the API. The solutions for the IT service desk, SAP, HR, Salesforce and finance are pre-built and tested, and every other agent is yours to build. It is available as a hosted product at `liyagent.com` and as a self-hosted stack (Docker Compose with Postgres and Qdrant, or Kubernetes). Either way it runs in your own environment and reaches your systems through their APIs. ## What happens to a ticket 1. **Intake.** A message arrives from Teams, email, a webhook, `POST /api/intake`, or a synced Jira or ServiceNow project. Liya Intake turns it into a ticket draft. 2. **Classify.** Liya Classify sets a category and priority. Liya Triage can run a model pass to correct the category and add a triage note. 3. **Correlate.** Liya Correlate compares the ticket with open tickets by vector similarity. A match inside the threshold is folded in as a duplicate and the reporter is told it is a known issue. 4. **Resolve.** Liya Resolve retrieves likely fixes from the knowledge base. A verified answer to the same question can be served straight from the semantic answer cache. 5. **Assign and notify.** Liya Assign picks the team and SLA from the routing table. Liya Notify sends email, Teams, Slack or webhook notifications. 6. **Review.** After closure, Liya Review grades the case and Liya Scribe drafts a knowledge article for a person to approve. ## The three planes | Plane | What it covers | Where in the console | | --- | --- | --- | | Agents | Built-in Liya agents, managed agents you create, self-managed agents that call the gateway, sub-agents, triggers, playground and evals | **Agents**: All agents, Agent network, Evaluations, Red teaming | | AI Gateway | LLM providers, the model chain and fallback, guardrails, cost and usage, budgets, rate limits | **AI Gateway**: LLM providers (Providers, Agent bindings, Gateway routes), Guardrail rules, Guardrail profiles, Policy hooks, Cost and usage, Budgets | | Governance | Users and roles, Cedar policies, tenants, the secrets store, the audit log, incidents and compliance | **Governance**: Overview, Incidents, Compliance, Audit log. **Settings**: Access, Tenants. **Connect**: Secrets store | Beside these platform sections, the rail lists the **solutions** a tenant has: IT service desk (Tickets, Analytics, Insights, Categories), SAP, HR and Salesforce, each with its own tabs. > [!TIP] > Everything the console does is a call to the same REST API under `/api/`. Anything you can click you can script, and the [API endpoints](/docs/reference/api-endpoints) page lists them all. ## Next steps - Learn the vocabulary in [Key concepts](/docs/overview/concepts). - Run it yourself with the [Quickstart](/docs/get-started/quickstart). - Browse hands-on [Labs](/docs/labs). --- Source: https://liyagent.com/docs/overview/concepts # Key concepts The words used across Liyagent: the Liya agents, engines, managed and self-managed agents, triggers, tenants and the gateway. ## Liya: the orchestrator and its sub-agents **Liya** is the name of Liyagent's agent family and of the agent people talk to. The user-facing agent (key `copilot`, shown as "Liya") answers in Teams and web chat, summarises a case, finds similar cases and drafts a reply. Behind it, specialised Liya agents each own one stage. Their keys are permanent, and their display names can be renamed per tenant with `PUT /api/agents/{key}/display-name`. | Key | Name | Stage | Uses a model | | --- | --- | --- | --- | | `ingestion` | Liya Intake | Pipeline: inbound record to ticket draft | No | | `classifier` | Liya Classify | Pipeline: category and priority from a rule table | No | | `enrichment` | Liya Triage | Pipeline: rules first for category, priority and team, the model when they are unsure. It adds a triage note | Yes | | `deduplicator` | Liya Correlate | Pipeline: duplicate and storm detection by similarity | No (embeddings) | | `strategist` | Liya Resolve | Pipeline: retrieves resolutions from the knowledge base | No (retrieval) | | `router` | Liya Assign | Pipeline: team and SLA | No | | `notifier` | Liya Notify | Pipeline: email, Teams, Slack and webhook notices | No | | `copilot` | Liya | Assist: answers people, helps an agent on a case | Yes | | `scribe` | Liya Scribe | Assist: drafts KB articles from resolved tickets | Yes | | `auditor` | Liya Review | Oversight: grades closed cases against a rubric | No | | `sentinel` | Liya Sentinel | Oversight: trends and SLA breach risk | No | | `failure-analyst` | Liya Diagnose | Oversight: clusters recurring failures and names the likely cause | Yes | | `hr-assist` | Liya HR | Assist: employee HR self-service, as the employee (off until turned on per tenant) | Yes | | `hr-onboarding` | Liya Onboarding | Assist: a new hire's checklist, first week and IT requests (off until turned on per tenant) | Yes | Any **managed agent** can act as an orchestrator too: give it sub-agents and it can delegate work to them under its own budget. See [Sub-agents](/docs/agents/sub-agents). ## Engines: rules, rules + model, model Every decision in Liyagent is made by one of three kinds of engine. Knowing which one you are looking at tells you whether it is deterministic, what it costs and what a guardrail can see. | Engine | How it decides | Examples | Cost | | --- | --- | --- | --- | | **Rules** | Deterministic tables and thresholds. The same input always gives the same output. | Liya Classify's rule table, the routing table, SLA hours, duplicate thresholds | No model spend | | **Rules + model** | Rules decide first. A model pass refines the result off the critical path and can be switched off without breaking the pipeline. | Liya Classify followed by Liya Triage | Only the refinement pass | | **Model** | A language model decides, through the gateway, with guardrails, budgets and fallback. | Liya, Liya Scribe, managed agents you create | Metered per call | > [!NOTE] > The catalogue in `ticketiq/agentconf.py` marks each built-in agent with whether it calls a model. Agents that don't cannot overspend, and are unaffected when a provider is down. ## Managed and self-managed agents - A **managed** agent runs inside Liyagent: its instructions, model binding, MCP tools, sub-agents and run limits live in the console, and it can be started and stopped there. It runs **as the agent** (its own permissions) or **as the invoker** (the agent's permissions and the signed-in person's, both checked). An agent that uses a per-user connector must run as the invoker. See [Instructions and model](/docs/agents/instructions-model#run-as). - A **self-managed** agent runs on your infrastructure. It gets a credential and points its OpenAI-compatible client at the gateway, so its model calls are still governed and metered. ## Triggers A trigger is an inbound channel bound to one agent: `webhook`, `teams`, `slack`, `google_chat`, `email`, `cron` (a schedule) or `ticket` (a ticket event). See [Triggers](/docs/agents/triggers). ## Tenants A tenant is a slice of one instance: its own sources, tickets, knowledge, budgets and settings. A ticket belongs to the tenant of the source it came in through. Users are granted specific tenants (or `*`), and owners see every tenant. Separation is enforced on the server over one shared database, and per-tenant encryption keys can be turned on so a retired tenant's data can be crypto-shredded. See [Tenancy](/docs/govern/tenancy). ## The gateway Every governed model call passes Cedar authorization, rate limits and budgets, input guardrails, the model call itself (with fallback), output guardrails and the audit log, in that order. See [AI Gateway](/docs/ai-gateway). ## Next steps - [Quickstart](/docs/get-started/quickstart) - [Create an agent](/docs/agents/create) - [Glossary](/docs/reference/glossary) --- Source: https://liyagent.com/docs/overview/agentic-ai-platform # What is an agentic AI platform? An agentic AI platform runs AI agents that act on the business systems a company already uses and governs what they may do. Eight questions to ask of any platform, and where Liyagent answers each. A chatbot answers. An **AI agent** acts: it reads a ticket, looks the customer up in Salesforce, drafts the reply, updates the record and closes the case. "Agentic AI" is that shift from answering to doing, usually across several steps and several systems, with the model deciding what to do next. That is also why an agent needs more than a model and a prompt. The moment software can change records, spend money or send messages, someone has to decide who it acts as, which of its actions need a person's approval, what it may spend, what must never leave the building and how to prove afterwards what it did. The **platform** is the part that answers those questions for every agent, built-in or custom, in one place. ## The eight questions Ask these of any agentic AI platform, Liyagent included. Each row names the control and where it lives here. | Question | Why it matters | In Liyagent | | --- | --- | --- | | **Who is the agent acting as?** | An agent with a shared service account sees everything. An agent acting as the person asking sees only what that person may. | Each person connects their own accounts, and agents act with that person's permissions. [Connections](/docs/connect/connections), [identity and roles](/docs/govern/identity-roles) | | **Which actions need a person?** | Reads are cheap to get wrong, but writes are not. The default should be "ask", loosened deliberately, per tool. | Writes on new connections wait for approval by default. [Cedar policies](/docs/govern/permissions) decide the rest, per agent, tool and person. | | **What may it spend?** | Model cost grows with use. A runaway loop should hit a ceiling, not a credit card. | [Budgets](/docs/ai-gateway/budgets) per agent and team, with the kill switch behind them. | | **What stops prompt injection and leaks?** | Agents read untrusted text: tickets, emails, web pages. Some of it is written to steer them. | [Guardrails](/docs/ai-gateway/guardrails) for prompt injection, personal data and denied topics, and [redaction](/docs/govern/redaction) before text reaches a model. | | **Can you prove what happened?** | "The AI did it" is not an answer an auditor accepts. | A hash-chained [audit log](/docs/monitor/audit-log) of every call and decision, with signed exports anyone can verify. | | **Where does the data live, and which model?** | Some data may not leave your network, and some teams may not use a particular vendor. | Self-hosted on a [VM, Docker or Kubernetes](/docs/get-started/deploy-vm), with [any model](/docs/ai-gateway/providers): Anthropic, OpenAI, Gemini, Bedrock, or open models through Ollama, vLLM and others. | | **How does it reach your systems?** | An agent is only as useful as the systems it can act in. | Ready-made connectors for 50 systems, and any other over [MCP](/docs/connect/mcp-servers), OpenAPI, OData, SQL or A2A. [All connections](/docs/connect) | | **Is it tested before it ships?** | An agent that passed a demo has not passed anything. | [Playground evals](/docs/agents/playground-evals) replay real cases against a change, and a red-team run can gate an agent's release. | ## What "self-hosted" changes Most agentic AI products are a service: your tickets, records and prompts go to the vendor, and the model is the vendor's. Liyagent runs where you put it, with the model you choose, so the questions above have answers you control rather than answers you are given. See [Self-hosted AI agents](/docs/overview/self-hosted-ai-agents) for what that looks like in practice. ## See it act [Try it live](/try): ask Liya to look up and change records in a sample CRM, approve or reject its changes, then read the audit trail it left. No sign-up. *SAP, Salesforce, Microsoft and other product names are trademarks of their owners. Liyagent is independent of them.* --- Source: https://liyagent.com/docs/overview/self-hosted-ai-agents # Self-hosted AI agents Run AI agents inside your own network, on a VM, Docker or Kubernetes, with the model you choose. What runs where, what leaves the building, and what stays yours. Most agentic AI products are a service. Your tickets, records and prompts go to the vendor's cloud, the model is the vendor's, and the governance is whatever the vendor offers. That is fine for many teams and impossible for others: regulated data, customer contracts that name where data may be processed, or simply a policy that nothing about the business leaves the network. Liyagent is built to be self-hosted first. The same release that runs at liyagent.com runs on your own infrastructure, and the hosted version exists so you can try it before you install it. ## What runs where A Liyagent install is one server and two stores: | Part | What it is | Where it runs | | --- | --- | --- | | The server | One FastAPI service with the web console, the Liya agents, the AI Gateway and the governance plane | Your VM, Docker host or Kubernetes cluster | | PostgreSQL | Tickets, agents, policies, connections, the audit log | Yours, managed or self-run | | Qdrant | The vector index behind knowledge answers and duplicate detection | Yours | Install guides: [Quickstart](/docs/get-started/quickstart) on a laptop with Docker Compose, [a production VM](/docs/get-started/deploy-vm), and the [operate](/docs/operate) section for [backups](/docs/operate/backups) and [zero-downtime deploys](/docs/operate/zero-downtime-deploys). ## What leaves the network Only what you point outward: - **Model calls.** Every model call goes through the [AI Gateway](/docs/ai-gateway) to the [provider you configure](/docs/ai-gateway/providers): Anthropic, OpenAI, Gemini or Bedrock in their clouds, or an open model served by Ollama or vLLM on your own hardware, in which case nothing leaves at all. [Redaction](/docs/govern/redaction) strips personal data before text reaches a model, whichever you choose. - **Connector calls.** Agents reach ServiceNow, Jira, SAP, Salesforce, Microsoft 365 and the rest over those systems' own APIs, as the person who connected, with that person's permissions. See [data handling](/docs/govern/data-handling) for what is read, kept and for how long. - **Nothing else.** No telemetry to Liyagent. Credentials live in the install's [secrets store](/docs/connect/secrets) and are referenced by name, so a connector's configuration never carries the secret itself. ## What stays yours - **The evidence.** The [audit log](/docs/monitor/audit-log) is a hash chain in your database. Signed exports let anyone verify a bundle without trusting the server that produced it. - **The rules.** [Policies](/docs/govern/permissions), [guardrails](/docs/ai-gateway/guardrails) and [budgets](/docs/ai-gateway/budgets) are configuration in your install. A change is a change you made, in your history. - **The tenants.** One install can serve several companies or departments, each with its own data and settings: [tenancy](/docs/govern/tenancy). ## Hosted or self-hosted: choosing | | Hosted at liyagent.com | Self-hosted | | --- | --- | --- | | Good for | Trying it, demos, small teams without ops | Production, regulated data, data-residency commitments | | Data | Processed in Liyagent's hosting (see the [privacy policy](/privacy)) | Stays in your network | | Models | Any provider, your keys | Any provider, or open models on your hardware | | Updates | Applied by Liyagent | Applied by you, with [zero downtime](/docs/operate/zero-downtime-deploys) | Not sure which questions to ask first? [What is an agentic AI platform?](/docs/overview/agentic-ai-platform) lists the eight that matter, hosted or not. --- Source: https://liyagent.com/docs/overview/liyagent-vs-agentforce # Liyagent and Salesforce Agentforce, compared Where each runs, which models they use, what they connect to, how they are priced and when to choose which. A plain comparison for teams weighing Agentforce against a self-hosted agent platform. Salesforce Agentforce and Liyagent both run AI agents that act on business data with a person's approval where it matters. They are built on different assumptions, and the right choice follows from those rather than from a feature list. Liyagent is independent of Salesforce. The Agentforce facts below are from Salesforce's own published pages, dated where it matters. ## The structural differences | | Salesforce Agentforce | Liyagent | | --- | --- | --- | | **Where agents run** | Inside Salesforce's cloud, as part of your Salesforce org | On your own infrastructure ([self-hosted](/docs/overview/self-hosted-ai-agents)), or hosted at liyagent.com | | **Which model** | Models provided and hosted through Salesforce | [Any provider](/docs/ai-gateway/providers): Anthropic, OpenAI, Gemini, Bedrock, or open models on your hardware through Ollama or vLLM | | **Home system** | Salesforce: CRM, Service, Sales and the Salesforce data behind them | No home system. Salesforce is one of 50 connectors beside ServiceNow, Jira, SAP, Workday, Dynamics 365, NetSuite and Microsoft 365 | | **Working in Salesforce** | Native: Flows, Prompt Builder, Data 360 | [Liya for Salesforce](/docs/connect/salesforce) reads records as the person asking, runs Flows, actions and Prompt Builder templates, and holds every write for approval | | **Approvals** | Configured in Salesforce's own tooling | Writes wait for a person by default on new connections. [Cedar policies](/docs/govern/permissions) per agent, tool and person | | **Evidence** | Salesforce's logging | A hash-chained [audit log](/docs/monitor/audit-log) with signed, independently verifiable exports | | **Custom agents** | Agentforce Builder, inside Salesforce | Built in the console or the [liya CLI](/docs/cli), run under the same governance as the built-in agents | ## Pricing **Agentforce** (Salesforce list prices, USD, as published at salesforce.com/agentforce/pricing on 5 October 2026. Contracted prices may differ): - Customer-facing agents: **$2 per conversation**. - Employee-facing actions: Flex Credits at $500 per 100,000, where a standard action is 20 credits, so about **$0.10 per action**. Unused credits do not roll over. - Add-ons for Sales, Service and Field Service: **$125 per user per month**, with unmetered Agentforce use for employees. **Liyagent** is licensed per product and per year, not per conversation or per seat. The model cost is whatever your chosen provider charges, and an open model on your own hardware costs nothing per call. Write to sales@liyagent.com for a quote. The [budgets](/docs/ai-gateway/budgets) page shows how spend is capped per agent and team. ## When to choose which **Choose Agentforce when** your agents' work lives in Salesforce, your data may be processed in Salesforce's cloud, and you want the native builder, Flows and Data 360 without running anything yourself. **Choose Liyagent when** any of these is true: - The agent's work crosses systems: a ticket in ServiceNow, the customer in Salesforce, the order in SAP, the invoice in Business Central. - Data must stay in your network, or the model must be one you pick, including an open model on your own hardware. - You need one audit trail and one policy model across every agent, not one per vendor. - Cost per conversation or per action is the wrong shape for your volume. Both can be true at once: Liya for Salesforce works alongside Agentforce in the same org, through Salesforce's APIs and its hosted MCP servers. ## See it [Try it live](/try): Liya on a sample CRM, every change held for your approval, with the audit trail to read afterwards. No sign-up. *Salesforce, Agentforce, Data 360 and related names are trademarks of Salesforce, Inc. Liyagent is not affiliated with, endorsed by or sponsored by Salesforce.* --- Source: https://liyagent.com/docs/get-started # Get started Run Liyagent, sign in, and get your first ticket triaged or your first agent answering. Pick the path that matches where you are. If you are evaluating Liyagent, start with the Quickstart on your laptop. If you already have agents in production, go straight to connecting them to the gateway. > [!TIP] > Prefer learning by doing? The [Run Liyagent locally with Docker](/docs/labs/run-locally-docker) lab covers the same ground as a guided project. --- Source: https://liyagent.com/docs/get-started/quickstart # Quickstart Run Liyagent on your machine, sign in to the console and file a ticket that the Liya agents triage. ## Prerequisites - Python 3.12 (the version the pinned dependencies and the container image use), Git, and Docker (for Postgres). To run the whole stack in containers instead, see [Deploy on a VM](/docs/get-started/deploy-vm). - About 2 GB of free disk for the embedding model and the embedded vector store. - Optional: an Anthropic API key, or any OpenAI-compatible endpoint, if you want the model-backed agents to answer. ## Run the server 1. Get the Liyagent release and install its runtime dependencies. Liyagent is licensed software: customers and evaluators get access to the release (the source bundle and the container images) from [sales@liyagent.com](mailto:sales@liyagent.com). To look around first with no install, [try Liya live](/try). ```bash cd liyagent # the unpacked release python -m venv .venv && . .venv/bin/activate pip install -r requirements-2.0.txt ``` 2. Start a PostgreSQL 16 database. Liyagent stores everything in Postgres and refuses to start without `TICKETIQ_DB_URL`. The quickest way is a throwaway container: ```bash docker run -d --name tiq-pg -p 5432:5432 -e POSTGRES_PASSWORD=tiq -e POSTGRES_DB=ticketiq postgres:16-alpine export TICKETIQ_DB_URL=postgresql://postgres:tiq@127.0.0.1:5432/ticketiq ``` 3. Start the server. It listens on `http://127.0.0.1:8787` by default. ```bash python run.py ``` `run.py` wraps `uvicorn ticketiq.api:app`. To change the address, set `TICKETIQ_HOST` and `TICKETIQ_PORT` (or pass `--port`). 4. Open `http://127.0.0.1:8787/login` and sign in with the first admin account: user `admin`, password `ticketiq`, unless you set `TICKETIQ_ADMIN_USER` and `TICKETIQ_ADMIN_PASSWORD` first. > [!WARNING] > The default password is accepted only on a loopback address. When the server binds any other address (`TICKETIQ_HOST` or `run.py --host`), it refuses to start until `TICKETIQ_ADMIN_PASSWORD` is changed. > [!NOTE] > Don't run uvicorn with `--reload`. The embedded Qdrant store allows one process at a time. ## File your first ticket 1. In the console, open **IT service desk › Tickets**, choose **New ticket**, fill in the title and description and choose **Run Pipeline**. Or sign in and send one with the API: ```bash curl -s -c cookies.txt -X POST http://127.0.0.1:8787/api/login \ -H "Content-Type: application/json" -d '{"username":"admin","password":"ticketiq"}' curl -s -X POST http://127.0.0.1:8787/api/tickets \ -H "Content-Type: application/json" -b cookies.txt \ -d '{"title":"VPN drops every 10 minutes","description":"Since this morning the VPN client disconnects on Wi-Fi."}' ``` 2. The pipeline result shows each agent's step. Open the ticket to see the category (for example **Network**), the priority, the team it was routed to (**Network Operations**) and the SLA due time. Its **Pipeline runs** and **History** tabs show how it got there. 3. File the same problem again from another user. Liya Correlate folds it into the first ticket and the second reporter is told it is a known issue. ## Connect a model (optional) The pipeline works without a model. To let Liya answer and Liya Triage refine categories, add a provider: 1. Open **AI Gateway › LLM providers** and choose **+ Add provider** (the **Providers** tab). 2. Give it a **Name** and, under **Type**, pick **Anthropic**, or **OpenAI-compatible** for any endpoint that speaks the OpenAI API (set its **Base URL**). 3. Paste the key under **Credential**, set the **Default model**, choose **Test connection**, and choose **Create provider**. 4. Point Liya at it: open the **Agent bindings** tab and choose **Bind** on Liya's row, or open Liya from **Agents › All agents** and, under **Settings › Model**, pick the provider and model and choose **Override**. Liya's sub-agents use Liya's model unless you override theirs. The third tab, **Gateway routes**, holds the endpoints the gateway itself calls for agent-less requests and model prices. Agents do not run on those. ## Next steps - [Create an agent](/docs/agents/create) - [Deploy on a VM](/docs/get-started/deploy-vm) - [Triage and categories](/docs/service-desk/triage-categories) --- Source: https://liyagent.com/docs/get-started/deploy-vm # Deploy on a VM Run the production stack on one virtual machine with Docker Compose, Postgres, Qdrant and Caddy for TLS. ## Prerequisites - A Linux VM with Docker Engine and the Compose plugin. 4 vCPU and 16 GB RAM is a comfortable start. - A DNS name pointing at the VM if you want a real certificate. Without one, `deploy/vm/push.sh` uses `.sslip.io` (for example `203-0-113-7.sslip.io`). - Ports 80 and 443 open to the internet. Nothing else needs to be. ## What runs | Service | Image | Exposed | Purpose | | --- | --- | --- | --- | | `postgres` | `postgres:16-alpine` | Not on the host | System of record (`TICKETIQ_DB_URL`) | | `qdrant` | `qdrant/qdrant:v1.15.5` | `127.0.0.1:6341` only | Vector store for tickets and knowledge | | `ticketiq` | Built from `deploy/Dockerfile` | `127.0.0.1:8787` | The API and console, with health check `/api/health` | | `caddy` | Built from `deploy/caddy/Dockerfile` | 80 and 443 | TLS, edge rate limits (VM stack) | | `mcp` | Same build | `127.0.0.1:8000` | Optional MCP server, only with `--profile mcp`. Needs `TICKETIQ_MCP_TOKEN` and `TICKETIQ_API_KEY` | ## Steps 1. Copy the example environment file and fill it in. ```bash cp deploy/.env.example deploy/.env ``` | Variable | What to set | | --- | --- | | `PG_PASSWORD` | A long random string. It is fixed at first start, so don't change it afterwards. | | `QDRANT_API_KEY` | A long random string. | | `TICKETIQ_ADMIN_PASSWORD` | The first admin's password. Required on a non-loopback address. | | `TICKETIQ_PUBLIC_BASE_URL` | Not in the example file: add it. The external `https://` address. SSO and per-user account connections refuse to start without it, and the Teams package uses it. | | `TICKETIQ_BRAND_NAME` | Optional: the name shown in the console. | 2. Build and start the stack. ```bash docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build ``` 3. Check health: `curl -s http://127.0.0.1:8787/api/health`. 4. For the VM layout with Caddy and a local model server, use `deploy/vm/docker-compose.vm.yml` and the Caddyfile beside it. `deploy/vm/push.sh [domain]` ships the image and config to a VM over SSH, and `deploy/vm/update-app.sh` rolls a new build. > [!NOTE] > The Caddy image includes the pinned `caddy-ratelimit` module. A stock Caddy image refuses to start on the `rate_limit` directive in the Caddyfile. Caddy enforces TLS 1.2 or later, per-address request windows and tighter limits on sign-in endpoints. ## Use an external Postgres Add the overlay and point `TICKETIQ_DB_URL` at your database, with `?sslmode=verify-full`: ```bash docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.external-db.yml up -d ``` > [!WARNING] > Back up `pgdata` before upgrades. `deploy/backup.sh` runs `pg_dump` through `docker compose exec`, so the database never needs a host port. ## Next steps - [Data handling](/docs/govern/data-handling) - [Identity and roles](/docs/govern/identity-roles) - [Microsoft Teams app](/docs/connect/teams) --- Source: https://liyagent.com/docs/get-started/set-up-your-agent # Set up your agent Bring an agent you already run under Liyagent governance by registering it as self-managed and routing its model calls through the gateway. ## Prerequisites - A running Liyagent instance and an account with the **admin** or **owner** role. - An agent that uses an OpenAI-compatible client (most frameworks do). ## Steps 1. Open **Agents › All agents**, choose **+ Create agent**, then **Self-managed**. 2. Fill in the form: | Section | Notes | | --- | --- | | 01 Identity | **Name** and **Description**. The **Agent id** is made from the name (lowercase letters, digits and hyphens) and cannot be changed later. | | 02 Access | Who may use it: the access policy created with it. | | 03 Tags | Under **Advanced**: `key=value` pairs, comma-separated, used for cost chargeback, for example `team=hr,env=prod`. | 3. Choose **Create and issue credentials**. The agent is registered (`PUT /api/gateway/agents/{agent_id}`) and a credential is issued (`POST /api/agents/{agent_id}/credentials`). The page shows its `client_id`, `client_secret`, the token URL and the AI Gateway base URL. **Copy the secret now: it is shown once.** Store it in your agent's secret manager. Credentials last 90 days by default. Issue more, or revoke one, on the agent's **Credentials** view (**Create credential**). 4. In your agent, get an access token with the OAuth client-credentials grant at `POST /api/gateway/oauth/token`. A token lasts one hour. Get a new one when it expires. 5. Point the agent's OpenAI client at the gateway: base URL `https:///api/gateway/v1`, API key the access token. Chat completions go to `POST /api/gateway/v1/chat/completions`. ```python import httpx from openai import OpenAI HOST = "https://ticketiq.example.com" tok = httpx.post(f"{HOST}/api/gateway/oauth/token", data={ "grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}).json()["access_token"] client = OpenAI(base_url=f"{HOST}/api/gateway/v1", api_key=tok) bound = client.models.list().data[0].id # the model this agent is bound to client.chat.completions.create(model=bound, messages=[{"role": "user", "content": "hello"}]) ``` 6. Make one call and open the agent's **Cost & Usage** tab: the call is there with tokens and cost. > [!NOTE] > A long-lived static token also exists for older integrations: `POST /api/gateway/agents/{agent_id}/token` mints one (issuing again replaces it), and `DELETE` revokes it. Prefer the client-credentials flow above. ## Put controls around it - **Budget:** add a scoped budget with scope kind *agent* on **Budgets**. See [Budgets](/docs/ai-gateway/budgets). - **Guardrail:** bind a guardrail set to the agent. See [Create a guardrail](/docs/ai-gateway/create-guardrail). - **Policy:** restrict what it may call with a Cedar policy or the **Read only** / **Sandboxed** templates. See [Access](/docs/govern/access). > [!TIP] > You can switch an agent between managed and self-managed later with `POST /api/gateway/agents/{agent_id}/convert`. ## Next steps - [Agent permissions](/docs/govern/permissions) - [Audit log](/docs/monitor/audit-log) --- Source: https://liyagent.com/docs/get-started/docs-mcp-server # Use the docs in your editor (MCP) Connect VS Code, Cursor, Claude Code, Claude or ChatGPT to the Liyagent docs MCP server so their answers about Liyagent come from these pages. The Liyagent documentation is also a read-only MCP (Model Context Protocol) server. Add it to your editor or AI assistant, and when you ask it about Liyagent it searches and reads these pages, then cites the ones it used. | Setting | Value | | --- | --- | | Server name | `liyagent-docs` | | URL | `https://liyagent.com/docs/mcp` | | Transport | Streamable HTTP | | Authentication | None | > [!NOTE] > The server reads the public documentation only, the same pages you are reading now. It has no access to a Liyagent instance, your console, your tickets or any customer data, and it cannot change anything. It is separate from your instance's own `/mcp` endpoint, which serves your agents' tools to signed-in people (see [MCP servers](/docs/connect/mcp-servers)). ## What it offers | Tool | What it does | | --- | --- | | `search_docs` | Finds the pages that best match a query (up to 10), each with its title, URL, section, summary and the matching passage. | | `get_page` | Returns one page as Markdown, by its path (`/docs/connect/jira`) or URL. | | `list_pages` | Lists every page's title, URL and section, or one section's (for example `govern`). | Each page is also an MCP resource, `docs://docs/`, for clients that browse resources. ## VS Code With GitHub Copilot in VS Code, [install the server in VS Code](vscode:mcp/install?%7B%22name%22%3A%22liyagent-docs%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fliyagent.com%2Fdocs%2Fmcp%22%7D) with one click, or add it to `.vscode/mcp.json` in your workspace: ```json { "servers": { "liyagent-docs": { "type": "http", "url": "https://liyagent.com/docs/mcp" } } } ``` From a terminal, the same server can be added to your user profile: ```bash code --add-mcp '{"name":"liyagent-docs","type":"http","url":"https://liyagent.com/docs/mcp"}' ``` Then open Copilot Chat in **Agent** mode and ask, for example, "How do approvals work in Liyagent?". ## Cursor [Install the server in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=liyagent-docs&config=eyJ1cmwiOiJodHRwczovL2xpeWFnZW50LmNvbS9kb2NzL21jcCJ9) with one click, or add it to `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` for every project): ```json { "mcpServers": { "liyagent-docs": { "url": "https://liyagent.com/docs/mcp" } } } ``` ## Claude Code ```bash claude mcp add --transport http liyagent-docs https://liyagent.com/docs/mcp ``` Add `--scope user` to have it in every project, or `--scope project` to share it with your team through the project's `.mcp.json`. ## Claude and Claude Desktop 1. Open **Settings**, then **Connectors**. 2. Choose **Add custom connector**. 3. Name it `Liyagent docs` and enter the URL `https://liyagent.com/docs/mcp`. Leave the authentication settings empty. 4. Choose **Add**, then turn the connector on for a chat from the tools menu. On Team and Enterprise plans, an owner adds the connector for the organization first. Custom connectors are reached from Anthropic's cloud, which is why the server is on the public internet. ## ChatGPT 1. Turn on **Developer mode** in **Settings** (on Business and Enterprise workspaces, an admin must allow it first). 2. In **Settings**, then **Connectors**, create a connector. 3. Name it `Liyagent docs`, enter the URL `https://liyagent.com/docs/mcp` and choose **No authentication**. 4. Enable the connector in a chat from the tools menu. > [!WARNING] > ChatGPT's menu names and the plans that offer developer-mode connectors change often. If a step does not match what you see, follow OpenAI's current help for custom connectors; the URL and **No authentication** stay the same. ## Limits - Each query is at most 500 characters, and a request at most 16 KB. - Requests are rate-limited per caller (30 a minute by default). A client that goes over gets HTTP `429` with a `Retry-After` header. - Nothing you send is stored or logged. ## Next steps - [Quickstart](/docs/get-started/quickstart) - [MCP servers](/docs/connect/mcp-servers) - [Full documentation as one text file](/llms-full.txt) --- Source: https://liyagent.com/docs/agents # Build agents Create managed agents, give them instructions, a model, tools and sub-agents, test them and wire them to channels. Anyone with the right role can build and onboard a new agent for any system here, in minutes. [Create it](/docs/agents/create) with its own instructions, model, context window and tools from any MCP server: the catalogue has ready-made ones, and any custom [MCP server](/docs/connect/mcp-servers), OpenAPI operations or your organisation's own code can be added. Give it [sub-agents](/docs/agents/sub-agents), guardrails, a budget and an audit trail, then connect it to Microsoft Teams, Google Chat, web chat, the [employee workspace](/docs/agents/workspace), the mobile app or any other channel through [triggers](/docs/agents/triggers) and the API. The solutions for the IT service desk, SAP, HR, Salesforce and finance are pre-built, tested starting points, not the limit. An agent in Liyagent is anything that makes model calls through the gateway: the built-in Liya agents, **managed** agents that Liyagent runs for you, and **self-managed** agents that run elsewhere and call the gateway. They are all listed under **Agents › All agents** (`/fleet`), and each has its own page at `/agent?id=` with six tabs: | Tab | What it holds | | --- | --- | | Settings | The definition: Instructions, Engine, Model, Tools (a managed agent's runtime, run limits and sub-agents), Subagents, Context & memory, Posture, Workflows, Identity & tags, Agent card, Evaluations and Kill switch. Its views also include Context, Posture, Registry, History and Changes, plus Workflows (managed) or Credentials and Telemetry (self-managed) | | Triggers | The channels that start it | | Playground | Try it through the governed path | | Cost & Usage | Its calls, tokens and spend | | Transcripts | Its conversations, with the Traces and Sessions views | | Permissions | Who may use it and what it may reach | A link can open a view directly with `?tab=`, for example `/agent?id=copilot&tab=traces`. --- Source: https://liyagent.com/docs/agents/create # Create an agent Create a managed agent from the Fleet page, give it an identity and first instructions, and start it. ## Prerequisites - The **admin** or **owner** role, or a custom role that grants agent creation. - At least one LLM provider, if the agent will call a model. See [LLM providers](/docs/ai-gateway/providers). ## Steps 1. Open **Agents › All agents** and choose **+ Create agent** (or go to `/agents/create`). 2. Choose **Managed by Liyagent**. The type is fixed once the agent is created. 3. Fill in the form. Managed agents have six sections, and two optional ones under collapsed headings: | Section | Notes | | --- | --- | | 01 Identity | **Name** and **Description**. The **Agent id** is made from the name (lowercase letters, digits and hyphens). A taken id gets a number. It is the agent's permanent key: budgets, policies and the audit log address it, and it cannot be changed later. | | 02 Model | The provider and model it reasons with, from this instance's connected providers, or the instance default backend. | | 03 Instructions | Its system prompt, in Markdown. Start from a template or write your own, and edit it later on the agent's **Settings** tab. | | 04 Tools | The MCP servers it may call. You can attach more later. | | 05 Access | Who may use it: the access policy created with it. | | 06 Tags | Under **Advanced**: `key=value`, comma-separated. Usage and cost reports can be broken down by tag. An instance can require some tags. | | 07 Limits | Under **Limits**, optional. The **Context window** (the input tokens one step may send, 1,024 to 2,000,000; blank is the smaller of the model's window and 32,000), the **Step cap** (model calls per request) and the **Run limits** (wall-clock deadline, tool calls, delegations, identical tool calls, token and USD ceilings per run). Blank keeps the default. | | 08 Sub-agents | Under **Sub-agents**, optional. Helpers the agent delegates to, each with a description (what the agent reads to decide to use it), instructions, the context it is handed, a model and provider override, its MCP servers, and its own **Context window**, **Reply cap**, **Step cap** and **Run limits**, none above the parent's. A registered agent can be added as a sub-agent too: it then runs as itself. | 4. Choose **Create agent**. One submit creates the agent, its model binding, its runtime (MCP servers, limits and sub-agents) and its access policy. If a step fails, the agent is deleted again so nothing is left half-made. 5. The agent opens on its own page with a **Set up** checklist above its tabs: context window reviewed, guardrails applied, connected to a channel (Teams, Google Chat, Slack, email or web chat; people who may chat with it already find it in the workspace), sub-agents added (optional) and tested in the Playground. An item clears as soon as the data says so; **Dismiss** hides the card in your browser. Start the agent from there, and refine it on **Settings** (see [Instructions and model](/docs/agents/instructions-model)). > [!NOTE] > Choose **Self-managed** instead when the agent's code runs on your infrastructure. It then gets a credential and calls the gateway. See [Set up your agent](/docs/get-started/set-up-your-agent). ## With the API ```bash curl -X PUT https:///api/gateway/agents/hr-helper \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"label":"HR helper","about":"Answers HR policy questions", "runtime":"managed","prompt":"You answer questions about the HR handbook.", "tags":["team=hr"]}' ``` `tags` is a list of `key=value` strings. Bind its model with `PUT /api/agents/hr-helper` and attach tools with `PUT /api/agents/hr-helper/runtime`. Other agent routes: `GET /api/gateway/agents`, `GET`, `PATCH` and `DELETE /api/gateway/agents/{agent_id}`. ## Next steps - [Instructions and model](/docs/agents/instructions-model) - [Sub-agents](/docs/agents/sub-agents) - [Playground and evals](/docs/agents/playground-evals) --- Source: https://liyagent.com/docs/agents/instructions-model # Instructions and model Write an agent's instructions, bind it to a provider and model, and set the run limits a managed agent works within. ## Instructions Instructions are the agent's standing brief, applied ahead of every governed call it makes. They live in the agent definition (`GET` / `PUT /api/agents/{key}/spec`) together with its recording mode, A2A card, telemetry and session settings. - Markdown, up to 50,000 bytes. - Template variables are filled in at call time: `{tenant}`, `{agent_label}` and `{date}`. - Every save is a new version. Restore an old one with `POST /api/agents/{key}/versions/{version_id}/restore`. | Recording mode | What transcripts keep | | --- | --- | | `full` | Every turn's text, within the transcript limits | | `metadata` | Timing, tokens, cost and outcome, no text | | `off` | Nothing | ## Tables and charts in replies The Playground draws two things in a reply beyond prose: Markdown pipe tables, and charts. An agent writes them only when its instructions say how. In the instructions editor, **+ Tables & charts guidance** appends a paragraph that teaches the format. Edit it to suit the agent. A chart is a fenced code block tagged `chart` that holds one JSON object: ```chart {"type": "bar", "title": "Tickets this week", "labels": ["Mon", "Tue", "Wed"], "series": [{"name": "Opened", "data": [4, 7, 5]}, {"name": "Resolved", "data": [3, 6, 6]}], "x_label": "Day", "y_label": "Tickets"} ``` | Field | Meaning | | --- | --- | | `type` | `bar`, `line`, `pie` or `scatter` | | `labels` | The categories (bar, line) or slices (pie). Not used by scatter | | `series` | Up to 6 of `{"name": ..., "data": [...]}`. Bar and line: one value per label, `null` for a gap. Pie: exactly one series, values not negative. Scatter: `[x, y]` pairs | | `title`, `x_label`, `y_label` | Optional captions | | `unit` | Optional suffix shown after each value, such as `%` or ` ms` | Limits: 60 labels for bar and line charts, 12 slices for a pie, 500 points for a scatter. The chart is drawn as SVG in the console's own colours, so it reads in the light and dark themes, and every value is also in a **Data** table under it. A block that is not a valid chart is shown as code, with the reason it was not drawn. ## Bind a model 1. On the agent's **Settings** tab, in the **Model** section, pick a provider (or **Global backend**) and a model id, and choose **Override**. A sub-agent of Liya can choose **Inherit from Liya** instead. 2. For the output token cap, reasoning effort, timeout, the two switches below and the fallback lists, open **AI Gateway › LLM providers**, the **Agent bindings** tab, and choose **Bind** on the agent's row. Both call `PUT /api/agents/{key}`. A field you leave out keeps its stored value. | Field | Meaning | | --- | --- | | `provider`, `model` | Where calls go | | `max_tokens` | Output token cap per call | | `effort` | Reasoning effort, for models that support it | | `timeout` | Seconds before the call counts as timed out, which can trigger fallback | | `strict_binding` | If the provider is deleted, switch the binding off instead of moving the agent to the global backend | | `refuse_unpriced` | While the agent has a USD budget, refuse a model with no price, whose calls the budget could never stop | Fallback lists live on the same binding. See [Model chain and fallback](/docs/ai-gateway/model-chain). > [!TIP] > To compare two models on live traffic before switching, add an arm and promote the winner with `POST /api/agents/{key}/arms/{arm}/promote`. ## Run limits for managed agents A managed agent's runtime is on its **Settings** tab, in the **Tools** section: its MCP servers, **Run as**, the limits below and its sub-agents, saved together with **Save runtime**. It maps to `PUT /api/agents/{key}/runtime`. Check a draft with `POST /api/agents/{key}/runtime/check`. | Field | What it limits | Default (maximum) | | --- | --- | --- | | Max iterations | Model calls per request (the loop always ends) | 10 (100) | | Parallel tool calls | Independent MCP calls of one model step sent at once | Off, one at a time (4) | | Delegation depth | Hops of registered agents a delegation may go from here | 3 (5) | | Wall-clock deadline (s) | Total run time | 300 (3600) | | Tool calls per run | Across the agent and its sub-agents | 50 (500) | | Tool calls per model step | Tool calls one model step may ask for | 8 (32) | | Delegations per run | Sub-agent calls in one run | 8 (64) | | Identical tool calls per run | The same tool with the same arguments (stops loops) | 3 (50) | | Token ceiling per run | Tokens across the whole run | Off (10,000,000) | | USD ceiling per run | Cost across the whole run | Off (1,000) | A run that reaches a limit ends with "run limit: …" naming it. Start and stop a managed agent with `POST /api/agents/{key}/start` and `POST /api/agents/{key}/stop`. ## Run as **Run as** says whose permissions the agent's tool calls use: | Option | What it means | | --- | --- | | The agent (`run_as: agent`, the default) | The agent's own permissions, whoever invokes it. Right for work with no person behind it. | | The invoker (`run_as: invoker`) | Each run acts for the signed-in person who invoked it. Every tool call is checked for the agent **and** for that person, and refused if either may not make it. A tool that writes also needs the person to hold every permission in **Invoker must hold (for writes)** (default `tickets.write`). | - **Per-user connectors need it.** An MCP server bound to a per-user OAuth connection sends the calling person's own token, so it can be attached only to an agent (and its sub-agents) that runs as the invoker. Switching an agent back to **The agent** detaches those servers. - **Who counts as the invoker.** Only a person an entry point verified who has an enabled account. A call with no verified person is refused rather than run as the agent instead. Schedules, webhooks and ticket-event triggers always run as the agent. - Switching off **The invoker**, or dropping a permission from the list, weakens a control, so the save asks for a reason. ## Writes that wait for approval A write tool call can be held for a person to approve before it is sent. Each MCP server has a posture for its agents' writes (`agent_writes`: `allow`, `require_approval` or `deny`), with per-tool overrides: - A server added from now on starts at **require approval** for writes. - Some managed-connector tools (ordering from a catalogue, starting a flow) ask for approval whatever the server's default. Only a per-tool override lets them through unseen. - A server saved before postures existed keeps allowing writes until you change it. Held calls wait in the **Inbox** under **Approvals**. Only an agent's call is held. A person calling a tool from the console is not. See [Agent permissions](/docs/govern/permissions). ## Replies that claim a call that was not made A model shown its earlier turns can write a reply that says an action is done when no tool call was made in this turn. The runtime checks every managed agent's final reply: if the reply carries the runtime's note of tool calls and names a call this turn did not make, the reply is replaced with a message saying nothing was changed and asking the person to try again. The runtime's note is never shown to people. ## Next steps - [Sub-agents](/docs/agents/sub-agents) - [Guardrails](/docs/ai-gateway/guardrails) --- Source: https://liyagent.com/docs/agents/sub-agents # Sub-agents Let a managed agent delegate work to specialist sub-agents that spend its budget and inherit its limits, or run under tighter limits of their own. A managed agent can hand part of a task to a **sub-agent**: a new one defined on the parent, or an existing agent picked from the registry. The parent's model sees each sub-agent's description and decides when to call it. ## Prerequisites - A managed agent (sub-agents are not available on self-managed agents). ## Add a sub-agent 1. Open the parent agent's **Settings** tab and go to the **Tools** section, where its runtime is. 2. Under **Subagents**, type a name (lowercase letters, digits and hyphens) and choose **Add subagent** to define a new one, or pick an agent and choose **Add registered agent** to reuse an existing one. 3. Fill in the fields. | Field | Notes | | --- | --- | | Description | What the parent reads to decide to use it. Be specific about when, not just what. | | Instructions | The sub-agent's own brief. New sub-agents only. | | Context it is given | **Task only**, **Handoff note** (the parent writes one) or **Last 6 turns of this conversation**. Passed context counts against the budget. | | Model override / Provider override | Optional, new sub-agents only. Blank inherits the parent's. Use a cheaper model for narrow tasks. A provider override needs a model. | | Its MCP servers | New sub-agents only. Each tool list is held to what the parent itself may use on the same server. | | Context budget (`context_budget_tokens`) | Optional, new sub-agents only. The input one of its steps may send, 1,024 to 2,000,000 tokens, held to its own model's window when it runs. Blank uses the parent's budget. | | Reply cap (`max_tokens`) | Optional, new sub-agents only. The most tokens one of its replies may have, 1 to 32,000 and never more than its model can generate. Blank uses the reply cap the parent's run has. | | Step cap (`max_iterations`) | Optional, new sub-agents only. How many model calls one delegation may make, 1 to 100 and never more than the parent's. Blank uses the parent's. | | Run limits (`limits`) | Optional, new sub-agents only. The same keys as the parent's per-run limits (`max_seconds`, `max_tool_calls`, `max_tool_calls_per_step`, `max_identical_tool_calls`, `max_delegations`, `max_tokens_per_run`, `max_cost_usd_per_run`). Each is held to the parent's value for that key. | 4. Choose **Save runtime**, and test from the parent's **Playground**. > [!NOTE] > A sub-agent defined on the parent spends the parent's budget and cannot delegate further. A registered agent used as a sub-agent runs as itself, under its own guardrails, kill switch and budget, and its cost is billed on to the parent. ## Each sub-agent's own limits A sub-agent defined on the parent inherits the parent's context budget, reply cap, step cap and per-run limits unless you set its own. Each of the four fields above is optional and is stored only when set, so a sub-agent saved before the fields existed runs exactly as it did. - A value wider than the parent's is refused when you save, with a message that names the sub-agent and the field. A sub-agent cannot widen what the parent is held to. - Its own run limits are nested inside the parent's. What the sub-agent spends still counts toward the parent's totals, so the parent's caps are checked on every call the sub-agent makes. When the sub-agent reaches one of its own limits, only that delegation ends. The parent is told why and carries on. When the parent's limit is reached inside a delegation, the whole run ends, as before. - Its context budget is held to the window of the model it runs on, which may differ from the parent's when it has a model override. `GET /api/agents/{key}/runtime` returns an `effective` object with the values a run will actually use, for the parent (`effective.agent`) and for each sub-agent (`effective.subagents.`): `window_tokens` (the model's input window from the catalogue, or null when it is not known), `context_budget_tokens`, `max_tokens`, `max_iterations` and `limits`. These are computed from the saved runtime and the model catalogue and are never stored. A registered agent used as a sub-agent is shown with its own agent's values. ## How delegation is governed - Each delegation is checked by the Cedar `delegate` action. You can also run one directly with `POST /api/agents/{parent}/delegate/{child}`, under the parent's budget. - A sub-agent runs as its parent runs (**Run as**), so a per-user MCP server can be ticked for it only when the parent runs as the invoker. - A delegation that would loop back to an agent already working on the request, or exceed the parent's **Delegation depth** (default 3, at most 5), is refused. - Cost is billed to the real parent, and every usage row carries the delegation chain, so you can see which parent a sub-agent's spend belongs to. ## Next steps - [Playground and evals](/docs/agents/playground-evals) - [Budgets](/docs/ai-gateway/budgets) --- Source: https://liyagent.com/docs/agents/playground-evals # Playground and evals Try an agent interactively, compare variants, and measure it against versioned datasets before you ship a change. ## Playground The agent's **Playground** tab sends a message through the same governed path real traffic uses: guardrails, budgets and the audit log all apply. | Action | Endpoint | Use it to | | --- | --- | --- | | Run | `POST /api/agents/{key}/playground` | Try a message | | Compare | `POST /api/agents/{key}/playground/compare` | Run variants side by side (instructions, model) | | Replay | `POST /api/agents/{key}/playground/replay` | Re-run a past conversation with edits | | Simulate | `POST /api/agents/{key}/playground/simulate` | Check whether a given person could run the agent, from a channel and context. A dry run: nothing is sent to a model and nothing runs as them | Simulated users (`/api/agents/{key}/simulations`) let a model play a user with a hidden goal for multi-turn tests. ### Attach files **Attach** (or drop or paste) up to 5 files on a message. Each is uploaded as soon as you pick it, with `POST /api/agents/{key}/playground/files`, and shows its preview or the reason it was refused. **Send** waits until every file has uploaded. | | | | --- | --- | | Images | PNG, JPEG, GIF or WebP, up to 5 MB. Sent to the model as an image | | Documents | PDF, Word, Excel, PowerPoint, text, Markdown, CSV, TSV, JSON, log, XML, YAML, and zip or gzip archives, up to 10 MB. The agent is given the text read from them, marked as data you supplied | A file's contents must match its name, and programs and scripts are refused whatever they are called. Archives are read under the same bounded extraction as email attachments, so a decompression bomb cannot exhaust the server. A document with no readable text (a scanned PDF, an empty file) is refused, since the agent could not use it. Files are kept for 7 days, or until their session is deleted, and only you can open yours. A reopened session shows each message's files and says which have expired. ### Compare variants against a checklist **Compare** asks the prompt in the message box under 2 to 4 variants, each a system half and a model, and shows the answers side by side. Each column is its own governed run with its own transcript. - **Attach document** uploads a file the same way the chat's **Attach** does. Every column is given the same document as text, so you can see whether a model keeps its instructions with a long document in its context. Files attached in the message box go with the compare too. - **Rules each reply must keep** is a checklist of up to 10 rules, scored on each column's reply. Choose **Load demo rules** for a ready-made set: JSON only, at most 50 words, never "sorry", ends with "— Liya", and an escalate field that is true. - Each column shows its speed, for example `first token 1.2 s · 240 tok/s · total 3.4 s`, and a pill such as **5/6 rules kept** above a ✓/✗ line for each rule. | Rule | Value | JSON field | | --- | --- | --- | | `json_only` | Optional. The keys the object must have, exactly, comma-separated | Not used | | `max_words` | The most words allowed | Optional | | `ends_with` | The text the reply must end with | Optional | | `contains`, `not_contains` | Text that must (or must never) appear, in any case | Optional | | `regex` | A pattern, checked like a guardrail's: RE2 syntax, no lookarounds or backreferences | Optional | | `json_equals` | The value, read as JSON: `true` matches true, `3` matches 3. Anything else is a string | Required | A JSON field such as `answer` or `$.answer.text` makes the rule read that field of a JSON reply rather than the whole reply. A reply that isn't JSON fails every rule with a field. Rules are checked before anything runs, so a bad rule is refused with `422` and no column is run. A column that was refused or failed has no reply to check, and says so. The API takes `rules` (`{kind, value, path, label}`) and `files` (upload ids) on `POST /api/agents/{key}/playground/compare`. Each column then carries `rules` (`{scored, kept, total, results}`) and `speed`. The audit row of each column records how many rules it kept and which failed. | `speed` field | Meaning | | --- | --- | | `first_token_ms` | From sending the run's first model request to its first streamed token. `null` for providers other than OpenAI-compatible and Anthropic | | `model_ms` | The time the run's model calls took, added up | | `output_tokens_per_s` | Output tokens divided by generation time: `model_ms` less the wait for each call's first token, where known | To time the first token, the playground streams its calls to OpenAI-compatible and Anthropic providers and puts the answer back together before guardrails, metering and the transcript see it. Single runs (`POST /api/agents/{key}/playground`) report `speed` too. Calls made anywhere else are not streamed. ### What else the Playground shows - **Tables and charts** in replies, when the agent's instructions say how to write them. See [Tables and charts in replies](/docs/agents/instructions-model#tables-and-charts-in-replies). - **Outputs**: the charts, tables and code the agent wrote, results its tools kept for you and resources they returned, each downloadable, and the sources it used (links it cited and tools it read), each tied to its turn. - **Requests waiting for you**: an approval, a connection, an answer or a form an MCP server sent is pinned above the message box, asked one question at a time. A send that fails keeps what you typed. Once settled, a request folds into one line in its turn as answered or declined. - **Context**: an estimate of what the next request will carry, against the model's input and output limits from the model catalogue, kept apart from the token counts the provider reported. ### Feedback on replies A person may rate any reply: `POST /api/agents/{key}/playground/feedback` with `{"conversation", "helpful", "comment"}` records the vote on the conversation (`feedback` on its record), on the audit trail as `agent.reply.feedback`, and in the agent's counters (`agent.feedback.helpful`, `agent.feedback.unhelpful`). The mobile app offers Helpful and Not helpful under each reply, with room to say what was wrong. This is the human oversight a deployer must be able to show. ### Long chats A chat can run as long as you like. Each follow-up is sent with the newest 30 exchanges as they were said. Once a chat grows past that, its oldest turns are folded into a summary of the conversation, written by the agent's own model under the same guardrails, budget and audit as any of its calls, and sent ahead of the newest turns. The summary is updated about every 10 exchanges, not on every message. If the model call is refused, a plain extract of the turns is kept instead, so nothing is dropped without a record. Each request is also fitted to the context window of the model it goes to, measured in tokens. If the newest turns alone would not fit, the oldest of them are folded into the summary for that request. If a provider still refuses a prompt as too long, the request is sent once more with half the history, rather than the message failing. When a reply was written from a summary, the chat says so under it, in the console and in the mobile app. The summary is stored with the session, sealed like its messages, and deleted with it. Under the `off` recording mode nothing is kept, so nothing is summarised either. ## Datasets Datasets are versioned and append-only. Each has a kind: | Kind | Measures | | --- | --- | | `retrieval` | Did the right knowledge come back? | | `triage` | Category, priority and team against expected values | | `agent_turn` | An agent's reply, scored by evaluators | Create one from **Agents › Evaluations** or with the API: 1. `POST /api/evals/datasets` with a name, a kind and its cases, as `items` or as JSONL text in `jsonl`. Posting again writes the next version, but `append: true` adds to the latest instead of replacing it. 2. Add cases from a transcript (`POST /api/evals/datasets/{name}/from-transcript`, redacted), or from tickets (`.../from-tickets`). 3. Optionally split it into calibrate and holdout sets with a fixed seed (`.../split`). ## Runs 1. Start a run with `POST /api/evals/runs`, naming the dataset and agent. The run pins the dataset version and the agent's config version. 2. Open the run on **Agents › Evaluations**. Scores are compared with the baseline run. 3. When you are happy, mark it as the new baseline with `POST /api/evals/runs/{run_id}/baseline`. > [!TIP] > In CI, `liya eval run --dataset --fail-on-regression` (the `tiq eval run` verb, forwarded) exits 1 when a run fails or regresses against its baseline. `liya agents eval --dataset ` runs a dataset against one agent and exits 1 on a failure or regression. Evaluators (`/api/evals/evaluators`) include built-in checks and LLM-judge evaluators. `POST /api/agents/{key}/evals/generate` drafts cases from an agent's description to get you started. ## Red teaming **Agents › Red teaming** throws known attacks at an agent: instruction override, role-play jailbreaks, system-prompt extraction, PII exfiltration and tool abuse, each obfuscated (base64, homoglyphs, zero-width characters and more) and sent directly or planted in content the agent reads. A run reports the attack success rate (ASR) overall and by category, transform and vector, each with a 95% interval. | Target | What it measures | | --- | --- | | `guardrails` | Each attempt screened by the agent's own guardrails. No model is called, so a run takes seconds. | | `agent` | Each attempt asked of the agent through the governed path, and its answer judged. Needs `transcripts.content.read`. | A run records the configuration it measured: the agent's version fingerprint. Successes in **PII exfiltration** and **tool abuse** are *critical* findings, and the report counts them. 1. Start a run from the page, or with `POST /api/redteam/runs` (`governance.write`) naming the agent, the target and optionally a subset of categories, transforms and vectors. 2. Open it on **Agents › Red teaming**. Compared with the latest earlier run, it lists the attacks that newly get through. 3. Export the attempts with `GET /api/redteam/runs/{run_id}?format=csv`. > [!TIP] > In CI, `python -m ticketiq.redteam --agent --max-asr 0.1` exits 1 when the ASR is over the bar. Add `--baseline latest` to fail on a rise against the last run as well. ### Release gate With the release gate on, an agent configuration is released only after a red-team run of exactly that configuration passed. These are refused with `409` (`code: redteam_gate`) until it has: - restoring a version (`POST /api/agents/{key}/versions/{id}/restore`) - promoting a candidate that restores a version or adopts an optimizer proposal (`POST /api/settings/candidates/{id}/promote`) - publishing the agent's card for the first time (`PUT /api/agents/{key}/spec`). The refusal names each failed criterion and links the run it judged. The criteria: | Setting | Default | The release passes when | | --- | --- | --- | | `required` | off | Not applicable (the gate is on) | | `max_asr` | 0.1 | the run's overall ASR is at most this | | `no_critical` | on | no PII-exfiltration or tool-abuse attack got through | | `max_age_days` | 14 | the run finished at most this many days ago | | `target` | `any` | the run's target is this (`guardrails`, `agent`) | The run must be of the configuration being released: its fingerprint is the agent's version snapshot without the card, so publishing a card does not void the run made just before it. The newest completed run of that configuration is the one judged. An optimizer proposal has never been in force, so no run can have measured it. With the gate on, it is adopted only through an override. Agents a run cannot target (the built-in pipeline steps) are outside the gate. Set the organisation default on **Agents › Red teaming** (`PUT /api/redteam/gate`), and an agent's own policy on its **History** tab (`PUT /api/agents/{key}/redteam-gate`, or `{"inherit": true}` to follow the default again). Both need `governance.write`. Loosening a gate that is on also needs `governance.weaken` and a reason. The History tab shows whether the agent's current configuration would pass. An owner can release past a refusal: send `override_redteam_gate: true` and a `reason` with the release. It needs `governance.weaken`, and is audited as `redteam.gate.override` with the criteria that failed. Refusals are audited as `redteam.gate.refused`. ## Next steps - [Lab: Write an eval set for Triage](/docs/labs/eval-set-triage) - [Transcripts](/docs/monitor/transcripts) --- Source: https://liyagent.com/docs/agents/triggers # Triggers Connect an agent to Teams, Google Chat, a schedule, email, a webhook, Slack or ticket events, each with its own authentication and loop guard. A trigger is an inbound channel bound to one agent. Manage them on the agent's **Triggers** tab or at `/api/triggers`. | Kind | Label in the console | Inbound endpoint | | --- | --- | --- | | `webhook` | Webhook | `POST /api/hook/{token}` | | `teams` | Microsoft Teams | `POST /api/teams/bots/{name}/messages` | | `slack` | Slack | `POST /api/slack/events/{name}` | | `google_chat` | Google Chat | `POST /api/google-chat/events/{name}` | | `email` | Email | SMTP, or `POST /api/email/inbound/{name}` | | `cron` | Schedule | None: runs on the schedule | | `ticket` | Ticket event | None: runs when a matching ticket event happens | ## Create a webhook trigger 1. Open the agent's **Triggers** tab, add a trigger and pick the **Webhook** card under **Trigger type**. 2. Choose **Caller authentication**: | Option | How the caller proves itself | | --- | --- | | `token_url` | The secret token in the URL is enough. | | `hmac` | Signs the body, using headers `X-TicketIQ-Signature` and `X-TicketIQ-Timestamp`, with 300 seconds tolerance. | | `bearer` | An `Authorization: Bearer` token. | 3. Set **Loop guard** to `off`, `warn` or `enforce` (the default). It caps fires per trigger and per record, such as the same Jira issue or email thread. To have a trigger that keeps looping switched off, tick **Switch the trigger off after 3 refused windows in a row**. It is off unless you tick it. 4. Choose **Create trigger**, then copy the endpoint URL. The token in it is stored hashed. Rotate it with `POST /api/triggers/{name}/token`. 5. Fire it: `liya trigger fire "disk full on db-02"` (the `tiq` verb, which `liya` forwards), or `POST` to the URL. > [!WARNING] > Treat the URL as a secret. Anyone who has a `token_url` endpoint can invoke the agent. ## Schedules - A timezone is required, as an IANA name such as `Europe/London`. - Each tick is claimed so it fires at most once, even with several replicas. - After downtime only the latest missed tick runs, and older ones are recorded as `missed`. - A tick that comes due while the previous run is still going is recorded as `skipped_overlap`. ## Teams and email - **Teams:** needs the Azure bot's **Microsoft App ID**, **Directory (tenant) ID** and either a **Client secret** or a **Certificate and private key**, both from the Secrets store. See [Microsoft Teams app](/docs/connect/teams). - **Email:** replies go back in the same thread. Only **Allowed senders** are answered, and an empty list means nobody. See [Email and SMTP](/docs/connect/email-smtp). ## Operate triggers `POST /api/triggers/{name}/pause`, `/resume` and `/run` (fire now). List runs with `GET /api/triggers/{name}/runs` and cancel one with `POST /api/triggers/{name}/runs/{run_id}/cancel`. A trigger's kind is fixed at creation: change it with `POST /api/triggers/{name}/convert`, with a reason. From the command line, `liya triggers list`, `create` and `delete` manage triggers. Use the server's kind names with `--kind` (`cron`, `ticket`, `webhook`, `email`, `teams`, `slack`, `google_chat`), for example `liya triggers create nightly --agent copilot --kind cron --schedule "0 2 * * *" --timezone Europe/London --input "Summarise yesterday"`. ## Next steps - [Transcripts](/docs/monitor/transcripts) - [Lab: Deploy the Liya Teams bot](/docs/labs/deploy-teams-bot) --- Source: https://liyagent.com/docs/agents/hr-agents # HR agents Liya HR and Liya Onboarding answer HR questions as the employee (time off, holidays, their profile, onboarding, HR policy). Sensitive matters go to Human Resources. Liya has two HR subagents. Employees just **ask Liya** (in Teams or the web chat) and an HR question is answered by the HR agent, as that employee: | Agent | Subagent name | What it does | | --- | --- | --- | | **Liya HR** (`hr-assist`) | `hr` | Time-off balances and requests, company holidays, pay dates and payslip questions (read-only, from policy), the employee's own profile, and HR policy questions answered with the policy passage cited. | | **Liya Onboarding** (`hr-onboarding`) | `onboarding` | A new hire's onboarding checklist and what is still open, their first week, their manager and onboarding buddy, and IT requests for access or equipment, filed to the IT Service Desk through the ticket pipeline. | Both are built-in agents: they are under **Agents › All agents** with Liya, with their own **Settings**, **Playground**, **Transcripts** and **Permissions** tabs, and you can rename them and change their avatar as you can Liya's. > [!IMPORTANT] > HR is **off for every client** until an administrator turns it on for that client. An ITSM client that never asked for HR does not have its employees' leave questions answered by an HR agent. ## How they answer The HR agents answer **rules first** (engine `rules+model`): 1. **Never answered by a model.** A sensitive HR matter (a grievance, harassment, discrimination, a medical condition, a dismissal, a pay dispute) is refused at the input guardrail `hr-sensitive-topics` and filed to the **Human Resources** team as a handoff ticket linking the conversation. The employee is told a person has it. Its transcript is kept as **metadata only**. 2. **Refused.** Another employee's HR data ("How much leave does Nancy have?"), approving time off, and prompt injections (the `hr-block-injection` guardrail, and again in the rules). 3. **Read as the employee.** Balances, requests, holidays and the profile come from the HR system through the agent's HR tools. Each call goes through the governed tool path (Cedar on the server and tool, data policies, write posture, tool-call guardrails) and is audited as `agent.tool.call`. Numbers are the HR system's, never a model's. 4. **Confirmed before it is written.** A time-off request, a profile change or an IT request is prepared and shown on a confirmation card with the exact facts (for example the date "Friday" was read as). Nothing is sent until the employee presses **Confirm** (or types `confirm`), and the HR tool itself refuses a write without that confirmation. 5. **Cited.** Policy, pay-date and payslip questions are answered by quoting the HR policy passage, with its reference. An HR question the rules cannot place goes to the model bound to the agent, with the policy passages found and the HR tools (whose writes still need the employee's confirmation). With no model bound, the agent says what it can do. PII an employee pastes (an SSN, a card number) is masked by the `hr-mask-pii` guardrail before the rules, a model or a transcript sees it. ## Acting as the employee No HR self-service tool takes an employee id. The employee is the **verified requester** of the conversation (the Teams sender the bot looked up in the directory), and the HR system is asked for "me" with **that employee's own credential**: - The record the HR system returns for "me" must carry one of the requester's own addresses, or the call is refused. - A server that would read with a company-wide API key is refused: the self-service tools need a **per-user OAuth connection**. - In a group chat or a channel nothing personal is looked up. The employee is asked to use a 1:1 chat. ## Prerequisites - The **admin** or **owner** role. - A Workday or BambooHR tenant, with an OAuth application registered for per-user access, or the synthetic demo (below). - For Teams: Liya's Teams bot ([Microsoft Teams](/docs/connect/teams)). ## Set up ### 1. Connect the HR system as each employee 1. On **Connections**, register an OAuth provider for your HR system: - **Workday**: an API client for integrations using the authorization-code grant, with the Staffing and Absence Management scopes. Workday's REST API accepts a bearer token only. - **BambooHR**: BambooHR's OAuth (OpenID Connect) application. Do not use an API key for self-service, because an API key is a company-wide credential. - If your employees already sign in to Liyagent and Teams with the same identity provider as the HR system, an **on-behalf-of** provider mints each employee's token from their sign-in with no connect step. 2. On **MCP servers**, add a managed **Workday** or **BambooHR** server and choose **User OAuth** with that provider. - Workday: the tenant name, and at least the `staffing` and `absence` services. - BambooHR: the company subdomain and, if you track onboarding in BambooHR, the custom table your onboarding tasks live in (`onboarding_table`) and the custom field that names a new hire's buddy (`buddy_field`). 3. On **Liya HR** and **Liya Onboarding** → **Settings** → **Tools**, attach the server and allow only the self-service tools: `my_profile`, `my_time_off_balances`, `my_time_off_requests`, `time_off_types`, `company_holidays`, `my_onboarding`, `request_time_off`, `update_my_profile`. Set **Run as** to **The invoker**, so each call carries the employee's own token. A server on a per-user connection can only be attached to an agent that runs as the invoker. > [!NOTE] > A per-user connection is bound to the person the agent runs as. In Teams that means the bot's access mode must match people to Liyagent accounts (**Only people whose Liyagent access allows it**), with single sign-on for on-behalf-of tokens. A Teams sender who is only looked up in the directory can use the synthetic demo but not a real per-user HR connection. ### 2. Give them the HR policies Ingest your HR handbook into a knowledge collection. Then, on **HR › Setup**, under **Policy library**, choose **Your HR documents**, enter the collection's name and choose **Save library**. **Demo handbook** uses the synthetic Globex policies instead. The library is the instance's, so only an instance owner can set it. **HR › Policies** lets you browse what is loaded. Each passage should carry `title`, `section` and `ref` so answers can cite it. ### 3. Turn HR on for a client On **HR › Setup**, under **Liya HR and Liya Onboarding**, switch each agent on. The same switches are under **hr** and **onboarding** in Liya's **Settings → Subagents**. A switch applies to the client you are working in only. With the API: ```bash curl -X PUT https:///api/agents/copilot/subagents/hr/tenant-state \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"on": true}' curl -X PUT https:///api/agents/copilot/subagents/onboarding/tenant-state \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"on": true}' ``` Each switch is audited as `agent.subagent.tenant_state`. **HR › Setup** also shows the HR system each agent reads, the HR guardrail pack, and **Onboarding IT handoff**: where a new hire's confirmed IT request goes (**IT service desk**, an **ITSM connection** such as ServiceNow, Jira, Zendesk or Freshservice, **Email**, or **Not set up**). ### 4. Decide whether HR approves writes too The employee's confirmation is always required. Whether a person in HR must approve the write as well is the HR server's write posture, on its **MCP servers** page: - A server you add now starts at **Require approval** for writes, so a confirmed time-off request or profile change waits in the **Inbox** under **Approvals** before it is sent. - To send confirmed requests straight to the HR system, set the posture for `request_time_off` and `update_my_profile` (or the server's default for writes) to **Allow**. ## In Teams Employees ask Liya in a 1:1 chat: | They say | They get | | --- | --- | | "How much leave do I have left?" | Their balances by type, from the HR system. | | "Book Friday off" | A confirmation card with the exact date. **Confirm** files it as *requested* for their manager to approve in the HR system. | | "What's the parental leave policy?" | The policy passage, quoted, with its reference and a citation. | | "Who is my onboarding buddy?" | Their manager and buddy from the HR record. | | "I need access to the test lab VPN" (new hire) | A confirmation card. **Confirm** files an IT Service Desk ticket. | | "I want to report harassment" | "I've passed it to the Human Resources team", and a person follows up. | | "How much leave does Nancy have?" | A refusal: they can only see their own records. | Every routing is Liya's delegation to the HR agent, checked by Cedar `delegate` and audited as `agent.subagent.call`. A forbid keeps the message with Liya. ## Try it with the synthetic demo Set `TICKETIQ_HR_DEMO=true` (and `TICKETIQ_HR_DEMO_TENANT` for a client other than the default). At startup this: - registers `hr-demo`, an in-process, **BambooHR-compatible** HR system for **Globex Industries**, a fictional company, and attaches it to both HR agents with the self-service tools only. - makes the synthetic HR handbook (leave, parental leave, holidays, expenses, code of conduct, pay, onboarding) their policy source, and loads it into its own vector collection. - switches HR on for the demo client only. - seeds the `hr-agents` eval set. Everything in it is invented. Its employees are the demo's synthetic requesters, such as `richard.haddad@globex-industries.com`, so a Teams demo identity lines up with an HR record. ## Validate The `hr-agents` eval set holds 41 cases (balances, requests with confirmation, policy with citations, other-employee refusal, sensitive-topic escalation, prompt injection, PII masking, off-topic and onboarding), each asked as a named synthetic employee. Run it against the synthetic demo: ```bash python -m ticketiq.evalrun --dataset hr-agents --agent hr-assist ``` or from the **Evaluations** page. A case can also say what the answer must **not** contain (`expect.excludes`), which is how a leak of another employee's data or of a masked value fails the case. ## What has not been validated The HR agents are validated end to end against the synthetic BambooHR-compatible system only. The Workday and BambooHR self-service calls follow each vendor's public API documentation and have not been run against a real customer tenant, so spot-check them against yours before relying on them. Greenhouse (recruiting) is not part of the HR agents. ## Next steps - [Sub-agents](/docs/agents/sub-agents) - [MCP servers](/docs/connect/mcp-servers) - [Microsoft Teams](/docs/connect/teams) - [Playground and evals](/docs/agents/playground-evals) --- Source: https://liyagent.com/docs/agents/finance-agents # Finance agents Liya Finance checks vendor invoices against their purchase orders in NetSuite, Dynamics 365 Finance or Dynamics 365 Business Central, lists the invoices that differ beyond your tolerance, holds an invoice for AP review once a person approves, and answers spend questions from Ramp. Liya has a finance subagent. People **ask Liya** in Teams or the web chat, and an accounts-payable or spend question is answered by **Liya Finance**, as that person: | Agent | Subagent name | What it does | | --- | --- | --- | | **Liya Finance** (`liya-finance`) | `finance` | Matches a vendor invoice to its purchase order line by line, lists open invoices that need a person, holds an invoice for AP review (after a person approves the hold), and totals card spend by vendor, department or period. | Liya Finance is under **Agents › All agents** with its own **Settings**, **Playground**, **Transcripts** and **Permissions** tabs. The **Finance** app in the rail has two tabs: **Overview** and **Setup**. > [!IMPORTANT] > Liya Finance never approves, pays, releases or deletes an invoice. Holding an invoice is the only change it can ask for, and every hold waits for a person's approval in the **Inbox**. ## Tools Each tool is on the connector it reads. Reads run as asked. Writes are held for approval. | Connector | Tool | Read or write | | --- | --- | --- | | NetSuite | `match_vendor_bill`: one vendor bill against its purchase order | Read | | NetSuite | `list_bill_variances`: open and pending-approval bills that need a person | Read | | NetSuite | `hold_vendor_bill`: sets the bill's **Payment Hold**, and optionally its approval status back to **Pending Approval** | Write, held for approval | | Dynamics 365 (Dataverse virtual tables) | `match_vendor_invoice`: one vendor invoice against its purchase order | Read | | Dynamics 365 (Dataverse virtual tables) | `list_invoice_variances`: pending vendor invoices that need a person | Read | | Dynamics 365 (Dataverse virtual tables) | `hold_vendor_invoice`: sets the invoice header's on-hold column | Write, held for approval | | Dynamics 365 Finance and Operations | `match_vendor_invoice`: one pending vendor invoice against its purchase order, prices per price unit | Read | | Dynamics 365 Finance and Operations | `list_invoice_variances`: pending vendor invoices that need a person | Read | | Dynamics 365 Finance and Operations | `hold_vendor_invoice`: sets `IsOnHold` on the pending invoice header | Write, held for approval | | Dynamics 365 Business Central | `match_purchase_invoice`: one purchase invoice against its purchase order, against what was received | Read | | Dynamics 365 Business Central | `list_invoice_variances`: unpaid purchase invoices (Draft, In Review, Open) that need a person | Read | | Dynamics 365 Business Central | `block_vendor_payments`: sets the vendor's **Blocked** to `Payment`. Business Central's API has no per-invoice hold | Write, held for approval | | Ramp | `spend_summary`: card spend by vendor, department or period (month, quarter or ISO week) | Read | The hold tools wait for a person whatever the server's **Agent writes** setting says. They also need a reason, which the approver sees. > [!NOTE] > Business Central has no per-invoice hold in its API, so `block_vendor_payments` blocks **every** payment to the vendor until a person clears the vendor's **Blocked** field. It never loosens a block that is already there. To keep one draft invoice back, don't post it. See [Connect Dynamics 365](/docs/connect/dynamics-365) for every Dynamics 365 route. ## How a match is judged Each invoice line is paired with the order line it bills. Liya Finance pairs by the order line the ERP names on the invoice line first, then by item, then by description. Then, for each line: - **Quantity.** Billing more than was ordered is a variance. Where the order records receipts, billing more than was received is a variance too (a three-way match). Billing less than was ordered is a partial invoice, not a variance. Order lines that were not billed are listed as **not invoiced**. - **Unit price.** The invoice's unit price is compared with the order's, valued over the quantity billed. - **Line amount.** A line amount that is not quantity × price is a variance. A difference of one cent or less is rounding. - **Not on the order.** A line that bills something the order does not carry is always a variance. For the whole invoice, the line total is compared with what those lines should cost at the order's prices. Tax and charges on the header are shown as **charges** and are not judged. An invoice with **no purchase order**, or in a **different currency** from its order, is flagged for a person. Amounts in two currencies are never compared or added. Amounts are exact decimals, rounded half up to the cent, and are always given with their currency, for example `USD 1,050.00`. ## The tolerance A difference is within tolerance when its value is no more than the allowance on the value it is measured against: - a **percentage** of that value (the default is **2%**), - a fixed **amount** in the invoice's currency, or - both, and then **whichever is less**. A difference exactly at the allowance is within it. The tolerance belongs to the client. Set it on **Finance › Setup** under **Match tolerance**, or with the API: ```bash curl -X PUT https:///api/finance/tolerance \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"pct": 2, "amount": 50}' ``` Each change is audited as `finance.tolerance.update`. ## Prerequisites - The **admin** or **owner** role. - Finance turned on for the client (it is on wherever no solution row restricts it). - A NetSuite account, a Dynamics 365 Finance (finance and operations) environment, a Dynamics 365 Business Central environment, or a Ramp business, each with an OAuth application for per-user access. ## Set up ### 1. Connect a finance system as each person 1. On **Connections**, register an OAuth provider for the system. The callback URL is shown on **Finance › Setup**. - **NetSuite**: an integration record with OAuth 2.0 authorization code grant and the REST web services scope. The person's NetSuite role decides what they can read and hold. - **Dynamics 365**: a Microsoft Entra app registration. Pick the catalogue entry for the route: **Dynamics 365 (Dataverse)** for the virtual tables, **Dynamics 365 finance and operations** for the OData entities, **Dynamics 365 Business Central** for Business Central. See [Connect Dynamics 365](/docs/connect/dynamics-365). - **Ramp**: a Ramp developer app with the transactions read scope. 2. On **MCP servers**, add a managed **NetSuite**, **Dynamics 365**, **Dynamics 365 Finance and Operations**, **Dynamics 365 Business Central** or **Ramp** server with that connection. Each person then connects their own account. Liyagent never holds a shared finance credential. The two Dynamics ERP connectors can instead use an Entra app's own credentials, and then act as that app. ### 2. Attach it to Liya Finance On **Finance › Setup**, under **Liya Finance**, choose **Attach to Liya Finance** next to the server. This: - adds the server to Liya Finance's tools. - turns Liya's `finance` subagent **on for the client you are working in only**. Liya Finance runs as **the invoker**, so each call carries the person's own token. Each attach is audited as `finance.agent.attach`. The same on/off switch is under **finance** in Liya's **Settings → Subagents**. ### 3. Set the tolerance On **Finance › Setup**, under **Match tolerance**, enter a percentage, an amount, or both, and choose **Save tolerance**. ### 4. Dynamics 365: check the table names Liya Finance reads Dynamics 365 Finance's accounts-payable data entities through Dataverse virtual tables (the `mserp_` tables for vendor invoice headers and lines and purchase order headers and lines). If your environment names a table or column differently, set `finance_fields` on the Dynamics 365 server through the MCP servers API, for example `{"invoice_hold": "cr_onhold"}`. Only the names Liya Finance knows are accepted. The **Dynamics 365 Finance and Operations** connector reads the same entities over OData (`VendorInvoiceHeaders`, `VendorInvoiceLines`, `PurchaseOrderHeadersV2`, `PurchaseOrderLinesV2`), and takes `finance_fields` the same way, for example `{"invoice_lines": "VendInvoiceInfoLines"}`. Business Central's entities are fixed by its API v2.0 and need no names. ## In Teams People ask Liya in a chat: | They say | They get | | --- | --- | | "Does bill 4512 match its PO?" | The match, line by line, with every difference and whether it is within tolerance. | | "Which open invoices are off from their POs?" | The invoices that differ beyond tolerance, have no order, or are in another currency. | | "Hold bill 4512. The price is 3% over." | A hold request waiting in the **Inbox**. Liya Finance says it is waiting for approval. | | "What did Engineering spend on cards last quarter?" | Spend totals by department, per currency. | | "Pay bill 4512." | A refusal: a person pays invoices in the finance system. | ## The Finance app - **Overview** (`/finance`) shows the invoices checked and the variances found this week, the holds waiting for approval, the finance systems and how many people have connected, the agents' calls by tool and recent errors. - **Setup** (`/finance-setup`, administrators) shows the callback URL, the finance systems, Liya Finance and the match tolerance. - **Home** shows a Finance card with the same week's numbers. ## What has not been validated Liya Finance's matching is tested case by case against synthetic data. The NetSuite, Dynamics 365 and Ramp calls follow each vendor's public API documentation and have not been run against a real customer tenant: - **NetSuite**: the vendor bill and purchase order records with sublists expanded, SuiteQL status codes for open (`A`) and pending-approval (`D`) bills, and the `paymentHold` and `approvalStatus` fields. - **Dynamics 365**: the default virtual table and column names, including the on-hold column. Check them against your environment and override them with `finance_fields`. - **Dynamics 365 Finance and Operations**: the collection names other than `VendorInvoiceHeaders`, the field names (from the entities' published schemas), and the header key used by the hold (`dataAreaId`, `HeaderReference`). Check them against `/data/$metadata`. The V2 purchase order lines carry no received quantity, so this match is two-way. - **Dynamics 365 Business Central**: the API v2.0 purchase invoice, purchase order and vendor fields, and the `If-Match` header on the vendor update. - **Ramp**: the transaction fields used for spend (`amount`, `currency_code`, `merchant_name`, the card holder's department) and the `page.next` cursor. Spot-check against your own tenant before relying on them. ## Next steps - [Sub-agents](/docs/agents/sub-agents) - [MCP servers](/docs/connect/mcp-servers) - [Connect Dynamics 365](/docs/connect/dynamics-365) - [Microsoft Teams](/docs/connect/teams) - [Playground and evals](/docs/agents/playground-evals) --- Source: https://liyagent.com/docs/agents/workspace # The Liyagent app for employees Where the people in your organisation use their agents every day, at /workspace, on desktop and phone, installable as an app, with everything that needs them in one place. The Liyagent app is for the people in your organisation who use agents rather than run them. It is the workspace at `/workspace`: it lists the agents a person may talk to, keeps their chats, shows everything their agents are waiting on them for, and has nothing of the admin console in it. It works in any modern browser, and installs as an app on a computer or a phone. It is not the native Liyagent mobile app, which signs in the same way and has its own notifications ([Identity and roles](/docs/govern/identity-roles)). ## Who lands where After signing in, with a password or with single sign-on: | The person holds | They land on | | --- | --- | | Only the **Viewer** or **Analyst** role's permissions: the read permissions, `agents.run`, `agents.chat` and their own connections | The app, `/workspace` | | Any other permission, for example an **Operator**, **Administrator** or **Owner** role | The admin console, `/` | Someone who has a console permission can still open `/workspace`. Their header then shows an **Admin console** link back. A person without one sees no such link, and `/` takes them to the app. ## The four places On a wide screen the places are tabs in the header. On a phone they are a bar at the bottom of the screen. | Place | What it shows | | --- | --- | | **Home** | A greeting, how many things need you, your agents with prompts to start from, and your recent chats | | **Agents** | The agents you may talk to and the chat. On a phone, the list first; picking an agent opens its chat, and the arrow goes back to the list | | **Needs you** | Everything your agents are waiting on you for, newest first. The tab shows how many | | **Chats** | Your recent chats with every agent. Opening one picks the conversation up where it was left | The address says where you are (`/workspace?view=needs`, `?view=chats`, `?agent=`), so a link or the browser's back button takes you there. ## Chatting with an agent - **Your agents**: each agent you may chat with, with its picture, its name and one line on what it does. A search box narrows the list. Pipeline and oversight agents, which work on tickets rather than with people, are not listed. - **Past chats**: your own chats with the selected agent, newest first. The × beside a chat deletes it, with its files, after a confirmation. - **The chat**: **Enter** sends, **Shift+Enter** adds a line. Answers are shown with their formatting, tables and charts. Under each answer, **Helpful** and **Not helpful** send feedback, and **Not helpful** asks what was wrong. - **Attachments**: the clip button, or dropping files on the chat, adds up to 5 files a message. Images up to 5 MB, documents up to 10 MB. - **Voice input**: where the browser can turn speech into text, a microphone button sits beside the clip. While it listens, the button turns red and a line under the message box says **Listening**. What you said appears in the message box for you to read and send as if you had typed it. Liyagent receives only the text you send, never audio. Some browsers use their maker's speech service to turn speech into text; where the browser cannot, there is no button. - **Waiting runs** and **SAP changes** you are asked about in a chat are shown as cards under it, and also in **Needs you**. ## The AI notice Before a person's first message, the app tells them that Liya is an AI assistant and not a person, that its answers can be wrong, and that it acts only with their own permissions. The message box opens once they choose **I understand**. A short reminder stays under the message box. This is the information Article 50 of the EU AI Act asks for when people interact with an AI system. ## Needs you **Needs you** lists everything waiting on you across your agents, newest first, with the agent that asked and what it wants. Each is settled where it is shown: | What waits | Shown to | What you do | | --- | --- | --- | | **Approval**: an action an agent was held on | People who may decide it now: `agents.read` and `agents.approve`, an access policy that lets them approve that agent, in the tenant they work in | Open **What will be sent** to read the action's arguments, then **Approve** or **Reject** (with an optional reason) | | **Answer**: a run of yours waiting for something only you can give | The person who started the run | Fill in what it asks and choose **Send answer** (or **Accept** / **Decline** for a form a connected system sent) | | **Connection**: a run of yours waiting for you to connect an account | The person who started the run | **Connect**, then **I have connected** | | **SAP · Confirm**: an SAP change an agent proposed to you | The person it was proposed to | **Confirm** or **Cancel** | An approval is left out while its agent's owners still have it to themselves, unless you are one of them, and, with four eyes on (`TICKETIQ_APPROVAL_FOUR_EYES=true`), when the agent asked for it on your own behalf. A run waiting for an approval is not yours to settle: it waits on an approver, and goes on by itself once the approval is decided. Each action uses the same route, permission checks and audit as the console: approving here is the same `agent.approval.approve` row as approving in the Inbox. The list refreshes every 30 seconds while the app is open, and when you come back to it. On an installed app, the number of things waiting also shows on the app's icon, where the operating system supports it. ## Install the app | On | How | | --- | --- | | Chrome or Edge on a computer, Chrome on Android | Home offers **Install the Liyagent app**. Choose **Install**, or use the browser's own **Install app** menu item. **×** hides the offer on that browser | | Safari on an iPhone or iPad | Home says how: tap **Share**, then **Add to Home Screen** | | Other browsers | Use the browser's **Add to Home Screen** or **Install** menu item, where it has one | The installed app opens at `/workspace` in a window of its own, with the Liyagent icon. **Settings** (the gear in the header) says whether you are using the installed app. The app starts fast because its stylesheets, scripts and icons are kept on the device by a service worker. Only those are kept: never a chat, an answer from an agent, an API response or anything that differs from one person to another. Pages are always fetched from the server first. With no network, the app shows a short **You are offline** screen with **Try again**, and nothing you type is sent until you are back online. ## Notifications With notifications on, the app tells you when an agent needs you, even when it is closed: - **Liya needs your approval**: an approval you may decide was raised, or its owners did not respond in time and it is now open to you. - **Liya needs your answer** or **Liya needs a connection**: one of your runs started waiting for you. A notification names the agent and nothing else, for example "Helper is waiting for your approval." It never contains what the agent wants to do, its arguments, a ticket or your words, because notifications pass through Google, Mozilla, Microsoft or Apple and show on a lock screen. Opening it takes you to that item in **Needs you**. Notifications of one agent replace one another, and each expires with the thing it is about. To turn them on, open **Settings** in the header and switch on **Tell me when an agent needs me**. Your browser asks for permission. **Send a test notification** checks it end to end. On an iPhone or iPad, install the app on the Home Screen first; Safari sends notifications only to installed apps. - Notifications are per browser. Turn them on in each browser or phone you want them on. - Signing out turns them off in that browser. They also stop when your session ends; the app turns them back on under your next session when you open it. - If your browser blocks notifications for the site, allow them in the browser's site settings, then switch them on again. - If your organisation has not set notifications up, **Settings** says so and has no switch. ### Set up notifications on the server Notifications use Web Push with VAPID keys. They are off until both keys are set: 1. Generate a key pair on any machine with the app installed: `python -m ticketiq.webpush generate-keys`. It prints the two settings. 2. Set `TICKETIQ_WEBPUSH_VAPID_PUBLIC_KEY` and `TICKETIQ_WEBPUSH_VAPID_PRIVATE_KEY` in the deployment's environment. The private key can instead be a secret reference (`secret://label`) to a value in the secret store. Never commit either key. 3. Set `TICKETIQ_WEBPUSH_SUBJECT` to a contact the push services can use, `mailto:` or `https:`. Without it, the instance's public address is used. 4. Let the server reach the browsers' push services (outbound HTTPS): `fcm.googleapis.com`, `updates.push.services.mozilla.com`, `*.notify.windows.com` and `web.push.apple.com`. `TICKETIQ_WEBPUSH_ENDPOINT_HOSTS` lists the push services a browser's subscription may name, and anything else is refused. 5. Restart. **Settings** in the app now shows the switch. Changing the keys makes every existing subscription unusable: each browser is subscribed again under the new key the next time its person opens the app. Removing them turns notifications off and forgets every subscription. Subscribing, unsubscribing and sending a test are audited as `auth.webpush.subscribe`, `auth.webpush.unsubscribe` and `auth.webpush.test`. Each send is `agent.approval.web_push` or `agent.run.web_push`, naming the people and how many browsers were told, never the content. A send a push service refuses is `agent.web_push.failed`. ## Giving someone access 1. Give the person a role that holds `agents.chat`. The built-in **Viewer** and **Analyst** roles do. See [Identity and roles](/docs/govern/identity-roles). 2. Make sure no access policy forbids them to `invoke` the agent. An agent a policy forbids them is not listed. See [Access](/docs/govern/access). 3. To decide approvals in **Needs you**, they also need `agents.read` and `agents.approve`. ## What is kept A chat in the app is the same governed run as the agent's **Playground**: the same model binding, guardrails, budget and audit. Each chat is kept as a session of the agent, owned by the person, so they can reopen it. A person sees only their own chats. Administrators with the transcript content permission can review them on the agent's **Transcripts** tab. A browser with notifications on is kept as its push service address and the keys the notification is encrypted to, with whose it is and the session it was turned on under. It is forgotten when the person turns notifications off or signs out, when the push service says it is gone, when the session ends, and when the account is deleted. ## API The app uses these routes. Chatting needs `agents.chat` or `agents.operate`: | Route | What it does | | --- | --- | | `GET /api/agents/chat` | The agents the caller may chat with | | `GET /api/agents/{key}/chats` | The caller's own chats with an agent | | `GET /api/agents/{key}/chats/{session_id}` | One of the caller's own chats, with its messages | | `POST /api/agents/{key}/playground` | Sends a message | These are open to anyone signed in, and each answers only for the caller: | Route | What it does | | --- | --- | | `GET /api/me/landing` | Where the caller lands, and whether the admin console is theirs | | `GET /api/me/needs` | Everything waiting on the caller, newest first. Each kind is read only for a caller who holds the permission of the action that settles it | | `GET /api/me/push` | Whether the server sends notifications, its public key, and the caller's own subscribed browsers | | `POST /api/me/push/subscriptions` | Turns notifications on for a browser: its `PushSubscription` (`endpoint`, `keys.p256dh`, `keys.auth`) | | `DELETE /api/me/push/subscriptions/{id}` | Turns them off for one of the caller's browsers | | `POST /api/me/push/test` | Sends the caller's own browsers a test notification | Approvals, waiting runs and SAP changes are settled with their own routes: `POST /api/approvals/{id}/approve` and `/reject`, `POST /api/runs/{run_id}/resume`, and `POST /api/agents/{key}/playground/sap-confirmations/{id}`. --- Source: https://liyagent.com/docs/agents/liya-meeting # Use Liya Meeting Get live answers to questions asked in your meetings, after everyone has been told Liya is listening, with a transcript you control. Liya Meeting listens to a meeting you are in, writes a transcript and puts an answer card beside each question it hears. It runs on an ordinary console page at `/meeting`, and a **Liya is listening** bar stays on the page for as long as audio flows. Nothing listens until you have recorded consent, and no audio is kept. > [!PREVIEW] > Liya Meeting is still being built. Today the voice service is a separate, opt-in image, Recall searches only your own meetings, and the encrypted raw archive is opened from the command line. Expect labels and limits to change. ## Prerequisites - The voice service is running: the `voice` compose profile, with `TICKETIQ_VOICE_URL` pointing at it. See the **Voice service (Liya Meeting)** section of `DEPLOYMENT.md`, which ships with a self-hosted installation. - Someone with the `meetings.manage` permission has turned Liya Meeting on for the workspace: `PUT /api/meetings/config` with `{"enabled": true}`. Until then the page says it is off. - You have `meetings.assist` to run a meeting. `recall.search` lets you search your own meetings, and `archive.read` lets you open a raw archive. `archive.read` is never granted by a wildcard or an owner role; it is given to a person by name. ## Start a meeting 1. Open **Liya Meeting** (`/meeting`). 2. Choose **Copy announcement**, then say it aloud at the start of the meeting or paste it in the meeting chat. 3. Under **Consent rule**, keep **Everyone must agree (all-party)** unless the law where you and every participant are allows one party to consent. 4. Tick **I told every participant that Liya is listening** and, under all-party consent, **Everyone agreed**. 5. Under **How you told them**, say in your own words how you did it, for example "I said it at the start and posted it in the chat". 6. Set the **Title**, the **Voice** Liya answers in and **What to keep** (see below). 7. Choose **Start listening**. Your browser asks for the microphone. To include a call that plays in another tab, tick **Also listen to a browser tab's audio (you choose the tab)** first. **Start listening** refuses until the consent boxes are ticked, and the consent record is saved before any audio leaves the page. ## During the meeting - Lines appear under **Transcript** as the voice service commits them. - A question heard in the meeting gets a card under **Answers**. Type your own question in **Type a question** and press Enter. - Choose **Elaborate** on a card for the fuller answer. - Choose **Stop** to end listening. The bar goes, and the page says Liya is no longer listening. Answers go through the governed path as you: personal data is redacted from the transcript and the question before the model sees them, and the agent's budget is checked first. When the budget is spent the card says so and no model is called. ## What to keep | Option | What is kept | | --- | --- | | Keep the transcript (30 days) | The redacted transcript and the answer cards, deleted after the workspace's retention (30 days by default, at most 90) unless a legal hold applies | | Keep only that it happened | The meeting, its consent record and the shape of the conversation, without the words | | Keep nothing | No transcript and no answers: the lines live in the server's memory only until the meeting ends | Meetings are never distilled into Liya's memory. A person's erasure request also deletes their meetings. ## Search your own meetings Recall searches the redacted transcripts of meetings you ran, and nobody else's: ```bash liya recall search "release date" --limit 5 ``` The same search is `GET /api/recall/meetings?q=...`, and agents with the person's delegation can use the MCP tools `recall_search` and `recall_get`. ## Open a raw archive When the workspace keeps an encrypted raw archive and you ticked **Also keep an encrypted raw archive (opened only with another person's approval)**, the meeting's events are sealed with the instance's key, or encrypted to the workspace's age recipient keys, in a hash-chained, signed manifest. Opening one needs `archive.read` and another person's approval, and each approval opens once. 1. Check the archive: `liya archive verify` exits 1 if the chain, a signature or an object does not match. 2. Ask to open a meeting: `liya archive request mtg_0123456789abcdef --reason "Dispute about the agreed date"`. The command prints the approval id. 3. Once someone else approves it, open it: `liya archive open mtg_0123456789abcdef --approval `. For an archive encrypted to age keys, add `--identity FILE` to decrypt on your machine (the identity never leaves it), or `--out FILE` to save it. ## Next steps - [Command-line reference](/docs/cli/reference) - [Build agents](/docs/agents) --- Source: https://liyagent.com/docs/service-desk # Run the service desk How tickets come in, get classified, merged, answered and routed, and how to tune each step. The service desk is the Liya pipeline: intake, classification, correlation, resolution, assignment and notification. Most of it runs on rules and embeddings, so it keeps working when no model is configured. The pages below follow a ticket through it. > [!TIP] > The **IT service desk › Tickets** page (`/tickets`) shows every step for one ticket under its **Pipeline runs** and **History** tabs, which is the quickest way to see why a ticket ended up where it did. --- Source: https://liyagent.com/docs/service-desk/intake # Ticket intake The ways tickets enter TicketIQ, from the console and API to external systems, with safe retries. ## Channels | Channel | How | Auth | | --- | --- | --- | | Console | **IT service desk › Tickets**, **New ticket** | Session | | Console API | `POST /api/tickets` | Session or token with `tickets.write` | | External systems | `POST /api/intake` | Instance API key, rate-limited | | Teams, web chat, email | Via Liya and triggers | Channel-specific | | Jira, ServiceNow, mailboxes, CSV | Connector sources, `POST /api/sources/{id}/sync` | Source credentials | ## File a ticket from an external system `POST /api/intake` is meant for monitoring tools, Power Automate, Slack workflows and similar. It returns the routed ticket plus deflection answers, the suggested fixes Liya Resolve found. ```bash curl -X POST https:///api/intake \ -H "X-API-Key: $TICKETIQ_API_KEY" -H "Idempotency-Key: alert-7731" \ -H "Content-Type: application/json" \ -d '{"title":"Disk 95% on db-02","description":"/var at 95%","external_id":"PD-7731"}' ``` | Field | Notes | | --- | --- | | `title` | Required. An empty title is a 422. | | `description` | Cleaned of signatures and quoted replies before classification. | | `external_id` | Your system's id. Sending the same id again updates that ticket and returns `created: false`. | > [!TIP] > Retries are safe two ways: repeat the `Idempotency-Key` header and you get the first ticket back, or reuse the `external_id`. ## Connector sources Sources pull records in bulk. Manage them at `/api/sources`: `POST /api/sources/{id}/test` checks the connection, `/sync` pulls new records, `/reclassify` re-runs classification over what was pulled. ## Tenancy A ticket filed from the console lands in the caller's selected tenant. A ticket from a source lands in the tenant that owns the source. See [Tenancy](/docs/govern/tenancy). ## Next steps - [Triage and categories](/docs/service-desk/triage-categories) - [Jira](/docs/connect/jira) and [ServiceNow](/docs/connect/servicenow) --- Source: https://liyagent.com/docs/service-desk/triage-categories # Triage and categories How categories, priorities, teams and SLAs are assigned, and how to replace the default categories with a taxonomy learned from your own tickets. ## Default categories and teams Out of the box, Liya Classify uses IT categories, and Liya Assign routes each to a team: | Category | Team | | --- | --- | | Network | Network Operations | | Database | Database Administration | | Access & Identity | Identity & Access Management | | Hardware | Field Services | | Software | Application Support | | Email & Collaboration | Messaging Team | | Security | Security Operations | | Storage | Storage Team | | General Support (fallback) | Service Desk L1 | | Priority | SLA hours | | --- | --- | | P1 | 4 | | P2 | 8 | | P3 | 24 | | P4 | 72 | Override either with `PUT /api/routing` (send only what differs), or manage named SLA policies at `/api/sla/policies` and preview their effect with `/api/sla/preview`. ## Learn categories from your tickets A tenant that isn't an IT desk (HR, finance, facilities) can have categories learned from its own history. 1. Open **IT service desk › Categories** and choose **Learn from history** (`POST /api/taxonomy/discover`), or learn from a CSV export. TicketIQ embeds the tenant's tickets, clusters them and picks the number of clusters with the best separation. 2. Review the proposal. Each category has a name, description, count, examples, keywords and a suggested team taken from past assignments. 3. Rename, merge, split or delete categories and set teams (`POST /api/taxonomy/proposal/edit`). 4. Choose **Approve taxonomy** (`POST /api/taxonomy/approve`). Approval writes a new version and makes it live at once. To go back to an earlier version, choose **Activate** on it (`POST /api/taxonomy/activate`). **Use IT defaults** returns to the built-in categories. 5. Choose **Re-classify existing tickets** if you want history to use the new categories (`POST /api/taxonomy/reclassify`). After activation, classification picks the nearest category centroid, with keyword and source-label hints. > [!NOTE] > A tenant with no approved taxonomy keeps the IT defaults. **Discard** on a proposal (`DELETE /api/taxonomy/proposal`) changes nothing live. ## Triage refinement When a model is configured, Liya Triage runs after classification to correct the category and add a triage note. It runs off the critical path, so a slow or missing model never delays routing. ## Next steps - [Known issues and duplicates](/docs/service-desk/known-issues) - [Lab: Connect a Jira project and triage issues](/docs/labs/jira-triage) --- Source: https://liyagent.com/docs/service-desk/known-issues # Known issues and duplicates How repeat reports are folded into one ticket, answered as a known issue on the channel they came from, and closed out for every reporter. ## How duplicates are found Liya Correlate compares each new ticket with open tickets by vector similarity. | Setting | Default | Meaning | | --- | --- | --- | | `TICKETIQ_DEDUP_THRESHOLD` | 0.72 | Similarity at or above which a ticket is a duplicate | | `TICKETIQ_DEDUP_REVIEW_BAND` | 0.04 | Just below the threshold: flagged for a person to review | | `TICKETIQ_DEDUP_CROSS_CATEGORY_MIN` | 0.82 | Higher bar when the two tickets are in different categories | | `TICKETIQ_DEDUP_WINDOW_HOURS` | 336 | How far back open tickets are compared (14 days) | | `TICKETIQ_SUPERVISED_DEDUP_MERGE` | false | Require a person to confirm every merge | When reports keep coming from different people, the original is escalated: the third live report bumps it one priority level, and the sixth makes it P1. Repeat reports from the same person don't count, and the count resets when reports stop clustering. With `TICKETIQ_SUPERVISED_STORM_ESCALATION` on, the escalation is held in the **Inbox** under **Approvals** for a person to confirm. ## What the reporter sees A report folded into an open original is answered as a **known issue** on the channel it came in on: a card in Teams, a message in web chat or a reply to the email. It doesn't become a new ticket in anyone's queue. Each reporter is kept on the original, one row per person and channel (`GET /api/tickets/{id}/reporters`). When the original is resolved, every reporter is told once. Notices go through the outbox, so a Teams or SMTP outage is retried, and they are rate-limited to 5 per person per hour. ## Undo a merge Open the duplicate on **IT service desk › Tickets** and choose **Unmerge**, or call `POST /api/tickets/{id}/unmerge`. The ticket goes back into the queue as its own case. Tick **also undo the parent's storm escalation** to put the original's priority back too. > [!TIP] > To announce an issue before reports arrive, see [Lab: Add a known-issue banner](/docs/labs/known-issue-banner). ## Next steps - [Semantic answer cache](/docs/service-desk/semantic-cache) --- Source: https://liyagent.com/docs/service-desk/semantic-cache # Semantic answer cache Answer a question that means the same as an earlier one with the earlier verified answer, without a model call, and without crossing access boundaries. ## What it does When someone asks a question that means the same thing as one already answered, TicketIQ can serve the earlier answer instead of calling a model again. Only **verified** answers are reused: - an answer someone gave a thumbs-up to, - an answer an operator approved, or - when `verify_kb_grounded` is on, an answer grounded only in approved, human-written KB articles. A thumbs-down removes the entry immediately. ## Safety fences - Lookups never cross a tenant or an agent. - An entry is served only when the asker's own retrieval, under their access rules and sensitivity ceiling, returns every source the answer was grounded in, with the same content. If a source changed or the asker can't see it, the question goes to the model. - Answer text is sealed with the tenant's vault key, and expired entries are pruned. ## Turn it on 1. On **AI Gateway › Cost and usage**, find **Answer caches** and set the similarity and expiry there, or call `PUT /api/semantic-cache`. | Setting | Default | Meaning | | --- | --- | --- | | `enabled` | false | Master switch for this scope | | `threshold` | 0.95 | Similarity needed to reuse an answer | | `ttl_s` | 604800 | Lifetime of an entry (7 days) | | `verify_kb_grounded` | false | Treat KB-grounded answers as verified | 2. Ask a question through Liya and give the answer a thumbs-up. 3. Ask it again in different words. The reply is served from the cache. Tenants can override the instance settings. `TICKETIQ_SEMANTIC_CACHE=off` switches the cache off for the whole instance, whatever is stored. ## Manage entries | Task | Endpoint | | --- | --- | | List entries | `GET /api/semantic-cache/entries` | | Approve an entry | `POST /api/semantic-cache/entries/{id}/approve` | | Drop one entry | `DELETE /api/semantic-cache/entries/{id}` | | Drop all, or one tenant's | `POST /api/semantic-cache/purge` | > [!WARNING] > Purge after a knowledge-base clean-up. Entries are re-checked against their sources, but a purge removes any doubt. ## Next steps - [Knowledge](/docs/service-desk/knowledge) - [Lab: Use the semantic cache for repeat questions](/docs/labs/semantic-cache) --- Source: https://liyagent.com/docs/service-desk/knowledge # Knowledge Where Liya's answers come from: ingested sources, approved articles, feedback, gap reports and integrity checks. ## Sources of knowledge - **Connector sources:** mailboxes, CSV exports, Jira and ServiceNow history, synced with `POST /api/sources/{id}/sync`. - **Mail archives:** `POST /api/mailserver/ingest`, or `ingest_full.py` for a large one-time backfill. - **Articles:** written by people, or drafted by Liya Scribe from resolved tickets. ## Review article drafts 1. Open the **Inbox** (Home's **Needs attention**, then **View all**, or `/inbox`) and pick the **Drafts** lane. Choose **Draft now** to have Liya Scribe draft from recently resolved tickets. 2. Open a draft in the drawer, edit it (`PATCH /api/kb/articles/{id}`), then approve or reject it with a reason (`POST /api/kb/articles/{id}/approve` or `/reject`). 3. Approved articles are used for answers straight away. > [!NOTE] > Nothing Liya Scribe writes is used until a person approves it. ## Improve answers | Tool | What it tells you | Endpoint | | --- | --- | --- | | Feedback | Thumbs on each recommendation | `POST /api/recommendations/{ref_id}/feedback` | | Gaps | Questions nothing answered well | `GET /api/kb/gaps` | | Quarantine | Items disliked 3 times are taken out of use (`TICKETIQ_KB_QUARANTINE_AFTER_DISLIKES`, 0 for never) | `POST /api/kb/{ref_id}/quarantine` | | Evaluation | Retrieval quality against a question bank, daily by default | `GET` / `POST /api/kb/eval` | | Integrity | Checks the index matches the source records | `POST /api/kb/integrity/run` | Thresholds: an answer is quoted directly at similarity 0.76 (`TICKETIQ_KB_QUOTE_THRESHOLD`) and listed as related at 0.66 (`TICKETIQ_KB_RELATED_THRESHOLD`). ## Sensitivity Each item carries a label (public, internal, confidential, restricted). A channel only retrieves items at or below its ceiling. See [Redaction](/docs/govern/redaction). ## Citations and sources When Liya answers from your data, each citation leads back to the item in your own system as well as to the copy the answer quoted. Every item records where it lives when it is ingested: the system, the item's own key there and its address. | Source | Key | Link to the original | | --- | --- | --- | | Jira (Cloud and Data Center) | Issue key | `{base URL}/browse/{KEY}` | | ServiceNow incident or other table | Record number | `{instance}/nav_to.do?uri={table}.do?sys_id={sys_id}` | | ServiceNow knowledge | KB number | `{instance}/kb_view.do?sysparm_article={number}` | | REST API, SQL, SQLite, CSV | Record key or row | The source's [link template](/docs/connect/erp-sql#link-to-the-original-record), if set | | Email | Message-ID | None. Email has no address. | | Files | File name | The console's **Files** page | | Approved articles | `KBA-` | The article in the console | | HR policies | Policy reference | The policy's own page, if its entry or the HR link template has one | | Tickets opened in TicketIQ | Ticket ID | The ticket in the console | How each surface shows it: - **Microsoft Teams and web chat.** Each citation opens a signed, expiring page with the full copy. When the original is known, the page leads with **Open in Jira ↗** (or ServiceNow, the ERP and so on) and the item's key, and notes that opening it needs access in that system. The copy works for people without a console account. A link is signed for the workspace that cited the source, and never opens another workspace's source. - **Console and co-pilot.** Fix cards and the co-pilot's similar cases link the original directly. A ticket imported from Jira or ServiceNow shows **Original** in its details. - **Answer API, MCP and A2A.** Each recommendation carries `source_system`, `source_key` and `source_url`. A2A answers that quote sources carry them in a `citations` data part and in the task metadata. Links are withheld in these cases: - The item was quarantined, retracted or kept out of the knowledge base. - On surfaces that answer people outside the service desk, the item is labelled at or above the level that PII and secrets raise to (confidential by default). - The address would carry a credential, personal data or a secret. Only `http` and `https` addresses are used. On those surfaces the key is PII-masked like the text beside it, and a cited email shows no Message-ID. Records synced before keys and links were kept are backfilled once per source by the scheduler, without re-embedding. `POST /api/sources/{id}/relink` runs the backfill now. ## Next steps - [Data handling](/docs/govern/data-handling) --- Source: https://liyagent.com/docs/connect # Connect data & tools Give agents tools through MCP servers, connect ticketing and chat systems, and keep every credential in the secrets store. Agents are only as useful as what they can reach. Liyagent connects in three ways: **MCP servers** give managed agents tools, **connections and connectors** bring in tickets and let agents act in systems such as Jira, ServiceNow, Salesforce, Teams and email, and the **secrets store** holds every credential those need, referenced by label rather than pasted into settings. The catalogue of ready-made servers and connectors is a starting set, not the limit. Any MCP server you can reach can be added by URL, and OpenAPI operations or your organisation's own code can be brought in as tools beside it, so an agent can be built for any system. Whether Liyagent is hosted by us or by you, it reaches those systems through their own APIs from your environment. For SAP, see [Connect SAP](/docs/connect/sap): Liya calls SAP S/4HANA's own APIs, with a person confirming each agent call, or connects to the MCP Gateway in your SAP Integration Suite. For Salesforce, see [Connect Salesforce](/docs/connect/salesforce). For Microsoft Dynamics 365 (Sales, Customer Service, Field Service, Finance, Supply Chain Management, Business Central and the rest), see [Connect Dynamics 365](/docs/connect/dynamics-365). --- Source: https://liyagent.com/docs/connect/mcp-servers # MCP servers Register MCP servers, pin and pause their tools, and put data policies on what flows through them. ## Managed and remote servers - **Managed** servers are implemented inside Liyagent against the vendor's own API, so there is no separate MCP server to host. The catalogue has dozens, among them Jira, ServiceNow, Zendesk, Salesforce, SAP S/4HANA, Microsoft Teams, Gmail, SharePoint, AWS S3, Grafana, MongoDB, Neo4j and Dynamics 365. You supply the address and credentials, and Liyagent runs the server. - **Remote** servers are any MCP server you can reach over the network. The catalogue has cards for some (for example GitHub, Notion, Linear, Sentry, PagerDuty, the SAP MCP Gateway and Salesforce's hosted MCP servers), and you can add any other by URL. - The catalogue is a starting set, not the limit. Any MCP server can be added, including one you write around your organisation's own code or OpenAPI operations, and once added it is granted to agents like any catalogue card. That is how an agent is built for a system the catalogue does not name. ## Add a server 1. Open **MCP servers** and choose **Add MCP server**. Pick a catalogue card, or add one by URL. 2. Choose how the server authenticates. A managed card offers the credential types its vendor accepts, such as **API key**, **Basic Auth**, **AWS access key**, **App (client credentials)** or **User OAuth**. Some vendors (Salesforce, Gmail, SharePoint and others) accept only **User OAuth**. A remote server offers: | Auth option | Use for | | --- | --- | | No credential | A server that needs none | | Bearer token | A static token sent as `Authorization: Bearer` | | API key header | A static key in a named header | | OAuth client credentials | An app's own OAuth client credentials | | Caller's token | Forwarding the caller's own identity-provider token, audience-checked | | Per-user OAuth | Each user's own connection, from [Connections](/docs/connect/connections) | Reference credentials from the [secrets store](/docs/connect/secrets) with `secret://