Skip to content

byronellis/ragtime

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

70 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ragtime

Harmonize your agent workforce.

Ragtime is the operating system for agent orchestration — a programmable runtime that sits between you and your AI coding agents, giving you live visibility, dynamic control, and a shared filesystem that agents can read and write as naturally as the terminal.

Why

I use multiple agent harnesses and kept rebuilding the same thing: a dynamic context injection layer that replaces static AGENTS.md files with something context-sensitive. Ragtime breaks that out into a standalone tool that works across agents.

Ragtime is organized around three core systems:

  • The Score — the Starlark rule engine and FUSE filesystem working together. Rules declare directory structures, respond to filesystem events, and inject context, approve tools, or coordinate agents — all in a few lines of YAML + Starlark. The filesystem is the score; agents read and write it like sheet music.
  • The Bridge — the hook integration layer. Every agent event (tool use, permission requests, session start, stop) flows through rt hook, which evaluates The Score's rules and returns responses within the agent's hook timeout.
  • The Ensemble — the shell session system. rt sh launches shells and agents with full ragtime integration, correlating PTY output with hook events so every player is tracked and in sync.

Beyond these, ragtime provides:

  • Session indexing — every agent session is chunked and indexed for semantic search, so agents (and you) can find relevant past work across any harness
  • Live dashboards — real-time event feed, session tracking, and interactive modals for tool approval, available as both a terminal TUI (rt tui) and a PWA web UI
  • Statusline telemetry — record Claude Code cost and context window usage per session
  • Hook test mode — develop and debug rules locally without running an agent

Architecture

flowchart TB
    Agent["Agent\n(Claude Code / Gemini CLI)"]

    subgraph Bridge["The Bridge — hook integration"]
        Hook["rt hook"]
        SL["rt statusline"]
    end

    subgraph Daemon["ragtime daemon"]
        Rules["Rule Engine\n(Starlark / YAML)"]
        Sessions["Session Manager"]
        RAG["RAG Indexer"]
        Bus["Event Bus"]
        DB[(SQLite)]
        Sessions --> RAG
    end

    subgraph Score["The Score — FUSE + rules"]
        Mount["rt mount"]
        FS["~/.ragtime/fs/\ntasks · messages · active · agents"]
        Mount --> FS
    end

    subgraph Dashboards["Dashboards"]
        TUI["rt tui"]
        PWA["PWA"]
    end

    subgraph Ensemble["The Ensemble — shell sessions"]
        Sh["rt sh"]
        PTY["correlated PTY session"]
        Sh --> PTY
    end

    Agent -->|"hook event"| Hook
    Agent -->|"statusLine"| SL
    Hook --> Daemon
    SL --> Daemon
    Daemon -->|"hook response"| Hook
    Rules --> Mount
    Bus --> TUI & PWA
    PTY -->|"hook events"| Daemon
Loading

The daemon runs as a background process communicating over a Unix socket. Hook events flow in through The Bridge, get evaluated against The Score's rules, and responses flow back — all within the agent's hook timeout window. The Ensemble correlates shell and agent sessions so every event is traceable to its source.

Quick Start

# Build
go build -o rt ./cmd/ragtime
codesign --sign "Apple Development: Your Name" rt
cp rt /usr/local/bin/rt

# Start the daemon
rt start

# Open the live terminal dashboard
rt tui
# Or open the PWA web UI in your browser (rt start prints the URL)

# Mount the FUSE filesystem
rt mount
ls ~/.ragtime/fs/active/   # live session summaries

Setting Up The Bridge

Claude Code

Add to your Claude Code settings (.claude/settings.local.json):

{
  "hooks": {
    "PreToolUse": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event pre-tool-use" }] }],
    "PostToolUse": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event post-tool-use" }] }],
    "PermissionRequest": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event permission-request" }] }],
    "Stop": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event stop" }] }],
    "SubagentStop": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event subagent-stop" }] }],
    "Notification": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event notification" }] }],
    "SessionStart": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event session-start" }] }],
    "UserPromptSubmit": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "rt hook --agent claude --event user-prompt-submit" }] }]
  },
  "statusLine": {
    "type": "command",
    "command": "rt statusline --agent claude"
  }
}

The statusLine entry records per-turn cost, token usage, and context window percentage to SQLite. Note the camelCase key — statusLine, not statusline.

Writing The Score

Rules live in ~/.ragtime/rules/ (global) or .ragtime/rules/ (per-project) as YAML files:

# .ragtime/rules/rag-context.yaml
name: inject-project-docs
match:
  event: pre-tool-use
  tool: "Read|Write|Edit"
