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_SECRETandWEBHOOK_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:
| Variable | Purpose |
|---|---|
HEPHAESTUS_PUBLIC_ACTIVITY_ENABLED | Supplies 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_AGE | Limits public visibility to recent successful repository syncs (48h). Use twice your metadata sync interval. See Public activity pages. |
SECURITY_CONTACT | One 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_ORGANIZATION | Names 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_REDIRECT | Post-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.enabled | Publish the OpenAPI description at /v3/api-docs and the interactive Swagger UI. Both default to false. |
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.
docker/.env does not forward every settingThe shipped Compose files forward SECURITY_CONTACT, so its docker/.env value works.
They do not forward these settings:
HEPHAESTUS_AUTH_ISSUER:application-serverfixes it athttps://${APP_HOSTNAME}.SPRINGDOC_API_DOCS_ENABLEDandSPRINGDOC_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.
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.
| Variable | Purpose |
|---|---|
HEPHAESTUS_INTEGRATION_SLACK_ENABLED | Enables the Slack OAuth admin surface (per-workspace connect). Off by default. |
SLACK_OAUTH_CLIENT_ID / SLACK_OAUTH_CLIENT_SECRET | Seed 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_NAME | Account-linking button label, default Slack. A Compose change requires an explicit application-server environment override. |
HEPHAESTUS_INTEGRATION_SLACK_CLIENT_ID | Slack app client ID used by the OAuth flow |
HEPHAESTUS_INTEGRATION_SLACK_CLIENT_SECRET | Slack app client secret used by the OAuth flow |
HEPHAESTUS_INTEGRATION_SLACK_REDIRECT_URI | Required 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_SECRET | Required 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.
- Add
<api-public-base-url>/oauth/callback/slackto OAuth redirect URLs for workspace app installation. - Add
<api-public-base-url>/login/oauth2/code/slackfor member account linking. - Set Events request URL to
https://<host>/webhooks/slack. - Set Interactivity request URL to
https://<host>/webhooks/slack/interactivity. - 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:
| Scope | Feature |
|---|---|
chat:write | Hephaestus replies in DMs, weekly digest notes, and monitored-channel announcements |
assistant:write | Hephaestus's streamed answers in DM threads |
im:history | DM message events with their edits and deletions, and DM history reads that look for an unsettled Hephaestus reply or weekly digest note |
reactions:write | The π reaction on a DM question while Hephaestus works on it |
channels:history / groups:history | Reading messages in workspace-admin-consented monitored channels (public / private) |
channels:read / groups:read | Channel directory listing for the admin channel picker and periodic channel-metadata refresh |
channels:join | Letting an admin add a public channel to monitoring directly from the picker |
users:read | Resolving 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.
- Create an enabled Hephaestus login provider with registration id
slackand typeSLACK. - Set its base URL to
https://slack.com. - 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
messageevents for conversation turns and channel ingestion. DM edits and deletions arrive asmessageevents too. - Channel-lifecycle events that update the monitored-channel allow-list.
- App-lifecycle events, and
app_home_openedfor 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 beyondchat: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.
- Apply the current
slack-app-manifest-template.ymlto the Slack app. - Reinstall the app in the Slack dashboard when Slack asks for it.
- 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.
| Variable | Purpose |
|---|---|
HEPHAESTUS_INTEGRATION_OUTLINE_ENABLED | Enables 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_ORIGINS | Comma-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_URL | Outline 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_ID | Outline OAuth app client ID (redirect https://<host>/api/login/oauth2/code/outline) |
OUTLINE_OAUTH_CLIENT_SECRET | Outline OAuth app client secret |
OUTLINE_OAUTH_DISPLAY_NAME | Label 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/β¦returns404. 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):
- 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. - Set Scope to
read. Outline uses plain OAuth2, not OIDC. Never requestopenid.POST /api/auth.info, the identity probe, needsread.
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_KEYorGH_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:
| Variable | Purpose |
|---|---|
GITLAB_OAUTH_CLIENT_ID | GitLab OAuth application client ID (callback https://<host>/api/login/oauth2/code/gitlab) |
GITLAB_OAUTH_CLIENT_SECRET | GitLab OAuth application client secret |
GITLAB_OAUTH_BASE_URL | GitLab 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_NAME | Login-button label, default GitLab. Example: gitlab.lrz.de. |
GITLAB_DEFAULT_SERVER_URL | The 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_ENABLED | Enables 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_CREATION | Enables GitLab workspace creation in the UI and API |
WEBHOOK_SECRET | Instance-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_SECRET | Signs 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_SECRET | Previous 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_URL | Public 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.
- Create the GitLab OAuth application on that instance.
- Set its Redirect URI to
https://<hostname>/api/login/oauth2/code/gitlab. - Set Scope to
read_user. - 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:
- 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.
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:
| Variable | Default | What 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_LOOPBACK | false | Permits 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_HTTP | false | Permits http:// base URLs for the hosts in HEPHAESTUS_LLM_EGRESS_PRIVATE_HOSTS. Every other host still needs HTTPS. |
HEPHAESTUS_LLM_FX_DAILY_URL | ECB daily reference feed | Override 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-pointsurface, includingreview_jobandmentor. The agent-job tag also hascap, 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:
- Leave
SPRING_MAIL_HOSTblank. - Leave
HEPHAESTUS_EMAIL_FROMblank.
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.
| Variable | Default | Purpose |
|---|---|---|
SPRING_MAIL_PROTOCOL | smtp | Transport 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_PORT | 587 | Relay 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_AUTH | true | Authenticate with the credentials above. |
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE / _REQUIRED | true / true | Upgrades 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_NAME | Hephaestus | Display 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_CAPACITY | 250 | Shared SMTP attempt budget per 24-hour refill interval. |
HEPHAESTUS_EMAIL_RATE_LIMIT_OPTIONAL_DAILY_CAPACITY | 200 | Optional-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:
- Enable SSL.
- Disable both STARTTLS settings.
- 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.
- Ask the relay operator which sender domains it signs.
- Set
HEPHAESTUS_EMAIL_FROMwithin one of those domains.
Verify it.
- Open Instance settings β Email.
- Select Send test email.
- 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.
- Set public application host and webapp URLs to externally reachable HTTPS addresses.
- Configure relay DKIM signatures to include both
List-UnsubscribeandList-Unsubscribe-Postheaders. - Keep SPF, DKIM, and DMARC aligned with the sender.
- 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:
- Which account incurs the quota charge?
- Do other applications share it?
- How does its window reset?
- 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.
| Variable | Default | Purpose |
|---|---|---|
MONITORING_RUN_ON_STARTUP | true | Run initial sync when application starts |
MONITORING_TIMEFRAME | 7 | Days of recent activity to sync each cycle |
MONITORING_SYNC_CRON | 0 0 * * * * | Cron schedule for sync (hourly by default) |
MONITORING_SYNC_COOLDOWN_IN_MINUTES | 60 | Minimum gap between syncs per repository |
MONITORING_BACKFILL_ENABLED | true | Enable historical data backfill |
MONITORING_BACKFILL_BATCH_SIZE | 50 | Issues/PRs per backfill batch |
MONITORING_BACKFILL_RATE_LIMIT_THRESHOLD | 500 | Skip backfill if rate limit below this |
MONITORING_BACKFILL_INTERVAL_SECONDS | 60 | Interval 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.