Skip to content

Internal Agent

Crucible’s internal agent is a built-in AI assistant that runs locally, logs sessions as markdown, and uses your kiln as its memory. Unlike external agents via ACP, the internal agent has direct access to your files and can persist state across sessions.

Overview

The internal agent provides:

  • Session logging - Conversations saved as markdown files
  • Task tracking - ACP-style task lists as working memory
  • Explicit search - Use /search to inject context when needed
  • Precognition - automatic kiln retrieval before each turn (tunable via the precognition_select/precognition_format hooks)
  • Lua hooks - the fourteen cru.on() events plus session lifecycle hooks — see Event Hooks
  • Compaction - /compact summarizes and continues in a new file

Memory Architecture

The agent operates with two tiers of memory, all stored as plaintext:

┌─────────────────────────────────────────────────┐
│ Memory Tiers │
├─────────────────────────────────────────────────┤
│ │
│ Session Memory Current conversation + tasks │
│ Logged to markdown file │
│ │
│ Kiln (via /search) Your notes + embeddings │
│ Explicit search injection │
│ │
└─────────────────────────────────────────────────┘

Session Memory

The current conversation, task list, and tool calls. Logged to a markdown file in your personal kiln’s session folder. Session files have index: false frontmatter so they don’t bloat your embedding database.

Use /search query to find relevant notes and inject them into the conversation. This explicit approach keeps you in control of what context the agent sees.

Session Files

Sessions are markdown notes with special frontmatter. They’re stored in your personal kiln and can be searched, linked, and analyzed like any other note.

Location

Sessions are stored by workspace (directory/repo name):

~/Documents/your-kiln/
└── sessions/
└── <WORKSPACE_DIR>/
├── 2024-12-24_1930.md # Session log
├── 2024-12-25_0900.md # Another session
└── ...

Where WORKSPACE_DIR is the name of the git repo or working directory you started the chat from.

Session Format

---
type: session
workspace: crucible
started: 2024-12-24T19:30:00Z
ended: 2024-12-24T21:00:00Z
---
# Session
## Tasks
- [x] Read existing agent crates
- [~] Design memory architecture
- [ ] Implement session logging
## Log
### User 19:30
Research internal agent abstractions...
### Agent 19:30:15
I'll start by exploring the existing code...
### Tool: semantic_search 19:30:20
```json
{"query": "agent handle trait", "limit": 5}

Result: Found 5 relevant notes…


Session ended

### Frontmatter Fields
| Field | Description |
|-------|-------------|
| `type: session` | Marks this as a session log |
| `workspace` | Workspace directory name |
| `started` | Session start timestamp |
| `ended` | Session end timestamp (added on close) |
### Embedding Exclusion
The `sessions` folder is configured as an embedding exclusion directory. This means:
- **Metadata is indexed** - searchable by properties like `type:session`
- **No embeddings generated** - saves vector DB space
```bash
# Search sessions by property
cru search --properties "type:session workspace:crucible"
# Text search within sessions folder
cru search "error handling" --folder sessions/crucible

Compaction

When sessions get long, use /compact to summarize and continue in a new file:

  1. Agent generates numbered summary of key points
  2. Summary appended to current file with link to continuation
  3. New file created with summary as context
  4. Logging continues in new file

File Structure

Sessions are folders so agents can write files to session namespace:

~/Documents/your-kiln/sessions/crucible/
├── 2024-12-24_1930/ # Session folder
│ ├── log.md # Conversation log
│ └── ... # Any files agent writes
└── 2024-12-24_1930_01/ # After compaction
└── log.md
~/.crucible/sessions/ # Hidden state (machine-readable)
├── index.json # Session discovery
└── state/crucible/
├── 2024-12-24_1930.json # Full conversation state
└── 2024-12-24_1930_01.json

Why this structure:

  • Session folders let agents write scratch files
  • Kiln stays clean markdown
  • JSON state hidden, enables full resume
  • Index enables fast session discovery

Example

End of 2024-12-24_1930/log.md:

