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
1 change: 1 addition & 0 deletions RockBot.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<Project Path="src/RockBot.Tools.Mcp/RockBot.Tools.Mcp.csproj" />
<Project Path="src/RockBot.Scripts.Abstractions/RockBot.Scripts.Abstractions.csproj" />
<Project Path="src/RockBot.Scripts.Container/RockBot.Scripts.Container.csproj" />
<Project Path="src/RockBot.Scripts.Docker/RockBot.Scripts.Docker.csproj" />
<Project Path="src/RockBot.Scripts.Local/RockBot.Scripts.Local.csproj" />
<Project Path="src/RockBot.Scripts.Remote/RockBot.Scripts.Remote.csproj" />
<Project Path="src/RockBot.Scripts.Manager/RockBot.Scripts.Manager.csproj" />
Expand Down
5 changes: 5 additions & 0 deletions deploy/docker-compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ BRAVE_API_KEY=BSA-your-brave-key-here
# OPTIONAL — your IANA timezone (defaults to America/Chicago)
# AGENT_TIMEZONE=America/New_York

# OPTIONAL — host path for the agent data volume (soul, memory, skills, etc.)
# When set, agent data is stored in this directory on the host instead of a Docker volume.
# The directory will be created automatically if it doesn't exist.
# AGENT_DATA_PATH=./agent-data

# OPTIONAL — text embedding model for hybrid vector search (BM25 + cosine similarity).
# When configured, memory, skills, and working memory recall improves via vector search.
# Falls back to BM25-only keyword search when not set — no functionality is lost.
Expand Down
53 changes: 48 additions & 5 deletions deploy/docker-compose/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
name: rockbot