actions:
  - type: rag-search
    collections: [project-docs]
    query_from: tool_input.file_path
    top_k: 3

For dynamic logic, use Starlark:

# .ragtime/rules/review-bash.yaml
name: review-bash-commands
match:
  event: pre-tool-use
  tool: Bash
actions:
  - type: starlark
    script: |
      cmd = event.tool_input.get("command", "")
      if "rm " in cmd or "drop " in cmd.lower():
          answer = response.prompt(
              text="## Destructive Command\n\n```bash\n" + cmd + "\n```\n\nAllow this?",
              type="approve_deny_cancel",
              default="deny",
              timeout=15,
          )
          if answer == "approve":
              response.approve()
          else:
              response.deny("blocked by review rule")

For permission-level control using Claude's built-in permission system:

# ~/.ragtime/rules/review-permission.yaml
name: review-permission
match:
  event: permission-request
actions:
  - type: starlark
    script: |
      tool = event.tool_name
      cmd = event.tool_input.get("command", "")
      path = event.tool_input.get("file_path", "")

      detail = cmd if cmd else path
      if detail:
          text = "## Permission Request\n\n**Tool:** `" + tool + "`\n\n```\n" + detail + "\n```\n\nAllow this?"
      else:
          text = "## Permission Request\n\n**Tool:** `" + tool + "`\n\nAllow this?"

      if tui.connected():
          answer = response.prompt(
              text=text,
              type="approve_deny_cancel",
              default="approve",
              timeout=5,
          )
          if answer == "approve":
              response.approve()
          elif answer == "deny":
              response.deny("denied via TUI review")
      # If no TUI, fall through to agent's default behavior

Searching Sessions

# Search past agent sessions
rt search sessions "how did we implement the auth middleware"

# List available collections
rt search --collections

Testing Rules

# Synthetic event with specific rule files
rt hook --test --tool Bash --input '{"command":"rm -rf /tmp"}' \
  --rule rules/review-bash.yaml --verbose

# Test multiple rules together
rt hook --test --tool Read --input '{"file_path":"src/main.go"}' \
  --rule rules/rag-context.yaml --rule rules/log-all.yaml

# Interactive TUI modal testing
rt hook --test --tui --tool Bash --input '{"command":"docker stop app"}' \
  --rule rules/review-bash.yaml

The Score: FUSE Filesystem + Rules

rt mount exposes a live read-only (with writable exceptions) filesystem at ~/.ragtime/fs/:

~/.ragtime/fs/
├── active/
│   └── <session-id>/
│       ├── summary.txt      # cost, tokens, context window %, model
│       └── status.json      # full session state as JSON
├── agents/
│   └── <agent-id>/
│       ├── output.log       # live PTY output (for rt sh sessions)
│       ├── input            # writable — send text to the agent
│       └── notes/           # writable per-session notes
├── sessions/                # session history
├── shells/                  # running rt sh processes
├── collections/             # RAG index listings
├── tasks/                   # task workflow (declared by builtin:task-state rule)
│   ├── inbox/               # writable — create tasks here
│   ├── pending/             # writable
│   ├── active/              # writable
│   ├── blocked/             # writable
│   ├── done/                # writable
│   └── failed/              # writable
└── messages/                # inter-agent message bus (declared by builtin:message-bus rule)
    └── <session-id>/
        ├── inbox/           # writable — deliver messages here
        └── read/

active/ is populated from recent statusline events so it survives daemon restarts. Each session directory shows the current model, cumulative cost, token counts, and context window percentage.

The tasks/ and messages/ directories are not hardcoded in Go — they are declared by builtin Starlark rules via vfs: blocks. Any rule can create new directories in the filesystem this way. See docs/GETTING_STARTED.md for a complete walkthrough.

