refactor: replace persistent subagents with ephemeral spawn tool (#280)

* refactor: replace persistent subagents with ephemeral spawn tool (#subagent)

- Drop subagents table, remove all persistent subagent infrastructure
- Add 'subagent' session type with parent_session_id on bot_sessions
- Rewrite subagent tool as single 'spawn' tool with parallel execution
- Create system_subagent.md prompt, add _subagent.md include for chat
- Limit subagent tools to file, exec, web_search, web_fetch only
- Merge subagent token usage into parent chat session in reporting
- Remove frontend subagent management page, update chat UI for spawn
- Fix UTF-8 truncation in session title, fix query not passed to agent

* refactor: remove history message page
This commit is contained in:
Acbox Liu
2026-03-22 19:03:28 +08:00
committed by GitHub
parent b88ca96064
commit b3a39ad93d
56 changed files with 716 additions and 4880 deletions
+11 -8
View File
@@ -78,7 +78,6 @@ import (
sessionpkg "github.com/memohai/memoh/internal/session"
"github.com/memohai/memoh/internal/settings"
"github.com/memohai/memoh/internal/storage/providers/containerfs"
"github.com/memohai/memoh/internal/subagent"
ttspkg "github.com/memohai/memoh/internal/tts"
ttsedge "github.com/memohai/memoh/internal/tts/adapter/edge"
"github.com/memohai/memoh/internal/version"
@@ -168,7 +167,6 @@ func runServe() {
browsercontexts.NewService,
policy.NewService,
mcp.NewConnectionService,
subagent.NewService,
conversation.NewService,
identities.NewService,
bind.NewService,
@@ -235,7 +233,6 @@ func runServe() {
provideServerHandler(handlers.NewScheduleHandler),
provideServerHandler(handlers.NewHeartbeatHandler),
provideServerHandler(handlers.NewCompactionHandler),
provideServerHandler(handlers.NewSubagentHandler),
provideServerHandler(handlers.NewChannelHandler),
provideServerHandler(feishu.NewWebhookServerHandler),
provideServerHandler(provideUsersHandler),
@@ -433,8 +430,16 @@ func provideAgent(log *slog.Logger, manager *workspace.Manager) *agentpkg.Agent
})
}
func injectToolProviders(a *agentpkg.Agent, providers []agenttools.ToolProvider) {
func injectToolProviders(a *agentpkg.Agent, msgService *message.DBService, providers []agenttools.ToolProvider) {
a.SetToolProviders(providers)
for _, p := range providers {
if sp, ok := p.(*agenttools.SpawnProvider); ok {
sp.SetAgent(agentpkg.NewSpawnAdapter(a))
sp.SetMessageService(msgService)
sp.SetSystemPromptFunc(agentpkg.SpawnSystemPrompt)
sp.SetModelCreator(agentpkg.SpawnModelCreatorFunc())
}
}
}
func provideChatResolver(log *slog.Logger, a *agentpkg.Agent, modelsService *models.Service, queries *dbsqlc.Queries, chatService *conversation.Service, msgService *message.DBService, settingsService *settings.Service, mediaService *media.Service, containerdHandler *handlers.ContainerdHandler, memoryRegistry *memprovider.Registry, sessionService *sessionpkg.Service, eventHub *event.Hub, compactionService *compaction.Service) *flow.Resolver {
@@ -494,7 +499,6 @@ func provideChannelRouter(
mediaService *media.Service,
ttsService *ttspkg.Service,
settingsService *settings.Service,
subagentService *subagent.Service,
scheduleService *schedule.Service,
mcpConnService *mcp.ConnectionService,
modelsService *models.Service,
@@ -530,7 +534,6 @@ func provideChannelRouter(
processor.SetCommandHandler(command.NewHandler(
log,
&command.BotMemberRoleAdapter{BotService: botService},
subagentService,
scheduleService,
settingsService,
mcpConnService,
@@ -595,7 +598,7 @@ func provideToolGatewayService(log *slog.Logger, fedGateway *handlers.MCPFederat
return svc
}
func provideToolProviders(log *slog.Logger, cfg config.Config, channelManager *channel.Manager, registry *channel.Registry, routeService *route.DBService, scheduleService *schedule.Service, settingsService *settings.Service, searchProviderService *searchproviders.Service, manager *workspace.Manager, mediaService *media.Service, memoryRegistry *memprovider.Registry, emailService *emailpkg.Service, emailManager *emailpkg.Manager, fedGateway *handlers.MCPFederationGateway, mcpConnService *mcp.ConnectionService, subagentService *subagent.Service, modelsService *models.Service, browserContextService *browsercontexts.Service, queries *dbsqlc.Queries, ttsService *ttspkg.Service, sessionService *sessionpkg.Service) []agenttools.ToolProvider {
func provideToolProviders(log *slog.Logger, cfg config.Config, channelManager *channel.Manager, registry *channel.Registry, routeService *route.DBService, scheduleService *schedule.Service, settingsService *settings.Service, searchProviderService *searchproviders.Service, manager *workspace.Manager, mediaService *media.Service, memoryRegistry *memprovider.Registry, emailService *emailpkg.Service, emailManager *emailpkg.Manager, fedGateway *handlers.MCPFederationGateway, mcpConnService *mcp.ConnectionService, modelsService *models.Service, browserContextService *browsercontexts.Service, queries *dbsqlc.Queries, ttsService *ttspkg.Service, sessionService *sessionpkg.Service) []agenttools.ToolProvider {
var assetResolver messaging.AssetResolver
if mediaService != nil {
assetResolver = &mediaAssetResolverAdapter{media: mediaService}
@@ -611,7 +614,7 @@ func provideToolProviders(log *slog.Logger, cfg config.Config, channelManager *c
agenttools.NewReadMediaProvider(log, manager, config.DefaultDataMount),
agenttools.NewEmailProvider(log, emailService, emailManager),
agenttools.NewWebFetchProvider(log),
agenttools.NewSubagentProvider(log, subagentService, settingsService, modelsService, queries, ""),
agenttools.NewSpawnProvider(log, settingsService, modelsService, queries, sessionService),
agenttools.NewSkillProvider(log),
agenttools.NewBrowserProvider(log, settingsService, browserContextService, manager, cfg.BrowserGateway),
agenttools.NewTTSProvider(log, settingsService, ttsService, channelManager, registry),
+12 -9
View File
@@ -79,7 +79,6 @@ import (
sessionpkg "github.com/memohai/memoh/internal/session"
"github.com/memohai/memoh/internal/settings"
"github.com/memohai/memoh/internal/storage/providers/containerfs"
"github.com/memohai/memoh/internal/subagent"
ttspkg "github.com/memohai/memoh/internal/tts"
ttsedge "github.com/memohai/memoh/internal/tts/adapter/edge"
"github.com/memohai/memoh/internal/version"
@@ -108,7 +107,6 @@ func runServe() {
searchproviders.NewService,
policy.NewService,
mcp.NewConnectionService,
subagent.NewService,
conversation.NewService,
identities.NewService,
bind.NewService,
@@ -161,7 +159,6 @@ func runServe() {
provideServerHandler(handlers.NewScheduleHandler),
provideServerHandler(handlers.NewHeartbeatHandler),
provideServerHandler(handlers.NewCompactionHandler),
provideServerHandler(handlers.NewSubagentHandler),
provideServerHandler(handlers.NewChannelHandler),
provideServerHandler(feishu.NewWebhookServerHandler),
provideServerHandler(provideUsersHandler),
@@ -357,8 +354,16 @@ func provideAgent(log *slog.Logger, manager *workspace.Manager) *agentpkg.Agent
})
}
func injectToolProviders(a *agentpkg.Agent, providers []agenttools.ToolProvider) {
func injectToolProviders(a *agentpkg.Agent, msgService *message.DBService, providers []agenttools.ToolProvider) {
a.SetToolProviders(providers)
for _, p := range providers {
if sp, ok := p.(*agenttools.SpawnProvider); ok {
sp.SetAgent(agentpkg.NewSpawnAdapter(a))
sp.SetMessageService(msgService)
sp.SetSystemPromptFunc(agentpkg.SpawnSystemPrompt)
sp.SetModelCreator(agentpkg.SpawnModelCreatorFunc())
}
}
}
func provideChatResolver(log *slog.Logger, a *agentpkg.Agent, modelsService *models.Service, queries *dbsqlc.Queries, chatService *conversation.Service, msgService *message.DBService, settingsService *settings.Service, mediaService *media.Service, containerdHandler *handlers.ContainerdHandler, memoryRegistry *memprovider.Registry, sessionService *sessionpkg.Service, eventHub *event.Hub, compactionService *compaction.Service) *flow.Resolver {
@@ -392,7 +397,7 @@ func provideChannelRegistry(log *slog.Logger, hub *local.RouteHub, mediaService
return registry
}
func provideChannelRouter(log *slog.Logger, registry *channel.Registry, hub *local.RouteHub, routeService *route.DBService, sessionService *sessionpkg.Service, msgService *message.DBService, resolver *flow.Resolver, identityService *identities.Service, botService *bots.Service, aclService *acl.Service, policyService *policy.Service, bindService *bind.Service, mediaService *media.Service, ttsService *ttspkg.Service, settingsService *settings.Service, subagentService *subagent.Service, scheduleService *schedule.Service, mcpConnService *mcp.ConnectionService, modelsService *models.Service, providersService *providers.Service, memProvService *memprovider.Service, searchProvService *searchproviders.Service, browserCtxService *browsercontexts.Service, emailService *emailpkg.Service, emailOutboxService *emailpkg.OutboxService, heartbeatService *heartbeat.Service, queries *dbsqlc.Queries, containerdHandler *handlers.ContainerdHandler, manager *workspace.Manager, rc *boot.RuntimeConfig) *inbound.ChannelInboundProcessor {
func provideChannelRouter(log *slog.Logger, registry *channel.Registry, hub *local.RouteHub, routeService *route.DBService, sessionService *sessionpkg.Service, msgService *message.DBService, resolver *flow.Resolver, identityService *identities.Service, botService *bots.Service, aclService *acl.Service, policyService *policy.Service, bindService *bind.Service, mediaService *media.Service, ttsService *ttspkg.Service, settingsService *settings.Service, scheduleService *schedule.Service, mcpConnService *mcp.ConnectionService, modelsService *models.Service, providersService *providers.Service, memProvService *memprovider.Service, searchProvService *searchproviders.Service, browserCtxService *browsercontexts.Service, emailService *emailpkg.Service, emailOutboxService *emailpkg.OutboxService, heartbeatService *heartbeat.Service, queries *dbsqlc.Queries, containerdHandler *handlers.ContainerdHandler, manager *workspace.Manager, rc *boot.RuntimeConfig) *inbound.ChannelInboundProcessor {
adapter, ok := registry.Get(qq.Type)
if !ok {
panic("qq adapter not registered")
@@ -412,7 +417,6 @@ func provideChannelRouter(log *slog.Logger, registry *channel.Registry, hub *loc
processor.SetCommandHandler(command.NewHandler(
log,
&command.BotMemberRoleAdapter{BotService: botService},
subagentService,
scheduleService,
settingsService,
mcpConnService,
@@ -473,7 +477,7 @@ func provideToolGatewayService(log *slog.Logger, fedGateway *handlers.MCPFederat
return svc
}
func provideToolProviders(log *slog.Logger, cfg config.Config, channelManager *channel.Manager, registry *channel.Registry, routeService *route.DBService, scheduleService *schedule.Service, settingsService *settings.Service, searchProviderService *searchproviders.Service, manager *workspace.Manager, mediaService *media.Service, memoryRegistry *memprovider.Registry, emailService *emailpkg.Service, emailManager *emailpkg.Manager, fedGateway *handlers.MCPFederationGateway, mcpConnService *mcp.ConnectionService, subagentService *subagent.Service, modelsService *models.Service, browserContextService *browsercontexts.Service, queries *dbsqlc.Queries, ttsService *ttspkg.Service, sessionService *sessionpkg.Service) []agenttools.ToolProvider {
func provideToolProviders(log *slog.Logger, cfg config.Config, channelManager *channel.Manager, registry *channel.Registry, routeService *route.DBService, scheduleService *schedule.Service, settingsService *settings.Service, searchProviderService *searchproviders.Service, manager *workspace.Manager, mediaService *media.Service, memoryRegistry *memprovider.Registry, emailService *emailpkg.Service, emailManager *emailpkg.Manager, fedGateway *handlers.MCPFederationGateway, mcpConnService *mcp.ConnectionService, modelsService *models.Service, browserContextService *browsercontexts.Service, queries *dbsqlc.Queries, ttsService *ttspkg.Service, sessionService *sessionpkg.Service) []agenttools.ToolProvider {
var assetResolver messaging.AssetResolver
if mediaService != nil {
assetResolver = &mediaAssetResolverAdapter{media: mediaService}
@@ -489,7 +493,7 @@ func provideToolProviders(log *slog.Logger, cfg config.Config, channelManager *c
agenttools.NewReadMediaProvider(log, manager, config.DefaultDataMount),
agenttools.NewEmailProvider(log, emailService, emailManager),
agenttools.NewWebFetchProvider(log),
agenttools.NewSubagentProvider(log, subagentService, settingsService, modelsService, queries, ""),
agenttools.NewSpawnProvider(log, settingsService, modelsService, queries, sessionService),
agenttools.NewSkillProvider(log),
agenttools.NewBrowserProvider(log, settingsService, browserContextService, manager, cfg.BrowserGateway),
agenttools.NewTTSProvider(log, settingsService, ttsService, channelManager, registry),
@@ -603,7 +607,6 @@ var (
"/schedule",
"/bind",
"/preauth",
"/subagents",
"/ping",
"/health",
}