Minimal, copy‑paste runnable example to go from zero → talking agent. Keep your design in design/, never edit gen/.
- Go 1.24+
- Goa v3 CLI (
go install github.com/CaliLuke/loom/cmd/loom@latest) - Temporal dev server (for workflow execution)
- Easiest: Docker one‑liner below, or use Temporalite
mkdir -p $GOPATH/src/example.com/quickstart && cd $_
go mod init example.com/quickstart
go get github.com/CaliLuke/loom@latest
go get goa.design/loom-mcp@latest
This declares one service (orchestrator) with a single agent (chat) and a tiny helper toolset.
package design
import (
. "github.com/CaliLuke/loom/dsl"
. "goa.design/loom-mcp/dsl"
)
var _ = API("orchestrator", func() {})
// Input and output types with inline descriptions (required by this repo style)
var AskPayload = Type("AskPayload", func() {
Attribute("question", String, "User question to answer")
Example(map[string]any{"question": "What is the capital of Japan?"})
Required("question")
})
var Answer = Type("Answer", func() {
Attribute("text", String, "Answer text")
Required("text")
})
var _ = Service("orchestrator", func() {
Agent("chat", "Friendly Q&A assistant", func() {
Use("helpers", func() {
Tool("answer", "Answer a simple question", func() {
Args(AskPayload)
Return(Answer)
})
})
RunPolicy(func() {
DefaultCaps(MaxToolCalls(2), MaxConsecutiveFailedToolCalls(1))
TimeBudget("15s")
})
})
})loom gen example.com/quickstart/design
loom example example.com/quickstart/designThis creates:
gen/- Generated code (never edit by hand)cmd/orchestrator/main.go- Runnable example using the bootstrapinternal/agents/bootstrap/bootstrap.go- Wires runtime and registers agentsinternal/agents/chat/planner/planner.go- Stub planner (edit to connect your LLM)
go run ./cmd/orchestratorExpected output:
RunID: orchestrator-chat-...
Assistant: Hello from example planner.
The generated example uses the in-memory engine, so no Temporal is needed for development.
For production, start Temporal and configure the runtime:
# Start Temporal dev server
docker run --rm -d --name temporal-dev -p 7233:7233 temporalio/auto-setup:latestThen modify the bootstrap to use the Temporal engine:
import (
"goa.design/loom-mcp/runtime/agent/engine/temporal"
"go.temporal.io/sdk/client"
// Your generated tool specs aggregate.
// The generated package exposes: func Spec(tools.Ident) (*tools.ToolSpec, bool)
specs "<module>/gen/<service>/agents/<agent>/specs"
)
eng, _ := temporal.NewWorker(temporal.Options{
ClientOptions: &client.Options{
HostPort: "127.0.0.1:7233",
Namespace: "default",
// Required: enforce loom-mcp's workflow boundary contract.
// Tool results/artifacts cross boundaries as canonical JSON bytes (api.ToolEvent/api.ToolArtifact).
DataConverter: temporal.NewAgentDataConverter(specs.Spec),
},
WorkerOptions: temporal.WorkerOptions{
TaskQueue: "<service>_<agent>_workflow",
},
})
rt := agentsruntime.New(agentsruntime.WithEngine(eng))Edit internal/agents/chat/planner/planner.go to connect your LLM:
func (p *examplePlanner) PlanStart(ctx context.Context, in *planner.PlanInput) (*planner.PlanResult, error) {
// 1. Get LLM client from runtime
// mc, _ := in.Agent.ModelClient("openai")
// 2. Build prompt from in.Messages
// 3. Decide: call tools or give final response
return &planner.PlanResult{
FinalResponse: &planner.FinalResponse{
Message: &model.Message{
Role: model.ConversationRoleAssistant,
Parts: []model.Part{model.TextPart{Text: "Your response here"}},
},
},
}, nil
}loom example also generated an HTTP JSON‑RPC server under cmd/orchestrator.
- Start it:
go run ./cmd/orchestrator -debug - It mounts the MCP‑compatible JSON‑RPC API on POST
/rpc. - Try a simple RPC (replace the tool name with one from your design):
curl -s http://localhost:8080/rpc \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tools/call",
"params":{
"name":"orchestrator.helpers.answer",
"arguments": {"question": "What is the capital of Japan?"}
}
}' | jq .Note: tool execution requires wiring executors. For a first run, the in‑process demo above is the simplest path. When you bind tools to service methods (BindTo in the design), loom example will scaffold executors you can fill in.
- Always change design in
design/*.gothen runloom gen(andloom exampleas needed). Never editgen/by hand. - Generated tool specs live under
gen/<svc>/agents/<agent>/specs/…with typed codecs. - Policies and caps are enforced by the runtime during execution; keep planners small and declarative.