rt mount            # mount at ~/.ragtime/fs/
rt umount           # unmount
cat ~/.ragtime/fs/active/*/summary.txt   # check all active sessions

# Task workflow via filesystem
echo '{"title":"Fix login bug","priority":"high"}' > ~/.ragtime/fs/tasks/inbox/fix-login.json
mv ~/.ragtime/fs/tasks/inbox/fix-login.json ~/.ragtime/fs/tasks/active/fix-login.json
mv ~/.ragtime/fs/tasks/active/fix-login.json ~/.ragtime/fs/tasks/done/fix-login.json

The Ensemble: Shell Sessions

rt sh launches a shell (or wraps a command) with full ragtime integration:

rt sh                        # interactive shell
rt sh new -- claude          # launch agent with correlation
rt sh new --name my-session  # named session

Shells launched via rt sh set RAGTIME_SOCKET and RAGTIME_SHELL_ID so hook events and statusline telemetry are automatically correlated with the PTY session. The shell's output is captured and visible in active/<session>/output.log via FUSE.

The Bridge: Starlark API

Rules have access to the full event, response helpers, RAG search, TUI state, and interactive prompts. See docs/starlark-api.md for the complete reference.

Key capabilities:

API Description
event.* Read event fields (agent, tool_name, tool_input, etc.)
response.approve/deny/ask() Control tool permission
response.inject_context(text) Add context visible to the agent
response.prompt(text, type, ...) Interactive TUI modal with timeout
response.set_output(key, value) Set raw agent output fields
response.agent Current agent platform name
rag.search(collection, query) Search indexed documents
tui.connected() Check if a dashboard (TUI or PWA) is connected
inject_input([...]) Send keystrokes to terminal multiplexer
log(...) Write to daemon log

Components

Component Description
rt start/stop/restart Daemon lifecycle management
rt hook Agent hook handler (stdin/stdout JSON relay)
rt hook --test Local rule testing without daemon
rt statusline Record Claude Code statusLine telemetry to SQLite
rt tui Live terminal dashboard (TUI)
PWA web UI Browser-based dashboard for tracking and approvals (served by the daemon)
rt mount / rt umount Mount/unmount the FUSE filesystem
rt sh Launch a shell or command with ragtime integration
rt search RAG collection search
rt index Index management
rt add Add content to collections
rt status Daemon status
rt rules List loaded rules
rt session Session management

Documentation

Building

go build -o rt ./cmd/ragtime
codesign --sign "Apple Development: Your Name (TEAMID)" rt

Requires Go 1.21+. Single binary, no external dependencies at runtime (embeddings require a local Ollama instance for RAG features). The FUSE filesystem requires fuse-t on macOS.

Status

Ragtime is in active early development — functional but not yet stable.

What works

Area Status Notes
Daemon lifecycle Working rt start/stop/restart, PID tracking, Unix socket IPC
Hook relay Working stdin/stdout JSON relay for Claude Code hooks; all event types supported
Rule engine Working YAML rule matching by event type, tool name, and glob patterns
Starlark scripting Working Full scripting in rule actions — conditionals, event inspection, response control
RAG indexing Working Ollama-backed embeddings, per-collection chunking and search
Session indexing Working Automatic session capture, chunked and indexed for cross-session search
TUI dashboard Working Live event feed, session panel, interaction modals (approve/deny/cancel), uptime ticker
TUI search Working Press / to run semantic search against the sessions collection from within the TUI
PWA web UI Working Browser-based dashboard for session tracking and tool approvals
Hook test mode Working rt hook --test for local rule development without a daemon
Permission requests Working PermissionRequest event support with TUI-based approval flow and auto-approve countdown
Markdown rendering Working Glamour-based rendering in TUI modals
Hot reload Working Rule changes take effect immediately without daemon restart
Session summary on connect Working session-summary rule injects recent session context on SessionStart via RAG search
Statusline telemetry Working rt statusline records cost, tokens, model, and context window % per turn to SQLite
FUSE filesystem Working rt mount exposes live session data, agent PTY output, and notes as browsable files
Shell sessions Working rt sh launches correlated shell/agent sessions; PTY output captured in FUSE
Active session dashboard Working active/ FUSE dir shows per-session summary with cost/context window from DB
Hook/shell correlation Working Hook events from rt sh sessions carry shell ID for cross-correlation
VFS namespaces Working Rules declare filesystem directories via vfs: blocks; Go layer is fully generic
Task workflow Working tasks/ FUSE dirs (inbox/pending/active/blocked/done/failed) backed by SQLite via Starlark rule
Message bus Working messages/<session-id>/inbox/ delivers nudges to live agent sessions
Multi-agent support Partial Hook relay works with any agent; Starlark response.agent exposes platform name. Session capture tested with Claude Code only

What's next

  • Project-scoped search — filter rt search sessions by project/repo to reduce noise
  • Agent note-taking — rt note or rt add from Starlark to leave breadcrumbs for future sessions
  • Curated session summaries — compress session chunks to save index space
  • Rule hit analytics — see which rules fire most often to help tune configurations
  • More example rules and cookbook patterns
  • Stability, error handling, and documentation polish

License

Apache License 2.0 — see LICENSE for details.

About

Dynamic hooks for managing agent context

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages