thor
Health Warn
- No license — Repository has no license file
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Fail
- network request — Outbound network request in docker-compose.yml
- process.env — Environment variable access in docker/ingress/10-thor-admin-emails.envsh.test.ts
- rm -rf — Recursive force deletion command in docker/mitmproxy/entrypoint.sh
- process.env — Environment variable access in docker/opencode/bin/rg.test.ts
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Ambient intelligence for product engineers.
Thor
An event-driven AI team member that watches Slack and scheduled jobs, resumes OpenCode sessions through the runner, and reaches external systems through remote-cli.
Architecture
ingress -> gateway -> runner -> opencode -> codex-lb -> ChatGPT
\
-> remote-cli -> MCP upstreams / CLI integrations
gatewayaccepts Slack, GitHub webhook, and cron events, batches them, and forwards them to the runner.runnermanages OpenCode session continuity and Slack progress updates.remote-cliexposesPOST /exec/*endpoints for git, gh, sandbox, scoutqa, metabase, aws, psql, MCP tool calls (Atlassian, Grafana, PostHog, Langfuse), direct Slack approval-card posting, and approval status/resolution.codex-lbis an OpenAI-compatible proxy that fronts ChatGPT for opencode, pooling one or more ChatGPT account credentials so no paid OpenAI API key is needed. Its account/quota dashboard sits behind the same SSO + admin-email gate as/admin/.
Services
| Service | Port | Package | Role |
|---|---|---|---|
codex-lb |
2455 | Docker image | ChatGPT-backed OpenAI-compatible proxy |
cron |
- | docker/cron |
Scheduled prompts |
mitmproxy |
3080 | docker/mitmproxy |
Explicit outbound HTTP(S) proxy |
gateway |
3002 | @thor/gateway |
Slack/GitHub webhook ingestion and batching |
remote-cli |
3004 | @thor/remote-cli |
CLI + MCP policy gateway |
admin |
3005 | @thor/admin |
Admin dashboard and workspace configuration |
ingress |
8080 | docker/ingress |
Reverse proxy + Vouch integration |
opencode |
4096 | Docker image | Headless agent runtime |
runner |
3000 | @thor/runner |
Session lifecycle + Slack progress updates |
vouch |
9090 | Docker image | OAuth/SSO proxy |
Quick Start
- Copy
.env.exampleto.envand fill in the required secrets. Per-integration env vars are documented in each integration's doc (see Integrations below). - Initialize the mitmproxy CA on the host:
./scripts/mitmproxy-ca-init.sh
All outbound HTTP(S) from OpenCode is routed through mitmproxy; see docs/feat/security-model.md Layer 1a for the routing path, built-in defaults, and custom rule format.
Create
/workspace/config/thor.json(on the host:docker-volumes/workspace/config/thor.json) fromdocs/examples/thor.json.Clone repos into the shared workspace:
docker compose run --rm remote-cli \
git clone https://github.com/your-org/your-repo.git
If the stack is already running, use docker compose exec remote-cli ... instead.
- Start the stack:
mkdir -p docker-volumes/codex-lb && chmod 777 docker-volumes/codex-lb
docker compose up --build -d
curl http://localhost:8080/global/health
codex-lb runs as a non-root user and writes a SQLite store to /var/lib/codex-lb; pre-creating the host directory world-writable avoids a root-owned auto-mount.
Link a ChatGPT account so opencode has an upstream model:
Visit
http://localhost:8080/dashboard(admin-gated by Vouch +THOR_ADMIN_EMAILS), sign in with Google, and add a ChatGPT account from the codex-lb dashboard. Once linked, opencode picks the model from its UI (the provider whitelist surfacesgpt-5.4,gpt-5.4-mini,gpt-5.5).
Integrations
Thor is an internal AI teammate for engineering and product work; it is not meant to mirror production infrastructure exactly. Each integration owns its own env vars, app/manifest setup, required permissions, and troubleshooting reasons.
- Slack —
docs/slack.md. Events API intake, signing-secret verification, per-channel repo override, app manifest. - GitHub App —
docs/github.md. Webhook intake, App permissions and event subscriptions, installation IDs, bot commit identity, CI wake gate. - Daytona sandboxes —
docs/daytona.md. On-demand cloud sandboxes for project builds/tests/lints. Custom snapshot publishing. - Outbound HTTP(S) (mitmproxy) —
docs/feat/security-model.mdLayer 1a. Routing path, built-in defaults (Atlassian/Slack/OpenAI), custom credential rules.
Runtime integration paths:
| Integration | Path | Auth | Notes |
|---|---|---|---|
| Git / GitHub CLI | remote-cli /exec/git, /exec/gh |
GitHub App token | Repo-scoped worktree edits |
| Atlassian MCP | remote-cli /exec/mcp |
Auth header | Read + approved writes |
| PostHog MCP | remote-cli /exec/mcp |
API key | Read + approved writes |
| Grafana MCP | remote-cli /exec/mcp |
Service account token | Logs and observability |
| Langfuse MCP | remote-cli /exec/mcp |
API key pair | Read-only LLM observability queries |
| Slack Web API | gateway + remote-cli + OpenCode over mitmproxy |
Bot token | Mentions, progress, approval cards, thread reads/writes |
| LaunchDarkly | remote-cli /exec/ldcli |
Access token | Read-only feature flag inspection |
| Metabase | remote-cli /exec/metabase |
API key | Read-only warehouse access |
| Postgres (psql) | remote-cli /exec/psql |
Per-profile DB creds | Read-only Postgres access by database alias |
Common usage patterns:
- PR merged, errors spike — a scheduled prompt checks telemetry, inspects recent merges through GitHub tools, prepares a fix in a worktree, and requests approval for the final write action.
- Jira issue triage — a webhook or Slack prompt asks Thor to investigate an issue; Thor reads Jira, checks recent commits, and reports likely owners and suspects.
- Daily delivery digest — a cron job asks Thor to summarize stale PRs, blocked issues, or failing tests and post the result to Slack.
Deployment Configuration
Integration-specific env vars live in each integration's doc. MCP integration credentials (Atlassian, PostHog, Grafana, Langfuse) support _<PROFILE_NAME> profile-suffixed overrides; multi-value integrations (Atlassian, Grafana, Langfuse) resolve all-or-nothing per scope — see docs/feat/profile.md. The psql passthrough's PSQL_DATABASES bundle is also profile-suffixed. Metabase uses the unsuffixed global METABASE_* values. Cross-cutting vars:
| Variable | Required | Service | Purpose |
|---|---|---|---|
CRON_SECRET |
Yes | gateway, cron |
Shared secret for cron endpoint auth |
THOR_ADMIN_EMAILS |
Yes | ingress |
Comma-separated authenticated Google emails allowed for OpenCode-backed and /admin/ ingress routes |
THOR_INTERNAL_SECRET |
Yes | remote-cli, gateway |
Secret-gates gateway↔remote-cli internal APIs |
THOR_E2E_TEST_HELPERS |
No | runner |
Enables secret-gated deterministic runner e2e helpers |
RUNNER_BASE_URL |
Yes | remote-cli |
Public base URL for Thor trigger viewer links in PR/Jira content |
INGRESS_PORT |
No | ingress |
Host port for the reverse proxy |
ATLASSIAN_AUTH |
No | remote-cli, mitmproxy |
Atlassian MCP auth header and mitmproxy default injection; required (with cloud ID) to enable Atlassian |
ATLASSIAN_CLOUD_ID |
No | remote-cli |
Atlassian cloud ID; required (with auth) to enable Atlassian; injected server-side into outbound MCP calls, hidden from model-facing tool schemas |
POSTHOG_API_KEY |
No | remote-cli |
PostHog MCP auth |
GRAFANA_URL |
No | remote-cli |
Grafana instance URL; required (with token + org ID) to enable Grafana |
GRAFANA_SERVICE_ACCOUNT_TOKEN |
No | remote-cli |
Grafana service account token; required (with URL + org ID) to enable Grafana |
GRAFANA_ORG_ID |
No | remote-cli |
Grafana org ID; required (with URL + token) to enable Grafana |
LANGFUSE_BASE_URL |
No | remote-cli |
Langfuse MCP base URL; all three LANGFUSE_* vars required to enable Langfuse |
LANGFUSE_PUBLIC_KEY |
No | remote-cli |
Langfuse MCP public key; required (with secret key + host) to enable Langfuse |
LANGFUSE_SECRET_KEY |
No | remote-cli |
Langfuse MCP secret key; required (with public key + host) to enable Langfuse |
METABASE_URL |
No | remote-cli |
Metabase instance URL |
METABASE_API_KEY |
No | remote-cli |
Metabase API key |
METABASE_DATABASE_ID |
No | remote-cli |
Metabase database ID |
METABASE_ALLOWED_SCHEMAS |
No | remote-cli |
Comma-separated schema allowlist |
AWS_ACCESS_KEY_ID |
No | remote-cli |
Credential for the aws CLI passthrough; omit to use an attached IAM role. Pair with AWS_SECRET_ACCESS_KEY (+ AWS_SESSION_TOKEN) |
AWS_SECRET_ACCESS_KEY |
No | remote-cli |
Secret for the aws CLI passthrough; required with AWS_ACCESS_KEY_ID |
AWS_SESSION_TOKEN |
No | remote-cli |
Session token for temporary aws CLI credentials |
AWS_REGION / AWS_DEFAULT_REGION |
No | remote-cli |
Default region for the aws CLI passthrough |
PSQL_DATABASES[_<PROFILE>] |
No | remote-cli |
JSON bundle of Postgres connection targets keyed by database alias for the read-only psql passthrough; profile-suffixed override falls back to the global value. See docs/feat/profile.md |
VOUCH_GOOGLE_CLIENT_ID |
Yes | vouch |
Google OAuth client ID |
VOUCH_GOOGLE_CLIENT_SECRET |
Yes | vouch |
Google OAuth client secret |
VOUCH_JWT_SECRET |
Yes | vouch |
Session JWT signing secret |
VOUCH_ALLOWED_EMAIL_DOMAINS |
No | compose -> vouch |
Rendered into Vouch's VOUCH_DOMAINS; comma-separated email domains, default scoutqa.cc |
VOUCH_CALLBACK_URL |
No | vouch |
OAuth callback URL |
VOUCH_COOKIE_DOMAIN |
No | vouch |
Cookie domain |
Workspace config (thor.json)
Lives at /workspace/config/thor.json inside containers, docker-volumes/workspace/config/thor.json on the host. Hot-reloaded — no restart needed after edits. Use docs/examples/thor.json as a starting point and packages/common/src/proxies.ts as the reference for the built-in upstream catalog.
The file carries operator-maintained registries:
owners.<owner>.github_app_installation_id— GitHub App installation IDs. Seedocs/github.md§2.profiles.<name>.channels[]/profiles.<name>.repos[]— integration credential routing profiles.channels[]also admits gated Slack surfaces;repos[]never admits Slack by itself. Profile shapes, repo fallback, conflict handling, and profile-suffixed environment variables are documented indocs/feat/profile.md. Slack admission details are indocs/slack.md§5.mitmproxy[]/mitmproxy_passthrough[]— outbound credential rules and passthrough hosts. Seedocs/feat/security-model.mdLayer 1a.users[]— human attribution (see below).
Profile edits hot-reload — no service restart needed after thor.json changes. Profile selection is entirely harness-side; there is no agent-facing profile argument.
Human attribution (users[])
email must be the Jira account email; Thor may write the name/email into Co-authored-by: commit trailers and use the email to resolve Jira assignees.
{
"users": [
{ "email": "[email protected]", "name": "Alice", "slack": "UABCDEF1", "github": "alice" },
{ "email": "[email protected]", "name": "Bob" }
]
}
To verify your entry, trigger Thor from Slack and look for attribution_applied with outcome: "applied" and your Slack id; skipped_no_user_record means the configured Slack id did not match the trigger.
The registry is maintained by operators from team Slack and GitHub membership records, with Jira account emails verified manually when needed. Keep source exports out of git if they contain personal data — commit only sanitized reconciliation decisions.
Operations Notes
- Tell Thor about durable cross-task context through the memory tiers in
docs/feat/memory.md: global, Slack channel, and person memory are runner-injected; repo context belongs in repo-localAGENTS.md,CLAUDE.md, and docs. - Clone source repos from the
remote-clicontainer so git credentials and filesystem ownership stay consistent. - Repos under
/workspace/reposare mounted read-only into OpenCode. Thor creates edits in/workspace/worktrees. - OpenCode and remote-cli share the same
/tmpvolume so temporary artifacts referenced by absolute path, such asslack-post-message --blocks-file /tmp/..., are readable by the posting service. - Scheduled prompts live in
docker-volumes/workspace/cron/crontab.
Security Model
Thor contains untrusted input — agent, OpenCode wrappers, external webhooks — through layered controls. In short:
- Vouch SSO + mitmproxy bound the network; remote-cli binds to
127.0.0.1only. - Inbound webhooks are HMAC-verified (Slack signing secret, GitHub
X-Hub-Signature-256); internal gateway↔remote-cli routes are gated withx-thor-internal-secret. - Channel/mention/self-loop gates filter authenticated traffic before it wakes the agent.
remote-cliis the only place tool policy is enforced: MCP allow/approve/hidden tiers, command allowlists, GitHub App installation tokens. OpenCode never holds direct upstream credentials.- Repos mount read-only into OpenCode; edits happen in
/workspace/worktrees. Tool calls are audit-logged under/workspace/worklog.
See docs/feat/security-model.md for the full layered breakdown.
Testing
pnpm test
REMOTE_CLI_GIT_REPO_URL=https://github.com/owner/repo \
REMOTE_CLI_GITHUB_REPO=owner/repo \
pnpm test:e2e
pnpm test:create-jira-approval-e2e # live Slack/OpenCode approval-card e2e for Atlassian approval-required tools
pnpm test:opencode-e2e # separate explicit OpenCode/LLM smoke path
pnpm typecheck
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found