Documentation Index Fetch the complete documentation index at: https://docs.cloudshipai.com/llms.txt
Use this file to discover all available pages before exploring further.
Code Style Guide
Station follows standard Go conventions with some project-specific guidelines.
Go Style
Official Guidelines
Station follows the official Go style guides:
# Format code (required before commit)
make fmt
# Or directly
gofmt -s -w .
goimports -w .
Linting
# Run linter
make lint
# Or directly
golangci-lint run
The project uses golangci-lint with these enabled linters:
gofmt - Format checking
goimports - Import organization
govet - Go vet checks
errcheck - Error handling
staticcheck - Static analysis
gosimple - Simplifications
ineffassign - Unused assignments
Naming Conventions
Packages
// Good - short, lowercase, no underscores
package services
package repositories
package config
// Bad
package agent_services
package AgentRepository
Functions and Methods
// Good - verb-noun, exported if needed
func ( s * AgentService ) Execute ( ctx context . Context , id int64 ) error
func ( r * AgentRepository ) GetByID ( ctx context . Context , id int64 ) ( * Agent , error )
func parseConfig ( path string ) ( * Config , error )
// Bad
func ( s * AgentService ) DoExecute ( ctx context . Context , id int64 ) error // redundant "Do"
func ( r * AgentRepository ) agent_by_id ( id int64 ) * Agent // snake_case
Interfaces
// Good - suffix with -er for single method, or describe behavior
type AgentExecutor interface {
Execute ( ctx context . Context , task string ) ( string , error )
}
type AgentService interface {
Execute ( ctx context . Context , id int64 , task string ) ( string , error )
Create ( ctx context . Context , agent * Agent ) ( int64 , error )
Update ( ctx context . Context , agent * Agent ) error
Delete ( ctx context . Context , id int64 ) error
}
// Bad
type IAgentService interface { } // Don't prefix with I
Variables
// Good
var defaultTimeout = 30 * time . Second
agentID := params . Get ( "id" )
mcpServer := config . MCPServers [ 0 ]
// Bad
var DefaultTimeout = 30 * time . Second // unexported should be lowercase
agent_id := params . Get ( "id" ) // snake_case
Constants
// Good
const (
DefaultMaxSteps = 5
DefaultAPIPort = 8585
StatusPending = "pending"
StatusRunning = "running"
StatusCompleted = "completed"
)
// Bad
const DEFAULT_MAX_STEPS = 5 // SCREAMING_SNAKE_CASE
Code Organization
File Structure
// Order of declarations in a file:
// 1. Package comment (if main file)
// 2. Package declaration
// 3. Imports (grouped: stdlib, external, internal)
// 4. Constants
// 5. Variables
// 6. Types (interfaces, then structs)
// 7. Functions (constructors first, then methods)
package services
import (
" context "
" database/sql "
" fmt "
" time "
" github.com/google/uuid "
" go.opentelemetry.io/otel "
" github.com/cloudshipai/station/internal/config "
" github.com/cloudshipai/station/internal/db/repositories "
)
const (
defaultTimeout = 30 * time . Second
)
var (
ErrNotFound = errors . New ( "not found" )
)
type AgentService interface {
Execute ( ctx context . Context , id int64 , task string ) error
}
type agentServiceImpl struct {
repo repositories . AgentRepository
config * config . Config
}
func NewAgentService ( repo repositories . AgentRepository , cfg * config . Config ) AgentService {
return & agentServiceImpl { repo : repo , config : cfg }
}
func ( s * agentServiceImpl ) Execute ( ctx context . Context , id int64 , task string ) error {
// ...
}
Import Grouping
import (
// Standard library
" context "
" fmt "
" time "
// External dependencies
" github.com/labstack/echo/v4 "
" go.opentelemetry.io/otel "
// Internal packages
" github.com/cloudshipai/station/internal/config "
" github.com/cloudshipai/station/internal/services "
)
Error Handling
Error Wrapping
// Good - wrap errors with context
func ( s * AgentService ) Execute ( ctx context . Context , id int64 , task string ) error {
agent , err := s . repo . GetByID ( ctx , id )
if err != nil {
return fmt . Errorf ( "get agent %d : %w " , id , err )
}
result , err := s . engine . Execute ( ctx , agent , task )
if err != nil {
return fmt . Errorf ( "execute agent %s : %w " , agent . Name , err )
}
return nil
}
// Bad - losing error context
func ( s * AgentService ) Execute ( ctx context . Context , id int64 , task string ) error {
agent , err := s . repo . GetByID ( ctx , id )
if err != nil {
return err // No context about where/why
}
return nil
}
Custom Errors
// Define sentinel errors
var (
ErrAgentNotFound = errors . New ( "agent not found" )
ErrInvalidInput = errors . New ( "invalid input" )
ErrExecutionTimeout = errors . New ( "execution timeout" )
)
// Use errors.Is for checking
if errors . Is ( err , ErrAgentNotFound ) {
return http . StatusNotFound , nil
}
Don’t Ignore Errors
// Good
result , err := s . Execute ( ctx , task )
if err != nil {
log . Error ( "execution failed" , "error" , err )
return err
}
// Bad
result , _ := s . Execute ( ctx , task ) // Ignoring error!
Context Usage
// Always pass context as first parameter
func ( s * AgentService ) Execute ( ctx context . Context , id int64 , task string ) error {
// Use context for cancellation
select {
case <- ctx . Done ():
return ctx . Err ()
default :
}
// Pass context to downstream calls
agent , err := s . repo . GetByID ( ctx , id )
if err != nil {
return err
}
return nil
}
Concurrency
Channel Patterns
// Use buffered channels when appropriate
results := make ( chan Result , len ( tasks ))
// Always close channels from producer
go func () {
defer close ( results )
for _ , task := range tasks {
results <- process ( task )
}
}()
// Consumer reads until closed
for result := range results {
handleResult ( result )
}
Mutex Usage
type SafeCounter struct {
mu sync . RWMutex
count int
}
func ( c * SafeCounter ) Increment () {
c . mu . Lock ()
defer c . mu . Unlock ()
c . count ++
}
func ( c * SafeCounter ) Value () int {
c . mu . RLock ()
defer c . mu . RUnlock ()
return c . count
}
Documentation
// Package services provides the core business logic for Station.
// It includes agent execution, MCP management, and environment configuration.
package services
// Execute runs the specified agent with the given task.
// It returns the agent's response or an error if execution fails.
//
// The execution respects the context deadline and will return
// context.DeadlineExceeded if the timeout is reached.
func ( s * AgentService ) Execute ( ctx context . Context , id int64 , task string ) ( string , error ) {
// ...
}
func ( s * AgentService ) Execute ( ctx context . Context , id int64 , task string ) error {
// Validate agent exists before execution
agent , err := s . repo . GetByID ( ctx , id )
if err != nil {
return err
}
// Initialize MCP connections for tool access
// This may take a few seconds for large environments
mcpManager , err := s . initMCP ( ctx , agent . Environment )
if err != nil {
return fmt . Errorf ( "init mcp: %w " , err )
}
defer mcpManager . Close ()
// Execute with timeout from agent config
timeout := time . Duration ( agent . TimeoutSeconds ) * time . Second
ctx , cancel := context . WithTimeout ( ctx , timeout )
defer cancel ()
return s . engine . Execute ( ctx , agent , task )
}
Testing Style
See the Testing Guide for detailed testing conventions.
// Use descriptive test names
func TestAgentService_Execute_ReturnsErrorWhenAgentNotFound ( t * testing . T ) { }
func TestAgentService_Execute_SuccessfulExecution ( t * testing . T ) { }
func TestAgentService_Execute_TimeoutExceeded ( t * testing . T ) { }
// Use table-driven tests
func TestValidateInput ( t * testing . T ) {
tests := [] struct {
name string
input string
wantErr bool
}{
{ "valid input" , "hello" , false },
{ "empty input" , "" , true },
}
// ...
}
Pre-commit Checklist
Before committing:
Format code : make fmt
Run linter : make lint
Run tests : make test
Check for issues : make check (runs fmt, lint, test)
Next Steps
Development Setup Set up your development environment
Testing Writing and running tests