Skip to content

Commit 0257465

Browse files
committed
chore: updated readme, added memory store, and added hashchaining for tamper prooof audit log in database.
1 parent ee65f2a commit 0257465

6 files changed

Lines changed: 114 additions & 43 deletions

File tree

README.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
1-
# beago: Get feedback on your code before it hits review.
1+
# beago: A Go framework for building LLM-powered applications.
22

33
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![Test](https://github.com/bit8bytes/beago/actions/workflows/tests.yml/badge.svg) ![Sec Scan](https://github.com/bit8bytes/beago/actions/workflows/sec_scan.yml/badge.svg)
44

5-
beago uses Go's own tooling to analyze your code --- the same way Go itself sees it. The result: feedback that's actually relevant to your codebase.
5+
beago provides composable building blocks for LLM-powered Go applications — pipes for structured output, agents for tool-using reasoning loops, and stores for conversation history. The core library has no external dependencies.
66

7-
How It Works:
7+
## Core Concepts
88

9-
1. Write your code
10-
2. Run `beago analyze` to check it against your entire codebase
11-
3. Adjust before you open the PR
9+
- **Pipes** — simple `Input → LLM → Output` pipelines with typed, structured responses
10+
- **Agents** — ReAct (Reasoning + Acting) loops that interleave LLM reasoning with tool execution
11+
- **Stores** — tamper-evident message history with a SHA-256 hash chain, keeping LLMs stateful across turns
12+
- **Tools** — implement the `Tool` interface to give agents new capabilities
1213

13-
It's that easy. Stop repeating the same review comments.
14+
## Quick Start
15+
16+
```go
17+
// Pipe: send messages and get structured output
18+
pipe := pipes.New(messages, model, parser)
19+
result, _ := pipe.Invoke(ctx)
20+
21+
// Agent: reason and act with tools
22+
agent, _ := agents.NewReAct(ctx, model, tools, storage)
23+
agent.Task(ctx, "Use the helloWorld tool with name Beago")
24+
res, _ := runner.New(agent).Run(ctx)
25+
```
26+
27+
See [Examples](/docs/EXAMPLES.md) for full working examples.
1428

1529
## Contributions
1630

17-
Contributions of any kind are welcome! 🙌 See [Get Involved](/docs/GET-INVOLVED.md) to get started.
31+
Contributions of any kind are welcome! See [Get Involved](/docs/GET-INVOLVED.md) to get started.

docs/EXAMPLES.md

Lines changed: 0 additions & 25 deletions
This file was deleted.

llms/llms.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@
33
// can work with any LLM without being coupled to a specific API.
44
package llms
55

6-
import "github.com/bit8bytes/beago/inputs/roles"
6+
import (
7+
"time"
8+
9+
"github.com/bit8bytes/beago/inputs/roles"
10+
)
711

812
// Message represents a single turn in a conversation with an LLM.
913
// Role identifies who produced the content (e.g. system, user, assistant),
1014
// which lets the model understand conversational context and respond appropriately.
15+
//
16+
// Timestamp records when the message was added to the store.
17+
// Hash is a SHA-256 chain digest: SHA256(prev_hash + role + timestamp + content).
18+
// Each store implementation computes these in Add() so the chain is tamper-evident —
19+
// modifying, inserting, or deleting any message breaks every subsequent hash.
1120
type Message struct {
12-
Role roles.Role `json:"role"`
13-
Content string `json:"content"`
21+
Role roles.Role `json:"role"`
22+
Content string `json:"content"`
23+
Timestamp time.Time `json:"timestamp"`
24+
Hash string `json:"hash"`
1425
}
1526

1627
// StreamHandler is a callback invoked incrementally as the model generates a response.

stores/chain.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package stores
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
"time"
7+
8+
"github.com/bit8bytes/beago/llms"
9+
)
10+
11+
// Stamp sets Timestamp and Hash on msg, chaining from prevHash.
12+
// Hash = SHA256(prevHash + role + timestamp + content).
13+
// Call this inside Add() before persisting, passing the hash of the last
14+
// stored message as prevHash (empty string for the first message).
15+
func Stamp(msg *llms.Message, prevHash string) {
16+
msg.Timestamp = time.Now().UTC()
17+
18+
h := sha256.New()
19+
h.Write([]byte(prevHash))
20+
h.Write([]byte(msg.Role))
21+
h.Write([]byte(msg.Timestamp.Format(time.RFC3339Nano)))
22+
h.Write([]byte(msg.Content))
23+
msg.Hash = hex.EncodeToString(h.Sum(nil))
24+
}
25+
26+
// Verify replays the hash chain over msgs and returns the index of the first
27+
// message whose hash does not match, or -1 if the chain is intact.
28+
// Pass the hash that preceded the first message as prevHash
29+
// (empty string if msgs starts from the beginning of the store).
30+
func Verify(msgs []llms.Message, prevHash string) int {
31+
for i, msg := range msgs {
32+
h := sha256.New()
33+
h.Write([]byte(prevHash))
34+
h.Write([]byte(msg.Role))
35+
h.Write([]byte(msg.Timestamp.Format(time.RFC3339Nano)))
36+
h.Write([]byte(msg.Content))
37+
want := hex.EncodeToString(h.Sum(nil))
38+
if msg.Hash != want {
39+
return i
40+
}
41+
prevHash = msg.Hash
42+
}
43+
return -1
44+
}

