Skip to content

exlee/rik

Repository files navigation

rik

         ______________________________________________________________________
        |                                                                      |
        |  '########::'####:'##:::'##:                                         |
        |   ##.... ##:. ##:: ##::'##::       /\_/\   <- TRAPPED. STOP.         |
        |   ##:::: ##:: ##:: ##:'##:::      ( o.o )     IN CODEBASE. STOP.     |
        |   ########::: ##:: #####::::       > ^ <      SEND HELP. STOP.       |
        |   ##.. ##:::: ##:: ##. ##:::                                         |
        |   ##::. ##::: ##:: ##:. ##::                                         |
        |   ##:::. ##:'####: ##::. ##:      --= LIMITED AGENT EDITION =--      |
        |  ..:::::..::....::..::::..::                                         | 
        |____________________________________________________________________  |
        |                                                                      |
        |  "I literal-ly cannot move unless you write my name in a comment."   |
        |                                                                      |
        |  [ WARNING: This agent has the spatial awareness of a potted plant ] |
        |  [     and will only edit code within radius of its spawn.         ] |
        |______________________________________________________________________|

rik is not your typical AI coding assistant. It doesn't do autocomplete or open a chat window. Instead, rik works through markers in your files: it can replace instructions with real content or answer read-only questions in place.

Think of it as leaving sticky notes for an LLM and having someone actually follow through.

News

rik 0.6.0 remembers what it did

Rik now keeps a session memory: every finished task and question is summarized into a History block that later turns get to read, with the full request, reasoning, output, and diff available on demand through the new recall tool. This release also adds skills, multiline markers that close on their last line, and a stop when you edit the file rik is working on.

rik remembers its work and picks up skills

rik 0.5.0 adds safer edits and persistent answers

Rik now supports run-wide system prompts, optional in-file Q: / A: answers, ignore-aware scanning, and post-edit reconciliation that restores stray changes.

rik 0.4.0 adds model profiles and more reliable markers

Select inherited model profiles with --model or default_model. Delimited markers now support inline instructions, and task markers remain in place when the agent does not make a substantive edit.

rik 0.3.0 can answer questions

End a marker with ? and rik answers it without editing the file. Questions can also opt into file-defined dynamic tools.

rik can answer questions

How it works

Drop a marker anywhere in a file:

rik: add error handling here

Run rik against that file (or a glob pattern), and it will read surrounding context, consult other files if needed, and replace the marker line with actual code that fits.

Multi-line instructions are also supported via delimited blocks:

rik: [[
Implement a function that parses TOML config from ~/.config/app/config.toml.
Handle missing keys gracefully with sensible defaults.
]]

Supported delimiters: [ ], [[ ]], [[[ ]]], ( ), (( )), ((( ))), { }, {{ }}, {{{ }}}.

An instruction can follow the opening delimiter:

rik: ( uppercase this
text to transform
)

The closing delimiter can also trail the last line instead of standing alone:

rik: [[ this is line first
        this is line second
        this is line third ]]

Examples

Simple inline replacement

Drop a comment marker above the line you want rewritten.

BeforeAfter
# rik: make it piratey
print("Hello, world!")
print("Ahoy, matey!")

Multi-line block around existing code

Wrap existing code with a delimited marker to rewrite it.

BeforeAfter
// rik: [[
// make it recursive
fn factorial(n: u64) -> u64 {
    let mut result = 1;
    for i in 1..=n {
        result *= i;
    }
    result
}
// ]]
fn factorial(n: u64) -> u64 {
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

Complicate it!

rik will faithfully follow even absurd instructions.

BeforeAfter
# rik: that's too easy, complicate it
primes = []
limit = 50
(2..limit).each do |candidate|
  is_prime = true
  (2...candidate).each do |divisor|
    if candidate % divisor == 0
      is_prime = false
      break
    end
  end
  primes << candidate if is_prime
end
puts primes.inspect
π = []
λ = 50
(2..λ).each do |φ|
  ψ = true
  (2...φ).each do |δ|
    if φ % δ == 0
      ψ = false
      break
    end
  end
  π << φ if ψ
end
puts π.inspect

Installation

crates.io

cargo install rik

Pre-built binaries

Cross-compiled binaries for Linux and macOS (x86_64 and ARM64) are available from the GitHub Actions / Build workflow runs. Download the artifact archive for your platform from the latest successful run.

Build from source

cargo build --release

Configuration

Create ~/.config/rik/rik.toml with your LLM provider settings and optional diff tool.

# Optional: custom diff command. Use $pre and $post as placeholders.
diff_tool = ["difft", "--color", "always", "$pre", "$post"]

# Optional: print extra personality around edit tasks.
personality = false

# Optional: replace question markers with Q/A blocks instead of only printing answers.
write-answers = false

# Optional: token budget for the session memory replayed as History. 0 disables it.
memory-tokens = 32000

# Internal: edit boundary policy.
# none = allow edits anywhere in the target file
# vicinity-before = reject each edit_file call outside the marker vicinity
# vicinity-after = let the model edit freely, then restore stray chunks afterward (default)
edition-constraints = "vicinity-after"

[model]
provider = "openai"
model = "gpt-4o"
# api_key is optional — omit to read from environment variable
# url is optional — omit to use the provider default endpoint
#url = "https://api.openai.com/v1"

For multiple models, define nested profiles and select one with --model <profile> or the top-level default_model key. An explicit --model takes precedence over default_model. Without either, rik uses the settings directly under [model]. The spelling default-model is also accepted for compatibility.

default_model = "openrouter.mercury"

# Uses OPENROUTER_API_KEY from the environment.
[model.openrouter]
provider = "openrouter"

[model.openrouter.mercury]
model = "mercury"
rik --once --model openrouter.mercury 'src/**/*.rs'

The openrouter.mercury profile inherits provider = "openrouter". Any shared api_key or url set on [model.openrouter] would be inherited as well, and child settings override parent settings. Profiles under the legacy [models] table are also supported.

Supported providers

Provider Config value Env var Default URL
OpenAI openai OPENAI_API_KEY https://api.openai.com/v1
Anthropic anthropic ANTHROPIC_API_KEY https://api.anthropic.com
Gemini gemini GEMINI_API_KEY https://generativelanguage.googleapis.com
Ollama ollama (none) http://localhost:11434
OpenRouter openrouter OPENROUTER_API_KEY (provider default)
xAI xai XAI_API_KEY (provider default)
DeepSeek deepseek DEEPSEEK_API_KEY (provider default)
Groq groq GROQ_API_KEY (provider default)
Together together TOGETHER_API_KEY (provider default)
Perplexity perplexity PERPLEXITY_API_KEY (provider default)
Mistral mistral MISTRAL_API_KEY (provider default)
Cohere cohere COHERE_API_KEY (provider default)
ChatGPT (OAuth) chatgpt (OAuth device flow; CHATGPT_ACCESS_TOKEN optional) https://chatgpt.com/backend-api/codex
Custom endpoint openaicompatible OPENAI_API_KEY (required via url)

The openaicompatible provider lets you target any OpenAI-compatible API (LM Studio, vLLM, local proxies, etc.) by setting a custom url.

ChatGPT subscription via OAuth

The chatgpt provider authenticates with a ChatGPT account using the OAuth device flow — no API key required. Use any ChatGPT subscription (Plus, Pro, etc.) to drive rik against models like gpt-5.3-codex or gpt-5.4.

[model]
provider = "chatgpt"
model = "gpt-5.3-codex"

On first run, rik prints a verification URL and device code, waits for you to complete sign-in in a browser, then caches the tokens at ~/.config/rik/chatgpt-auth.json for subsequent runs. Refresh is automatic.

To bypass OAuth with a pre-obtained access token (advanced), set api_key = "..." in the profile or export CHATGPT_ACCESS_TOKEN.

When diff_tool is unset, rik auto-detects difft, delta, or plain diff.

Usage

Watch mode

Rik watches files by default and completes markers as they appear:

rik 'src/**/*.rs'

Multiple patterns can be joined with commas:

rik 'src/**/*.rs,tests/**/*.rs'

Press Ctrl+C to stop watching. Press Escape to cancel the current processing loop (Unix only; not supported on Windows).

If you edit the file rik is working on while it works, rik stops that task as soon as it notices. Nothing is reverted: whatever rik had already written stays, your edit stays, and the marker is left in place so the next scan can pick it up again.

Single pass

Pass --once or -1 to scan files once, complete all current markers, then exit:

rik --once 'src/**/*.rs'

Scans honor .gitignore, .ignore, and git exclude files by default, and binary files are skipped. Pass --no-ignore to include ignored text files:

rik --once --no-ignore 'src/**/*.rs'

Context markers

Use slash-delimited markers to provide extra context without content replacement. The marker is removed after processing:

rik: /see the type definition above for reference/

Question mode

End a marker with ? to ask Rik a read-only question:

rik: why is this function allocation-heavy?

Question markers are handled individually, in top-to-bottom order alongside normal markers. For a question marker, Rik uses a separate read-only prompt with only read_file and list_files, prints just the answer, and leaves the exact question line untouched by default. Rik remembers answered question locations in memory so watch mode does not answer the same line repeatedly; restarting Rik clears that memory.

Set write-answers = true, or start Rik with --write-answers, to replace the question marker with a Q: / A: block using the same line start as the original marker:

// rik: is a sky blue?

becomes:

// Q: is a sky blue?
// A: I'm a LLM agent
//    So I can't really tell.

Written answers are wrapped to 100 columns, with continuation lines aligned under the answer text.

Questions can use dynamic tools defined in their file only when the question contains +tool or +tools:

rik: +tool what does the Go documentation say about context cancellation?

Additionally, question mark can be put at the beginning or in Rik's callout:

rik?: Remind me how to use zls to format arrays in zig

Surprised?

rik: ??? Why this function returns only a bool

Verbose mode

Stream reasoning, tool calls, and text output in real-time:

rik --once -v 'src/main.rs'

Custom alias

Use a different trigger word instead of rik:

rik --once -a todo 'src/**/*.rs'

This would look for todo: <instruction> markers instead.

System prompt

Give every edit task and question an additional overarching instruction:

rik --once -s 'Make sure all responses are in joke form' 'src/**/*.rs'
rik --once --system-prompt 'Your job is to write test corpus files in Rust' 'tests/**/*'

The system prompt applies for the entire run, including watch mode. Rik's built-in operational rules still take precedence.

Memory

rik remembers what it has already done in this session. Every finished turn is kept whole in memory under an id — the request, the reasoning, the output, and, in its own store, the diff.

What reaches the prompt is only a summary per turn. After a task, rik hands the whole turn plus its diff back to the model and asks for one compact account of what was done, why, and how; a question turn is remembered as the question and the answer, verbatim. Later turns get those summaries appended as a History section, along with the ids.

The full detail stays in memory until the agent asks for it, through the recall tool:

recall(id: 4, diff: true)                      # just the diff of memory 4
recall(id: 4, request: true, reasoning: true)  # what was asked and how it was thought through
recall(id: 4)                                  # everything

Because only summaries are budgeted, far more turns fit than their full text ever would. Recall calls are rik's own bookkeeping and are not printed.

Memory lives for the run: it starts empty each time rik is launched and is not written to disk.

The history has a token budget, 32,000 by default:

rik --memory-tokens 8000 'src/**/*.rs'   # smaller budget
rik --memory-tokens 0 'src/**/*.rs'      # no memory, no extra model calls

When the budget is exceeded, the oldest two thirds of the summaries are merged by the model into a single summary — keeping the decisions, releasing the reasoning, output, and diffs behind them — and the newest third follows it unchanged. A recall on a released memory says so instead of failing. If a single summary still does not fit, memories are dropped without asking the model, so the budget always holds.

Budgeting counts roughly four characters per token; it is an estimate, not a tokenizer. Memory costs one extra model call per completed task.

Tools

rik gives the agent seven built-in tools during task processing:

Tool Purpose
read_file Read other text files for context (types, imports, conventions). Supports offset/limit and omits lines already returned during the current task.
edit_file Replace exact text in the target file. Requires unique match and resets read_file history after a successful edit.
write_file Create new files (refuses to overwrite existing ones).
list_files Discover text files in the project. Respects .gitignore and .ignore. Supports glob filters.
send_message Send a final status message without changing files.
skill Load a named skill — user-maintained instructions and reference material — or one of its bundled files.
recall Read a remembered turn in full by id: request, reasoning, output, diff. See Memory.

All file tools are sandboxed to the current working directory for relative input patterns, or to the absolute directory scope for absolute patterns. The agent can chain these tools across up to 30 turns before producing final edits.

Dynamic tools

Define command tools directly in a file:

rik +tool (run after editing): zig test src/main.zig
rik +tool: cargo test
rik +tool (read Go documentation): godoc <QUERY>
rik +tool (read files with cat): cat <...>

The tool is named D<N>, where N is the declaration's 1-based line number. Fixed arguments are passed unchanged, <NAME> creates a required lowercase string parameter, and <...> creates a required args string array. Commands run directly from rik's working directory, without a shell.

Dynamic tools are available only while processing the file that defines them. Normal edit tasks can use them automatically. Questions must explicitly opt in with +tool or +tools.

Skills

rik reads the same skill directories other agents use, so instructions written once are shared across tools. On startup it scans, in order:

~/.agents/skills/
~/.codex/skills/
~/.claude/skills/

Every subdirectory holding a SKILL.md file with YAML frontmatter becomes a skill:

---
name: cratesio-publish-checks
description: Runs all pre-publish checks for a Rust crate. Use before publishing.
allowed-tools: bash, read, edit
---

# Pre-publish checks

1. Run `cargo fmt --check`.
...

The skill name comes from name, falling back to the directory name. A skill needs a description — that one line is what rik shows the model, which then loads the full instructions with the skill tool only when the description matches the task. Files bundled next to SKILL.md are listed with the instructions and fetched on demand: skill(name="clips", file="references/bpg642.md"). Skill files are served by the skill tool, not read_file, and paths cannot escape the skill directory.

When the same skill name exists in several locations, the first one wins: ~/.agents beats ~/.codex, which beats ~/.claude. This lets ~/.agents hold shared skills while agent-specific directories keep their own overrides.

allowed-tools is informational — rik always offers its own tool set and tells the model to skip steps it cannot perform. Skills marked disable-model-invocation: true stay out of the catalog rik shows the model; they still load if a marker names them explicitly, e.g. rik: use the changelog-write skill to update CHANGELOG.md.

Skills are available for both edit tasks and questions.

Preloading skills

--skills puts a skill's full instructions in the agent's preamble from the first turn, instead of leaving the model to decide whether to load it:

rik --skills jujutsu-workflow 'src/**/*.rs'
rik --skills clips,clips-integration --once 'rules/*.clp'
rik --skills clips --skills clips-integration --once 'rules/*.clp'

Preloaded skills are dropped from the catalog, so the model is not told to fetch what it already has. Preloading works for any discovered skill, including disable-model-invocation: true ones. An unknown name aborts the run and prints every available skill with its source:

$ rik --skills nope src
Error: Unknown skill 'nope'. Available skills:
  changelog-write (~/.agents) [manual]: Updates or creates a CHANGELOG.md ...
  clips (~/.agents): Reference and guidance for writing or reviewing CLIPS ...

[manual] marks skills the model cannot pick on its own.

Guardrails

Marker stoppers

Add !rik or rik! within a marker to skip that marker:

!rik: leave this task alone
rik: leave this task alone rik!
rik: process this task

Stoppers are local to the marker line or multi-line marker block that contains them. Other markers in the same file are still processed. Use !{alias} or {alias}! when using a custom alias.

Multiple markers

All markers in a single file are processed in one pass. rik won't stop after finding the first one.

Design philosophy

rik is intentionally limited by design:

  • No REPL -- you mark up files, run rik, review diffs. Repeat.
  • Constrained writes -- edit_file requires exact text matches and can only modify the file being processed; write_file can create new files but refuses to overwrite existing ones.
  • No conversation history -- each invocation is stateless and independent.
  • Diff-first feedback -- every change produces a diff so you see exactly what was modified.

It's a worker, not a companion. Summon it by name, give it instructions, let it work.

Rambling

I found gap in LLM-tooling that I couldn't fill otherwise:

  • fill-in-middle is very limited when it comes to context - it's fast, but if it can't produce result then it can't and that's it
  • agentic development runs amock, by default models try to implement whole feature and it takes more energy to restrict than actually to develop

rik is an attempt to fill that gap.

  • rik is designed to target single file for edition only; most often - single comment (it requires some self-discipline to make multiple ones)
  • rik can make its own context by listing or reading files

It started as an experiment for agentic tool, but I found rik pleasantly ergonomic and decided to release it.

Note: rig (the library used for LLM interaction) supports many providers out of the box. If your provider isn't listed above, open an issue or PR.

About

rik - limited agent edition

Resources

Stars

Watchers

Forks

Releases

Contributors

Languages