Agach Shared Packages
Public Packages (pkg/)
pkg/server/types.go — HTTP Request/Response Types
All REST API request and response structs with validate: struct tags.
- Project: CreateProjectRequest, UpdateProjectRequest, ProjectResponse, ProjectSummaryResponse
- Agent: CreateAgentRequest, UpdateAgentRequest, AgentResponse, CloneAgentRequest
- Specialized: CreateSpecializedAgentRequest, UpdateSpecializedAgentRequest, SpecializedAgentResponse
- Skill: CreateSkillRequest, UpdateSkillRequest, SkillResponse, AddSkillToAgentRequest
- Task: CreateTaskRequest, UpdateTaskRequest, MoveTaskRequest, CompleteTaskRequest, BlockTaskRequest, RequestWontDoRequest, RejectWontDoRequest, ReorderTaskRequest, MoveTaskToProjectRequest, TaskResponse, TaskWithDetailsResponse
- Comment: CreateCommentRequest, UpdateCommentRequest, CommentResponse
- Board: ColumnResponse, BoardResponse, ColumnWithTasksResponse, DependencyContextResponse, AddDependencyRequest
- Feature: CreateFeatureRequest, UpdateFeatureRequest, UpdateFeatureStatusRequest, FeatureResponse, FeatureWithSummaryResponse
- Dockerfile: CreateDockerfileRequest, UpdateDockerfileRequest, DockerfileResponse, SetProjectDockerfileRequest
- Notification: CreateNotificationRequest, NotificationResponse
- Chat: StartChatSessionRequest, ChatSessionResponse
- Stats: ToolUsageStatResponse, TimelineEntryResponse, ColdStartStatResponse, TasksByAgentResponse
- Assignment: AssignAgentToProjectRequest, RemoveAgentFromProjectRequest, BulkReassignTasksRequest/Response
- Errors: ErrInvalidProjectRequest, ErrInvalidTaskRequest, etc. (using apierror.Error)
pkg/server/client/client.go — HTTP REST Client SDK
New(baseURL)— validates URL (http/https only, rejects link-local/metadata IPs)- Max response: 10 MB, max session ID: 512 chars
- Methods: ListProjects, GetProject, CreateProject, ListProjectRoles, CreateProjectAgent, UpdateProjectAgent, DeleteProjectAgent, GetNextTasks, WaitForNextTask (SSE blocking), UpdateTaskSessionID, UpdateTask, ListTasks, CreateTask, CompleteTask, BlockTask, MoveTask, GetColumnCounts, AddComment, ListComments, GetColumns, AddDependency
pkg/daemonws/ — Daemon WebSocket Protocol
- types.go: Message (type, request_id, payload, error), BuildEvent, PruneEvent Types: docker.list, docker.rebuild, docker.logs, docker.prune, build.event, prune.event, error
- chat.go: ChatStartRequest, ChatStartResponse, ChatUserMessage, ChatMessageEvent, ChatStatsEvent, ChatEndEvent, ChatErrorEvent, ChatTTLWarningEvent Types: chat.start, chat.message, chat.user_msg, chat.end, chat.error, chat.stats, chat.ping, chat.ttl_warning
pkg/domainerror/error.go — Domain Error Type
Errorstruct: Code (string), Message (string), wraps underlying errorIsDomainError(err) bool— type assertion helper- Implements: Error(), Unwrap(), ErrorCode(), ErrorMessage()
Internal Shared Infrastructure (internal/pkg/)
internal/pkg/apierror/apierror.go — API Error Wrapper
Errorstruct: Code, Message, Err- Distinct from domain errors; used by inbound layer for HTTP error responses
- Implements: Error(), Unwrap(), ErrorCode(), ErrorMessage()
internal/pkg/controller/controller.go — HTTP Response Helpers
NewController(logger)— registers custom validators:entity_id(UUID),slug(lowercase alphanum+hyphens, max 100)SendSuccess(w, r, data)— 200 OK with JSend wrapper{status: "success", data: ...}SendFail(w, r, statusCode, err)— 4xx with{status: "fail", error: {code, message}}SendError(w, r, err)— 500 with{status: "error", error: {code, message}}DecodeAndValidate(r, dest, validationErr)— JSON decode + struct tag validation, rejects non-application/jsonCodedErrorinterface — allows errors to carry Code/Message without circular imports
internal/pkg/middleware/middleware.go — HTTP Middleware
RequestLogger— logs method, path, status, duration, remote IPNewRequireAuth(authValidator)— JWT Bearer validation, injects Actor into context (ActorContextKey = "actor") Sets headers: X-Content-Type-Options, X-Frame-Options, Cache-Control, CORSLimitBodySize— 512 KB request body limit (413 if exceeded)RateLimit— 5 req/s per IP, 10 burst, 10min cleanup of old limiters (429 if exceeded) Uses real RemoteAddr only (never trusts X-Forwarded-For)
internal/pkg/websocket/ — WebSocket Hub
- hub.go: Hub (max 1000 clients, 256 broadcast buffer), Client (conn, send channel, project_id, is_daemon, node_id)
Broadcast(Event)— project-scoped delivery,SendToDaemon(nodeID, data)— targeted deliveryRegisterHandler(msgType, fn)— message type routing,NewRelayHandler()— daemon↔client relay Read/write: 64KB read limit, 10s write deadline, 60s pong wait, 54s ping period - pump.go: Generic
RunWritePump[M]()andHandleUnregister[C]()helpers - constants.go: WriteWait(10s), PongWait(60s), PingPeriod(54s), MaxMessageSize(4KB)
internal/pkg/sse/hub.go — Server-Sent Events Hub
- Max 1000 subscribers per project
- 1-second heartbeat (":" keep-alive)
Subscribe(projectID)→ (chan string, unsubscribe func)Publish(projectID, data)— sanitizes newlines, evicts slow consumersHasSubscribers(projectID) bool
Client Configuration (internal/agachconfig/)
config.go — Daemon/Client Config
Configstruct: BaseURL (yaml: base_url)FindConfigFile(dir, filename, maxDepth)— walks up directory tree (max 5 levels)LoadSecureYAML(path, dest)— requires file permissions ≤ 0600ValidateBaseURL(url)— enforces https for remote hostsLoad(dir)— looks for .agach.yml in dir and parents