Skip to main content

Integration settings

This page owns the deployment settings of the optional integrations: GitLab, Slack, Outline, practice review, email, and sync. Kubernetes and the deprecated Docker Compose install own the base installation. First steps owns the GitHub OAuth app and the first admin.

Integrations owns the workspace connections that a workspace admin makes in the UI. Runtime roles owns the container contract of the webapp image. The Install guide owns the clock requirement. The Configuration reference lists every variable with its default.

Base environment variables​

These base values are required. On Kubernetes, Secret custody gives their lifecycle constraints. On Docker Compose, the Install guide gives them. First steps covers the login provider and the first admin.

  • HEPHAESTUS_SECURITY_ENCRYPTION_KEY.
  • HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY.
  • HEPHAESTUS_AUTH_STATE_COOKIE_KEY.
  • WEBHOOK_SECRET and WEBHOOK_ROUTING_SECRET.
  • POSTGRES_PASSWORD.
  • One login provider.
  • HEPHAESTUS_AUTH_BOOTSTRAP_ADMINS.

The self-host installer generates its internal secrets. Operators of the multi-file Compose stack must supply equivalent values through their own secret management. Rotate integration credential keys with the credential-key rotation procedure.

These deployment settings complement the install table. SECURITY_CONTACT and HEPHAESTUS_RESEARCH_ORGANIZATION also ship in the self-host .env.example:

VariablePurpose
HEPHAESTUS_PUBLIC_ACTIVITY_ENABLEDSupplies the public activity instance default (false). A stored administrator choice takes precedence. Workspace publication also requires its own setting. See Public activity pages.
HEPHAESTUS_PUBLIC_ACTIVITY_REPOSITORY_VISIBILITY_MAX_AGELimits public visibility to recent successful repository syncs (48h). Use twice your metadata sync interval. See Public activity pages.
SECURITY_CONTACTOne mailto:, https:// or tel: URI, the security contact of this deployment. With it, the webapp serves /.well-known/security.txt. Empty serves none. Canonical is APPLICATION_CLIENT_URL only for one https:// origin without workspace addresses. It expires 364 days after the container start. Every release restarts the webapp.
HEPHAESTUS_RESEARCH_ORGANIZATIONNames the organization that runs optional research on this deployment. With a value, first-login setup asks each account Allow research use of your data? and shows this name. Empty by default. With no value, it never asks. A change to the value asks every account again. Read the operator obligations in Legal Pages first.
HEPHAESTUS_INTEGRATION_OAUTH_SUCCESS_REDIRECT / HEPHAESTUS_INTEGRATION_OAUTH_FAILURE_REDIRECTPost-callback landing URLs for integration OAuth. Success defaults to /integrations?status=success, failure to the success base + ?status=error. Set absolute URLs when the SPA host differs from the API host.
springdoc.api-docs.enabled / springdoc.swagger-ui.enabledPublish the OpenAPI description at /v3/api-docs and the interactive Swagger UI. Both default to false.
CAUTION: The enabled API description needs no authentication

