🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

goatchain

Package Overview
Dependencies
Maintainers
2
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

goatchain

npmnpm
Version
0.0.8
Version published
Weekly downloads
94
203.23%
Maintainers
2
Weekly downloads
 
Created
Source

GoatChain 🐐

A lightweight, extensible TypeScript SDK for building AI agents with streaming support, tool calling, and middleware pattern.

npm version License: MIT

中文文档 (Chinese Documentation)

✨ Features

  • 🔄 Agentic Loop - Automatic tool calling loop with configurable max iterations
  • 📡 Streaming First - Real-time streaming responses with detailed events
  • 🧅 Middleware Pattern - Koa-style onion model for extensible hooks
  • 🔧 Tool System - Easy-to-use tool registration and execution
  • 💾 State Management - Two-level state store (Agent + Session level)
  • 📸 Snapshot/Restore - Full persistence support for agents and sessions
  • 🎯 TypeScript Native - Full type safety with comprehensive type exports

📦 Installation

pnpm add goatchain

🚀 Quick Start

Simple Agent Loop example:

import process from 'node:process'
import { Agent, createModel, createOpenAIAdapter } from 'goatchain'

// Create model
const model = createModel({
  adapter: createOpenAIAdapter({
    defaultModelId: 'gpt-4o',
    apiKey: process.env.OPENAI_API_KEY!,
  }),
})

// Create Agent
const agent = new Agent({
  name: 'Simple Assistant',
  systemPrompt: 'You are a helpful assistant.',
  model,
})

// Stream responses
const session = await agent.createSession()
session.send('Hello!')
for await (const event of session.receive()) {
  if (event.type === 'text_delta') {
    process.stdout.write(event.delta)
  } else if (event.type === 'done') {
    console.log('\nDone:', event.stopReason)
  }
}

📖 Full Documentation: See docs/getting-started.md for more examples and complete guide.

🧰 CLI

GoatChain CLI is a terminal UI (TUI) built on @simon_he/vue-tui.

Installation

Global installation (recommended):

npm install -g goatchain-cli@latest

Or use npx without installation:

npx goatchain-cli@latest

Usage

After installation, simply run:

goatchain

Inside the TUI:

  • Ctrl+P opens the command palette (Sessions / New Session / Settings / Tool Approvals; Chat also has Theme)
  • Slash commands in the input: /settings, /approvals, /sessions, /new (Chat also supports /redo)

Development

Run it from this repo:

bun run cli

See docs/cli.md for the full feature list and flowcharts.

🔌 ACP Server

GoatChain can be exposed as an ACP (Agent Client Protocol) server for integration with editors like Zed.

bun run acp-server

Configuration for Zed (settings.json):

{
  "agent_servers": {
    "goatchain": {
      "command": "pnpm",
      "args": ["--dir", "/path/to/GoatChain", "acp-server"]
    }
  }
}

Features:

  • File operations (read, write, edit)
  • Search tools (glob, grep, ast-grep)
  • Web search and task management
  • Same tool set as CLI agent mode

Available servers:

  • pnpm acp-server - Simple agent (recommended)
  • pnpm acp-server:plan - With plan mode (experimental)

📖 Full Documentation: See docs/acp-server.md for configuration and troubleshooting.

🏗️ Architecture

classDiagram
    direction TB

	    class Agent {
	        +id: string
	        +name: string
	        +systemPrompt: string
	        +model: ModelClient
	        +tools: ToolRegistry
	        +stateStore: StateStore
	        +sessionManager: BaseSessionManager
	        +stats: AgentStats
	        +use(middleware): this
	        +createSession(options): BaseSession
	        +resumeSession(sessionId, options): BaseSession
	        +setModel(modelOrRef): void
	    }

	    class ModelClient {
	        <<interface>>
	        +modelId: string
	      +stream(request): AsyncIterable~ModelStreamEvent~
	      +run?(request): Promise~ModelRunResult~
	    }

    class StateStore {
        <<interface>>
        +deleteOnComplete: boolean
        +saveCheckpoint(checkpoint): Promise~void~
        +loadCheckpoint(sessionId): Promise~AgentLoopCheckpoint~
        +deleteCheckpoint(sessionId): Promise~void~
        +listCheckpoints(): Promise~AgentLoopCheckpoint[]~
    }

    class BaseTool {
        <<abstract>>
        +name: string
        +description: string
        +parameters: JSONSchema
      +execute(args, ctx?): Promise~unknown~
    }

    class ToolRegistry {
        +register(tool): void
        +unregister(name): boolean
        +get(name): BaseTool
        +list(): BaseTool[]
        +toOpenAIFormat(): OpenAITool[]
    }

    class BaseSession {
        <<abstract>>
        +id: string
        +status: SessionStatus
        +messages: Message[]
        +usage: Usage
        +configOverride: SessionConfigOverride
        +addMessage(message): void
        +save(): Promise~void~
        +toSnapshot(): SessionSnapshot
        +restoreFromSnapshot(snapshot): void
    }

    class BaseSessionManager {
        <<abstract>>
        +create(sessionId?): Promise~BaseSession~
        +get(sessionId): Promise~BaseSession~
        +list(): Promise~BaseSession[]~
        +destroy(sessionId): Promise~void~
    }

    class Middleware {
        <<function>>
        (ctx: AgentLoopState, next: NextFunction) => Promise~void~
    }

    class AgentLoopState {
        +sessionId: string
        +messages: Message[]
        +iteration: number
        +pendingToolCalls: ToolCallWithResult[]
        +currentResponse: string
        +shouldContinue: boolean
        +usage: Usage
    }

    Agent --> ModelClient : uses
    Agent --> ToolRegistry : uses
    Agent --> StateStore : uses
    Agent --> BaseSessionManager : uses
    Agent ..> Middleware : applies
    Agent ..> AgentLoopState : manages
    ToolRegistry --> BaseTool : contains
    BaseSessionManager --> BaseSession : manages

🧅 Middleware Pattern

GoatChain uses a Koa-style onion model for middleware. Each middleware wraps around the core execution:

outer:before → inner:before → exec (model.stream) → inner:after → outer:after

Named Middleware

Middlewares can be named for easier management and removal:

// Add named middleware (recommended)
agent.use(async (state, next) => {
  const start = Date.now()
  console.log(`[${state.iteration}] Before model call`)
  const nextState = await next(state)
  console.log(`[${state.iteration}] After model call (${Date.now() - start}ms)`)
  return nextState
}, 'logging')

// Remove by name
agent.removeMiddleware('logging')

// View all middleware names
console.log(agent.middlewareNames) // ['logging', 'compression', ...]

// Use unsubscribe function
const unsubscribe = agent.use(middleware, 'temp')
unsubscribe() // Remove middleware

Built-in Middleware Default Names

GoatChain's built-in middleware factories automatically provide default names:

// Plan mode middleware - automatically named 'plan-mode'
agent.use(createPlanModeMiddleware())

// Context compression middleware - automatically named 'context-compression'
agent.use(createContextCompressionMiddleware({
  maxTokens: 128000,
}))

// View all middleware
console.log(agent.middlewareNames)
// Output: ['plan-mode', 'context-compression']

// Remove by default name
agent.removeMiddleware('plan-mode')

// Or customize the name
agent.use(createPlanModeMiddleware({ name: 'my-plan' }))
agent.use(createContextCompressionMiddleware({
  maxTokens: 128000,
}), 'my-compression') // Override default name

Middleware Examples

// Logging middleware
agent.use(async (state, next) => {
  const start = Date.now()
  console.log(`[${state.iteration}] Before model call`)

  const nextState = await next(state) // Execute model stream

  console.log(`[${state.iteration}] After model call (${Date.now() - start}ms)`)
  return nextState
}, 'logging')

// Error handling middleware
agent.use(async (state, next) => {
  try {
    return await next(state)
  }
  catch (error) {
    state.shouldContinue = false
    state.stopReason = 'error'
    state.error = error
    return state
  }
}, 'error-handler')

// Rate limiting middleware
agent.use(async (state, next) => {
  await rateLimiter.acquire()
  return next(state)
}, 'rate-limiter')

📡 Event Types

The session receive stream emits the following events:

EventDescription
iteration_startBeginning of a loop iteration
text_deltaPartial text response from LLM
thinking_startThinking phase begins (if supported)
thinking_deltaThinking content delta (if supported)
thinking_endThinking phase ends (if supported)
tool_call_startTool call begins
tool_call_deltaTool call arguments delta
tool_call_endTool call is complete
tool_resultTool execution completed
iteration_endEnd of a loop iteration (includes usage)
doneStream completed (includes usage)
errorError occurred
interface AgentEvent {
  type:
    | 'text_delta'
    | 'tool_call_start'
    | 'tool_call_delta'
    | 'tool_call_end'
    | 'tool_result'
    | 'thinking_start'
    | 'thinking_delta'
    | 'thinking_end'
    | 'error'
    | 'done'
    | 'iteration_start'
    | 'iteration_end'
  // ... event-specific fields
}

iteration_end and done events include optional usage: Usage with cumulative token counts.

💾 Checkpoint & Resume

Built-in checkpoint support for resuming interrupted agent executions:

import { Agent, FileStateStore } from 'goatchain'

// Create state store with configuration
const stateStore = new FileStateStore({
  dir: './checkpoints',
  deleteOnComplete: true, // Clean up after successful completion
})

// Agent automatically saves checkpoints when stateStore is provided
const agent = new Agent({
  name: 'MyAgent',
  systemPrompt: 'You are helpful.',
  model,
  stateStore,
})

// Run agent - checkpoints are saved automatically
const session = await agent.createSession()
session.send('Hello')
for await (const event of session.receive()) {
  console.log(event)
}

// If interrupted, resume from checkpoint
const checkpoint = await stateStore.loadCheckpoint(session.id)
if (checkpoint) {
  const resumed = await agent.resumeSession(session.id)
  for await (const event of resumed.receive()) {
    console.log(event)
  }
}

Available state stores:

  • FileStateStore - File-based persistence
  • InMemoryStateStore - In-memory (for testing)

📖 API Reference

Agent

MethodDescription
constructor(options)Create a new agent
use(middleware)Add middleware
createSession(options)Create a session
resumeSession(sessionId, options)Resume a session
setModel(modelOrRef)Switch/pin model at runtime

Session

MethodDescription
send(input, options?)Queue input for the session
receive(options?)Stream events, resuming from checkpoint if present
messagesConversation history

📄 License

MIT © Simon He

FAQs

Package last updated on 17 Jan 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts