-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
214 lines (206 loc) · 9.48 KB
/
docker-compose.yml
File metadata and controls
214 lines (206 loc) · 9.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# End-to-end dev harness for Foragent.
#
# Stands up:
# - rabbitmq — shared bus (RockBot agents are bus-subscribed)
# - foragent — this project; exposes HTTP A2A on port 5210
# - rockbot-init — seeds /data/agent with RockBot profile + well-known-agents.json
# pointing at foragent
# - rockbot — rockylhotka/rockbot-agent:0.9.15, configured to know Foragent
# as an A2A peer it can delegate tasks to. 0.9.11 brings
# the structured-data invoke_agent surface (PR #291) so
# RockBot can consume Foragent's FormSchema JSON results
# natively, not as text.
# - blazor — rockylhotka/rockbot-blazor:latest, web UI for chatting with
# the rockbot agent. Open http://localhost:8080 to test.
#
# Run:
# docker compose up --build
#
# Smoke-test Foragent directly (skips RockBot entirely):
# curl http://localhost:5210/.well-known/agent-card.json
# curl -X POST http://localhost:5210/ \
# -H "X-Api-Key: rockbot-calls-foragent" \
# -H "Content-Type: application/json" \
# -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"role":"ROLE_USER","messageId":"m1","parts":[{"text":"{\"intent\":\"fetch the page title\",\"url\":\"https://example.com\",\"allowedHosts\":[\"example.com\"]}"}]},"metadata":{"skill":"browser-task"}}}'
# Note: the A2A v1-preview schema uses protobuf-style enum values (ROLE_USER, not "user")
# and parts are bare {"text":"..."} objects — no "kind" field.
#
# RockBot's LLM config is passed through from .env (see .env.example for the shape —
# `cp .env.example .env` and fill in your key). Without it the agent falls back to
# EchoChatClient and won't reason its way to invoke_agent — but foragent itself is
# exercised end-to-end by the curl above.
name: foragent
services:
rabbitmq:
image: rabbitmq:4-management
hostname: rabbitmq
ports:
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: foragent
RABBITMQ_DEFAULT_PASS: foragent
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
interval: 5s
timeout: 3s
retries: 15
foragent-init:
# One-shot ownership fix for the foragent-data named volume. The Foragent
# Dockerfile chowns /data to the non-root `foragent` user at build time, but
# Docker mounts a fresh named volume root-owned and masks the chown —
# FileSkillStore then hits UnauthorizedAccessException on first boot. This
# init container runs as root once per volume creation and mirrors the
# rockbot-init pattern. Subsequent boots skip the mkdir+chown if already set.
image: busybox:latest
user: root
command: ["sh", "-c", "mkdir -p /data/foragent/skills /data/foragent/memory && chmod -R 777 /data/foragent"]
volumes:
- foragent-data:/data/foragent
foragent:
build:
context: .
depends_on:
rabbitmq:
condition: service_healthy
foragent-init:
condition: service_completed_successfully
ports:
- "5210:8080"
environment:
ASPNETCORE_URLS: http://+:8080
RabbitMq__HostName: rabbitmq
RabbitMq__Port: "5672"
RabbitMq__UserName: foragent
RabbitMq__Password: foragent
RabbitMq__VirtualHost: /
Gateway__AgentName: Foragent
Gateway__InternalAgentName: Foragent
Gateway__Description: "Browser agent — browser-task (generalist), learn-form-schema, execute-form-batch"
# RockBot will call Foragent with header X-Api-Key: rockbot-calls-foragent
ApiKeys__rockbot-calls-foragent__AgentId: RockBot
ApiKeys__rockbot-calls-foragent__DisplayName: RockBot
# LLM required for the browser-task planner and form-schema enrichment.
# Namespaced so Foragent can point at a different model than the RockBot side.
ForagentLlm__Endpoint: ${FORAGENT_LLM_ENDPOINT:?FORAGENT_LLM_ENDPOINT is required}
ForagentLlm__ModelId: ${FORAGENT_LLM_MODEL_ID:?FORAGENT_LLM_MODEL_ID is required}
ForagentLlm__ApiKey: ${FORAGENT_LLM_API_KEY:?FORAGENT_LLM_API_KEY is required}
# Optional embeddings. Empty values (default) → BM25-only skill + memory
# retrieval. Set any one of Endpoint/ModelId/ApiKey empty and the others
# are ignored (all-or-nothing at startup). Separate subscription from
# ForagentLlm because embedding deployments often live elsewhere.
ForagentEmbeddings__Endpoint: ${FORAGENT_EMBEDDINGS_ENDPOINT:-}
ForagentEmbeddings__ModelId: ${FORAGENT_EMBEDDINGS_MODEL_ID:-}
ForagentEmbeddings__ApiKey: ${FORAGENT_EMBEDDINGS_API_KEY:-}
# Step 7: skills + long-term memory (spec §5.6). Paths align with the
# mounted volume below so learned site knowledge survives restarts.
ForagentMemory__SkillsPath: /data/foragent/skills
ForagentMemory__MemoryPath: /data/foragent/memory
# Step 7.5: daily dream pass to consolidate accumulated skills +
# memory. Disabled by default in appsettings.json so `dotnet run` smoke
# tests don't burn tokens; opt-in here because the compose harness is
# the "full operating mode" shape. CronSchedule default is 03:00 UTC
# daily — override via ForagentDreams__CronSchedule if needed.
ForagentDreams__Enabled: "true"
# Optional Bluesky credential used by future credentialed browser-task
# runs. Flat id (no slashes) because env-var keys use __ to separate
# config segments. Leave unset to disable.
Credentials__bluesky-rocky__Kind: username-password
Credentials__bluesky-rocky__Values__identifier: ${FORAGENT_BLUESKY_IDENTIFIER:-}
Credentials__bluesky-rocky__Values__password: ${FORAGENT_BLUESKY_APP_PASSWORD:-}
volumes:
- foragent-data:/data/foragent
rockbot-init:
image: rockylhotka/rockbot-agent:0.9.15
user: root
entrypoint: ["/bin/sh", "-c"]
command:
- |
set -e
echo "Seeding RockBot agent data..."
for f in soul.md directives.md subagent-directives.md style.md memory-rules.md \
dream.md skill-dream.md common-directives.md session-evaluator.md \
session-start.md heartbeat-patrol.md skill-optimize.md \
dlq-dream.md routing-dream.md; do
src="/app/agent/$$f"; dst="/data/agent/$$f"
[ -f "$$src" ] && [ ! -s "$$dst" ] && cp "$$src" "$$dst"
done
cp /seed/agent-name.md /data/agent/agent-name.md
cp /seed/well-known-agents.json /data/agent/well-known-agents.json
[ ! -s /data/agent/agent-trust.json ] && cp /seed/agent-trust.json /data/agent/agent-trust.json
[ ! -f /data/agent/mcp.json ] && echo '{"mcpServers":{}}' > /data/agent/mcp.json
for model_dir in /app/model-behaviors/*/; do
[ -d "$$model_dir" ] || continue
model_name=$$(basename "$$model_dir")
mkdir -p "/data/agent/model-behaviors/$$model_name"
for src in "$$model_dir"*; do
[ -f "$$src" ] || continue
dst="/data/agent/model-behaviors/$$model_name/$$(basename $$src)"
[ ! -s "$$dst" ] && cp "$$src" "$$dst"
done
done
mkdir -p /data/agent/memory /data/agent/skills /data/agent/conversations /data/agent/feedback
chmod -R 777 /data/agent
echo "RockBot agent data ready."
volumes:
- rockbot-data:/data/agent
- ./deploy/rockbot-seed:/seed:ro
rockbot:
image: rockylhotka/rockbot-agent:0.9.15
depends_on:
rockbot-init:
condition: service_completed_successfully
rabbitmq:
condition: service_healthy
foragent:
condition: service_started
environment:
RabbitMq__HostName: rabbitmq
RabbitMq__Port: "5672"
RabbitMq__UserName: foragent
RabbitMq__Password: foragent
RabbitMq__VirtualHost: /
AgentProfile__BasePath: /data/agent
Memory__BasePath: /data/agent/memory
Skill__BasePath: /data/agent/skills
McpBridge__ConfigPath: /data/agent/mcp.json
ModelBehaviors__BasePath: /data/agent/model-behaviors
Agent__Timezone: ${AGENT_TIMEZONE:-America/Chicago}
LLM__Provider: ${LLM_PROVIDER:-}
GITHUB_TOKEN: ${GITHUB_TOKEN:-}
LLM__Balanced__ModelId: ${LLM_MODEL_ID:-}
LLM__Balanced__Endpoint: ${LLM_ENDPOINT:-}
LLM__Balanced__ApiKey: ${LLM_API_KEY:-}
volumes:
- rockbot-data:/data/agent
- rockbot-shared:/rockbot/shared
blazor:
# Built from sibling ../rockbot checkout — the published
# rockylhotka/rockbot-blazor:latest image (2026-04-02) predates commit
# 9eab9d5 (2026-04-13) that scoped message-bus topics by agent name, so it
# still publishes to plain `user.message` instead of `user.message.RockBot`
# and the rockbot-agent container never picks the message up.
build:
context: ../rockbot
dockerfile: src/RockBot.UserProxy.Blazor/Dockerfile
depends_on:
rabbitmq:
condition: service_healthy
rockbot:
condition: service_started
ports:
- "8080:8080"
environment:
RabbitMq__HostName: rabbitmq
RabbitMq__Port: "5672"
RabbitMq__UserName: foragent
RabbitMq__Password: foragent
RabbitMq__VirtualHost: /
# Target the rockbot agent's message topics — rockbot subscribes to
# user.message.{agentName}. Without this, blazor publishes to plain
# user.message and nobody picks it up. The blazor image reads Agent:Name
# (not UserProxy:AgentName) in its Program.cs.
Agent__Name: RockBot
volumes:
rockbot-data:
rockbot-shared:
foragent-data: