Documentation
¶
Overview ¶
Package examples provides runnable examples for flyto-agent.
All examples in this package use the Go Example function convention (ExampleXxx) so they are indexed by pkgsite/godoc automatically.
Examples that require a real API key are guarded by os.Getenv checks and will produce no output when run without credentials.
Run a specific example:
go test -v -run Example_basic ./examples/
Example (Basic) ¶
Example_basic demonstrates starting an agent in minimal form.
package main
import (
"context"
"fmt"
"os"
"git.flytoex.net/yuanwei/flyto-agent/pkg/engine"
"git.flytoex.net/yuanwei/flyto-agent/pkg/execenv"
"git.flytoex.net/yuanwei/flyto-agent/pkg/providers/anthropic"
)
func main() {
agent, err := engine.New(&engine.Config{
Model: "claude-sonnet-4-6",
Provider: anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
}),
Cwd: ".",
Executor: execenv.DefaultExecutor{},
Tools: []string{"Bash", "Read", "Edit", "Glob", "Grep"},
})
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
return
}
defer agent.Close()
for event := range agent.Run(context.Background(), "查看当前目录结构") {
switch e := event.(type) {
case *engine.TextDeltaEvent:
fmt.Print(e.Text)
case *engine.ToolUseEvent:
fmt.Printf("\n🔧 %s\n", e.ToolName)
case *engine.DoneEvent:
fmt.Printf("\n--- done: %d turns $%.4f ---\n", e.TurnCount, e.TotalCostUSD)
case *engine.ErrorEvent:
fmt.Fprintln(os.Stderr, "error:", e.Err)
}
}
}
Output:
Example (Debate) ¶
Example_debate demonstrates Agent Teams peer-to-peer messaging primitives without calling a real LLM.
Two workers (proposer/critic) exchange messages through inbox.Router, simulating a proposal debate. Replace agent names and content strings to adapt to finance / medical / logistics / legal scenarios.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"git.flytoex.net/yuanwei/flyto-agent/pkg/inbox"
)
func main() {
ctx := context.Background()
router := inbox.NewRouter()
defer router.Close()
const (
proposer = "proposer"
critic = "critic"
leader = "leader"
)
mustSend(router, proposer, critic, "建议采用方案 A: Redis 做 task list. 低延迟, 成熟.")
msg := mustRecvFrom(ctx, router, critic)
fmt.Printf("[critic from=%s] %s\n", msg.From, payloadContent(msg))
mustSend(router, critic, proposer, "反对. Redis 非持久化, SaaS 需要审计表. 方案 B: PostgreSQL.")
msg = mustRecvFrom(ctx, router, proposer)
fmt.Printf("[proposer from=%s] %s\n", msg.From, payloadContent(msg))
mustSend(router, proposer, critic, "折中: Postgres 主存 + Redis 缓存, CDC 同步.")
msg = mustRecvFrom(ctx, router, critic)
fmt.Printf("[critic from=%s] %s\n", msg.From, payloadContent(msg))
mustSend(router, critic, leader, "达成折中方案, 请裁决.")
msg = mustRecvFrom(ctx, router, leader)
fmt.Printf("[leader from=%s] %s\n", msg.From, payloadContent(msg))
}
func mustSend(router *inbox.Router, from, to, content string) {
msg, err := inbox.NewMessage(from, to, inbox.MsgTaskAssignment, map[string]string{"content": content})
if err != nil {
log.Fatal(err)
}
if err := router.Send(to, msg); err != nil {
log.Fatal(err)
}
}
func payloadContent(msg *inbox.Message) string {
var p map[string]string
if err := json.Unmarshal(msg.Payload, &p); err != nil {
return string(msg.Payload)
}
return p["content"]
}
func mustRecvFrom(ctx context.Context, router *inbox.Router, to string) *inbox.Message {
msg, err := router.Inbox(to).Recv(ctx)
if err != nil {
log.Fatalf("recv %s: %v", to, err)
}
return msg
}
Output:
Example (TasklistCustom) ¶
Example_tasklistCustom demonstrates plugging in a custom Store backend (finance compliance audit scenario).
Implement tasklist.Store (Get / CAS / List / Close) to connect any storage: PostgreSQL, HIPAA-encrypted store, WMS wave records, etc.
package main
import (
"context"
"fmt"
"sort"
"sync"
"git.flytoex.net/yuanwei/flyto-agent/pkg/tasklist"
)
func main() {
ctx := context.Background()
store := newFinanceAuditStore()
tl := tasklist.New(store)
defer tl.Close()
trade, err := tl.Add(ctx, "审核: 买入 AAPL 10000 股", "价格 $180, 客户 XYZ-001")
if err != nil {
fmt.Println("error:", err)
return
}
if _, err := tl.Claim(ctx, trade.ID, "risk-analyst-03"); err != nil {
fmt.Println("error:", err)
return
}
done, err := tl.Complete(ctx, trade.ID, "批准: VaR=2.1% 在阈值内")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("completed: %s\n", done.Result)
for i, e := range store.auditLog() {
fmt.Printf("%d. op=%s\n", i+1, e.operation)
}
}
type auditEntry struct {
operation string
taskID string
}
type financeAuditStore struct {
mu sync.Mutex
data map[string]tasklist.Task
log []auditEntry
}
func newFinanceAuditStore() *financeAuditStore {
return &financeAuditStore{data: make(map[string]tasklist.Task)}
}
func (s *financeAuditStore) Get(_ context.Context, id string) (tasklist.Task, error) {
s.mu.Lock()
defer s.mu.Unlock()
t, ok := s.data[id]
if !ok {
return tasklist.Task{}, tasklist.ErrTaskNotFound
}
return t, nil
}
func (s *financeAuditStore) CAS(_ context.Context, id string, expectedVersion int, newTask tasklist.Task) error {
s.mu.Lock()
defer s.mu.Unlock()
current, exists := s.data[id]
if expectedVersion == 0 {
if exists {
return tasklist.ErrTaskAlreadyExists
}
s.data[id] = newTask
s.log = append(s.log, auditEntry{"CREATE", id})
return nil
}
if !exists {
return tasklist.ErrTaskNotFound
}
if current.Version != expectedVersion {
return tasklist.ErrConcurrentModification
}
s.data[id] = newTask
op := "UPDATE"
switch newTask.Status {
case tasklist.StatusClaimed:
op = "CLAIM"
case tasklist.StatusCompleted:
op = "COMPLETE"
case tasklist.StatusFailed:
op = "FAIL"
}
s.log = append(s.log, auditEntry{op, id})
return nil
}
func (s *financeAuditStore) List(_ context.Context) ([]tasklist.Task, error) {
s.mu.Lock()
defer s.mu.Unlock()
out := make([]tasklist.Task, 0, len(s.data))
for _, t := range s.data {
out = append(out, t)
}
sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt.Before(out[j].CreatedAt) })
return out, nil
}
func (s *financeAuditStore) Close() error { return nil }
func (s *financeAuditStore) auditLog() []auditEntry { return s.log }
Output:
Example (TasklistMarkdown) ¶
Example_tasklistMarkdown demonstrates MarkdownStore cross-tool interop.
The tasks.md file format is compatible with Claude Code v2.1.32: files written by Flyto can be read by Claude Code and vice versa.
package main
import (
"context"
"fmt"
"os"
"git.flytoex.net/yuanwei/flyto-agent/pkg/tasklist"
)
func main() {
ctx := context.Background()
path := "/tmp/flyto_example_tasks.md"
defer os.Remove(path)
tl := tasklist.New(tasklist.NewMarkdownStore(path))
defer tl.Close()
var ids []string
for _, s := range []string{"分析日志找错误模式", "编写 login 模块单元测试"} {
t, err := tl.Add(ctx, s, "")
if err != nil {
fmt.Println("error:", err)
return
}
ids = append(ids, t.ID)
fmt.Printf("[add] %s\n", t.Subject)
}
if _, err := tl.Claim(ctx, ids[0], "worker-A"); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("[worker-A claimed]")
done, err := tl.Complete(ctx, ids[1], "8 edge cases covered")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("[worker-B completed] %s\n", done.Subject)
}
Output:
Example (WithPricing) ¶
Example_withPricing demonstrates full integration: provider / pricing / tools / cost tracking.
package main
import (
"context"
"fmt"
"os"
"time"
"git.flytoex.net/yuanwei/flyto-agent/pkg/config"
"git.flytoex.net/yuanwei/flyto-agent/pkg/engine"
"git.flytoex.net/yuanwei/flyto-agent/pkg/execenv"
"git.flytoex.net/yuanwei/flyto-agent/pkg/pricing"
"git.flytoex.net/yuanwei/flyto-agent/pkg/providers/anthropic"
)
func main() {
registry := config.NewModelRegistry()
if n, err := pricing.LoadAndRegister(registry); err != nil {
fmt.Fprintf(os.Stderr, "warning: pricing unavailable: %v\n", err)
} else {
fmt.Fprintf(os.Stderr, "loaded pricing for %d models\n", n)
}
agent, err := engine.New(&engine.Config{
Provider: anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
BaseURL: os.Getenv("ANTHROPIC_BASE_URL"),
EnableCaching: true,
}),
Model: "claude-sonnet-4-6",
Cwd: ".",
Executor: execenv.DefaultExecutor{},
Tools: []string{"Bash", "Read", "Glob", "Grep"},
Models: registry,
MaxTurns: 20,
MaxBudgetUSD: 0.50,
SystemPrompt: "你是一个简洁的代码探索助手,回答精准。",
})
if err != nil {
fmt.Fprintln(os.Stderr, "fatal:", err)
return
}
defer agent.Close()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
var toolCalls int
start := time.Now()
for event := range agent.Run(ctx, "用 Glob 找出当前目录下所有 .go 文件,告诉我数量") {
switch e := event.(type) {
case *engine.TextDeltaEvent:
fmt.Print(e.Text)
case *engine.ToolUseEvent:
fmt.Printf("\n🔧 %s\n", e.ToolName)
toolCalls++
case *engine.WarningEvent:
fmt.Fprintf(os.Stderr, "⚠️ [%s] %s\n", e.Code, e.Message)
case *engine.ErrorEvent:
fmt.Fprintln(os.Stderr, "error:", e.Err)
case *engine.DoneEvent:
fmt.Printf("\n--- done: %d turns %v $%.4f %d tool calls ---\n",
e.TurnCount, time.Since(start).Round(time.Millisecond), e.TotalCostUSD, toolCalls)
}
}
}
Output: