secai

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 14, 2025 License: MIT Imports: 19 Imported by: 0

README

secai is an AtomicAgents-like framework for autonomous LLM daemons. It's implemented on top of a unified state graph and makes a solid foundation for complex, proactive, and long-lived AI Agents with deep and structured memory. It offers a dedicated set of devtools and is written in the Go programming language. By having graph-based flow, secai allows for precise behavior modeling of agents, including interruptions and fault-tolerancy.

Features

  • atomicity on the state level
  • atomic consensus with relations and negotiation
  • declarative flow definitions
  • REPL & CLI
  • TUI debugger (+zellij dashboards)
  • automatic diagrams (SVG, D2, mermaid)
  • automatic observability (Prometheus, Grafana, Jaeger)
  • cancellation support (interrupts)
  • prompt history in SQL (embedded SQLite)
  • chat TUI (tview)

Tools

Planned

  • typesafe arguments struct
  • history DSL with a vector format (WIP)
  • pro-active scenarios (WIP)
  • MCP
  • dynamic tools
  • dynamic state graph
  • i18n

Implementation

  • pure Golang
  • typesafe state & prompt schemas
  • asyncmachine-go for graphs and control flow
  • instructor-go for the LLM layer
  • network transparency (aRPC, debugger, REPL)
  • structured concurrency (multigraph-based)
  • tview for chat TUI

Components

  • Agent (actor)
    • state-machine schema
  • Tool (actor)
    • state-machine schema
  • Prompt (stateless)
    • params schema
    • result schema
  • Document
    • title, content

Comparison

Feature secai AtomicAgents
Model unified state graph BaseAgent class
Debugger multi-client with time travel X
Diagrams customizable level of details X
Observability logging & Grafana & Otel X
REPL & CLI network-based X
History state-based and prompt-based prompt-based
Pkg manager Golang in-house
Control Flow declarative & fault tolerant imperative
CLI bubbletea, lipgloss rich
TUI tview, cview textual

Go vs Python

  • just works, batteries included, no magic
  • 1 package manager vs 4
  • single binary vs interpreted multi-file source
  • coherent static typing vs maybe
  • easy & stable vs easy
  • no ecosystem fragmentation
  • million times faster /s

Try It

Unlike Python apps, you can start it with a single command:

  1. Install Go
  2. Set either of the API keys:
    • export OPENAI_API_KEY=myapikey
    • export DEEPSEEK_API_KEY=myapikey
  3. Run go run github.com/pancsta/secai/examples/deepresearch/cmd@latest

Demo

Screenshots are also available.

[!NOTE] This tech demo is a 5min captions-only screencast, showcasing all 9 ways an agent can be seen, in addition to the classic chat view.

Example

Code snippets from /examples/deepresearch. Both the state and prompt schemas are pure and debuggable Golang code.

State Schema

// ResearchStatesDef contains all the states of the Research state machine.
type ResearchStatesDef struct {
    *am.StatesBase

    // PROMPTS

    CheckingInfo string
    NeedMoreInfo string

    Searching  string
    SearchDone string

    Answering string
    Answered  string

    *ss.AgentStatesDef
}

// ResearchGroupsDef contains all the state groups Research state machine.
type ResearchGroupsDef struct {
    Info    S
    Search  S
    Answers S
}

// ResearchSchema represents all relations and properties of ResearchStates.
var ResearchSchema = SchemaMerge(
    // inherit from Agent
    ss.AgentSchema,

    am.Schema{

        // PROMPTS

        // Choice "agent"
        ssR.CheckingInfo: {
            Require: S{ssR.Start},
            Remove:  sgR.Info,
        },
        ssR.NeedMoreInfo: {
            Require: S{ssR.Start},
            Add:     S{ssR.Searching},
            Remove:  sgR.Info,
        },

        // Query "agent"
        ssR.Searching: {
            Require: S{ssR.NeedMoreInfo},
            Remove:  sgR.Search,
        },
        ssR.SearchDone: {
            Require: S{ssR.Start},
            Remove:  sgR.Search,
        },

        // Q&A "agent"
        ssR.Answering: {
            Require: S{ssR.Start},
            Remove:  SAdd(sgR.Info, sgR.Answers, sgR.Search),
        },
        ssR.Answered: {
            Require: S{ssR.Start},
            Remove:  SAdd(sgR.Info, sgR.Answers, sgR.Search),
        },
    })

var sgR = am.NewStateGroups(ResearchGroupsDef{
        Info:    S{ssR.CheckingInfo, ssR.NeedMoreInfo},
        Search:  S{ssR.Searching, ssR.SearchDone},
        Answers: S{ssR.Answering, ssR.Answered},
    })

Prompt Schema

func NewCheckingInfoPrompt(agent secai.AgentApi) *secai.Prompt[ParamsCheckingInfo, ResultCheckingInfo] {
    return secai.NewPrompt[ParamsCheckingInfo, ResultCheckingInfo](
        agent, ssR.CheckingInfo, `
            - You are a decision-making agent that determines whether a new web search is needed to answer the user's question.
            - Your primary role is to analyze whether the existing context contains sufficient, up-to-date information to
            answer the question.
            - You must output a clear TRUE/FALSE decision - TRUE if a new search is needed, FALSE if existing context is
            sufficient.
        `, `
            1. Analyze the user's question to determine whether or not an answer warrants a new search
            2. Review the available web search results 
            3. Determine if existing information is sufficient and relevant
            4. Make a binary decision: TRUE for new search, FALSE for using existing context
        `, `
            Your reasoning must clearly state WHY you need or don't need new information
            If the web search context is empty or irrelevant, always decide TRUE for new search
            If the question is time-sensitive, check the current date to ensure context is recent
            For ambiguous cases, prefer to gather fresh information
            Your decision must match your reasoning - don't contradict yourself
        `)
}

// CheckingInfo (Choice "agent")

type ParamsCheckingInfo struct {
    UserMessage  string
    DecisionType string
}

type ResultCheckingInfo struct {
    Reasoning string `jsonschema:"description=Detailed explanation of the decision-making process"`
    Decision  bool   `jsonschema:"description=The final decision based on the analysis"`
}

Read the schema file in full.

Screenshots

SVG graph am-dbg Grafana Jaeger REPL
SQL IDE Bash Prompts  
 
Dashboard 1
Dashboard 2

Documentation

Getting Started

We can use /examples/deepresearch as a starting template. It allows for further updates of the base framework.

  1. git clone https://github.com/pansta/secai
  2. install task ./secai/scripts/deps.sh
  3. copy the agent cp -R secai/examples/deepresearch MYAGENT
  4. copy agent's config cp secai/template.env MYAGENT/.env
  5. copy project configs cp -R secai/config MYAGENT
  6. cd MYAGENT
  7. go mod init github.com/USER/MYAGENT
  8. go mod tidy
  9. task install-deps
  10. task start
  11. task --list

Chat TUI

A simple chat TUI with UI states is included in /tui, consisting of:

  • senders & msgs scrollable view with links
  • multiline prompt with blocking and progress
  • send / stop button

Bash Scripts

arpc offers a CLI access to remote agents, including subscription. It's perfect for quick and simple integrations, scripts, or experiments.

Example: arpc -f tmp/deepresearch.addr -- when . Requesting && echo "REQUESTING"

  1. Connect to the address from tmp/deepresearch.addr
  2. When the last connected agent (.) goes into state Requesting
  3. Print "REQUESTING" and exit

Acknowledgements

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func InitAgent

func InitAgent[G AgentApi](
	ctx context.Context, id string, states am.S, machSchema am.Schema, agent G,
) (G, error)

func ToolAddToPrompts

func ToolAddToPrompts(t ToolApi, prompts ...PromptApi)

Types

type Agent

type Agent struct {
	*am.ExceptionHandler
	*ssam.DisposedHandlers

	// UserInput is a prompt submitted the user, owned by [schema.AgentStatesDef.Prompt].
	UserInput string
	// contains filtered or unexported fields
}

func (*Agent) DB

func (a *Agent) DB() *sql.DB

func (*Agent) DBReadyEnd

func (a *Agent) DBReadyEnd(e *am.Event)

func (*Agent) DBSavingEnter

func (a *Agent) DBSavingEnter(e *am.Event) bool

func (*Agent) DBSavingState

func (a *Agent) DBSavingState(e *am.Event)

func (*Agent) DBStartingState

func (a *Agent) DBStartingState(e *am.Event)

func (*Agent) Log

func (a *Agent) Log(txt string, args ...any)

func (*Agent) Mach

func (a *Agent) Mach() *am.Machine

func (*Agent) OpenAI

func (a *Agent) OpenAI() *instructor.InstructorOpenAI

func (*Agent) Output

func (a *Agent) Output(txt string, from shared.From)

func (*Agent) PromptEnd

func (a *Agent) PromptEnd(e *am.Event)

func (*Agent) PromptState

func (a *Agent) PromptState(e *am.Event)

func (*Agent) Queries

func (a *Agent) Queries() *db.Queries

func (*Agent) RequestingExit

func (a *Agent) RequestingExit(e *am.Event) bool

func (*Agent) RequestingLLMEnd

func (a *Agent) RequestingLLMEnd(e *am.Event)

func (*Agent) RequestingLLMEnter

func (a *Agent) RequestingLLMEnter(e *am.Event) bool

func (*Agent) RequestingLLMExit

func (a *Agent) RequestingLLMExit(e *am.Event) bool

func (*Agent) RequestingToolEnd

func (a *Agent) RequestingToolEnd(e *am.Event)

func (*Agent) RequestingToolEnter

func (a *Agent) RequestingToolEnter(e *am.Event) bool

func (*Agent) RequestingToolExit

func (a *Agent) RequestingToolExit(e *am.Event) bool

func (*Agent) SetMach

func (a *Agent) SetMach(m *am.Machine)

func (*Agent) SetOpenAI

func (a *Agent) SetOpenAI(c *instructor.InstructorOpenAI)

func (*Agent) Start

func (a *Agent) Start() am.Result

