Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: '>=1.17.0'
go-version: '>=1.26.0'
- name: Run tests
run: |
make tests
Expand All @@ -29,6 +29,6 @@ jobs:
uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: '>=1.17.0'
go-version: '>=1.26.0'
- run: |
make tests-e2e
4 changes: 2 additions & 2 deletions Dockerfile.webui
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ COPY webui/react-ui/ ./
# Build the React UI
RUN bun run build

# Use a temporary build image based on Golang 1.24-alpine
FROM golang:1.24-alpine AS builder
# Use a temporary build image based on Golang 1.26-alpine
FROM golang:1.26-alpine AS builder

# Define argument for linker flags
ARG LDFLAGS="-s -w"
Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Try on [![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-b

Create customizable AI assistants, automations, chat bots and agents that run 100% locally. No need for agentic Python libraries or cloud service keys, just bring your GPU (or even just CPU) and a web browser.

**LocalAGI** is a powerful, self-hostable AI Agent platform that allows you to design AI automations without writing code. Create Agents with a couple of clicks, connect via MCP and give it skills with [skillserver](https://github.com/mudler/skillserver). Every agent exposes a complete drop-in replacement for OpenAI's Responses APIs with advanced agentic capabilities. No clouds. No data leaks. Just pure local AI that works on consumer-grade hardware (CPU and GPU).
**LocalAGI** is a powerful, self-hostable AI Agent platform that allows you to design AI automations without writing code. Create Agents with a couple of clicks, connect via MCP, and use built-in **Skills** (manage skills in the Web UI and enable them per agent). Every agent exposes a complete drop-in replacement for OpenAI's Responses APIs with advanced agentic capabilities. No clouds. No data leaks. Just pure local AI that works on consumer-grade hardware (CPU and GPU). Skills follow the [skillserver](https://github.com/mudler/skillserver) format and can be created, imported, or synced from git.

## 🛡️ Take Back Your Privacy

Expand All @@ -39,6 +39,7 @@ LocalAGI ensures your data stays exactly where you want it—on your hardware. N
- 💾 **Memory Management**: Control memory usage with options for long-term and summary memory.
- 🖼 **Multimodal Support**: Ready for vision, text, and more.
- 🔧 **Extensible Custom Actions**: Easily script dynamic agent behaviors in Go (interpreted, no compilation!).
- 📚 **Built-in Skills**: Manage reusable agent skills in the Web UI (create, edit, import/export, git sync). Enable "Skills" per agent to inject skill tools and the skill list into the agent.
- 🛠 **Fully Customizable Models**: Use your own models or integrate seamlessly with [LocalAI](https://github.com/mudler/LocalAI).
- 📊 **Observability**: Monitor agent status and view detailed observable updates in real-time.

Expand Down Expand Up @@ -195,7 +196,7 @@ Good (relatively small) models that have been tested are:
- **✓ Flexible Model Integration**: Supports GGUF, GGML, and more thanks to [LocalAI](https://github.com/mudler/LocalAI).
- **✓ Developer-Friendly**: Rich APIs and intuitive interfaces.
- **✓ Effortless Setup**: Simple Docker compose setups and pre-built binaries.
- **✓ Feature-Rich**: From planning to multimodal capabilities, connectors for Slack, MCP support, LocalAGI has it all.
- **✓ Feature-Rich**: From planning to multimodal capabilities, connectors for Slack, MCP support, built-in Skills, LocalAGI has it all.

## 🌟 Screenshots

Expand Down Expand Up @@ -224,6 +225,7 @@ Explore detailed documentation including:
- [REST API Documentation](#rest-api)
- [Connector Configuration](#connectors)
- [Agent Configuration](#agent-configuration-reference)
- [Skills](#3-skills)

### Environment Configuration

Expand All @@ -242,6 +244,8 @@ LocalAGI supports environment configurations. Note that these environment variab
| `LOCALAGI_API_KEYS` | A comma separated list of api keys used for authentication |
| `LOCALAGI_CUSTOM_ACTIONS_DIR` | Directory containing custom Go action files to be automatically loaded |

Skills are stored in a fixed `skills` subdirectory under `LOCALAGI_STATE_DIR` (e.g. `/pool/skills` in Docker). Git repo config for skills lives in that directory. No extra environment variables are required.

## Installation Options

### Pre-Built Binaries
Expand Down Expand Up @@ -693,6 +697,16 @@ You can create MCP servers in any language that supports the MCP protocol and ad
- **Testing**: Test your MCP servers independently before integrating with LocalAGI
- **Resource Management**: Ensure your MCP servers properly clean up resources

### 3. Skills

LocalAGI includes built-in **Skills** management. Skills are reusable instructions and resources (scripts, references, assets) that agents can use when "Enable Skills" is turned on for that agent.

- **Skills section (Web UI)**: Open **Skills** in the sidebar. Skills are stored under the state directory (`STATE_DIR/skills`). Create, edit, search, import, and export skills. You can also add git repositories to sync skills from.
- **Per-agent**: In agent creation or settings, enable **Enable Skills** in Advanced Settings. The agent will receive a list of available skills in its context and have access to skill tools (list, read, search, resources) via the built-in skills MCP.
- Skills use the same format as [skillserver](https://github.com/mudler/skillserver) (e.g. `SKILL.md` in a directory). You can export skills from LocalAGI and use them with the standalone skillserver, or import skills created elsewhere.

In Docker, the state directory is persisted (`/pool`), so skills are stored in `/pool/skills`. To use a host folder for skills, mount it over that path in your compose file (e.g. `- ./my-skills:/pool/skills`).

### Development

The development workflow is similar to the source build, but with additional steps for hot reloading of the frontend:
Expand Down
23 changes: 22 additions & 1 deletion core/agent/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,34 @@ func (a *Agent) initMCPActions() error {
generatedActions = append(generatedActions, actions...)
}

// Pre-connected MCP sessions (e.g. in-process skills server); already in a.mcpSessions after closeMCPServers()
for _, session := range a.options.extraMCPSessions {
actions, err := a.addTools(session)
if err != nil {
xlog.Error("Failed to add tools for extra MCP session", "error", err.Error())
continue
}
a.mcpSessions = append(a.mcpSessions, session)
generatedActions = append(generatedActions, actions...)
}

a.mcpActionDefinitions = generatedActions

return err
}

func (a *Agent) closeMCPServers() {
extraSet := make(map[*mcp.ClientSession]bool)
for _, e := range a.options.extraMCPSessions {
extraSet[e] = true
}
var keep []*mcp.ClientSession
for _, s := range a.mcpSessions {
s.Close()
if extraSet[s] {
keep = append(keep, s)
} else {
s.Close()
}
}
a.mcpSessions = keep
}
10 changes: 10 additions & 0 deletions core/agent/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"
"time"

"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/mudler/LocalAGI/core/types"
)

Expand Down Expand Up @@ -80,6 +81,7 @@ type options struct {
mcpServers []MCPServer
mcpStdioServers []MCPSTDIOServer
mcpPrepareScript string
extraMCPSessions []*mcp.ClientSession
newConversationsSubscribers []func(*types.ConversationMessage)

observer Observer
Expand Down Expand Up @@ -329,6 +331,14 @@ func WithPrompts(prompts ...DynamicPrompt) Option {
}
}

// WithMCPSession adds a pre-connected MCP client session (e.g. in-process skills MCP) to the agent.
func WithMCPSession(session *mcp.ClientSession) Option {
return func(o *options) error {
o.extraMCPSessions = append(o.extraMCPSessions, session)
return nil
}
}

// WithDynamicPrompts is a helper function to create dynamic prompts
// Dynamic prompts contains golang code which is executed dynamically
// // to render a prompt to the LLM
Expand Down
18 changes: 18 additions & 0 deletions core/state/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ type AgentConfig struct {
EnableReasoning bool `json:"enable_reasoning" form:"enable_reasoning"`
EnableForceReasoningTool bool `json:"enable_reasoning_tool" form:"enable_reasoning_tool"`
EnableGuidedTools bool `json:"enable_guided_tools" form:"enable_guided_tools"`
EnableSkills bool `json:"enable_skills" form:"enable_skills"`
KnowledgeBaseResults int `json:"kb_results" form:"kb_results"`
CanStopItself bool `json:"can_stop_itself" form:"can_stop_itself"`
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
SkillsPrompt string `json:"skills_prompt" form:"skills_prompt"`
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
SummaryLongTermMemory bool `json:"summary_long_term_memory" form:"summary_long_term_memory"`
ConversationStorageMode string `json:"conversation_storage_mode" form:"conversation_storage_mode"`
Expand Down Expand Up @@ -330,6 +332,14 @@ func NewAgentConfigMeta(
HelpText: "Long-term objective for the agent to pursue",
Tags: config.Tags{Section: "PromptsGoals"},
},
{
Name: "skills_prompt",
Label: "Skills Prompt",
Type: "textarea",
DefaultValue: "",
HelpText: "Optional instructions for using skills. Used when Enable Skills is on. If empty, default instructions are used.",
Tags: config.Tags{Section: "PromptsGoals"},
},
{
Name: "standalone_job",
Label: "Standalone Job",
Expand Down Expand Up @@ -404,6 +414,14 @@ func NewAgentConfigMeta(
HelpText: "Filter tools through guidance using their descriptions; creates virtual guidelines when none exist",
Tags: config.Tags{Section: "AdvancedSettings"},
},
{
Name: "enable_skills",
Label: "Enable Skills",
Type: "checkbox",
DefaultValue: false,
HelpText: "Inject available skills into the agent and expose skill tools (list, read, search, resources) via MCP",
Tags: config.Tags{Section: "AdvancedSettings"},
},
{
Name: "parallel_jobs",
Label: "Parallel Jobs",
Expand Down
22 changes: 22 additions & 0 deletions core/state/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ import (
"github.com/mudler/LocalAGI/pkg/localrag"
"github.com/mudler/LocalAGI/pkg/utils"

"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/mudler/xlog"
)

// SkillsProvider supplies the skills dynamic prompt and MCP session when skills are enabled for an agent.
type SkillsProvider interface {
GetSkillsPrompt(config *AgentConfig) (DynamicPrompt, error)
GetMCPSession(ctx context.Context) (*mcp.ClientSession, error)
}

type AgentPool struct {
sync.Mutex
file string
Expand All @@ -37,6 +44,7 @@ type AgentPool struct {
filters func(*AgentConfig) types.JobFilters
timeout string
conversationLogs string
skillsService SkillsProvider
}

type Status struct {
Expand Down Expand Up @@ -78,6 +86,7 @@ func NewAgentPool(
filters func(*AgentConfig) types.JobFilters,
timeout string,
withLogs bool,
skillsService SkillsProvider,
) (*AgentPool, error) {
// if file exists, try to load an existing pool.
// if file does not exist, create a new pool.
Expand Down Expand Up @@ -111,6 +120,7 @@ func NewAgentPool(
filters: filters,
timeout: timeout,
conversationLogs: conversationPath,
skillsService: skillsService,
}, nil
}

Expand Down Expand Up @@ -139,6 +149,7 @@ func NewAgentPool(
availableActions: availableActions,
timeout: timeout,
conversationLogs: conversationPath,
skillsService: skillsService,
}, nil
}

Expand Down Expand Up @@ -303,6 +314,11 @@ func (a *AgentPool) startAgentWithConfig(name, pooldir string, config *AgentConf

connectors := a.connectors(config)
promptBlocks := a.dynamicPrompt(config)(ctx, a)
if a.skillsService != nil && config.EnableSkills {
if prompt, err := a.skillsService.GetSkillsPrompt(config); err == nil && prompt != nil {
promptBlocks = append(promptBlocks, prompt)
}
}
actions := a.availableActions(config)(ctx, a)
filters := a.filters(config)
stateFile, characterFile := a.stateFiles(name)
Expand Down Expand Up @@ -488,6 +504,12 @@ func (a *AgentPool) startAgentWithConfig(name, pooldir string, config *AgentConf
}
}

if a.skillsService != nil && config.EnableSkills {
if session, err := a.skillsService.GetMCPSession(ctx); err == nil && session != nil {
opts = append(opts, WithMCPSession(session))
}
}

var ragClient *localrag.WrappedClient
if config.EnableKnowledgeBase {
ragClient = localrag.NewWrappedClient(effectiveLocalRAGAPI, effectiveLocalRAGKey, name)
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ services:
- "host.docker.internal:host-gateway"
volumes:
- localagi_pool:/pool
# Optional: mount a host directory for skills (replaces the default state-dir/skills path)
# - ./skills:/pool/skills

volumes:
postgres_data:
Expand Down
38 changes: 28 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/mudler/LocalAGI

go 1.24.4
go 1.26.0

require (
github.com/Masterminds/sprig/v3 v3.3.0
Expand All @@ -16,11 +16,11 @@ require (
github.com/google/go-github/v69 v69.2.0
github.com/google/uuid v1.6.0
github.com/jung-kurt/gofpdf v1.16.2
github.com/modelcontextprotocol/go-sdk v1.1.0
github.com/modelcontextprotocol/go-sdk v1.2.0
github.com/mudler/cogito v0.9.2-0.20260220220546-7e5c0264aac4
github.com/mudler/xlog v0.0.1
github.com/onsi/ginkgo/v2 v2.25.3
github.com/onsi/gomega v1.38.2
github.com/onsi/ginkgo/v2 v2.27.5
github.com/onsi/gomega v1.39.0
github.com/philippgille/chromem-go v0.7.0
github.com/robfig/cron/v3 v3.0.1
github.com/sashabaranov/go-openai v1.41.2
Expand All @@ -29,7 +29,7 @@ require (
github.com/tmc/langchaingo v0.1.14
github.com/traefik/yaegi v0.16.1
github.com/valyala/fasthttp v1.68.0
golang.org/x/crypto v0.43.0
golang.org/x/crypto v0.47.0
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
maunium.net/go/mautrix v0.17.0
mvdan.cc/xurls/v2 v2.6.0
Expand All @@ -40,6 +40,8 @@ require (
github.com/JohannesKaufmann/dom v0.2.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
Expand All @@ -59,24 +61,41 @@ require (
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.8 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.4 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/jsonschema-go v0.3.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/mudler/skillserver v0.0.5-0.20260221145827-0639a82c8f49 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
Expand Down Expand Up @@ -121,12 +140,11 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.mau.fi/util v0.3.0 // indirect
go.starlark.net v0.0.0-20250417143717-f57e51f710eb // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
golang.org/x/net v0.46.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.36.8 // indirect
maunium.net/go/maulogger/v2 v2.4.1 // indirect
Expand Down
Loading