springdoc.api-docs.enabled controls whether the endpoint exists, not whether it requires login. Whenever springdoc registers, /v3/api-docs/** and /swagger-ui/** answer any request. Activation publishes the instance's complete route list, including admin routes, to anyone. Leave both off in production. Generate the spec at build time instead.

CAUTION: docker/.env does not forward every setting

The shipped Compose files forward SECURITY_CONTACT, so its docker/.env value works. They do not forward these settings:

  • HEPHAESTUS_AUTH_ISSUER: application-server fixes it at https://${APP_HOSTNAME}.
  • SPRINGDOC_API_DOCS_ENABLED and SPRINGDOC_SWAGGER_UI_ENABLED: Compose does not forward them.
  • The two OAuth redirect variables: no Compose file passes them through, so defaults still apply.
  • HEPHAESTUS_DEV_SEED_ENABLED: a flag for local development only. It adds endpoints that write practice versions and feedback. Never set it in production.

Values for these settings only in docker/.env have no effect. To change the issuer, change APP_HOSTNAME. To override any of these settings, add it to the application-server service's own environment: block. Leave the API description endpoints disabled unless you supply an explicit service environment override.

CAUTION: GH_, not GITHUB_

The GitHub OAuth keys in docker/.env are GH_OAUTH_CLIENT_ID / GH_OAUTH_CLIENT_SECRET. GitHub Actions reserves the GITHUB_ prefix. Compose maps the keys to GITHUB_OAUTH_CLIENT_ID / _SECRET on the server. A GITHUB_OAUTH_CLIENT_ID value directly in docker/.env has no effect. The provider is skipped, with no sign-in button.

Slack app configuration​

After you create the app, upload slack-app-icon-512.png as its icon. Slack applies its own corner treatment. Do not round the asset before upload.

VariablePurpose
HEPHAESTUS_INTEGRATION_SLACK_ENABLEDEnables the Slack OAuth admin surface (per-workspace connect). Off by default.
SLACK_OAUTH_CLIENT_ID / SLACK_OAUTH_CLIENT_SECRETSeed the Slack account-linking provider. If unset, they fall back to the workspace-install OAuth credentials below. Compose forwards the integration credentials, not these separate overrides.
SLACK_OAUTH_DISPLAY_NAMEAccount-linking button label, default Slack. A Compose change requires an explicit application-server environment override.
HEPHAESTUS_INTEGRATION_SLACK_CLIENT_IDSlack app client ID used by the OAuth flow
HEPHAESTUS_INTEGRATION_SLACK_CLIENT_SECRETSlack app client secret used by the OAuth flow
HEPHAESTUS_INTEGRATION_SLACK_REDIRECT_URIRequired whenever Slack is enabled. Use the public application-server callback URL, including any proxy path prefix β€” e.g. https://<host>/api/oauth/callback/slack.
HEPHAESTUS_INTEGRATION_SLACK_SIGNING_SECRETRequired when Slack is enabled, on both the webhook server (Events API) and the application server (interactivity). A blank value fails the boot of either while Slack is enabled.

OAuth issues per-workspace bot tokens. The connection table stores them encrypted at rest. There is no global bot token.

When Slack is enabled, configure one Slack app. OAuth URLs use the public application-server base URL, including any proxy path prefix. In the production compose stack, that base is https://<host>/api.

  1. Add <api-public-base-url>/oauth/callback/slack to OAuth redirect URLs for workspace app installation.
  2. Add <api-public-base-url>/login/oauth2/code/slack for member account linking.
  3. Set Events request URL to https://<host>/webhooks/slack.
  4. Set Interactivity request URL to https://<host>/webhooks/slack/interactivity.
  5. Enable App Home.

Slack opens Home when someone selects the app. Hephaestus uses Home for account linking and privacy controls. Hephaestus uses the Messages tab, which is the DM with the app.

Events and interactivity go to different roles. Events are published to the event stream, and the webhook role receives them. A button press changes consent or erasure state, and that change is committed before Slack gets its acknowledgement. Only the application-server role can commit it, so interactivity goes there. The webhook role does not answer /webhooks/slack/interactivity.

Route this path to the webhook role:

  • /webhooks/slack.

Route this exact path to the application-server role, without stripping its prefix:

  • /webhooks/slack/interactivity.

Its rule must outrank the webhook role's /webhooks prefix rule. The production compose stack routes it this way on the public host. If the webhook receiver has a separate public host, point the Interactivity request URL at a host that reaches the application server.

Route OAuth and account-linking paths to the application-server role:

  • /oauth/callback/slack.
  • /auth/login.
  • /oauth2/authorization/*.
  • /login/oauth2/code/slack.

Include the same public prefix where applicable.

The bot token scope set stays minimal β€” every scope maps to a feature the code exercises:

ScopeFeature
chat:writeHephaestus replies in DMs, weekly digest notes, and monitored-channel announcements
assistant:writeHephaestus's streamed answers in DM threads
im:historyDM message events with their edits and deletions, and DM history reads that look for an unsettled Hephaestus reply or weekly digest note
reactions:writeThe πŸ‘€ reaction on a DM question while Hephaestus works on it
channels:history / groups:historyReading messages in workspace-admin-consented monitored channels (public / private)
channels:read / groups:readChannel directory listing for the admin channel picker and periodic channel-metadata refresh
channels:joinLetting an admin add a public channel to monitoring directly from the picker
users:readResolving a Slack user to a workspace member for Hephaestus

The user token scope set is only for Sign in with Slack account linking:

openid
profile
email

Workspace app installation gives Hephaestus a bot token. Hephaestus in DMs also requires the member's Slack account connected to their Hephaestus account.

  1. Create an enabled Hephaestus login provider with registration id slack and type SLACK.
  2. Set its base URL to https://slack.com.
  3. Set its scopes to openid profile email.

Do not add unused broad scopes unless new code uses them. These include chat:write.public, commands, team:read, app_mentions:read, im:write, mpim:read, and users:read.email.

Use slack-app-manifest-template.yml as the dashboard baseline. It owns scopes, event subscriptions, and request URLs. Its settings.event_subscriptions covers these event groups:

  • DM and channel message events for conversation turns and channel ingestion. DM edits and deletions arrive as message events too.
  • Channel-lifecycle events that update the monitored-channel allow-list.
  • App-lifecycle events, and app_home_opened for the Home tab.
  • agent_session_stopped, which Slack sends when a developer selects Stop while Hephaestus works in a Slack thread. It needs no OAuth scope beyond chat:write, which also sets that Stop's status.

Hephaestus does not read Slack reactions.

The weekly digest note needs no other scope. The bot posts the note with chat:write to the developer's Slack user ID. Thus, the app does not need im:write.

The nightly Slack history-reconciliation sweep replays conversations.history for already-consented channels through this bot token. It needs no additional scopes.

Update an existing Slack app​

A Slack app that you created from an older manifest does not have reactions:write. Without it, Hephaestus cannot show its working reaction on a DM.

  1. Apply the current slack-app-manifest-template.yml to the Slack app.
  2. Reinstall the app in the Slack dashboard when Slack asks for it.
  3. In each Hephaestus workspace with Slack, use Reconnect Slack with the same Slack workspace (Integrations).

Only a new authorization gives the stored bot token the new scope. MIGRATION.md gives the full upgrade procedure.

A Slack app from an older manifest also does not subscribe to agent_session_stopped. Without it, Slack shows no Stop while Hephaestus works in a Slack thread, and developers stop Hephaestus in the app. Apply the current manifest to add the subscription. It adds no OAuth scope, so a Slack app that already has reactions:write keeps its bot token.

Outline integration​

Outline supplies documentation content. A workspace admin connects one Outline instance under Administration β†’ Integrations β†’ Outline. They supply the wiki base URL and API key, then register collections to mirror. The connection table encrypts the key at rest.

There is no default Outline operator. Before you enable the integration, record these facts for the exact origin in the processor checklist:

  • Legal role.
  • Hosting region.
  • Transfer basis.
  • Retention terms.
  • AVV status.

A workspace admin cannot approve a new operator by entering its URL.

VariablePurpose
HEPHAESTUS_INTEGRATION_OUTLINE_ENABLEDEnables Outline beans for connect, sync, webhook, and content projection. Every bean requires it. Defaults to off. Without it, there is no admin surface, sync, or webhook verification.
HEPHAESTUS_INTEGRATION_OUTLINE_ALLOWED_ORIGINSComma-separated publicly routable HTTPS origins approved by the instance operator, such as https://wiki.example.com. Empty by default. No paths, queries, fragments, credentials, or private/site-local addresses are permitted. An invalid entry fails startup. All server, worker, and webhook roles need the same value. After a change, restart all roles. An origin removal blocks its connections, API sync, webhook collection, evidence projection, and identity linking. Its mirror remains until workspace disconnection.
OUTLINE_OAUTH_BASE_URLOutline instance for account linking, such as https://wiki.example.com. Required whenever OUTLINE_OAUTH_CLIENT_ID is set. Self-hosted Outline has no default origin. If blank or invalid, the provider seed is skipped. The reason appears at ERROR with the variable name. The app still boots, but has no linking button.
OUTLINE_OAUTH_CLIENT_IDOutline OAuth app client ID (redirect https://<host>/api/login/oauth2/code/outline)
OUTLINE_OAUTH_CLIENT_SECRETOutline OAuth app client secret
OUTLINE_OAUTH_DISPLAY_NAMELabel shown on the account-linking button (defaults to Outline)

Set HEPHAESTUS_INTEGRATION_OUTLINE_ENABLED on all three roles. The compose files pass the same value to each:

  • application-server: connect/admin API, collection registration, reconcile scheduler, and webhook-subscription registrar.
  • application-worker: assembles agent context. The Outline content source has no runtime-role gate. Without the flag, reviews that require wiki evidence decline, and conversation turns omit wiki context.
  • webhook-server: without the flag, no Outline delivery endpoint registers. Every delivery to POST /webhooks/outline/… returns 404. Outline counts these as failures and auto-disables the subscription after 25 failures.

There is no Outline signing-secret environment variable. Slack has one global signing secret. Each Outline subscription has its own secret, which Hephaestus derives from WEBHOOK_ROUTING_SECRET and the subscription's address. Nothing stores it.

A workspace connection's activation automatically registers the subscription. Its delivery URL is https://<host>/webhooks/outline/<key>/<route>, one route for each subscription. It comes from WEBHOOK_EXTERNAL_URL, which defaults to the application host.

OUTLINE_OAUTH_* is independent of the workspace connection. It seeds a link-only login provider with registration id outline. Members use it to link their Outline identity to their Hephaestus account for document attribution. There is no "sign in with Outline". The provider never appears on the login picker.

A blank OUTLINE_OAUTH_CLIENT_ID leaves it unseeded. You can add the provider later under Instance admin β†’ Login providers.

Registering the OAuth app in Outline (Settings β†’ Applications, admin-only):

  1. Set Redirect URI to https://<host>/api/login/oauth2/code/outline. Outline matches exact strings. Include your proxy's API path prefix, without a trailing slash. It allows 1–10 URIs, all HTTPS except loopback.
  2. Set Scope to read. Outline uses plain OAuth2, not OIDC. Never request openid. POST /api/auth.info, the identity probe, needs read.

PKCE S256 is sent automatically, with no configuration necessary. Access tokens last one hour. Refresh tokens last 30 days and rotate on use with reuse detection.

API keys do not self-rotate. Outline's apiKeys.create and apiKeys.delete require an interactive session, never a bearer token. Hephaestus cannot rotate its own key. The connection admin card shows expiry before it happens, using apiKeys.list when the key can see itself. To renew it without erasing the mirror, follow Replace an Outline key without erasing the mirror.

An expired key reports accepted=false. The mirror stops refreshing until replacement.

GitHub integration​

Hephaestus connects to GitHub with a Personal Access Token (PAT) or GitHub App. GitHub Integration owns the mode comparison, required App permissions, webhook events, and manual webhook creation. Unlike GitLab, GitHub has no auto-registration.

GitHub App mode uses these environment variables:

  • GH_APP_ID.
  • GH_APP_PRIVATE_KEY or GH_APP_PRIVATE_KEY_LOCATION.
  • GH_APP_INSTALLATION_URL.
  • GH_APP_CLIENT_ID.
  • GH_APP_CLIENT_SECRET.

Without them (GH_APP_ID=0, the default), workspaces use PATs configured through the UI.

GitLab rollout bundle​

GitLab login, workspaces, webhook auto-registration, and practice review are not independent switches. For a GitLab practice-review rollout, enable this bundle together:

VariablePurpose
GITLAB_OAUTH_CLIENT_IDGitLab OAuth application client ID (callback https://<host>/api/login/oauth2/code/gitlab)
GITLAB_OAUTH_CLIENT_SECRETGitLab OAuth application client secret
GITLAB_OAUTH_BASE_URLGitLab instance for login. Defaults to https://gitlab.com. For self-hosted GitLab, set the instance URL, such as https://gitlab.lrz.de.
GITLAB_OAUTH_DISPLAY_NAMELogin-button label, default GitLab. Example: gitlab.lrz.de.
GITLAB_DEFAULT_SERVER_URLThe one GitLab instance for workspace creation, sync, and checkout, not login. Falls back to GITLAB_OAUTH_BASE_URL. Give the application server and every worker the same value.
GITLAB_ENABLEDEnables GitLab on the application server and every worker. The webhook receiver does not read it. Without it, a worker cannot check out merge requests. Each claimed GitLab review fails preparation with SCM Git preparation is unavailable. It stops before any result.
GITLAB_WORKSPACE_CREATIONEnables GitLab workspace creation in the UI and API
WEBHOOK_SECRETInstance-wide secret for GitHub webhook verification and operator-created GitLab hooks on shared POST /webhooks/gitlab (X-Gitlab-Token). Hephaestus group hooks instead use the connection endpoint and signed route credential. See WEBHOOK_ROUTING_SECRET. For manually supplied signing tokens on GitLab 19.0 and later, only the signature verifies the hook. This value must then be that whsec_… token.
WEBHOOK_ROUTING_SECRETSigns each Hephaestus-registered GitLab group hook token. Each hook can deliver only for its registered GitLab connection. Requires at least 32 characters. Must differ from WEBHOOK_SECRET and every encryption key. Give the application server and webhook receiver the same value. Required for every production deployment, including GitHub-only instances. Compose and readiness reject absent values.
WEBHOOK_ROUTING_PREVIOUS_SECRETPrevious value, used only during WEBHOOK_ROUTING_SECRET rotation. Its signed hooks keep delivering until registration replaces them with new-value hooks. After replacement, remove it.
WEBHOOK_EXTERNAL_URLPublic GitLab webhook base URL. Reference Compose leaves it unset so auto-registration derives it from the application host URL. For an override, add it to the application-server service's environment: block.

The login provider supports gitlab.com by default. For self-hosted GitLab, set GITLAB_OAUTH_BASE_URL to that instance.

  1. Create the GitLab OAuth application on that instance.
  2. Set its Redirect URI to https://<hostname>/api/login/oauth2/code/gitlab.
  3. Set Scope to read_user.
  4. Enable Confidential client.

The environment seeds one default login provider, with registration id gitlab. An instance admin can add other GitLab instances at runtime under Instance admin β†’ Login providers, without redeployment.

GitLab workspaces exist only on GITLAB_DEFAULT_SERVER_URL. Hephaestus reads every GitLab project, member, and event from that instance. It refuses any other server before token validation, group listing, or token storage. A login provider for another instance permits sign-in and identity linking, not workspace hosting.

The workspace owner is the creator's GitLab account on the default instance. Workspace creation requires a login provider for that instance and a creator linked to it.

The auto-registered group webhook subscribes to these events:

  • Merge request, issue, and note.
  • Push, tag push, and pipeline.
  • Milestone and member.
  • Subgroup and project.

An upgrade does not add subscriptions to an older release's hook. For a workspace connected before pipeline events had a consumer:

  1. Enable Pipeline events under the group's Settings β†’ Webhooks. Alternatively, delete the hook. Hephaestus then registers it again on the next sync.

Until then, only scheduled sync supplies the head pipeline's state. Hephaestus then has no pipeline jobs as evidence, and no review starts when a pipeline finishes. Before GitLab 18.6, merge request events omit each reviewer's review state. On those versions, only scheduled sync (MONITORING_SYNC_CRON) supplies approvals, change requests, or renewed review requests.

The server runtime role inspects GitLab tokens daily at 03:00 UTC. This is independent of repository sync and webhook registration. A token within hephaestus.webhook.token-rotation.threshold-days of expiry (default 7 days) self-rotates with configured validity (default 90 days). It needs GitLab api or self_rotate scope and an instance version with self-rotation support. The server serializes rotation with token replacement.

CAUTION: Do not share a token that Hephaestus rotates

Rotation immediately revokes the old token. Do not share that token with other applications. Hephaestus never automatically retries an uncertain rotation response. If the provider rotated the token but its response was lost, replace the token.

If rotation fails or is not permitted, workspace admins subscribed to Workspace connection alerts receive one email for the recorded problem. A refused token records a separate credential problem and sends one alert. Rotation or replacement clears the problem and sends a recovery email. Repeated checks do not repeat alerts for the same problem revision.

The connection page shows the observed expiry date, no expiry, or unknown expiry until inspection succeeds. It never shows the token. Replace refused or expiring tokens there. GitHub App installation tokens renew automatically without expiry action. GitHub personal access token expiry is not tracked. Workspace admins must track and replace them.

Slack revocation alerts and Outline expiry information stay unchanged.

Practice review​

Practice Review owns these subjects:

  • Deployment variables and workspace setup in screen order.
  • Each step's cost.
  • Campaigns versus recurring sweeps.
  • Agent queue-health metrics and retention.
  • Checks for a quiet workspace.

Two deployment-topology facts belong here. The worker runtime role (hephaestus.runtime.worker.enabled), not AGENT_ENABLED, activates the Docker sandbox. Only application-worker runs sandboxes and holds the Docker socket, for both queued reviews and the conversation host. application-server disables its worker role and uses connected workers.

NATS_ENABLED controls webhook-driven sync consumption. It is not part of the practice-review bundle. Production startup refuses a server or webhook role without NATS, as Runtime roles describes.

Each worker runs one conversation host for all conversation turns that it claims. The host starts with the first turn on that worker. Runtime Roles gives its fixed limits. Size each worker for this host in addition to its review sandboxes. The application server needs no memory for Conversation runtimes. Runtime Roles explains dispatch, capacity, and the host lifecycle.

Instance-wide LLM settings​

Every sandbox uses the LLM proxy on its worker, whether for Hephaestus or queued practice reviews. That proxy is its only path to a provider key. Keys are never injected directly. There is no separate intelligence service to configure.

Environment variables never set a provider, model, price, or credential. Those are runtime catalog data entered in the admin UI. Connect an AI Provider explains each field, the connection test, and a price calculation example.

Five optional instance-wide LLM variables hold deployment facts rather than catalog data. Each defaults to off or its standard value:

VariableDefaultWhat it does
HEPHAESTUS_LLM_DISPLAY_CURRENCY(empty)Shows converted spend alongside USD. Empty disables the second currency. Only EUR is supported. Other values, even valid codes such as GBP, fail startup with an explanation of accepted values. An unsupported currency cannot silently appear as a missing feature.
HEPHAESTUS_LLM_EGRESS_ALLOW_LOOPBACKfalsePermits saved and connected http://localhost provider base URLs. Never set in production. It exposes host-local services to workspace-admin "provider" requests through SSRF. Private, link-local, and CGNAT checks remain unconditional.
HEPHAESTUS_LLM_EGRESS_PRIVATE_HOSTS(empty)Model endpoints on your own network, as comma-separated host names, IP addresses, or CIDR ranges. Set the same value on the server and the worker. Only private addresses qualify. Loopback, link-local, and the cloud metadata ranges listed there stay refused. A range wider than /16 (IPv4) or /48 (IPv6), outside the private ranges, or covering the server's own address stops startup. Model endpoints on your own network.
HEPHAESTUS_LLM_EGRESS_PRIVATE_HOSTS_ALLOW_HTTPfalsePermits http:// base URLs for the hosts in HEPHAESTUS_LLM_EGRESS_PRIVATE_HOSTS. Every other host still needs HTTPS.
HEPHAESTUS_LLM_FX_DAILY_URLECB daily reference feedOverride only on an air-gapped instance with an internal mirror of the ECB file.

Conversation turns run in the conversation host of a worker, for both the webapp and Slack DMs. The application server keeps the conversation store and makes a first admission check when a question arrives. The worker admits the turn again, with the budget check, and builds the turn's context. With its worker role disabled, it creates no Docker client. A turn waits in the queue until a worker has free capacity.

Runtime roles owns dispatch, leases, drain, and the conversation host. The proxy resolves each model's upstream base URL, auth header, and key from its bound connection. No process-level configuration needs to fix those values. In split-pod deployments, worker and app pods use the same provider catalog. No LLM credentials pass between pods.

Each conversation turn reads the model and route of the workspace's Hephaestus binding when it starts. A change to the binding applies to the next turn without a restart of the conversation host.

Workspace budgets and alerting​

Two monthly LLM budgets exist. They are never added together:

  • The instance budget caps a workspace's shared model spend on your registered connections, billed to you. An instance admin sets it under Instance admin β†’ AI usage β†’ Set budget. It applies per workspace and UTC calendar month.
  • The workspace's own budget caps its spend on its own connected provider, billed to its provider account. A workspace admin sets it on their AI usage page.

Both are uncapped by default. For either cap, Remove cap clears it. An exact 0 cap pauses that purse immediately. This is the supported way to stop spend mid-month.

Each cap pauses only work it funds. An exhausted instance budget stops shared-model work, not own-provider work. An exhausted workspace budget does the reverse. The instance must not stop spend it does not pay for or know the price of. To stop all AI work, change the workspace status or feature switches, not its budget.

Once confirmed monthly spend reaches the cap, that purse's verdict becomes EXHAUSTED. New funded agent jobs are refused at submission. Queued jobs have another check immediately before claim. Conversation turns are refused when a worker claims them, before the conversation host runs them. Work resumes automatically next UTC calendar month, or about a minute after a cap increase or removal. The pause appears on both workspace AI usage and AI models pages.

Practice reviews and conversations fail differently. A queued practice review is refused before execution and posts nothing. The end user sees no error. Hephaestus does not go silent. A conversation turn that uses an exhausted purse gets a reply that states the monthly cap and who can increase it. The workspace admin controls the own-provider cap, and the instance admin controls the shared budget.

A silently lost turn would appear broken. Users therefore see and may report an exhausted cap. Plan for support contact, not only a dashboard change.

Some started attempts may return no trustworthy usage counters. Migrated legacy attempts may lack an admission price snapshot. These usage records have no confirmed cost and are excluded from monthly totals. Missing rates and unverified measurements are separate reasons for an unavailable cost. The reported total is then a lower bound on spend. The UI shows the confirmed total and a separate count of runs with unavailable cost across both purses.

The purse reads UNVERIFIABLE instead of WITHIN. EXHAUSTED still takes priority. A non-zero unpriced count indicates real spend may exceed the display. New models require a price or No metered API cost before activation. The latter counts as confirmed $0 spend only when the usage measurement is accepted.

There is no setting for UNVERIFIABLE-month behavior. An uncapped purse never pauses for unpriced usage because it has no cap to breach. A capped UNVERIFIABLE purse pauses exactly like an exhausted one because its cap cannot be verified. Adding missing rates cannot resolve an unknown or unverified measurement. Existing unpriced usage continues to block a capped purse for the current UTC month. AI providers explains measurement verification, historical records, and the stopped-writer upgrade requirement.

Alerting hook: there is no built-in push notification for a crossed workspace cap. Use the private endpoint and example rules in Scrape metrics for these Micrometer counters:

  • llm.budget.exhausted: increments once per usage event whose confirmed contribution crosses the workspace's monthly instance cap. This is your funded purse and the one that alerts you.
  • llm.budget.blocked: tagged by admission-point surface, including review_job and mentor. The agent-job tag also has cap, which names the purse that refused work.

Each EXHAUSTED transition logs a WARN. Compatible endpoint reasoning tokens remain in telemetry when reported. They have no separate price. OpenAI-compatible usage includes them in billable output tokens. A second reasoning rate would count the same work twice.

Email​

Hephaestus uses one SMTP relay for the whole instance. Account-security and deletion confirmations are essential mail. New product feedback emails, connection alerts, and survey invitations are separate opt-in subscriptions in personal settings. Email stays off until a relay host is set. There is no per-workspace mail configuration or connection authorization. Spring Boot's spring.mail.* is the transport, so each variable is the uppercase property path.

The weekly digest is also an opt-in subscription in personal settings. Its email note, of kind mentor-digest, goes only to a developer with a verified address whom Hephaestus cannot reach in Slack. This note is optional mail and expires if it is not sent within 24 hours.

Running without email is supported. For no-email operation:

  1. Leave SPRING_MAIL_HOST blank.
  2. Leave HEPHAESTUS_EMAIL_FROM blank.

Startup, sign-in, account management, product feedback, and in-app surveys require no SMTP service. Account-security and deletion emails are withheld, but their operations complete. Do not promise email confirmation as the only action record.

Personal settings hide email choices if transport is unconfigured and the account has no active subscriptions. After previous activation, only retained subscriptions remain visible so people can disable them. New opt-ins stay hidden. Users see no SMTP setup warning. Instance admins retain Instance settings β†’ Email for setup and verification. Survey administration keeps results and request history but replaces the send action with a setup link.

A direct invitation request returns 503 without email request records. Removal of SMTP configuration does not erase preferences. Attempts without configuration complete as NOT_CONFIGURED rather than waiting for reactivation. Existing subscriptions apply to future events after reconfiguration. Temporary relay outages and silent mode do not hide controls. Configuration, transport health, and deliberate withholding are separate states.

VariableDefaultPurpose
SPRING_MAIL_PROTOCOLsmtpTransport protocol: smtp (STARTTLS or implicit SSL) or smtps (implicit TLS).
SPRING_MAIL_HOST(unset β€” email off)Relay hostname. Setting it turns email on.
SPRING_MAIL_PORT587Relay port. 587 uses submission with STARTTLS. For implicit TLS, use 465 with SPRING_MAIL_PROPERTIES_MAIL_SMTP_SSL_ENABLE=true.
SPRING_MAIL_USERNAME / SPRING_MAIL_PASSWORD(unset)Relay credentials. For an unauthenticated internal relay, leave both unset. Set SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=false.
SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTHtrueAuthenticate with the credentials above.
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE / _REQUIREDtrue / trueUpgrades with STARTTLS. Refuses relays that cannot upgrade. Disable both only for implicit TLS or an unauthenticated relay inside a trusted private network. The latter requires SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=false and no credentials.
HEPHAESTUS_EMAIL_FROM(unset)Sender address, required once the host is set. The relay must be allowed to send for its domain (SPF/DKIM), or receivers reject or junk the mail.
HEPHAESTUS_EMAIL_FROM_NAMEHephaestusDisplay name next to the sender address.
HEPHAESTUS_EMAIL_REPLY_TO(unset)Where a human reply lands. Unset means replies go to the sender address.
HEPHAESTUS_EMAIL_RATE_LIMIT_DAILY_CAPACITY250Shared SMTP attempt budget per 24-hour refill interval.
HEPHAESTUS_EMAIL_RATE_LIMIT_OPTIONAL_DAILY_CAPACITY200Optional-mail attempt budget. Must be below the total to reserve essential-mail capacity.

TLS verifies the relay certificate and hostname. Authenticated SMTP without implicit TLS or required STARTTLS fails startup to prevent credential exposure. For implicit TLS on port 465:

  1. Enable SSL.
  2. Disable both STARTTLS settings.
  3. Leave certificate verification enabled.

Alternatively, select SPRING_MAIL_PROTOCOL=smtps with port 465. Both protocols verify the relay hostname and use the timeouts below. Protocol-specific overrides use mail.smtp.* or mail.smtps.* respectively. For authenticated relays, keep authentication and certificate verification enabled.

Connection, read, and write timeouts default to 5 s, 10 s, and 10 s respectively. A relay that stops answering cannot leave a socket operation waiting indefinitely. Only application-server reads these variables. Workers and webhook roles never send email.

A university or corporate relay usually requires port 587, STARTTLS, and a service account, with a daily recipient cap. The defaults fit that model.

  1. Ask the relay operator which sender domains it signs.
  2. Set HEPHAESTUS_EMAIL_FROM within one of those domains.

Verify it.

  1. Open Instance settings β†’ Email.
  2. Select Send test email.
  3. Check the outcome in the response.

The outcomes are:

  • SENT: includes the message id to find in the relay log.
  • SILENT_MODE: silent mode is on, as on a fresh instance.
  • NOT_CONFIGURED.
  • INVALID_ADDRESS.
  • REJECTED: refused mailbox.
  • UNAVAILABLE: unreachable relay or refused credentials.
  • RATE_LIMITED: shared attempt budget exhausted or its store unavailable.

The configuration readiness fact notification.email reports the same setting pair. /actuator/health gains a mail indicator that connects to the relay on each check. SMTP is in neither the readiness nor liveness group. Startup does not require connection validation. A relay outage must not take the application offline.

Delivery. The transaction that changes state also writes a notification to event_publication. Delivery follows immediately after commit. If the relay is unreachable, the row remains FAILED. A job retries every five minutes, in batches of 50. An account-deletion confirmation expires at its purge deadline.

Its next attempt completes without delivery, records EXPIRED, and deletes the registry row. The listener handles expiration, not a batch filter. Silent mode, absent or unverified addresses, and refused mailboxes are final outcomes. The row completes, and notification.email.delivery counts the outcome by kind and outcome. Logs contain the message id, never the address. Delivery is at-least-once.

A crash after SMTP acceptance but before registry completion can produce duplicates. SENT means relay acceptance, not inbox arrival. Opt-out and eligibility checks apply to later attempts. An SMTP attempt already in progress cannot be recalled.

Capacity. The existing PostgreSQL-backed Bucket4j store shares limits across server replicas. Keep hephaestus.auth.rate-limit.postgres-backed=true in production. An SMTP attempt uses capacity even if the relay rejects it or disconnects. A refund after an uncertain failure could exceed the relay limit. Withheld mail uses no capacity.

Durable notifications wait for capacity on a later retry. A synchronous test reports the outcome and does not queue itself. The 24-hour interval starts at first bucket use, not midnight or the relay's billing day. Match limits conservatively to relay policy, with allowance for other applications on the account. After limit changes, restart. Existing buckets keep their configuration until expiry.

Optional-mail delivery. Optional emails contain a visible unsubscribe confirmation link and an RFC 8058 one-click POST endpoint. A GET never changes subscriptions. The local HTTP Mailpit loop deliberately omits the HTTPS-only one-click header.

  1. Set public application host and webapp URLs to externally reachable HTTPS addresses.
  2. Configure relay DKIM signatures to include both List-Unsubscribe and List-Unsubscribe-Post headers.
  3. Keep SPF, DKIM, and DMARC aligned with the sender.
  4. Follow Google's sender requirements and RFC 8058.

Headers added in Hephaestus alone do not satisfy the standard.

A mail provider's published limits do not confirm your service account's allowance. Ask support:

  1. Which account incurs the quota charge?
  2. Do other applications share it?
  3. How does its window reset?
  4. How does it indicate quota rejection?

A shared From address alone does not establish a separate quota. Allow capacity for other senders and retries. For example, this instance might have 200 total and 150 optional attempts from a shared 250/day account. This operating budget does not guarantee compliance with a rolling-window relay quota. Application interval refills can permit bursts across a boundary.

For a larger research campaign, obtain an approved higher limit or suitable relay. Do not increase only the application budget. Five reports to two subscribed admins need ten attempts. A daily digest is unnecessary at that volume. Survey invitations and reminders need separate capacity planning.

Privacy. The relay operator receives personal data: an address and the fact that its owner has an account. Before you set the host, complete the review in the processor checklist.

Data sync and backfill​

The sync scheduler fetches recent GitHub issues, PRs, and reviews for monitored repositories. Optional backfill syncs historical data in batches to avoid rate-limit exhaustion.

VariableDefaultPurpose
MONITORING_RUN_ON_STARTUPtrueRun initial sync when application starts
MONITORING_TIMEFRAME7Days of recent activity to sync each cycle
MONITORING_SYNC_CRON0 0 * * * *Cron schedule for sync (hourly by default)
MONITORING_SYNC_COOLDOWN_IN_MINUTES60Minimum gap between syncs per repository
MONITORING_BACKFILL_ENABLEDtrueEnable historical data backfill
MONITORING_BACKFILL_BATCH_SIZE50Issues/PRs per backfill batch
MONITORING_BACKFILL_RATE_LIMIT_THRESHOLD500Skip backfill if rate limit below this
MONITORING_BACKFILL_INTERVAL_SECONDS60Interval between backfill batches

Backfill behavior: after recent sync completes, backfill works backward from the highest issue number in small batches. It pauses below the rate-limit threshold and resumes on the next cycle. It records progress per repository.

A workspace connection disconnect or workspace purge permanently erases the mirror. Disconnect is an erase gives the details.