jess

package module
v0.0.0-...-86b5f70 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 10 Imported by: 0

README

jess

Go Reference Go Report Card CI

A memory- and skill-augmented agent facade for Go. The leather strap on the falcon's leg.

What it is

jess is an easy agent harness over agentcore with durable memory, skills, subagents, and baked-in audit + a fail-closed tool gate. jess.New(opts...) returns a real *agentcore.Agent; drive it with jess.Stream or directly with agentcore's API.

  • jess (root)New(opts...) *agentcore.Agent, Stream(ctx, agent, input), Once(supportsTools, fn). Options: WithModel, WithAgentID, WithSystemPrompt, WithTools, WithMemory, WithSkills, WithMaxTurns, WithApprover, WithLedger, AllowAll, WithToolGate, WithSubagents, WithAgentcoreOptions.

  • jess/memory — durable agent memory. Typed Kind (user, feedback, project, reference) with per-kind retrieval policy. Three pluggable Stores (in-memory, JSONL, chromem-go vector). A pure-Go embedder (GoMLX + sentence-transformers, no CGO). Recallers compose: SimpleRecaller (token overlap) + VectorRecaller (cosine) fused via HybridRecaller (reciprocal rank fusion). RememberTool and RecallTool let the model write and read memory. Wired via jess.WithMemory(store, recaller); recalled entries are injected on every turn.

  • jess/skill — registerable capability bundles. A Skill is a name, description, system-prompt contribution, and zero-or-more tools. Loads from disk (filesystem layout mirrors Claude Code skills) or by direct registration. Wired via jess.WithSkills(set).

  • jess/ledger — durable provenance ledger (agentcore-free). Every tool request, gate decision, and run boundary is recorded. The ledger is structured as a chain triad: request, available context, and actions. SQLite is the default durable backend (pure-Go, no CGO). DiscardSink{} turns recording off explicitly; it is never off silently.

  • jess/gate — fail-closed tool gate. Tools implementing SafeTool are auto-approved. Everything else goes to the Approver if one is wired; without an approver, non-safe tools are denied. AllowAll() is the explicit opt-out.

Install

go get github.com/guygrigsby/jess

Go 1.26+. Pure Go — no CGO, no external runtimes. Cross-compiles to every OS/arch combo go build supports.

Quickstart

Full runnable, offline example at examples/quickstart. Sketch:

import (
    ac "github.com/voocel/agentcore"
    "github.com/guygrigsby/jess"
    "github.com/guygrigsby/jess/ledger"
    "github.com/guygrigsby/jess/memory"
)

// 1. Durable memory. Seed a core (always-include) user fact.
store := memory.NewInMemoryStore()
store.Append(ctx, memory.Entry{
    AgentID: "demo",
    Kind:    string(memory.KindUser), // AlwaysInclude: injected every turn
    Text:    "User prefers concise, example-first answers.",
})

// 2. Build the agent. jess.New returns a *agentcore.Agent.
agent := jess.New(
    jess.WithModel(yourModel),  // any agentcore.ChatModel; jess.Once for local fns
    jess.WithAgentID("demo"),
    jess.WithMemory(store, memory.NewSimpleRecaller()),
    jess.WithLedger(ledger.DiscardSink{}), // or omit for durable SQLite default
)

// 3. Drive a run and observe its event channel.
ch, wait := jess.Stream(ctx, agent, "What kind of answers do I like?")
for ev := range ch {
    switch ev.Type {
    case ac.EventToolExecStart:
        fmt.Printf("-> tool %s\n", ev.Tool)
    case ac.EventError:
        fmt.Printf("! error: %v\n", ev.Err)
    }
}
sum := wait() // *agentcore.RunSummary; nil on abort

The agent:

  • Sees user and feedback memories on every turn (always-include).
  • Sees project and reference memories when they're relevant.
  • Can save and query facts via the remember / recall tools.
  • Persists across restarts (with a durable Store).

Provenance and safety

Every jess.New agent is safe by default. Two controls are baked in.

Provenance ledger. Every tool request, gate decision (allow or deny), and run boundary is recorded in a durable SQLite ledger under the user cache dir (~/.cache/jess/ledger.db on macOS/Linux). The ledger stores a chain triad per run: the triggering request, available context (memory recall and safe tool reads), and actions (effectful calls). Each action record embeds its own rationale: RunID, CallID, tool name, args, and a reference back to the request. Reading the chain backward answers "why did the agent do X?" without external documentation. Pass jess.WithLedger(ledger.DiscardSink{}) to turn recording off explicitly.

