一键导入
grpc
Guidelines for working with the Connect RPC (gRPC) API. Apply when modifying proto definitions, implementing server handlers, or creating API clients.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guidelines for working with the Connect RPC (gRPC) API. Apply when modifying proto definitions, implementing server handlers, or creating API clients.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | grpc |
| description | Guidelines for working with the Connect RPC (gRPC) API. Apply when modifying proto definitions, implementing server handlers, or creating API clients. |
XAGENT uses Connect RPC for its API, providing protocol flexibility (gRPC, gRPC-Web, Connect protocol) over HTTP.
proto/xagent/v1/xagent.protointernal/proto/xagent/v1/ (gitignored)webui/src/gen/ (committed)internal/server/server.gointernal/xagentclient/@connectrpc/connect-query with TanStack QueryEdit proto/xagent/v1/xagent.proto:
service XAgentService {
// ... existing RPCs ...
rpc MyNewMethod(MyNewRequest) returns (MyNewResponse);
}
message MyNewRequest {
int64 id = 1;
string name = 2;
}
message MyNewResponse {
bool success = 1;
}
mise run generate
This runs:
go tool buf generate - generates protobuf messages and Connect client/server interfaces (Go)go generate ./... - runs any other code generatorsFor the frontend TypeScript client, run:
cd webui && pnpm run generate
This generates:
webui/src/gen/xagent/v1/xagent_pb.ts - TypeScript types and message schemaswebui/src/gen/xagent/v1/xagent-XAgentService_connectquery.ts - Connect-Query method exportsThe server embeds UnimplementedXAgentServiceHandler, which provides default "not implemented" responses for all RPCs. Override the method in internal/server/server.go:
func (s *Server) MyNewMethod(ctx context.Context, req *xagentv1.MyNewMethodRequest) (*xagentv1.MyNewMethodResponse, error) {
// Validate input
if req.Id == 0 {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("id is required"))
}
// Call store layer
result, err := s.tasks.DoSomething(req.Id)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// Return response
return &xagentv1.MyNewMethodResponse{
Success: true,
}, nil
}
Always use appropriate Connect error codes:
import "connectrpc.com/connect"
// Common error codes
connect.CodeInvalidArgument // Bad request parameters
connect.CodeNotFound // Resource doesn't exist
connect.CodeInternal // Server-side error
connect.CodeUnimplemented // Method not supported
connect.CodeAlreadyExists // Duplicate resource
connect.CodePermissionDenied // Authorization failure
import "github.com/icholy/xagent/internal/xagentclient"
// HTTP client
client := xagentclient.New("http://localhost:6464")
// Unix socket client (used inside containers)
client := xagentclient.New("unix:///xagent/socket")
import xagentv1 "github.com/icholy/xagent/internal/proto/xagent/v1"
// Unary call
resp, err := client.GetTask(ctx, &xagentv1.GetTaskRequest{Id: 123})
if err != nil {
// Handle error
}
task := resp.Task
// Call with multiple fields
resp, err := client.CreateTask(ctx, &xagentv1.CreateTaskRequest{
Name: "My Task",
Workspace: "default",
Instructions: []*xagentv1.Instruction{
{Text: "Do something", Url: "https://example.com"},
},
})
The server converts between proto messages and store domain models:
// Proto to store (in handler)
instructions := make([]store.Instruction, len(req.Instructions))
for i, inst := range req.Instructions {
instructions[i] = store.Instruction{
Text: inst.Text,
URL: inst.Url, // Note: proto uses "Url", store uses "URL"
}
}
// Store to proto (for responses)
func taskToProto(t *store.Task) *xagentv1.Task {
return &xagentv1.Task{
Id: t.ID,
Name: t.Name,
Status: string(t.Status),
CreatedAt: timestamppb.New(t.CreatedAt),
}
}
parent_id, created_atParentId, CreatedAtimport "google/protobuf/timestamp.proto";
message Task {
google.protobuf.Timestamp created_at = 7;
}
Convert with timestamppb:
import "google.golang.org/protobuf/types/known/timestamppb"
// Go time to proto
protoTime := timestamppb.New(time.Now())
// Proto to Go time
goTime := protoTimestamp.AsTime()
message ListTasksResponse {
repeated Task tasks = 1;
}
In proto3, all fields are optional by default. Empty/zero values are not serialized.
message McpServer {
map<string, string> env = 4;
}
Connect RPC endpoints are accessible via HTTP POST with JSON:
# List tasks
curl -X POST http://localhost:6464/xagent.v1.XAgentService/ListTasks \
-H "Content-Type: application/json" \
-d '{"statuses": ["pending", "running"]}'
# Get task
curl -X POST http://localhost:6464/xagent.v1.XAgentService/GetTask \
-H "Content-Type: application/json" \
-d '{"id": 123}'
URL pattern: /{package}.{service}/{method}
Mock the client interface for testing:
//go:generate go tool moq -pkg mypackage -out client_moq_test.go ../xagentclient Client
func TestMyHandler(t *testing.T) {
mockClient := &ClientMock{
GetTaskFunc: func(ctx context.Context, req *xagentv1.GetTaskRequest) (*xagentv1.GetTaskResponse, error) {
return &xagentv1.GetTaskResponse{
Task: &xagentv1.Task{Id: req.Id, Name: "Test"},
}, nil
},
}
// Use mockClient in tests
}
Configures the protobuf module and linting rules:
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
Specifies code generation plugins:
version: v2
plugins:
- remote: buf.build/protocolbuffers/go
out: internal/proto
opt: paths=source_relative
- remote: buf.build/connectrpc/go
out: internal/proto
opt:
- paths=source_relative
- simple
s.log.Info("task created", "id", task.ID, "workspace", task.Workspace)
task, err := s.tasks.Get(req.Id)
if err != nil {
return nil, connect.NewError(connect.CodeNotFound, err)
}
func (s *Server) UploadLogs(ctx context.Context, req *xagentv1.UploadLogsRequest) (*xagentv1.UploadLogsResponse, error) {
for _, entry := range req.Entries {
log := &model.Log{
TaskID: req.TaskId,
Type: entry.Type,
Content: entry.Content,
}
if err := s.logs.Create(ctx, log); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
}
return &xagentv1.UploadLogsResponse{}, nil
}
The web UI uses @connectrpc/connect-query for type-safe API calls with TanStack Query integration.
The transport is configured in webui/src/lib/transport.ts and provided via TransportProvider in main.tsx. This is already set up.
Import the generated method descriptor and use it with useQuery from @connectrpc/connect-query:
import { useQuery } from '@connectrpc/connect-query'
import { listTasks } from '@/gen/xagent/v1/xagent-XAgentService_connectquery'
function TaskList() {
const { data, isLoading, error } = useQuery(listTasks, {
statuses: ['pending', 'running'],
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data?.tasks.map(task => (
<li key={String(task.id)}>{task.name}</li>
))}
</ul>
)
}
import { useMutation, useQueryClient } from '@connectrpc/connect-query'
import { createTask, listTasks } from '@/gen/xagent/v1/xagent-XAgentService_connectquery'
import { create } from '@bufbuild/protobuf'
import { InstructionSchema } from '@/gen/xagent/v1/xagent_pb'
function CreateTaskButton() {
const queryClient = useQueryClient()
const mutation = useMutation(createTask, {
onSuccess: () => {
// Invalidate and refetch tasks list
queryClient.invalidateQueries({ queryKey: [listTasks.service.typeName] })
},
})
const handleCreate = () => {
mutation.mutate({
name: 'New Task',
workspace: 'default',
instructions: [
create(InstructionSchema, { text: 'Do something', url: '' }),
],
})
}
return <button onClick={handleCreate}>Create Task</button>
}
Protobuf int64 fields are generated as TypeScript bigint. When displaying or using these values:
// Convert to string for display
<span>Task ID: {String(task.id)}</span>
// Convert to number if needed (be careful with large values)
const numId = Number(task.id)
// Pass bigint directly to other proto messages
const request = { id: task.id } // OK - keeps bigint type
The generated types are in webui/src/gen/xagent/v1/xagent_pb.ts:
import type { Task, Event, TaskLink } from '@/gen/xagent/v1/xagent_pb'
function TaskCard({ task }: { task: Task }) {
return (
<div>
<h3>{task.name}</h3>
<p>Status: {task.status}</p>
<p>Workspace: {task.workspace}</p>
</div>
)
}
The TypeScript code generation is configured in webui/buf.gen.yaml:
version: v2
inputs:
- directory: ../proto
plugins:
- local: protoc-gen-es
out: src/gen
opt: target=ts
- local: protoc-gen-connect-query
out: src/gen
opt: target=ts
Guidelines for writing Go tests. Apply when creating or modifying test files.
Execute an already-agreed plan by delivering it as a stack of "layer cake" PRs — one xagent task per PR, strictly one at a time. Use when the design is settled (usually an accepted proposal) and the job is to build it. You delegate each layer to a task, review the PR it opens, and the human merges. A merge is the go signal to start the next layer. Feedback on a PR — yours or the user's — is relayed back to the task (which wakes on the PR event) rather than fixed by hand. Mute all channel notifications and unmute only the tasks you create. Track progress in a GitHub issue with a checkbox per layer so the work can be resumed after context loss.
Create a design proposal for a feature or change. Use when the user wants to plan or design something before implementing it.
Act as an engineering manager who delegates implementation to xagent tasks. Use for sessions where the user wants you to design, delegate, and review work rather than write production code yourself. You scope work, kick off xagent tasks, and review the proposals and PRs they produce — requesting changes by commenting on the PR and tagging the author.
Create xagent tasks using the MCP tools. Use when the user wants to create a task for the xagent system.
Web UI development guidelines for the v2 React UI in webui/. Apply when working on files in webui/, creating React components, or using TanStack Router/Query.