stores/memory/memory.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import (
1212
)
1313

1414
type store struct {
15-
mu sync.Mutex
16-
messages []llms.Message
15+
mu sync.Mutex
16+
messages []llms.Message
17+
lastHash string
1718
}
1819

1920
func New() stores.Store {
@@ -24,7 +25,11 @@ func (s *store) Add(_ context.Context, msgs ...llms.Message) error {
2425
s.mu.Lock()
2526
defer s.mu.Unlock()
2627

27-
s.messages = append(s.messages, msgs...)
28+
for i := range msgs {
29+
stores.Stamp(&msgs[i], s.lastHash)
30+
s.lastHash = msgs[i].Hash
31+
s.messages = append(s.messages, msgs[i])
32+
}
2833
return nil
2934
}
3035

@@ -34,6 +39,7 @@ func (s *store) Clear(_ context.Context) error {
3439

3540
// Retain the underlying array to avoid an allocation on the next Add.
3641
s.messages = s.messages[:0]
42+
s.lastHash = ""
3743
return nil
3844
}
3945

stores/stores.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,40 @@ import (
2020

2121
// Store is the interface that wraps the basic message persistence methods.
2222
//
23-
// Implementations of Store must be safe for concurrent use by multiple goroutines.
23+
// Implementations must be safe for concurrent use by multiple goroutines.
24+
//
25+
// # Tamper-evident chain
26+
//
27+
// Every implementation must call [Stamp] on each message inside Add, passing
28+
// the hash of the last stored message as prevHash (empty string for the first
29+
// message). Stamp sets Message.Timestamp and computes Message.Hash as:
30+
//
31+
// SHA256(prevHash + role + timestamp + content)
32+
//
33+
// This chains every message to its predecessor. Any modification, insertion,
34+
// or deletion of a message breaks every subsequent hash, making the history
35+
// tamper-evident. Callers can verify integrity by replaying the chain with
36+
// [Verify].
37+
//
38+
// # Minimal implementation checklist
39+
//
40+
// - Add: call Stamp on each message before persisting; track lastHash
41+
// - List: return messages in insertion order; never mutate the stored slice
42+
// - Clear: reset lastHash to "" alongside clearing messages
43+
// - Close: release database connections or file handles; no-op is valid
2444
type Store interface {
2545
// Add appends one or more messages to the store. The agent calls this after
2646
// every LLM turn so the full conversation history is available on the next
27-
// Plan call.
47+
// Plan call. Implementations must stamp each message via [Stamp] before
48+
// persisting to maintain the hash chain.
2849
Add(ctx context.Context, msgs ...llms.Message) error
2950

3051
// List returns all messages in insertion order. The agent passes the full
3152
// history to the LLM on every turn so it has context from prior steps.
3253
List(ctx context.Context) ([]llms.Message, error)
3354

34-
// Clear removes all messages, resetting the conversation. Useful when
35-
// reusing a store across multiple independent agent runs.
55+
// Clear removes all messages and resets the hash chain, allowing the store
56+
// to be reused across independent agent runs.
3657
Clear(ctx context.Context) error
3758

3859
// Close releases any resources held by the store (e.g. database connections).

0 commit comments

Comments
 (0)