Fail-closed gate. A tool not implementing gate.SafeTool is denied unless an Approver is wired via jess.WithApprover. No approver means deny, not allow. jess.AllowAll() is the explicit, greppable opt-out. See examples/gated for a stdin approver stand-in for an async Telegram confirm flow.

No durable record, no action. The audit middleware in internal/core enforces this independently of the gate: before any non-safe tool runs, a fully self-explaining KindAction event must be durably committed to a ledger.DurableSink. Plain sinks (JSONLSink, DiscardSink) are observation-only and cannot authorize actions. A permissive gate (AllowAll) does not bypass this: the record obligation lives in the middleware, which runs after the gate. Denied non-safe attempts are also recorded as KindAction(denied) by the gate, so blocked calls stay visible in the chain.

Project layout

jess/
├── jess.go, adapters.go, gate_opts.go, ledger_opts.go, subagent_opts.go
│                                 # New, Stream, Once, With* options
├── ledger/                       # provenance ledger (agentcore-free)
│   ├── event.go                  # Event, Kind, Ref, RefSource, Verdict, NewEventID
│   ├── sink.go                   # Sink, DurableSink, DiscardSink
│   ├── jsonl.go                  # JSONLSink (observation-only)
│   ├── sqlite.go                 # SQLite (DurableSink + Reader, pure-Go modernc)
│   └── chain.go                  # Chain, Action, Reader, Resolver, AssembleChain
├── gate/                         # fail-closed tool gate (SafeTool, Approver, Policy)
├── memory/                       # the memory subsystem (agentcore-free)
│   ├── memory.go                 # Entry, Store, Query, Recaller, VectorStore
│   ├── kind.go                   # Kind constants, KindPolicy, KindRegistry
│   ├── tool.go                   # RememberTool (agentcore.Tool)
│   ├── recall_tool.go            # RecallTool (agentcore.Tool)
│   ├── store_inmemory.go         # InMemoryStore
│   ├── store_jsonl.go            # JSONLStore (durable, tombstones, Compact)
│   ├── store_chromem.go          # ChromemStore (vector, on chromem-go)
│   ├── recall.go                 # SimpleRecaller (token overlap)
│   ├── recall_vector.go          # VectorRecaller + HybridRecaller (RRF)
│   ├── embedder.go               # Embedder interface
│   └── embed/gomlx/              # in-process pure-Go embedder
├── skill/                        # skill bundles (agentcore-free)
│   ├── skill.go                  # Skill, Set, Loader
│   └── filesystem.go             # SKILL.md walker (Claude Code layout)
├── subagent/                     # bounded Pool for fan-out subagents
├── internal/core/                # Config + Agent builder, Once, Stream, audit middleware
└── examples/
    ├── quickstart/               # offline echo-model demo with memory
    └── gated/                    # stdin approver stand-in for Telegram confirm

Design choices

Brief notes on the non-obvious calls:

Typed Kind with policy. KindUser and KindFeedback always inject (stable facts the model needs every turn). KindProject and KindReference are recall-only. Unknown kinds get a conservative fallback. Hosts can override the registry per-agent.

Key supersession. Setting Entry.Key makes re-Append REPLACE the prior entry at the same (AgentID, Key). "User prefers tabs" → later "user prefers spaces" → one entry, not two. The model doesn't have to resolve the contradiction every turn.

Hybrid retrieval, not pure vector. Vector retrieval famously misses keyword-exact hits ("what was that flag?" drifts toward semantically related text). HybridRecaller fuses SimpleRecaller (BM25-equivalent token overlap) with VectorRecaller (chromem-go cosine) via reciprocal rank fusion (K=60). Either alone misses cases the other catches.

Pure-Go embedder. jess/memory/embed/gomlx runs sentence-transformers/all-MiniLM-L6-v2 (or any BERT-family ONNX sentence model) in-process via GoMLX's pure-Go backend. No CGO. No subprocess. No ONNX Runtime sidecar. Cross-compile intact. ~50ms per embedding on a modern Mac CPU (model load is one-shot at construction).

Model weights are NOT bundled. NewEmbedder() downloads from HuggingFace on first run (~90MB for MiniLM) into the standard HuggingFace cache ($HF_HOME or ~/.cache/huggingface); subsequent runs are warm. Cache layout matches the Python huggingface_hub client's, so a user who already has the model cached from another tool gets a warm start for free.

The download path is pure Go HTTP via gomlx/go-huggingface/hub — no huggingface-cli install, no Python, no pip. Just stdlib net/http against huggingface.co (or $HF_ENDPOINT for mirrors / air-gapped installs).

The model's license stays the model's license; jess takes no position on redistribution.

