> ## 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.

# System Instruction

System instructions define the behavior and personality of an agent. The SDK provides flexible ways to create system instructions, from simple strings to dynamic templates loaded from remote sources.

## Basic Usage

The simplest way to provide a system instruction is using a plain string:

```go theme={null}
agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name:        "Assistant",
    Instruction: hastekit.NewPrompt("You are a helpful assistant."),
    LLM:         model,
})
```

## Template Variables

System instructions support template variables using Go template syntax. Variables are resolved at runtime using resolvers.

### Simple Template Variables

Use `{{variable}}` syntax in your instruction string, and provide a resolver to populate the values:

```go theme={null}
contextData := map[string]any{
    "name": "Alice",
    "role": "developer",
}

agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name:        "Personalized Assistant",
    Instruction: hastekit.NewPrompt(
        "You are a helpful assistant. You are interacting with {{name}}, who is a {{role}}.",
    ),
    LLM: model,
})

agent.Execute(context.Background(), &agents.AgentInput{
	RunContext: map[string]any{
	    "name": "Bob",
		"role": "admin",
	},
})
```

The template syntax `{{variable}}` is automatically converted to Go template format `{{ .variable }}` for resolution.

### Custom Resolvers

You can create custom resolvers functions for more complex template resolution logic. The resolver function will have access to the raw prompt and the run context.

```go theme={null}
import "github.com/hastekit/hastekit-sdk-go/pkg/agents/prompts"

customResolver := func(promptStr string, runContext map[string]any) (string, error) {
    // Your logic to resolve the prompt into the final system format
}

agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name:        "Dynamic Assistant",
    Instruction: hastekit.NewPrompt("Hello {{userName}}, current time is {{timestamp}}", prompts.WithResolver(customResolver)),
    LLM:         model,
})
```

## Skills

You can inject **skills** into the system prompt using `prompts.WithSkills`. Each skill adds more specialised context, instructions and scripts that the agent can pull in when a task requires it.

```go theme={null}
import "github.com/hastekit/hastekit-sdk-go/pkg/agents/prompts"

agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name: "Skilled Assistant",
    Instruction: hastekit.NewPrompt(
        "You are a helpful assistant.",
        prompts.WithSkills([]agents.Skill{
            {
                Name:         "pdf-generation",
                Description:  "Generate PDF documents from structured data.",
                FileLocation: "skills/pdf-generation/SKILL.md",
            },
        }),
    ),
    LLM: model,
})
```

The `agents.Skill` fields are loaded from a skill's `SKILL.md`:

| Field            | Type     | Description                                          |
| :--------------- | :------- | :--------------------------------------------------- |
| **Name**         | `string` | Skill name from the `SKILL.md` frontmatter           |
| **Description**  | `string` | Skill description from the `SKILL.md` frontmatter    |
| **FileLocation** | `string` | Path to the `SKILL.md` file relative to sandbox-data |

## Remote Prompts

You can load system instructions from the HasteKit Gateway server using a `hastekitgateway.Config`'s `NewPrompt`.

```go theme={null}
cfg := &hastekitgateway.Config{
    Endpoint:    "https://api.hastekit.ai",
    VirtualKey:  "vk_...",
    OrgName:     "my-org",
    ProjectName: "my-project",
    HttpClient:  http.DefaultClient,
}

agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name:        "Remote Assistant",
    Instruction: cfg.NewPrompt("my-prompt-name", "production"),
    LLM:         model,
})
```

### Parameters

* **`name`**: The name of the prompt stored in the HasteKit Gateway server
* **`alias`**: The prompt alias (`"production"` or `"latest"`)

<Note>For this to work, you should have configured the `hastekitgateway.Config` with the gateway's endpoint, organisation name, project name and an API key.</Note>

### Remote Prompt with Resolvers

You can combine remote prompts with resolvers for dynamic content:

```go theme={null}
agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name:        "Versioned Assistant",
    Instruction: cfg.NewPrompt(
        "assistant-prompt",
        "production",
        prompts.WithResolver(customResolver),
    ),
    LLM: model,
})

contextData := map[string]any{
    "environment": "production",
    "version": "1.0.0",
}

agent.Execute(context.Background(), &agents.AgentInput{
	RunContext: contextData,
})
```

## Custom Prompt Loaders

For advanced use cases, you can implement a custom `PromptLoader` interface to load prompts from any source:

```go theme={null}
type CustomLoader struct {
    // Your custom fields
}

func (l *CustomLoader) LoadPrompt(ctx context.Context) (string, error) {
    // Load prompt from your custom source
    // e.g., database, file system, external API, etc.
    return "Your prompt content", nil
}

customLoader := &CustomLoader{}
agent := hastekit.NewAgent(&hastekit.AgentConfig{
    Name:        "Custom Assistant",
    Instruction: hastekit.NewPromptWithLoader(customLoader),
    LLM:         model,
})
```

## Complete Example

Here's a complete example demonstrating different ways to use system instructions:

```go theme={null}
package main

import (
	"context"
	"log"
	"os"

	"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-4o-mini")

	// Example 1: Simple string instruction
	agent1 := hastekit.NewAgent(&hastekit.AgentConfig{
		Name:        "Simple Assistant",
		Instruction: hastekit.NewPrompt("You are a helpful assistant."),
		LLM:         model,
	})

	// Example 2: Template with variables
	agent2 := hastekit.NewAgent(&hastekit.AgentConfig{
		Name:        "Personalized Assistant",
		Instruction: hastekit.NewPrompt(
			"You are a helpful assistant for {{userName}}, who is a {{userRole}}.",
		),
		LLM: model,
	})

	// Example 3: Remote prompt (requires a configured hastekitgateway.Config)
	// agent3 := hastekit.NewAgent(&hastekit.AgentConfig{
	//     Name:        "Remote Assistant",
	//     Instruction: cfg.NewPrompt("my-prompt", "production"),
	//     LLM:         model,
	// })

	// Execute agent
	handle, err := agent2.Execute(context.Background(), &agents.AgentInput{
		Message: history.Message{
            Messages: []responses.InputMessageUnion{
			    responses.UserMessage("Hello!"),
			},
		},
		RunContext: map[string]any{
			"userName": "Bob",
			"userRole": "developer",
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	out, err := handle.Result()
	if err != nil {
		log.Fatal(err)
	}

	log.Println(out.Output[0].OfOutputMessage.Content[0].OfOutputText.Text)
}
```

## Notes

* Template variables use the syntax `{{variable}}` which is automatically converted to Go template format
* Remote prompts require the SDK client to be initialized with `endpoint`, organisation name and `projectName`
* Custom prompt loaders must implement the `PromptLoader` interface with a `LoadPrompt(ctx context.Context) (string, error)` method
