A multi-agent event bus for automating workflows across GitHub, Gmail, Google Calendar, Linear, and Slack. Built on embedded NATS with JetStream.
Seven binaries — sekiad (daemon), sekiactl (CLI), four agents (sekia-github, sekia-slack, sekia-linear, sekia-google), and sekia-mcp (MCP server) — communicate over NATS. The daemon and CLI also use a Unix socket.
┌──────────────────────────────────────────────────────────────────┐
│ sekiad │
│ │
│ ┌───────────────┐ ┌──────────┐ ┌───────────┐ ┌────────────┐ │
│ │ Embedded NATS │ │ Registry │ │ Workflow │ │ Sentinel │ │
│ │ + JetStream │ │ │ │ Engine │ │ (AI checks)│ │
│ └───────┬───────┘ └──────────┘ └─────┬─────┘ └────────────┘ │
│ │ │ │
│ ┌───────┴───────┐ ┌──────────┐ ┌─────┴─────┐ ┌────────────┐ │
│ │ AI Client │ │ Skills │ │ Conver- │ │ HTTP API │ │
│ │ (Anthropic) │ │ │ │ sations │ │ │ │
│ └───────────────┘ └──────────┘ └───────────┘ └──────┬─────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────┐ │ │
│ │ Web Dashboard (htmx + SSE) :8080 │ │ │
│ └────────────────────────────────────────────────────┘ │ │
└─────────────────────────────────────────────────────────┼────────┘
│ NATS (in-process) │ Unix socket
│ │
┌──────────┼────────────────────────────┐ ┌─────┴──────┐
│ │ │ │ sekiactl │
│ ┌─────┴──────┐ ┌──────────┐ │ └────────────┘
│ │ *.lua │ │ SKILL.md │ │ ┌────────────┐
│ │ workflows │ │ handlers │ │ │ sekia-mcp │
│ └────────────┘ └──────────┘ │ │ (stdio) │
│────────────┼────────────┼─────────────│ └────────────┘
▼ ▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐
│ sekia- │ │ sekia- │ │ sekia- │ │ sekia- │
│ github │ │ slack │ │ linear │ │ google │
└───┬────┘ └────┬────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
▼ ▼ ▼ ▼
GitHub Slack Linear Gmail/Calendar
- Start embedded NATS with JetStream
- Create registry (subscribes to
sekia.registryandsekia.heartbeat.>) - Start workflow engine, load
.luafiles, optionally start file watcher - Start HTTP API on Unix socket
- Block on OS signal or stop channel
- Shutdown in reverse order
| Subject | Purpose |
|---|---|
sekia.registry |
Agent registration announcements |
sekia.heartbeat.<name> |
Per-agent heartbeats (30s interval) |
sekia.events.<source> |
Event publishing |
sekia.commands.<name> |
Command delivery to agents |
Each component is a separate formula. Install what you need:
# Daemon + CLI (required)
brew install sekia-ai/tap/sekia
# Agents (install the ones you need)
brew install sekia-ai/tap/sekia-github
brew install sekia-ai/tap/sekia-slack
brew install sekia-ai/tap/sekia-linear
brew install sekia-ai/tap/sekia-google
brew install sekia-ai/tap/sekia-mcpEach formula (except sekia-mcp) includes a launchd service. Use brew services to manage them:
# Start the daemon as a background service
brew services start sekia
# Start agents as background services
brew services start sekia-github
brew services start sekia-slack
# Check running services
brew services list
# Stop a service
brew services stop sekia-githubLogs are written to $(brew --prefix)/var/log/<formula>.log.
go install github.com/sekia-ai/sekia/cmd/sekiad@latest
go install github.com/sekia-ai/sekia/cmd/sekiactl@latest
go install github.com/sekia-ai/sekia/cmd/sekia-github@latest
go install github.com/sekia-ai/sekia/cmd/sekia-slack@latest
go install github.com/sekia-ai/sekia/cmd/sekia-linear@latest
go install github.com/sekia-ai/sekia/cmd/sekia-google@latest
go install github.com/sekia-ai/sekia/cmd/sekia-mcp@latest# Run the full stack
docker compose up
# Or just the daemon
docker compose up sekiadAgent credentials are read from environment variables. Copy .env.example to .env and fill in your tokens:
cp .env.example .env
# Edit .env with your GITHUB_TOKEN, SLACK_BOT_TOKEN, etc.
docker compose upIndividual images can be built with targets:
docker build --target sekiad -t sekia/sekiad .
docker build --target sekia-github -t sekia/sekia-github .go build ./cmd/sekiad ./cmd/sekiactl ./cmd/sekia-github ./cmd/sekia-slack ./cmd/sekia-linear ./cmd/sekia-google ./cmd/sekia-mcp./sekiad./sekiactl status
./sekiactl agents
./sekiactl workflowssekia uses TOML config files searched in /etc/sekia, ~/.config/sekia, and .. Environment variables with the SEKIA_ prefix are also supported.
Defaults:
| Key | Default |
|---|---|
server.socket |
~/.config/sekia/sekiad.sock (or $XDG_RUNTIME_DIR/sekia/sekiad.sock) |
server.listen |
127.0.0.1:7600 |
nats.embedded |
true |
nats.data_dir |
~/.local/share/sekia/nats |
workflows.dir |
~/.config/sekia/workflows |
workflows.hot_reload |
true |
workflows.verify_integrity |
false |
ai.provider |
anthropic |
ai.model |
claude-sonnet-4-20250514 |
ai.max_tokens |
1024 |
ai.persona_path |
~/.config/sekia/persona.md |
skills.dir |
~/.config/sekia/skills |
conversation.max_history |
50 |
conversation.ttl |
1h |
sentinel.enabled |
false |
sentinel.interval |
5m |
web.listen |
(empty — disabled) |
See configs/sekia.toml for an example.
Secret values in config files can be encrypted or referenced inline using three backends:
| Format | Backend | Description |
|---|---|---|
ENC[...] |
age | Local age keypair encryption |
KMS[...] |
AWS KMS | Encrypt/decrypt via AWS KMS API |
ASM[...] |
AWS Secrets Manager | Fetch plaintext secret by name/ARN |
# age: generate keypair + encrypt
sekiactl secrets keygen
sekiactl secrets encrypt "ghp_mytoken123" # → ENC[...]
# AWS KMS: encrypt with a KMS key
sekiactl secrets kms-encrypt --key-id alias/sekia "ghp_mytoken123" # → KMS[...]All three formats can be mixed in the same config file:
[github]
token = "ENC[YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IF...]"
[webhook]
secret = "KMS[AQIDAHhB...]"
[nats]
token = "ASM[prod/sekia/nats-token]"For age, the decryption key can live off-machine — set SEKIA_AGE_KEY (raw key) or SEKIA_AGE_KEY_FILE (path). For AWS backends, credentials use the standard SDK default chain (env, profile, instance role). See SECURITY.md for details.
sekia includes an embedded web dashboard for monitoring agents, workflows, and live events. Enable it by setting web.listen:
[web]
listen = ":8080"Then open http://localhost:8080/web in your browser. The dashboard shows:
- System status — uptime, NATS status, agent/workflow counts
- Connected agents — name, status, version, event/error counters, last heartbeat
- Loaded workflows — name, handler count, event/error counters, patterns
- Live events — real-time event stream via Server-Sent Events (SSE)
Status and agent/workflow panels auto-refresh every 5–10 seconds via htmx. The event stream updates in real-time. No external dependencies — htmx and Alpine.js are vendored and embedded in the binary.
Security hardening is built in: Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security headers are set on every response. SSE connections are capped at 50 to prevent DoS. CSRF protection via double-submit cookie is enforced on all state-changing requests.
The daemon exposes an HTTP API over its Unix socket.
| Endpoint | Description |
|---|---|
GET /api/v1/status |
Daemon status, uptime, agent count, workflow count |
GET /api/v1/agents |
List registered agents with capabilities and stats |
GET /api/v1/workflows |
List loaded workflows with handler patterns and stats |
POST /api/v1/workflows/reload |
Reload all workflows from disk |
GET /api/v1/skills |
List loaded skills with descriptions and triggers |
The pkg/agent package provides an SDK for building agents that auto-register and send heartbeats.
package main
import (
"github.com/sekia-ai/sekia/pkg/agent"
"github.com/sekia-ai/sekia/pkg/protocol"
)
func main() {
a, err := agent.New(agent.Config{
Registration: protocol.Registration{
Name: "my-agent",
Version: "0.2.3",
Capabilities: []string{"read", "write"},
Commands: []string{"sync"},
},
NATSURLs: "nats://127.0.0.1:4222",
})
if err != nil {
panic(err)
}
defer a.Close()
// Use a.Conn() for custom NATS subscriptions
// Call a.RecordEvent() / a.RecordError() to update counters
}Workflows are Lua scripts that react to events and send commands to agents. Place .lua files in the workflow directory (default ~/.config/sekia/workflows/).
-- ~/.config/sekia/workflows/github_labeler.lua
sekia.on("sekia.events.github", function(event)
if event.type ~= "github.issue.opened" then return end
local title = string.lower(event.payload.title or "")
local label = "triage"
if string.find(title, "bug") then label = "bug" end
sekia.command("github-agent", "add_label", {
owner = event.payload.owner,
repo = event.payload.repo,
number = event.payload.number,
label = label,
})
sekia.log("info", "labeled issue #" .. event.payload.number)
end)| Function | Description |
|---|---|
sekia.on(pattern, handler) |
Register handler for NATS subject pattern (* and > wildcards) |
sekia.publish(subject, type, payload) |
Emit a new event |
sekia.command(agent, command, payload) |
Send command to an agent |
sekia.log(level, message) |
Log a message (debug, info, warn, error) |
sekia.ai(prompt [, opts]) |
Call an LLM and return the response text. Options: model, max_tokens, temperature, system |
sekia.ai_json(prompt [, opts]) |
Like sekia.ai but requests JSON and returns a parsed Lua table |
sekia.skill(name) |
Returns full instructions for a named skill (from SKILL.md files) |
sekia.conversation(platform, channel, thread) |
Returns a conversation handle with :append(), :reply(), :history(), :metadata() |
sekia.schedule(interval_seconds, handler) |
Register a timer-driven handler (minimum 1s interval) |
sekia.name |
The workflow's name (derived from filename) |
Workflows run in a sandboxed Lua VM with only base, table, string, and math libraries available. Dangerous functions (os, io, debug, dofile, load) are removed.
When hot_reload is enabled (default), editing or adding .lua files automatically reloads the affected workflows.
When workflows.verify_integrity is enabled, the daemon verifies each .lua file against a SHA256 manifest (workflows.sha256) before loading it. This prevents tampered or unsigned workflows from executing.
[workflows]
verify_integrity = trueGenerate the manifest with sekiactl:
sekiactl workflows sign
# Signed 3 workflow(s) in ~/.config/sekia/workflows
# a1b2c3... github-labeler.lua
# d4e5f6... slack-responder.lua
# 789abc... linear-triage.luaThe manifest uses sha256sum-compatible format. When hot-reload is enabled, updating the manifest file automatically triggers a full reload of all workflows.
Workflows can call an LLM using sekia.ai() and sekia.ai_json(). Configure the AI provider in sekia.toml:
[ai]
# api_key can also be set via SEKIA_AI_API_KEY env var
api_key = ""
model = "claude-sonnet-4-20250514"
max_tokens = 1024Both functions are synchronous and return (result, nil) on success or (nil, error_string) on failure. If no API key is configured, they return nil, "AI not configured".
sekia.ai(prompt, opts) returns the raw response text. sekia.ai_json(prompt, opts) requests a JSON response and parses it into a Lua table.
Options (all optional): model, max_tokens, temperature, system.
Example — AI issue classifier (configs/workflows/ai-issue-classifier.lua):
sekia.on("sekia.events.github", function(event)
if event.type ~= "github.issue.opened" then return end
local prompt = "Classify this GitHub issue. Reply with exactly one word: bug, feature, question, or docs.\n\n"
.. "Title: " .. (event.payload.title or "") .. "\n"
.. "Body: " .. (event.payload.body or "")
local result, err = sekia.ai(prompt, {
max_tokens = 16,
temperature = 0,
})
if err then
sekia.log("error", "AI classification failed: " .. err)
return
end
local label = string.lower(string.gsub(result, "%s+", ""))
sekia.command("github-agent", "add_label", {
owner = event.payload.owner,
repo = event.payload.repo,
number = event.payload.number,
label = label,
})
end)Example — AI PR summary (configs/workflows/ai-pr-summary.lua):
sekia.on("sekia.events.github", function(event)
if event.type ~= "github.pr.opened" then return end
local result, err = sekia.ai(
"Write a brief one-paragraph summary of this pull request.\n\n"
.. "Title: " .. (event.payload.title or "") .. "\n"
.. "Body: " .. (event.payload.body or ""),
{ system = "You are a helpful code review assistant. Be concise and technical." }
)
if err then return end
sekia.command("github-agent", "create_comment", {
owner = event.payload.owner,
repo = event.payload.repo,
number = event.payload.number,
body = "**AI Summary:** " .. result,
})
end)A markdown file defines your agent's personality, communication style, values, and boundaries. This content is automatically prepended to every AI system prompt.
# Create your persona
cat > ~/.config/sekia/persona.md << 'EOF'
You are a helpful engineering assistant. Be concise, technical, and direct.
When in doubt, ask for clarification rather than guessing.
EOFConfig: ai.persona_path in sekia.toml (default ~/.config/sekia/persona.md).
Skills are directory-based capability definitions with YAML frontmatter and natural language instructions. Place them in the skills directory (default ~/.config/sekia/skills/).
<!-- ~/.config/sekia/skills/pr-review/SKILL.md -->
---
name: pr-review
description: Reviews GitHub PRs for code quality
triggers:
- github.pr.opened
version: "1.0"
---
When reviewing a PR:
1. Check for test coverage
2. Look for security issues
3. Approve if only minor style issuesSkills metadata is automatically injected into AI prompts. Use sekia.skill(name) in workflows to get full instructions for a specific skill. Optional handler.lua files in skill directories are auto-loaded as workflows.
The conversation API provides in-memory multi-turn conversation state with TTL eviction:
sekia.on("sekia.events.slack", function(event)
if event.type ~= "slack.mention" then return end
local conv = sekia.conversation("slack", event.payload.channel,
event.payload.thread_ts or event.payload.timestamp)
local text = event.payload.text:gsub("<@[^>]+>%s*", "")
local response, err = conv:reply(text)
if err then return end
sekia.command("slack-agent", "send_reply", {
channel = event.payload.channel,
thread_ts = event.payload.thread_ts or event.payload.timestamp,
text = response,
})
end)Conversations are keyed by (platform, channel, thread). The :reply(prompt) method appends the user message, sends the full conversation history to the LLM, and stores the assistant response. Use :metadata(key, [value]) for per-conversation state.
Use sekia.schedule() to run handlers on a timer, enabling autonomous agent behavior:
sekia.schedule(300, function() -- every 5 minutes
local result, err = sekia.ai_json("Review open PRs needing attention", {
system = sekia.skill("pr-review")
})
if err then return end
for _, pr in ipairs(result.prs or {}) do
sekia.command("github-agent", "create_comment", {
owner = pr.owner, repo = pr.repo,
number = pr.number, body = pr.review_comment,
})
end
end)Sentinel reads a markdown checklist on a configurable interval, gathers system context, and asks the AI if anything needs attention:
<!-- ~/.config/sekia/sentinel.md -->
- Are there GitHub PRs open >3 days without review?
- Are there urgent Linear tickets with no assignee?
- Have any agents gone offline since last check?[sentinel]
enabled = true
interval = "5m"Events are published to sekia.events.sentinel and handled by workflows like any other event.
Each agent is a standalone binary that connects to the daemon's NATS bus, publishes events from an external service, and executes commands dispatched by Lua workflows.
All agents support a --name flag for running multiple instances of the same agent type with different configurations:
# Two GitHub agents for different accounts
sekia-github --name github-personal # reads ~/.config/sekia/sekia-github-personal.toml
sekia-github --name github-work # reads ~/.config/sekia/sekia-github-work.tomlWhen --name is set:
- Config file becomes
sekia-{agent}-{name}.toml(unless--configoverrides) - NATS registration uses the instance name (e.g.,
github-workinstead ofgithub-agent) - Command subject becomes
sekia.commands.{name}— target instances from workflows viasekia.command("github-work", "add_label", payload)
Without --name, behavior is unchanged.
Use sekiactl service to manage named instances as background services (launchd on macOS, systemd on Linux):
# Create a service
sekiactl service create sekia-github --name github-work
# Start/stop/restart
sekiactl service start github-work
sekiactl service stop github-work
sekiactl service restart github-work
# List all managed services
sekiactl service list
# NAME BINARY STATUS PID
# github-work sekia-github running 12345
# github-personal sekia-github stopped -
# Remove a service (stops and deletes)
sekiactl service remove github-workOptional flags on create:
--config /path/to/config.toml— pass an explicit config file--env KEY=VALUE— set environment variables (repeatable; stored in plaintext, preferENC[...]config values)
Logs are written to ~/.config/sekia/logs/{name}.log. The default brew services-managed instance is not affected.
Ingests GitHub events via webhooks and/or REST API polling, and executes GitHub API commands.
Webhook mode (default):
export GITHUB_TOKEN=ghp_...
./sekia-githubPoint your GitHub repository's webhook settings to http://<host>:8080/webhook. Optionally set GITHUB_WEBHOOK_SECRET to verify signatures.
Polling mode — useful when the agent cannot receive inbound webhooks (e.g., behind a firewall or in local development). Both modes can run simultaneously.
# sekia-github.toml
[poll]
enabled = true
interval = "30s"
repos = ["myorg/myrepo"]To use polling only (no webhook server), set webhook.listen = "".
Config: configs/sekia-github.toml. Env vars: GITHUB_TOKEN, GITHUB_WEBHOOK_SECRET, SEKIA_NATS_URL.
Events:
| GitHub Event | sekia Event Type | Source |
|---|---|---|
| Issue opened/closed/reopened/labeled/assigned | github.issue.<action> |
Webhook |
| Issue opened/closed | github.issue.opened, github.issue.closed |
Polling |
| Issue updated (any change) | github.issue.updated |
Polling only |
| PR opened/closed/merged/review_requested | github.pr.<action> |
Webhook |
| PR opened/closed/merged | github.pr.opened, github.pr.closed, github.pr.merged |
Polling |
| PR updated (any change) | github.pr.updated |
Polling only |
| PR matched (cycling) | github.pr.matched |
PR-match mode (match_prs = true) |
| Push | github.push |
Webhook only |
| Issue comment created | github.comment.created |
Both |
Polled events include payload.polled = true so workflows can distinguish them from webhook events if needed.
PR-match mode: Set match_prs = true in [poll] to cycle through all PRs matching state (and optionally labels) every interval. Emits github.pr.matched events with full PR metadata including author, draft, labels, branches. Useful for auto-approval or triage workflows.
Commands:
| Command | Required Payload | Action |
|---|---|---|
add_label |
owner, repo, number, label |
Add a label to an issue/PR |
remove_label |
owner, repo, number, label |
Remove a label |
create_comment |
owner, repo, number, body |
Post a comment |
close_issue |
owner, repo, number |
Close an issue |
reopen_issue |
owner, repo, number |
Reopen an issue |
approve_pr |
owner, repo, number |
Submit an approving review (optional: body) |
add_to_project |
owner, repo, number, project_id |
Add to a Projects v2 board (optional: fields) |
Example workflow: configs/workflows/github-auto-label.lua
Connects to Slack via Socket Mode (WebSocket). No public URL required.
export SLACK_BOT_TOKEN=xoxb-...
export SLACK_APP_TOKEN=xapp-...
./sekia-slackSetup: Create a Slack app at api.slack.com/apps with Socket Mode enabled. Required bot token scopes: chat:write, reactions:write, channels:history, groups:history, im:history. Generate an app-level token with connections:write scope. Enable Interactivity in your app settings to receive button click events (no Request URL needed with Socket Mode).
Config: configs/sekia-slack.toml. Env vars: SLACK_BOT_TOKEN, SLACK_APP_TOKEN, SEKIA_NATS_URL.
Events:
| Slack Event | sekia Event Type | Payload Fields |
|---|---|---|
| New message (not from bot) | slack.message.received |
channel, user, text, timestamp, thread_ts (if threaded) |
| Reaction added | slack.reaction.added |
user, reaction, channel, timestamp |
| Channel created | slack.channel.created |
channel_id, channel_name, creator |
| Message mentioning the bot | slack.mention |
channel, user, text, timestamp |
| Button clicked | slack.action.button_clicked |
action_id, value, block_id, user, user_name, channel, message_ts, message_text, trigger_id |
Commands:
| Command | Required Payload | Action |
|---|---|---|
send_message |
channel, text, blocks (optional) |
Post a message. When blocks is provided (array of Block Kit objects), sends a rich message with text as notification fallback |
add_reaction |
channel, timestamp, emoji |
Add a reaction to a message |
send_reply |
channel, thread_ts, text |
Reply in a thread |
update_message |
channel, timestamp, text, blocks (optional) |
Update an existing message. When blocks is provided, updates with rich Block Kit content |
Example workflow: configs/workflows/slack-auto-reply.lua
sekia.on("sekia.events.slack", function(event)
if event.type ~= "slack.mention" then return end
sekia.command("slack-agent", "send_reply", {
channel = event.payload.channel,
thread_ts = event.payload.timestamp,
text = "Hi <@" .. event.payload.user .. ">, thanks for reaching out!",
})
end)Polls the Linear GraphQL API for updated issues and comments. No webhooks or public URL required.
export LINEAR_API_KEY=lin_api_...
./sekia-linearSetup: Create a personal API key at linear.app/settings/api.
Config: configs/sekia-linear.toml. Env vars: LINEAR_API_KEY, SEKIA_NATS_URL.
| Setting | Default | Description |
|---|---|---|
poll.interval |
30s |
How often to poll Linear |
poll.team_filter |
(empty) | Limit to a team key (e.g., ENG) |
Events:
| Trigger | sekia Event Type | Payload Fields |
|---|---|---|
| New issue (created since last poll) | linear.issue.created |
id, identifier, title, state, priority, team, url, assignee, labels |
| Issue updated | linear.issue.updated |
(same as above) |
| Issue moved to Done/Completed/Canceled | linear.issue.completed |
(same as above) |
| New comment | linear.comment.created |
id, body, author, issue_id, issue_identifier |
Commands:
| Command | Required Payload | Action |
|---|---|---|
create_issue |
team_id, title, description (optional) |
Create a new issue |
update_issue |
issue_id, plus state_id/assignee_id/priority |
Update an issue |
create_comment |
issue_id, body |
Add a comment to an issue |
add_label |
issue_id, label_id |
Add a label to an issue |
Example workflow: configs/workflows/linear-auto-triage.lua
sekia.on("sekia.events.linear", function(event)
if event.type ~= "linear.issue.created" then return end
sekia.command("linear-agent", "create_comment", {
issue_id = event.payload.id,
body = "Auto-triaged. Team: " .. (event.payload.team or "unknown"),
})
end)Bridges Gmail and Google Calendar to the NATS event bus via Google REST APIs with OAuth2 authentication.
# First time: authorize via browser
sekia-google auth --config configs/sekia-google.toml
# Run the agent
./sekia-googleSetup: Create OAuth credentials at console.cloud.google.com/apis/credentials with application type "Desktop app". Run sekia-google auth to authorize via browser.
Config: configs/sekia-google.toml. Env vars: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_TOKEN_PATH, SEKIA_NATS_URL.
Gmail Events:
| Trigger | sekia Event Type | Payload Fields |
|---|---|---|
| New message | gmail.message.received |
id, thread_id, message_id, from, to, subject, body, date, labels |
Calendar Events:
| Trigger | sekia Event Type | Payload Fields |
|---|---|---|
| New event | google.calendar.event.created |
id, summary, description, location, start, end, organizer, attendees, html_link |
| Event updated | google.calendar.event.updated |
(same as above) |
| Event deleted | google.calendar.event.deleted |
id, summary, status |
| Event starting soon | google.calendar.event.upcoming |
(same as created) + minutes_until |
Gmail Commands:
| Command | Required Payload | Action |
|---|---|---|
send_email |
to, subject, body |
Send a new email |
reply_email |
thread_id, in_reply_to, to, subject, body |
Reply to an email |
add_label |
message_id, label |
Add a label to a message |
remove_label |
message_id, label |
Remove a label from a message |
archive |
message_id |
Archive a message (remove INBOX label) |
trash |
message_id |
Move a message to trash |
untrash |
message_id |
Move a trashed message back to inbox |
delete |
message_id |
Permanently delete a message |
Calendar Commands:
| Command | Required Payload | Action |
|---|---|---|
create_event |
summary, start, end |
Create a calendar event |
update_event |
event_id, plus optional fields |
Update a calendar event |
delete_event |
event_id |
Delete a calendar event |
Example workflow: configs/workflows/google-calendar-notify.lua
Exposes sekia capabilities to AI assistants (Claude Desktop, Claude Code, Cursor) via the Model Context Protocol. Uses stdio transport — the MCP client launches sekia-mcp as a subprocess.
./sekia-mcpConfig: configs/sekia-mcp.toml. Env vars: SEKIA_NATS_URL, SEKIA_DAEMON_SOCKET.
MCP Tools:
| Tool | Description |
|---|---|
get_status |
Daemon health, uptime, NATS status, agent/workflow counts |
list_agents |
Connected agents with capabilities, commands, and heartbeat data |
list_workflows |
Loaded Lua workflows with handler patterns and event/error counts |
reload_workflows |
Hot-reload all .lua workflow files from disk |
publish_event |
Emit a synthetic event onto the NATS bus to trigger workflows |
send_command |
Send a command to a connected agent (Slack message, GitHub comment, etc.) |
Claude Desktop setup: Add to your MCP settings (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"sekia": {
"command": "sekia-mcp",
"env": {
"SEKIA_NATS_URL": "nats://127.0.0.1:4222",
"SEKIA_DAEMON_SOCKET": "~/.config/sekia/sekiad.sock"
}
}
}
}The real power of sekia is connecting agents together. Here's a workflow that posts to Slack when a GitHub issue is opened, and creates a Linear tracking issue:
-- ~/.config/sekia/workflows/issue-tracker.lua
sekia.on("sekia.events.github", function(event)
if event.type ~= "github.issue.opened" then return end
local title = event.payload.title
local url = event.payload.url
local repo = event.payload.repo
-- Notify the team in Slack
sekia.command("slack-agent", "send_message", {
channel = "C_ENGINEERING",
text = "New issue in " .. repo .. ": " .. title .. "\n" .. url,
})
-- Create a tracking issue in Linear
sekia.command("linear-agent", "create_issue", {
team_id = "TEAM_ID_HERE",
title = "[" .. repo .. "] " .. title,
description = "GitHub issue: " .. url,
})
end)go test ./...Each agent has end-to-end integration tests that start the full daemon with embedded NATS, connect the agent in-process, and verify the complete event-to-command flow through Lua workflows.
Apache 2.0