Embedder is replaceable. Embedder is an interface. Ship Ollama and OpenAI adapters when needed; hosts that prefer those swap one line.

Development

Make targets wrap the checks CI runs:

make test           # go test -race ./...
make vet            # go vet ./...
make lint           # golangci-lint (config in .golangci.yml)
make license-audit  # go-licenses: fail on any GPL/AGPL-class dep

lint and license-audit need nothing installed beyond the Go toolchain (both fall back to go run at a pinned version).

CI (.github/workflows/test.yml) runs all four on every push and PR to main, plus a non-blocking govulncheck. Tests, lint, and the license audit are blocking; a GPL/AGPL dependency fails the build.

A versioned pre-commit hook runs lint when .go files are staged and the license audit when go.mod / go.sum change. Opt in once per clone:

git config core.hooksPath .githooks

See CONTRIBUTING.md for the full workflow.

Status

Pre-1.0. API may change before v1; the facade and its subpackages have shipping implementations with test coverage. See CHANGELOG.md.

Two upstream deps pinned to main (not a tagged release):

  • github.com/voocel/agentcore — pinned for the post-PR-409 compute.Backend API; falls back to a tagged version once one ships.
  • github.com/philippgille/chromem-go — pinned for ListDocuments and GetByMetadata from main; v0.7.0 lacks both.

Both moves are temporary. Will revert to tagged versions when upstream cuts releases.

License

MIT. Dependency licenses:

  • agentcore — Apache 2.0
  • chromem-go — MPL 2.0 (file-level copyleft; safe to depend on from MIT projects)
  • gomlx, onnx-gomlx, go-huggingface — Apache 2.0

No GPL or AGPL anywhere in the tree. CI enforces this via make license-audit (see Development).

Documentation

Overview

Package jess is an easy agent harness over github.com/voocel/agentcore. It adds durable memory, registerable skills, subagents, and two baked-in safety controls: an append-only audit log and a fail-closed tool gate.

jess.New is an option-assembler: pass functional options (WithModel, WithMemory, WithSkills, WithTools, WithApprover, ...) and it returns a real *agentcore.Agent. Drive it with agentcore's own API, or with jess.Stream, which exposes the event channel and a Wait for the run summary and aborts the run when its context is cancelled (the kill switch):

agent := jess.New(
	jess.WithModel(m),                // any agentcore.ChatModel, or jess.Once for a local one
	jess.WithMemory(store, recaller), // durable recall, injected each turn
	jess.WithSkills(set),             // capability bundles
)
ch, wait := jess.Stream(ctx, agent, "hello")
for ev := range ch { /* observe */ }
summary := wait()

agentcore types are exposed directly (ADR 0002); jess does not wrap the harness. Portability insurance is keeping jess/memory and jess/skill agentcore-free, so those stores and skills travel to any harness.

Pre-1.0 — API may change before v1. See CHANGELOG.md and the examples/ directory for runnable wiring.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(opts ...Option) *ac.Agent

New assembles a ready *agentcore.Agent: model, memory, skills, tools, gate, audit, subagents. It returns the real agentcore type; drive it with agentcore's API or jess.Stream.

The gate is fail-closed by default: without an approver (or AllowAll), any tool not marked Safe is denied. Audit is on by default (a durable SQLite sink under the user cache dir); pass WithLedger(ledger.DiscardSink{}) to turn it off explicitly. With audit off, non-safe actions cannot run: "no durable record, no action".

func NewMemoryManager

func NewMemoryManager(store memory.Store, recaller memory.Recaller) ac.ContextManager

NewMemoryManager builds the agentcore.ContextManager that injects recalled memory each turn. Use it when driving a raw agentcore.NewAgent directly.

func Once

func Once(supportsTools bool, fn GenerateFunc) ac.ChatModel

Once adapts a one-shot function into an agentcore.ChatModel, so a local model stays a one-liner. supportsTools advertises tool capability to the loop.

func Stream

func Stream(ctx context.Context, agent *ac.Agent, input string) (<-chan ac.Event, func() *ac.RunSummary)

Stream drives one prompt and returns the event channel plus a Wait for the final summary. Cancelling ctx aborts the run (the kill switch).

Types

type Approver

type Approver = gate.Approver

