GitOps with liya
Export agents, providers, guardrails, policies and triggers as YAML, review changes as a diff, and apply them from CI.
liya export writes an instance's configuration as YAML files you can commit. liya apply makes an instance match those files, and --dry-run shows you the diff first.
Export
liya export -o infra/This writes one file per resource:
infra/agents/<name>.yaml kind: AgentBundle the server's own agent bundle
infra/providers/<name>.yaml kind: Provider never an API key or other secret value
infra/guardrails/<name>.yaml kind: Guardrail
infra/policies/<name>.yaml kind: Policy Cedar
infra/triggers/<name>.yaml kind: TriggerEvery document has the same shape:
apiVersion: ticketiq.io/v1
kind: Trigger
metadata:
name: nightly-digest
spec:
agent: copilot
kind: cron
schedule: "0 2 * * *"
timezone: Europe/London
input: Summarise yesterday's ticketsEach spec holds exactly the fields that the server's own request schema accepts for that resource, which liya reads from the server's /openapi.json. Timestamps, etags and other server-managed fields are left out, so two exports diff cleanly. Fields that hold secret values (api_key, …secret, …password, …token, credentials_json) are never exported. A trigger's kind is the server's name for it: cron, ticket, webhook, email, teams, slack or google_chat. Fields that point to a stored secret (…_ref) are kept.
To export only some kinds, pass --kind Agent --kind Policy.
Plan, then apply
liya apply -f infra/ --dry-run # a unified diff per resource; exit 5 if anything would change
liya apply -f infra/ # make it so
liya apply -f infra/triggers/nightly-digest.yaml-f takes a single file or a directory. A directory is read recursively for *.yaml, *.yml and *.json, and multi-document YAML files work. Resources are applied in dependency order: providers, guardrails, policies, agents, then triggers.
- Agents go through
POST /api/agents/apply, in one transaction with every gate the console applies. A dry run shows the server's own plan: which sections would change, or why it would be refused. - Other kinds are compared with what is on the server over every field the
PUTwrites, and then written. APUTreplaces the whole record, so a field you leave out of the file is reset to its default: the diff shows it as a change rather than calling the resource unchanged. Fields the server manages (timestamps, etags) and secret values are never compared. A resource that already matches is reported asunchangedand not written. - If a change weakens a control, the server asks for a reason. Pass it with
--reason "…".
A file that contains a secret value is refused before anything is sent. Store the secret with liya secrets set and refer to it with a …_ref field.
If the connected server has no endpoint for a kind, that kind is reported as not supported by this server version and skipped. The rest of the run continues.
In CI
- name: Plan
run: liya apply -f infra/ --dry-run # exit 5 = changes pending, 2 = invalid, 3 = auth, 1 = other
env:
LIYA_URL: https://ticketiq.example.com
LIYA_TOKEN: ${{ secrets.LIYA_SERVICE_ACCOUNT }} # svc/<name>:<secret>
- name: Apply
if: github.ref == 'refs/heads/main'
run: liya apply -f infra/ --reason "merged ${{ github.sha }}"The tiq bundle commands (plan, apply, delete -f) still work through liya for pipelines that already use them. See examples/ci/ticketiq.yml.