func (*Agent) StartEnter

func (a *Agent) StartEnter(e *am.Event) bool

func (*Agent) StartState

func (a *Agent) StartState(e *am.Event)

func (*Agent) Stop

func (a *Agent) Stop(disposeCtx context.Context) am.Result

type AgentApi

type AgentApi interface {
	Output(txt string, from shared.From)

	Mach() *am.Machine
	SetMach(*am.Machine)

	SetOpenAI(c *instructor.InstructorOpenAI)
	OpenAI() *instructor.InstructorOpenAI

	Start() am.Result
	Stop(disposeCtx context.Context) am.Result
	Log(txt string, args ...any)

	DB() *sql.DB
	Queries() *db.Queries
}

type CheckpointSamples

type CheckpointSamples struct {
	// activation distance of the checkpoints
	Distance int
	// step of the scenario
	ScenarioStep int
	// input-results for the given scenario step and distance
	InRes []InRes
}

type Document

type Document struct {
	// contains filtered or unexported fields
}

func NewDocument

func NewDocument(title string, content ...string) *Document

func (*Document) AddPart

func (d *Document) AddPart(parts ...string) *Document

func (*Document) Clear

func (d *Document) Clear() *Document

func (*Document) Clone

func (d *Document) Clone() Document

func (*Document) Parts

func (d *Document) Parts() []string

func (*Document) Title

func (d *Document) Title() string

type InRes

type InRes struct {
	Active   uint8
	Expected uint8
}

type Prompt

type Prompt[P any, R any] struct {
	Conditions   string
	Steps        string
	Result       string
	SchemaParams P
	SchemaResult R

	// number of previous messages to include
	HistoryMsgLen int
	// number of previous machine times to include
	HistoryStateLen int

	State string
	A     AgentApi
	// contains filtered or unexported fields
}

func NewPrompt

func NewPrompt[P any, R any](agent AgentApi, state, condition, steps, results string) *Prompt[P, R]

func (*Prompt[P, R]) AddTool

func (p *Prompt[P, R]) AddTool(tool ToolApi)

func (*Prompt[P, R]) AppendHistOpenAI

func (p *Prompt[P, R]) AppendHistOpenAI(msg *openai.ChatCompletionMessage)

func (*Prompt[P, R]) Generate

func (p *Prompt[P, R]) Generate() string

func (*Prompt[P, R]) HistOpenAI

func (p *Prompt[P, R]) HistOpenAI() []openai.ChatCompletionMessage

func (*Prompt[P, R]) MsgsOpenAI

func (p *Prompt[P, R]) MsgsOpenAI() []openai.ChatCompletionMessage

func (*Prompt[P, R]) Run

func (p *Prompt[P, R]) Run(params P, model string) (*R, error)

TODO accept model as general opts obj

type PromptApi

type PromptApi interface {
	AddTool(tool ToolApi)

	HistOpenAI() []openai.ChatCompletionMessage
	AppendHistOpenAI(msg *openai.ChatCompletionMessage)
}

type PromptSchemaless

type PromptSchemaless = Prompt[any, any]

type S

type S = am.S

type Scenario

type Scenario struct {
	// Name of the scenario
	Name string

	// scenario only applicable if these states are active (optional)
	RequireActive am.S
	// scenario only applicable if these states are inactive (optional)
	RequireInactive am.S

	// states possible to trigger (separately)
	Inputs am.S
	// states expected to happen
	Checkpoints am.S
	// additional context states (optional)
	Contexts am.S

	// number of future transitions to check for checkpoint activations, eg [1, 5, 10]
	// will mark a checkpoint true if it gets activated in 1 or 5 or 10 txes
	// from the input state
	// default: [1]
	ActivationDistances []int
	// number of mutations to simulate and check for checkpoint confirmations
	ScenarioSteps int // default: 1
}

Scenario produces ScenarioSamples for checkpoint states.

type ScenarioSamples

type ScenarioSamples struct {
	// active checkpoints in the beginning of the scenario
	StartingCheckpoints []string
	// names of input states, with only one being called at a time
	Inputs []string
	// names of checkpoint states
	Checkpoint []string
	// names of context states
	Contexts []string
	// samples with Checkpoint states as inputs
	Samples []*CheckpointSamples
}

type Tool

type Tool struct {
	Doc *Document
	// contains filtered or unexported fields
}

func NewTool

func NewTool(
	agent AgentApi, idSuffix, title string, states am.S, stateSchema am.Schema,
) (*Tool, error)

func (*Tool) Mach

func (t *Tool) Mach() *am.Machine

func (*Tool) SetMach

func (t *Tool) SetMach(m *am.Machine)

type ToolApi

type ToolApi interface {
	Mach() *am.Machine
	SetMach(*am.Machine)
	Document() *Document
}

Directories

Path Synopsis
examples
deepresearch
Package deepresearch is a port of atomic-agents/deepresearch to secai.
Package deepresearch is a port of atomic-agents/deepresearch to secai.
Package schema contains a stateful schema-v2 for Agent.
Package schema contains a stateful schema-v2 for Agent.
tools

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL