> ## Documentation Index
> Fetch the complete documentation index at: https://hastekit.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# History

Conversation history enables agents to maintain context across multiple interactions. By preserving previous messages, agents can reference earlier exchanges and build upon prior context, facilitating natural, context-aware conversations.

## Overview

When a conversation history manager is provided to an agent, it performs the following operations:

* **Loads Previous Messages**: Automatically retrieves conversation history based on the thread ID
* **Saves Automatically**: Persists new messages after each execution

## Enabling Conversation History

To enable conversation history, create a history manager with `hastekit.NewFileHistory` and pass the created instance to the agent's `History` field. While invoking the agent, optionally set `Namespace` to bucket the conversations by namespaces.

```go theme={null}
cm := hastekit.NewFileHistory("./conversations")
agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name:        "Hello world agent",
    LLM:         model,
    History:     cm,
})

handle, err := agent.Execute(context.Background(), &agents.AgentInput{
    Namespace: "default",
    Message: history.Message{
        Messages: []responses.InputMessageUnion{
            responses.UserMessage("Hello! My name is Alice"),
        },
    },
})
out, err := handle.Result()
```

## Continuing the conversation

To continue the conversation, pass same thread ID while invoking the agent again.

```go theme={null}
// First invocation
threadID := uuid.NewString()
handle, err := agent.Execute(context.Background(), &agents.AgentInput{
    Namespace: "default",
    ThreadID:  threadID,
    Message: history.Message{
        Messages: []responses.InputMessageUnion{
            responses.UserMessage("Hello! My name is Alice"),
        },
    },
})
out, err := handle.Result()

// Second invocation
handle, err = agent.Execute(context.Background(), &agents.AgentInput{
    Namespace: "default",
    ThreadID:  threadID, // Pass the same thread ID to continue the conversation
    Message: history.Message{
        Messages: []responses.InputMessageUnion{
            responses.UserMessage("What's my name?"),
        },
    },
})
out, err = handle.Result()
```

***

## Persistence

The conversation manager supports three persistence configurations for storing conversation history:

### 1. File Persistence

When the SDK client is initialized without an `endpoint`, messages are persisted as JSON files written to the disk (under `./conversations`).

### 2. HasteKit Gateway Persistence

When the SDK client is initialized with an `endpoint`, the conversation manager automatically persists messages to the HasteKit's Gateway server.

### 3. Custom Persistence

To implement custom persistence, implement the `ConversationPersistenceAdapter` interface:

```go theme={null}
type ConversationPersistenceAdapter interface {
	NewConversationID(ctx context.Context) string
	NewRunID(ctx context.Context) string
	LoadMessages(ctx context.Context, namespace string, threadID string, previousMessageID string) ([]history.ConversationMessage, error)
	SaveMessages(ctx context.Context, namespace, msgId, previousMsgId, threadID string, conversationId string, messages []history.Message, meta map[string]any) error
	SaveSummary(ctx context.Context, namespace string, summary history.Summary) error
}
```

Then pass your implementation to the conversation manager constructor:

```go theme={null}
cm := history.NewConversationManager(yourAdapter)
```

When you use `hastekit.NewFileHistory("./conversations")`, the SDK wires a file-backed adapter that persists messages as JSON on disk.

## Message Filtering & Attribution

Each stored message bundle (`messages.Message`) carries a `SenderID`, which lets the conversation manager attribute turns to a specific participant in multi-participant conversations.

You can transform a run's message bundles before they are attributed and sent to the provider by supplying a `MessageFilter` via `history.WithMessageFilter`. A common use is rewriting each bundle's `SenderID` from an opaque id into a human-friendly name.

```go theme={null}
type MessageFilter interface {
	Filter(ctx context.Context, msgs []messages.Message, agentID string) []messages.Message
}

cm := history.NewConversationManager(
	yourAdapter,
	history.WithMessageFilter(yourFilter),
)
```

The other available option is `history.WithSummarizer`, covered on the [Summarization](/docs/hastekit-sdk/agents/conversations/summarization) page.

## Listing Threads

`agent.History()` returns the agent's `*history.CommonConversationManager`. When its persistence adapter implements `history.ThreadLister`, you can enumerate stored threads. Pass an empty namespace (`""`) to list across all namespaces.

```go theme={null}
type ThreadLister interface {
	ListThreads(ctx context.Context, namespace string) ([]ThreadInfo, error)
}

if lister, ok := agent.History().ConversationPersistenceAdapter.(history.ThreadLister); ok {
	threads, err := lister.ListThreads(context.Background(), "")
	// ...
}
```

## Complete Example

The following example demonstrates an agent with conversation history:

```go theme={null}
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/bytedance/sonic"
	"github.com/google/uuid"
	"github.com/hastekit/hastekit-sdk-go/pkg/agents"
	"github.com/hastekit/hastekit-sdk-go/pkg/agents/history"
	"github.com/hastekit/hastekit-sdk-go/pkg/gateway/llm/responses"
	hastekit "github.com/hastekit/hastekit-sdk-go"
)

func main() {
    client := hastekit.NewLLMClient([]hastekit.ProviderConfig{
        {
            ProviderName:  hastekit.ProviderOpenAI,
            BaseURL:       "",
            CustomHeaders: nil,
            ApiKeys: []*hastekit.APIKeyConfig{
                {
                    Name:   "Key 1",
                    APIKey: os.Getenv("OPENAI_API_KEY"),
                },
            },
        },
    })

	model := client.Model("OpenAI/gpt-4.1-mini")

	cm := hastekit.NewFileHistory("./conversations")
	agent := hastekit.NewAgent(&hastekit.AgentConfig{
		Name:        "Hello world agent",
		Instruction: hastekit.NewPrompt("You are helpful assistant."),
		LLM:         model,
		History:     cm,
	})

    threadID := uuid.NewString()

	handle, err := agent.Execute(context.Background(), &agents.AgentInput{
		Namespace: "default",
		ThreadID:  threadID,
		Message: history.Message{
            Messages: []responses.InputMessageUnion{
			    responses.UserMessage("Hello! My name is Alice"),
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	out, err := handle.Result()
	if err != nil {
		log.Fatal(err)
	}

	b, _ := sonic.Marshal(out)
	fmt.Println(string(b))

	// Agent itself is stateless - you can either re-create another agent or reuse the same agent instance, but ensure to pass the correct `ThreadID`
	agent2 := hastekit.NewAgent(&hastekit.AgentConfig{
		Name:        "Hello world agent",
		Instruction: hastekit.NewPrompt("You are helpful assistant."),
		LLM:         model,
		History:     cm,
	})

	handle, err = agent2.Execute(context.Background(), &agents.AgentInput{
		Namespace: "default",
		ThreadID:  threadID,
		Message: history.Message{
            Messages: []responses.InputMessageUnion{
			    responses.UserMessage("What's my name?"),
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	out, err = handle.Result()
	if err != nil {
		log.Fatal(err)
	}

	b, _ = sonic.Marshal(out)
	fmt.Println(string(b))
}

```
