ai-support-agent

Version, currently main branch1 version
  • main branchlatestOct 12, 2026

github.com/PlaceOS/ai-support-agent

PlaceOS service that investigates incident signals and generates evidence-backed diagnostic reports.

0 stars
0 dependents
License: not declared

Installation

# Add this to your shard.yml
dependencies:
  ai-support-agent:
    github: PlaceOS/ai-support-agent
    branch: main

main is a branch, not a release, so this tracks it rather than pinning a version.

Then run:

shards install

shard.yml

Crystal
>= 1.20.0
Target
  • ai-support-agent from src/app.cr

Dependencies

Runtime Dependencies

Development Dependencies

  • faker*github: askn/fakerdev
  • webmock*github: manastech/webmock.cr, branch: masterdev

README

PlaceOS AI Support Agent

PlaceOS service that investigates incident signals and generates evidence-backed diagnostic reports. The current implementation is report-only and does not execute remediation.

Environment Variables

Database

  • PG_DATABASE_URL = PostgreSQL connection URL. Required for persistence, incident claiming, and module changefeed ingestion.

The following PgORM settings can be used instead of PG_DATABASE_URL:

  • PG_HOST = PostgreSQL hostname (default: localhost)
  • PG_PORT = PostgreSQL port (default: 5432)
  • PG_DATABASE = PostgreSQL database name (default: test)
  • PG_USER = PostgreSQL username (default: postgres)
  • PG_PASSWORD = PostgreSQL password (default: empty)
  • PG_QUERY = query parameters appended to the generated PostgreSQL URL (default: empty)
  • PG_LOCK_TIMEOUT = database lock timeout in seconds (default: 5)

PlaceOS API

  • PLACE_URI = PlaceOS REST API base URL. Required for PlaceOS diagnostic context.
  • PLACE_API_KEY = PlaceOS API key with administrator access for cluster process evidence. Preferred authentication method.
  • PLACE_INSECURE = disable TLS certificate verification when set to 1 or true (default: false)

User authentication can be used instead of PLACE_API_KEY. All of the following are required when using user authentication:

  • PLACE_EMAIL = PlaceOS user email
  • PLACE_PASSWORD = PlaceOS user password
  • PLACE_AUTH_CLIENT_ID = PlaceOS OAuth client ID
  • PLACE_AUTH_SECRET = PlaceOS OAuth client secret

OpenAI

  • OPENAI_API_KEY = OpenAI API key. Enables AI-assisted incident analysis.
  • OPENAI_API_BASE = alternate OpenAI-compatible or Azure OpenAI API base URL
  • OPENAI_MODEL = model used for incident analysis (default: gpt-4o-mini)

Service Desk Tickets

  • TICKET_WEBHOOK_TOKEN = shared token a service desk must send as the X-Ticket-Token header or the token query parameter on POST /api/ai-support/v1/tickets and POST /api/ai-support/v1/tickets/jira. No check when unset.
  • TICKET_EXTRACTION_MODEL = model used to read tickets (default: OPENAI_MODEL). Ticket reading works without a model; the model adds names and symptoms the regular expressions miss.
  • JIRA_URL, JIRA_EMAIL, JIRA_API_TOKEN = the Jira Cloud site and the account the agent writes as. When all three are set, a new ticket gets an internal triage note and a resolved ticket gets its Root cause field filled in when a person left it empty. Unset, the writes are recorded as skipped deliveries.
  • GOOGLE_CHAT_WEBHOOK_URL = incoming webhook of the Google Chat space the agent announces new tickets in: what it read, what matched, the likely cause and the recommended resolution. Unset, the message is recorded as a skipped delivery.
  • JIRA_ROOT_CAUSE_FIELD = the Root cause custom field id (default: customfield_10035).

Playbooks And Report Templates

  • PLAYBOOKS_PATH = workflow and procedure catalogue root (default: auto-discovered playbooks directory)
  • REPORT_TEMPLATES_PATH = report template directory (default: auto-discovered templates/reports directory)
  • REPORT_TEMPLATE_ID = report template ID (default: operator-report)

Report Delivery

  • REPORT_WEBHOOK_URL = outbound webhook URL for generated reports

  • REPORT_EMAIL_TO = comma-separated email recipients. Enables email delivery when SMTP is configured.

  • REPORT_DELIVERY_TIMEOUT_SECONDS = webhook connect/read and SMTP DNS/connect/read/write timeout, clamped to 1-120 seconds (default: 10)

  • REPORT_DELIVERY_MAX_ATTEMPTS = attempts per channel, clamped to 1-10 (default: 3). Set to 1 to disable retries.

  • REPORT_DELIVERY_RETRY_BASE_SECONDS = first retry delay, clamped to 1-3600 seconds (default: 30). Subsequent delays double: 30s, 60s, etc.