---
**Continued in:** [[2024-12-24_1930_01/log]]

New 2024-12-24_1930_01/log.md:

---
type: session
workspace: crucible
started: 2024-12-24T19:30:00Z
continued_from: 2024-12-24_1930
---
# Session (continued)
## Summary
1. Researched internal agent patterns
2. Designed session logging with markdown files
3. Decided on embedding exclusion approach
## Log
### User 20:15
...

Task List

The agent uses an ACP-style task list as working memory. Tasks track progress within a session:

## Tasks
- [x] Completed task
- [~] In progress task
- [ ] Pending task

Task States

MarkerStatusDescription
[ ]pendingNot started
[~]in_progressCurrently working on
[x]completedFinished

Precognition (Auto Context)

Precognition ships: the daemon searches your kiln before each LLM call and injects relevant context automatically. /search remains available for explicit injection, and the precognition_select / precognition_format hooks let Lua decide which notes survive and how they render — see Event Hooks.

Lua Integration

Lua hooks and plugin storage ship.

Plugin Storage

cru.storage is a key-value property store, automatically namespaced per plugin (plugin:<name>) — there is no require("cru.storage") module or namespace() call:

-- Set a property on an entity
cru.storage.set("entity-id", "key", "value")
-- Get a property (nil if missing)
local val = cru.storage.get("entity-id", "key")
-- List all properties for an entity ({key = value, ...})
local props = cru.storage.list("entity-id")
-- Find entity ids with a matching property
local ids = cru.storage.find("status", "active")
-- Delete a property (true if deleted)
local ok = cru.storage.delete("entity-id", "key")

All calls are async and scoped to the calling plugin’s namespace; before a kiln’s storage is open they are stubs that return nil/empty.

Hook Points

There is no agent:* event namespace — these are the events that actually fire. See Event Hooks for each one’s payload and return contract.

EventDescription
cru.on_session_start(fn)Session beginning (can refuse the session)
cru.on_session_end(fn)Session closing
pre_llm_callBefore the LLM call — transform the prompt
transform_contextBefore the LLM call — rewrite the message list
turn:completeAfter the assistant response, can inject a follow-up
pre_tool_callTool about to execute (observe/transform/cancel/handle)
tool_resultTool finished, patch what the model receives

Example: Custom Context Injection

-- Prepend recently modified notes to the prompt. Registered at load time;
-- `pre_llm_call` hands the handler `{ prompt, model }` and a returned
-- `{ prompt = ... }` replaces it. Returning `{ cancel = true }` cancels the
-- turn outright.
cru.on("pre_llm_call", { priority = 100 }, function(ctx, event)
local recent = cru.kiln.search({
modified_after = os.time() - 86400 -- 24 hours
})
if #recent == 0 then
return
end
return {
prompt = "## Recent Activity\n" .. table.concat(recent, "\n")
.. "\n\n" .. event.prompt,
}
end)

Using the Internal Agent

From CLI

Terminal window
# Start a new session (logs to sessions/<workspace>/<timestamp>.md)
cru chat
# Resume most recent open session for this workspace
cru chat --resume

Session Commands

During a session:

CommandAction
/tasksShow current task list
/searchSearch kiln and inject context
/compactSummarize and continue in new file

Configuration

The internal agent is configured through the ordinary [chat] and [llm] sections of ~/.config/crucible/config.toml, and stores its sessions in the default kiln:

default_kiln = "notes"
[kilns]
notes = "~/Documents/crucible-testing"
[chat]
agent_preference = "crucible"

See Configuration for every field.

Best Practices

Workspace Organization

Sessions are automatically organized by the workspace directory you run cru chat from:

sessions/
├── crucible/ # Work in ~/code/crucible
├── my-app/ # Work in ~/code/my-app
└── dotfiles/ # Work in ~/dotfiles

Start chat from the relevant project directory to keep sessions organized.

When to Use Internal vs External Agents

Use Internal AgentUse External Agent (ACP)
Local-first operationCloud AI services
Session persistenceStateless queries
Kiln integrationExternal tool access

See Also