services:
rabbitmq:
image: rabbitmq:4-management
Expand Down Expand Up @@ -37,10 +39,12 @@ services:
if [ -f /app/agent/well-known-agents.json ] && [ ! -s /data/agent/well-known-agents.json ]; then
cp /app/agent/well-known-agents.json /data/agent/well-known-agents.json
fi
# Seed an empty mcp.json so the agent doesn't load the image default
# (which references cluster-internal URLs that don't exist locally)
# Seed mcp.json with compose-local MCP server URLs
# (overrides the image default which references cluster-internal URLs)
if [ ! -f /data/agent/mcp.json ]; then
echo '{"mcpServers":{}}' > /data/agent/mcp.json
cat > /data/agent/mcp.json <<'MCPEOF'
{"mcpServers":{"introspection":{"type":"sse","url":"http://introspection-mcp:8080/"}}}
MCPEOF
fi
# Per-model behavior files
for model_dir in /app/model-behaviors/*/; do
Expand All @@ -60,7 +64,7 @@ services:
chmod -R 777 /data/agent
echo "Agent data volume ready."
volumes:
- agent-data:/data/agent
- ${AGENT_DATA_PATH:-agent-data}:/data/agent

agent:
image: rockylhotka/rockbot-agent:latest
Expand All @@ -69,6 +73,8 @@ services:
condition: service_completed_successfully
rabbitmq:
condition: service_healthy
introspection-mcp:
condition: service_started
environment:
# -- RabbitMQ --
RabbitMq__HostName: rabbitmq
Expand Down Expand Up @@ -97,8 +103,45 @@ services:
# -- Timezone (set to your IANA timezone) --
Agent__Timezone: ${AGENT_TIMEZONE:-America/Chicago}
volumes:
- agent-data:/data/agent
- ${AGENT_DATA_PATH:-agent-data}:/data/agent

scripts-init:
image: docker:cli
entrypoint: ["docker", "image", "pull", "python:3.12-slim"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock

scripts-manager:
image: rockylhotka/rockbot-scripts-manager:latest
user: root # Required for Docker socket access
depends_on:
scripts-init:
condition: service_completed_successfully
rabbitmq:
condition: service_healthy
environment:
RabbitMq__HostName: rabbitmq
RabbitMq__Port: "5672"
RabbitMq__UserName: rockbot
RabbitMq__Password: rockbot
RabbitMq__VirtualHost: /
Scripts__Provider: Docker
Scripts__Docker__Image: python:3.12-slim
Scripts__Docker__CpuLimit: "500m"
Scripts__Docker__MemoryLimit: 256Mi
Scripts__Docker__NetworkMode: bridge
volumes:
- /var/run/docker.sock:/var/run/docker.sock

introspection-mcp:
image: rockylhotka/rockbot-introspection-mcp:latest
depends_on:
agent-init:
condition: service_completed_successfully
environment:
AgentName__Path: /data/agent/agent-name.md
volumes:
- ${AGENT_DATA_PATH:-agent-data}:/data/agent
blazor:
image: rockylhotka/rockbot-blazor:latest
depends_on:
Expand Down
2 changes: 2 additions & 0 deletions deploy/helm/rockbot/templates/todoapp-mcp/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ metadata:
app.kubernetes.io/component: mcp-todo
spec:
replicas: {{ .Values.todoappMcp.replicaCount }}
strategy:
type: Recreate
selector:
matchLabels:
app: mcp-todo
Expand Down
34 changes: 34 additions & 0 deletions src/McpServer.Introspection/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

# Clear Windows-specific NuGet fallback folders that break Linux builds
RUN printf '<configuration><fallbackPackageFolders><clear /></fallbackPackageFolders></configuration>' \
> /src/NuGet.config

# Copy project file for layer-cached restore
COPY Directory.Build.props .
COPY src/McpServer.Introspection/McpServer.Introspection.csproj src/McpServer.Introspection/

RUN dotnet restore src/McpServer.Introspection/McpServer.Introspection.csproj

COPY src/McpServer.Introspection/ src/McpServer.Introspection/

WORKDIR /src/src/McpServer.Introspection
RUN dotnet build McpServer.Introspection.csproj -c $BUILD_CONFIGURATION -o /app/build --no-restore

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish McpServer.Introspection.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "McpServer.Introspection.dll"]
3 changes: 2 additions & 1 deletion src/RockBot.Agent/UserMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ internal sealed class UserMessageHandler(
SessionStartTracker sessionStartTracker,
IOptions<AgentProfileOptions> profileOptions,
IWipTracker wipTracker,
AgentNameHolder agentNameHolder,
ILogger<UserMessageHandler> logger,
TierRoutingLogger tierRoutingLogger,
ISkillUsageStore? skillUsageStore = null) : IMessageHandler<UserMessage>
Expand Down Expand Up @@ -619,7 +620,7 @@ private async Task PublishReplyAsync(
{
Content = content,
SessionId = sessionId,
AgentName = agent.Name,
AgentName = agentNameHolder.DisplayName ?? agent.Name,
IsFinal = isFinal
};
var envelope = reply.ToEnvelope<AgentReply>(source: agent.Name, correlationId: correlationId);
Expand Down
29 changes: 29 additions & 0 deletions src/RockBot.Host/AgentProfileLoader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RockBot.Messaging;
using RockBot.UserProxy;

namespace RockBot.Host;

Expand All @@ -15,6 +17,8 @@ internal sealed class AgentProfileLoader : IHostedService, IDisposable
private readonly ProfileHolder _holder;
private readonly AgentNameHolder _nameHolder;
private readonly AgentProfileOptions _options;
private readonly IMessagePublisher _publisher;
private readonly AgentIdentity _agent;
private readonly ILogger<AgentProfileLoader> _logger;
private FileSystemWatcher? _watcher;
private Timer? _debounce;
Expand All @@ -25,12 +29,16 @@ public AgentProfileLoader(
ProfileHolder holder,
AgentNameHolder nameHolder,
IOptions<AgentProfileOptions> options,
IMessagePublisher publisher,
AgentIdentity agent,
ILogger<AgentProfileLoader> logger)
{
_provider = provider;
_holder = holder;
_nameHolder = nameHolder;
_options = options.Value;
_publisher = publisher;
_agent = agent;
_logger = logger;
}

Expand Down Expand Up @@ -131,6 +139,8 @@ private void LoadAgentName()
{
try
{
var previousName = _nameHolder.DisplayName;

var path = ResolveAgentNamePath();
if (File.Exists(path))
{
Expand All @@ -147,13 +157,32 @@ private void LoadAgentName()
_nameHolder.Update(null);
_logger.LogDebug("Agent name file not found at {Path}, using identity name", path);
}

var currentName = _nameHolder.DisplayName ?? _agent.Name;
if (!string.Equals(previousName, _nameHolder.DisplayName, StringComparison.Ordinal))
_ = PublishNameChangedAsync(currentName);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load agent name, keeping previous value");
}
}

private async Task PublishNameChangedAsync(string agentName)
{
try
{
var notification = new AgentNameChanged { AgentName = agentName };
var envelope = notification.ToEnvelope<AgentNameChanged>(source: _agent.Name);
await _publisher.PublishAsync(UserProxyTopics.UserResponse, envelope);
_logger.LogInformation("Published agent name change notification: {Name}", agentName);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to publish agent name change notification");
}
}

private string ResolveAgentNamePath()
{
var namePath = _options.AgentNamePath;
Expand Down
Loading
Loading