Skip to content

Commit c4db7f6

Browse files
Merge pull request #29 from milliondreams/feat/cli
Feat/cli
2 parents 7a72989 + f7f2060 commit c4db7f6

38 files changed

Lines changed: 5352 additions & 272 deletions

docs/guild-cli.md

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# Forge Guild CLI
2+
3+
A command-line interface for running and debugging Forge guilds locally without
4+
the rustic-ui frontend.
5+
6+
## Features
7+
8+
- Launch guilds from JSON/YAML specs
9+
- Interactive REPL for chatting with guilds
10+
- Real-time message flow visualization
11+
- Agent status monitoring
12+
- Routing decision display
13+
- Ctrl+C signal handling for clean shutdown
14+
- Auto-detection of Python 3.13+
15+
- Quiet mode to hide noisy logs
16+
- Guild spec validation and inspection
17+
18+
## Prerequisites
19+
20+
- Python 3.13+ (auto-detected from pyenv or system)
21+
- Go 1.25+ (for building; see `forge-go/go.mod`)
22+
- Redis or NATS backend
23+
24+
## Installation
25+
26+
From the `forge-go` directory:
27+
28+
```bash
29+
go build -o forge ./cmd/forge
30+
# Optionally install to PATH
31+
sudo cp forge /usr/local/bin/
32+
```
33+
34+
## Quick Start
35+
36+
```bash
37+
# Run a guild (quiet mode)
38+
./forge guild run -q ../guilds/echo_app.json
39+
40+
# Type messages to interact with the guild
41+
> hello!
42+
43+
# Use commands
44+
> /status
45+
> /help
46+
> /quit
47+
```
48+
49+
## Usage
50+
51+
### Run a Guild
52+
53+
```bash
54+
# Basic usage
55+
./forge guild run ../guilds/echo_app.json
56+
57+
# With a specific Python interpreter
58+
./forge guild run --python /path/to/python3.13 ../guilds/echo_app.json
59+
60+
# Quiet mode (minimal startup output, clean display)
61+
./forge guild run -q ../guilds/echo_app.json
62+
63+
# Verbose mode (show all message details)
64+
./forge guild run -v ../guilds/echo_app.json
65+
66+
# Show routing information
67+
./forge guild run --show-routing ../guilds/echo_app.json
68+
```
69+
70+
### Inspect a Guild Spec
71+
72+
```bash
73+
./forge guild inspect ../guilds/echo_app.json
74+
```
75+
76+
Shows guild structure, agents, routing rules, and dependencies.
77+
78+
### Validate a Guild Spec
79+
80+
```bash
81+
./forge guild validate ../guilds/echo_app.json
82+
```
83+
84+
Checks for syntax errors and validates configuration.
85+
86+
## Interactive Commands
87+
88+
Once the REPL starts, you can use:
89+
90+
- Type any text to send a chat message to the guild
91+
- `/status` - Show current agent status
92+
- `/help` - Show help message
93+
- `/quit` or `/exit` - Exit the REPL
94+
- `Ctrl+C` - Shutdown cleanly
95+
96+
## Flags
97+
98+
### Common Flags
99+
100+
- `--backend` - Messaging backend: `redis` or `nats` (default: `nats`)
101+
- `--org-id` - Organization ID (default: `local-dev`)
102+
- `--user-id` - User ID for sending messages (default: `test-user`)
103+
- `--user-name` - User display name (default: `Test User`)
104+
- `--supervisor` - Supervisor type: `process`, `docker`, or `bubblewrap` (default: `process`)
105+
- `--python` - Python executable path (auto-detected if not specified)
106+
107+
### Output Control
108+
109+
- `-q, --quiet` - Minimal startup output, hide noisy logs (recommended)
110+
- `-v, --verbose` - Show full message details including payloads
111+
- `--show-routing` - Show routing history and transformations (default: `true`)
112+
113+
## Message Display
114+
115+
The CLI automatically filters noisy internal messages:
116+
117+
**Shown by default:**
118+
- User chat messages
119+
- Agent responses
120+
- Errors and warnings
121+
- Important state changes
122+
123+
**Hidden by default** (use `-v` to see):
124+
- Health checks and heartbeats
125+
- Internal state updates
126+
- Infrastructure events
127+
- HTTP request logs
128+
129+
## Python Version
130+
131+
The CLI requires **Python 3.13+**. It auto-detects in this order:
132+
133+
1. `pyenv which python` (preferred - gets the real path, not the shim)
134+
2. `python` from `PATH`
135+
3. `python3` from `PATH`
136+
137+
### Setting up Python 3.13
138+
139+
If you hit a Python version error, recreate your virtual environment with
140+
Python 3.13. From the repository root:
141+
142+
```bash
143+
# Remove the old venv
144+
rm -rf .env
145+
146+
# Create a new venv with Python 3.13
147+
python3.13 -m venv .env
148+
# ...or, if pyenv already resolves to 3.13:
149+
python -m venv .env
150+
151+
# Activate and install
152+
source .env/bin/activate
153+
pip install -e ./forge-python
154+
```
155+
156+
## Troubleshooting
157+
158+
### "Python 3.12 does not satisfy Python>=3.13"
159+
160+
Your virtual environment was created with an older Python. See "Setting up
161+
Python 3.13" above.
162+
163+
### "could not find forge root"
164+
165+
Run the CLI from the `forge-go` directory or any subdirectory of the forge
166+
repository.
167+
168+
### Server logs cluttering output
169+
170+
Use the `-q` flag for quiet mode. Server logs are redirected to a per-run temp
171+
directory (`<tmp>/forge-cli-*/server.log`).
172+
173+
### Guild won't launch
174+
175+
1. Check the Python version reported at startup (should be 3.13+).
176+
2. Use `/status` to check whether agents are running.
177+
3. Look at the server log written under `<tmp>/forge-cli-*/server.log`.
178+
4. Confirm the agent registry was seeded: look for "Seeding agent registry" at
179+
startup.
180+
181+
## Examples
182+
183+
### Echo Guild (recommended for testing)
184+
185+
```bash
186+
./forge guild run -q ../guilds/echo_app.json
187+
```
188+
189+
Type messages and see them echoed back by the agent.
190+
191+
### Custom Configuration
192+
193+
```bash
194+
./forge guild run \
195+
--backend redis \
196+
--org-id my-org \
197+
--user-id alice \
198+
--user-name "Alice Smith" \
199+
--python "$(pyenv which python)" \
200+
-q \
201+
../guilds/echo_app.json
202+
```
203+
204+
## Development
205+
206+
### Project Structure
207+
208+
```
209+
forge-go/
210+
├── cli/
211+
│ ├── guild_runtime.go # Embedded runtime + guild lifecycle
212+
│ ├── subscription.go # Message subscriptions
213+
│ └── message_builder.go # Message construction
214+
├── command/
215+
│ ├── guild.go # Command group
216+
│ ├── guild_run.go # Interactive REPL
217+
│ ├── guild_inspect.go # Guild inspection
218+
│ └── guild_validate.go # Guild validation
219+
└── go.mod # Dependencies
220+
```
221+
222+
### Architecture
223+
224+
```
225+
┌─────────────────────────────────────────┐
226+
│ CLI REPL (guild_run.go) │
227+
│ - User input handling │
228+
│ - Message display │
229+
│ - Command processing │
230+
└─────────────────┬────────────────────────┘
231+
232+
┌─────────────────▼────────────────────────┐
233+
│ GuildRuntime (guild_runtime.go) │
234+
│ - Embedded forge server │
235+
│ - Agent registry seeding │
236+
│ - Guild lifecycle management │
237+
└─────────────────┬────────────────────────┘
238+
239+
┌─────────────────▼────────────────────────┐
240+
│ Forge Server (embedded) │
241+
│ - Redis/NATS messaging │
242+
│ - Agent supervision │
243+
│ - Guild management API │
244+
└─────────────────┬────────────────────────┘
245+
246+
┌─────────────────▼────────────────────────┐
247+
│ Python Agents (Python 3.13+) │
248+
│ - Guild manager agent │
249+
│ - User-defined agents │
250+
│ - Message processing │
251+
└──────────────────────────────────────────┘
252+
```
253+
254+
## License
255+
256+
Same as the Forge project.

forge-go/api/catalog.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,15 @@ func handleLaunchGuildFromBlueprint(s store.Store, pusher protocol.ControlPusher
13951395
ReplyError(w, http.StatusUnprocessableEntity, "invalid guild spec")
13961396
return
13971397
}
1398+
// Resolve mustache {{ }} placeholders from the (merged) configuration bag,
1399+
// mirroring the Python API server's GuildBuilder._from_spec_dict(...) at
1400+
// launch. Without this, placeholders leak into the launched guild spec.
1401+
rendered, err := guild.RenderConfiguration(&guildSpec)
1402+
if err != nil {
1403+
ReplyError(w, http.StatusUnprocessableEntity, "invalid guild spec: "+err.Error())
1404+
return
1405+
}
1406+
guildSpec = *rendered
13981407
if req.GuildID != nil {
13991408
guildSpec.ID = *req.GuildID
14001409
}

0 commit comments

Comments
 (0)