Approver is the human decision for a non-safe call (the daemon's confirm plugs in here).

type GenerateFunc

type GenerateFunc = core.GenerateFunc

GenerateFunc is a one-shot generation function (see Once).

type Option

type Option func(*core.Config, *newState)

Option configures the agent jess.New builds.

func AllowAll

func AllowAll() Option

AllowAll is the explicit, greppable opt-out from the fail-closed default.

func WithAgentID

func WithAgentID(id string) Option

WithAgentID scopes memory and tags audit. Empty is the global scope.

func WithAgentcoreOptions

func WithAgentcoreOptions(o ...ac.AgentOption) Option

WithAgentcoreOptions passes raw agentcore options through (the long tail: stop guard, extra middlewares, concurrency).

WARNING: passing ac.WithMiddlewares here does NOT install custom tool middleware alongside jess's audit enforcement — it will be overridden. jess appends the audit-enforcement middleware last (after all Extra options) so that "no durable record, no action" cannot be bypassed. Custom tool middleware is not supported through this escape hatch.

func WithApprover

func WithApprover(a gate.Approver) Option

WithApprover installs the human approver for dangerous calls. Without it the gate is fail-closed (non-safe tools are denied).

func WithLedger

func WithLedger(sink ledger.Sink) Option

WithLedger redirects the audit sink. Pass ledger.DiscardSink{} to turn audit off explicitly; it is never off silently.

func WithMaxTurns

func WithMaxTurns(n int) Option

WithMaxTurns caps the agent loop turns. 0 leaves the agentcore default.

func WithMemory

func WithMemory(store memory.Store, recaller memory.Recaller) Option

WithMemory wires durable memory (recall injected each turn). Both required.

func WithModel

func WithModel(m ac.ChatModel) Option

WithModel sets the LLM (agentcore.ChatModel, or jess.Once for a local one).

func WithSkills

func WithSkills(set *skill.Set) Option

WithSkills attaches a skill set (system blocks + tools).

func WithSubagents

func WithSubagents(specs ...subagent.Spec) Option

WithSubagents registers subagents. Empty Spec fields inherit from the parent (model, gate, audit, agentID). jess.New builds and owns the pool and wires the delegating "subagent" tool into the agent's tool set.

func WithSystemPrompt

func WithSystemPrompt(s string) Option

WithSystemPrompt sets the base system prompt.

func WithToolGate

func WithToolGate(g ac.ToolGate) Option

WithToolGate installs a fully custom gate, bypassing the default policy.

func WithTools

func WithTools(t ...ac.Tool) Option

WithTools registers tools the model may call.

type Request

type Request = gate.Request

Request is what an Approver sees for one non-safe call.

type SafeTool

type SafeTool = gate.SafeTool

SafeTool is the marker a tool implements to be auto-approved by the gate.

Directories

Path Synopsis
examples
gated command
Command gated shows jess's fail-closed tool gate plus the provenance ledger.
Command gated shows jess's fail-closed tool gate plus the provenance ledger.
quickstart command
Command quickstart shows jess end to end with no network access: an in-memory store seeds a core memory, a local echo model reveals what the agent received (including the injected memory), and the run is driven through jess.New -> jess.Stream with its live event channel.
Command quickstart shows jess end to end with no network access: an in-memory store seeds a core memory, a local echo model reveals what the agent received (including the injected memory), and the run is driven through jess.New -> jess.Stream with its live event channel.
Package gate is jess's preventive control: a fail-closed tool gate.
Package gate is jess's preventive control: a fail-closed tool gate.
internal
core
Package core is jess's agentcore-touching implementation: the option-assembler (Config + Agent) that wires a model, system blocks, tools, the memory ContextManager, the tool gate, and the audit middleware into a ready *agentcore.Agent.
Package core is jess's agentcore-touching implementation: the option-assembler (Config + Agent) that wires a model, system blocks, tools, the memory ContextManager, the tool gate, and the audit middleware into a ready *agentcore.Agent.
Package ledger is the durable, agentcore-free record of everything an agent does.
Package ledger is the durable, agentcore-free record of everything an agent does.
Package memory is jess's durable agent memory: the facts an agent keeps across turns and sessions, and the machinery to recall the right ones for the current prompt.
Package memory is jess's durable agent memory: the facts an agent keeps across turns and sessions, and the machinery to recall the right ones for the current prompt.
embed/gomlx
Package gomlx provides an in-process sentence embedder for jess memory, backed by the pure-Go gomlx/compute/gobackend ML runtime.
Package gomlx provides an in-process sentence embedder for jess memory, backed by the pure-Go gomlx/compute/gobackend ML runtime.
Package skill provides registerable capability bundles for jess agents, wired via jess.WithSkills and vendor-free (no agentcore types in its API).
Package skill provides registerable capability bundles for jess agents, wired via jess.WithSkills and vendor-free (no agentcore types in its API).
Package subagent runs jess subagents with bounded concurrency.
Package subagent runs jess subagents with bounded concurrency.

Jump to

Keyboard shortcuts

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