The first attempt on each configured channel stays inline. Connection, DNS, TLS and timeout failures, HTTP 408/425/429 and 5xx responses retry in background fibers. Other HTTP responses, configuration errors and rendering errors do not retry. Every attempt is recorded separately.

Pending retries are lost when the process stops. The escalation record keeps the first attempt's outcome even if a retry later succeeds.

  • REPORT_FALLBACK_WEBHOOK_URL = separate webhook used when a primary channel gives up
  • REPORT_FALLBACK_EMAIL_TO = comma-separated fallback recipients, using the same SMTP settings

Every configured fallback channel receives the same rendered report once a primary gives up, including non-retryable transport or configuration failures. Fallback starts once per incident in the background, even if both primaries fail. Fallback attempts use the same timeout and retry policy, and their failures do not trigger further fallback. Records use fallback_generic_webhook and fallback_email destinations.

Skipped deliveries (including maintenance suppression) and rendering failures do not trigger fallback. Fallback settings alone leave delivery disabled and produce a startup warning. The once-per-incident fallback guard is process-local, as are pending retry fibers.

SMTP

  • SMTP_SERVER = SMTP server hostname. Required when REPORT_EMAIL_TO is configured.
  • SMTP_PORT = SMTP server port (default: 25)
  • SMTP_SECURE = SMTP transport mode: NONE, STARTTLS, or SMTPS (default: NONE)
  • SMTP_USER = SMTP username. Must be set with SMTP_PASS.
  • SMTP_PASS = SMTP password. Must be set with SMTP_USER.
  • SMTP_FROM_EMAIL = report sender address. Required when REPORT_EMAIL_TO is configured.
  • SMTP_FROM_NAME = report sender name (default: PlaceOS Support Agent)
  • SMTP_HELO_DOMAIN = SMTP HELO/EHLO domain (default: sender email domain)

Incident Claiming

  • INCIDENT_CLAIM_LEASE_SECONDS = incident claim lease in seconds, clamped to 10-300 (default: 30)
  • INCIDENT_CLAIM_WAIT_MILLISECONDS = maximum wait for another worker to complete a claimed incident, clamped to 0-30000 (default: 2000)
  • INCIDENT_CLAIM_POLL_MILLISECONDS = claim polling interval, clamped to 10-1000 (default: 100)

Service

  • SG_ENV = runtime environment (default: development)
  • SG_SERVER_HOST = HTTP server bind address (default: 127.0.0.1)
  • SG_SERVER_PORT = HTTP server port (default: 3015)
  • SG_PROCESS_COUNT = HTTP server worker count (default: 1)
  • LOG_LEVEL = log severity (default: info in production, debug otherwise)
  • PLACE_COMMIT = commit identifier embedded at build time (default: DEV)

Service Desk Tickets

A support ticket enters the same incident workflow as a webhook. POST /api/ai-support/v1/tickets takes a normalised ticket:

{
  "reference": "SD-8601",
  "summary": "Module Error Stopping Azure AD to PlaceOS User Sync",
  "description": "One of the modules was throwing an error and stopping the user sync.",
  "organisation": "Suncorp",
  "priority": "Medium",
  "request_type": "Report a bug",
  "reporter_email": "reporter@example.com",
  "event": "created",
  "resolved": false,
  "comments": [{"author": "Reporter", "body": "Still failing today", "public": true}],
  "attachments": [{"filename": "error.png", "mime_type": "image/png"}]
}

Point the Jira webhook at /tickets/jira with a JQL filter that excludes request types the agent should not investigate (map changes, commercial requests); every ticket it receives becomes an incident. POST /api/ai-support/v1/tickets/jira takes a Jira Cloud webhook body (issue created, updated or commented) or a Jira issue document and normalises it. GET /api/ai-support/v1/tickets/:reference returns the incident for a ticket.

The service reads the ticket for PlaceOS ids, hostnames, room and module names, an environment and error text, then looks the names up through the PlaceOS REST API (domains, systems, zones, modules searches). One clear match sets the incident's module or system; several equally good matches are recorded as candidates and the incident escalates for a person to choose. The incident's correlation key is ticket:<reference>, so a later comment on the same ticket counts as a repeat and a resolved ticket counts as recovery.

With Jira configured, the agent writes back to the ticket the way a support engineer would on first contact: an internal (agent-only) comment with what it read, what it matched or the candidates it could not choose between, the classification and evidence, the next steps, and a suggested reply to the reporter including the questions a support engineer would ask (environment, which room or system, the error text rather than a screenshot). A person decides whether to send the reply. When a person resolves the ticket and leaves Root cause empty, the agent fills it with its classification and the evidence. Each write leaves a delivery record on the incident (jira:<key>:triage, jira:<key>:root_cause).

Development

shards install
./test