Skip to content
אצלי
Go back

My AI setup: Claude, Abacus, and OpenCode

Updated:
Espaço de trabalho futurista com codificação no monitor
Foto de Jakub Zerdzicki na Pexels

After posts on prompts for SRE/SysAdmin/DevOps, local and open source agents, and development with AI, and a long run of aggregators and self-hosted models, my daily setup settled on three tools:

I still test new tools. What I stopped doing is rebuilding my workflow for each one. The shared base is one instruction file, one skills folder, RTK, and hooks for the rules that can’t depend on the model.

Update (October 2026): Claude Code now reads AGENTS.md natively, OpenCode v2 loads the global and project AGENTS.md together (the first version of this post said it didn’t, which was wrong), and ~/.agents/skills/ became the skills folder most tools read. The post below describes the current state, and the setup got simpler for it.

RTK: token savings

RTK (Rust Token Killer) is a CLI proxy that rewrites shell commands before execution to save tokens, condensing git/kubectl/terraform output before it reaches the context. It’s a single Rust binary with no dependencies.

curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh

Alternatives: brew install rtk-ai/tap/rtk, or cargo install --git https://github.com/rtk-ai/rtk --branch master rtk (plain cargo install rtk installs an unrelated crate, “Rust Type Kit”).

rtk init -g              # Claude Code: hook + ~/.claude/RTK.md + @RTK.md + settings.json
rtk init -g --opencode    # OpenCode: installs only the plugin
rtk init --show           # shows what is installed, per tool

rtk init -g registers a PreToolUse hook in Claude Code, writes ~/.claude/RTK.md for the agent to consult, and adds @RTK.md to ~/.claude/CLAUDE.md. That’s why my AGENTS.md ends with that import.

Don’t trust --show alone. After the move to OpenCode v2 it reported [ok] plugin installed while OpenCode refused to load the plugin, which was still written for the v1 plugin API. RTK 1.0.1 fixed it. To check, run a command through OpenCode and look for it as rtk ... in rtk gain --history; when the plugin fails, ~/.local/share/opencode/log/opencode.log has a failed to load plugin line.

One AGENTS.md for every tool

My global instruction file is in ai-md-stack. It covers writing style, commit conventions, and how the agent should approach a change. An excerpt:

# Global Rules
- Write codes and comments in English by default; use Brazilian Portuguese only when explicitly requested.
- Do not use the em dash (U+2014, `—`, `—`) as punctuation; use a comma, colon, or rewrite.

## Git Commits
- Commit messages MUST describe only the *what* and *why* of the change.
- Follow Conventional Commits v1.0.0: https://www.conventionalcommits.org/en/v1.0.0/
- Subject line: imperative mood, 50 chars max, no trailing period.

## Imports
@RTK.md

OpenCode and AbacusAI read AGENTS.md. Claude Code reads it too since v2.1.277, but only at project scope: there is no user-level equivalent, so ~/.claude/AGENTS.md is ignored unless ~/.claude/CLAUDE.md points to it. The canonical file lives in ~/.claude/ and the other tools link to it:

$EDITOR ~/.claude/AGENTS.md                # canonical file
cd ~/.claude && ln -s AGENTS.md CLAUDE.md   # relative symlink, same folder
ln -s ~/.claude/AGENTS.md ~/.config/opencode/AGENTS.md

OpenCode also falls back to ~/.claude/CLAUDE.md when it finds no global AGENTS.md (OPENCODE_DISABLE_CLAUDE_CODE=1 turns that off), but the symlink makes the source of truth explicit.

Project scope

Each tool’s /init (Claude Code, OpenCode, and AbacusAI since 2.10) writes a different file. On the same empty repository, Claude Code left placeholders, while OpenCode with Kimi K2 refused to guess the toolchain at all. So I let each /init run on its own, and write the project AGENTS.md by hand when I want the same text everywhere.

A project with only AGENTS.md now works in all three tools. The ln -s AGENTS.md CLAUDE.md I used to add per repository is no longer needed, but it’s harmless: Claude Code reads the content once. Two details:

For Claude Code-specific rules on top of the shared file, a project CLAUDE.md can import it:

@AGENTS.md

## Claude Code
- Use plan mode for changes under src/billing/.

Cowork ignores the global file

In a Claude Cowork session on desktop, project files and project imports work normally. The user scope doesn’t: a ~/.claude/CLAUDE.md that is a symlink is skipped, and so is an @import in it that resolves outside the session’s working directory. My setup trips both. The only way to get global rules into Cowork is pasting the text literally into ~/.claude/CLAUDE.md, at the cost of keeping two copies in sync.

AbacusAI CLI only reads the project file

Abacus’s documentation says AGENTS.md works “at the user and project level”. To check, I put a behavior rule (“always end the response with tag X”) in ~/AGENTS.md and ran abacusai -p "What's 2+2?" in a folder with no project file. No tag. With the same rule in the project’s AGENTS.md, the tag appeared, and --no-agents-md removed it again. The CLI has no user-level AGENTS.md; the “user level” in the docs applies to skills. For AbacusAI, every project needs its own AGENTS.md.

Imports and precedence

@path imports are Claude Code syntax. OpenCode doesn’t expand them; its equivalent is the instructions field in opencode.json, which takes paths, globs, and URLs and combines them with the AGENTS.md files:

{
  "instructions": ["docs/guidelines.md", ".cursor/rules/*.md"]
}

AbacusAI has neither: its AGENTS.md is one file, read whole.

How the global and project files combine:

I tested OpenCode v2.0.29 with one rule per file in an empty git repository:

# global (~/.config/opencode/AGENTS.md)
- Always end every response with the literal tag [GLOBAL-OK].
# project (./AGENTS.md)
- Always start every response with the literal tag [PROJECT-OK].
$ opencode run --standalone -m opencode-go/glm-5.3-flash "What is 2+2?"
[PROJECT-OK] 2 + 2 = **4** [GLOBAL-OK]

Both apply, so there’s no need for an instructions entry pointing back at the global file. The same project file under claude -p printed [PROJECT-OK].

When testing instruction changes in OpenCode v2, use --standalone or opencode service restart first: opencode run talks to a background service by default, and in my test it kept applying an AGENTS.md I had already deleted.

Hooks for rules that must hold

AGENTS.md is context. The model tries to follow it, and in a long session it may not. A hook is code the client runs before or after a tool call, and it can block or rewrite the call whatever the model decided. Anything that must always hold goes into a hook.

Hooks don’t port between tools. Claude Code uses PreToolUse/PostToolUse in settings.json. OpenCode uses JS/TS plugins in ~/.config/opencode/plugins/ (or .opencode/plugins/) with events like tool.execute.before, so porting a hook means rewriting it in TypeScript. AbacusAI documents nothing equivalent.

Mine are in ai-md-stack: settings.example.json, four hooks, and a shared library, all shellcheck-clean. _common.sh checks whether a command is actually invoked, so terraform plan triggers a guard and jq --arg c 'terraform plan' doesn’t:

is_command_invoked() {
  local line="$1" name="$2"
  line=$(printf '%s' "$line" | tr '\n' ';')
  line=$(printf '%s' "$line" \
    | sed -E "s/'[^']*'/''/g" \
    | sed -E 's/"[^"]*"/""/g')
  printf '%s' "$line" | grep -qE "(^|[;&|(]|&&|\|\|)[[:space:]]*(sudo[[:space:]]+)?${name}([[:space:]]|$)"
}

Without it, a guard fires whenever the command name appears as data, and a guard with false positives gets ignored.

The InstructionsLoaded event is useful for confirming an imported or symlinked file reached the session. It doesn’t fire for an AGENTS.md that Claude Code reads natively, only for one a CLAUDE.md imports or links to.

Skills: one folder, ~/.agents/skills

All three tools use the same skill format: a directory named after the skill with a SKILL.md that has name/description frontmatter. They differ in where they look:

~/.agents/skills/ is read by every tool except Claude Code, and it’s where the skills CLI (npx skills add <repo>) installs, recording each source in ~/skills-lock.json and creating the Claude Code symlink itself. That makes it the canonical folder. Skills I write live in their own repositories, with one link into ~/.agents/skills/ and a second one for Claude Code:

ln -s ~/GIT/humanize-br ~/.agents/skills/humanize-br
ln -s ../../.agents/skills/humanize-br ~/.claude/skills/humanize-br
$ ls -l ~/.agents/skills/ ~/.claude/skills/
/home/esli/.agents/skills/:
cf-cli/
humanize-br -> /home/esli/GIT/humanize-br
v-repo-audit/
...
/home/esli/.claude/skills/:
cf-cli -> ../../.agents/skills/cf-cli
humanize-br -> ../../.agents/skills/humanize-br
v-repo-audit -> ../../.agents/skills/v-repo-audit
...

Asking each tool to list its skills showed the same seven in all three. Don’t keep another copy in ~/.config/opencode/skills/: OpenCode reads all three folders, and with duplicate names only the first one found loads. The frontmatter name must match the directory name, and Claude Code ignores symlinked user files in some sessions (Cowork again), so invoke a skill to confirm it loaded.

Conclusion

The clients are converging, so every workaround needs re-testing after a major upgrade. In this update, two of mine had become unnecessary, one claim was wrong, and the RTK plugin for OpenCode had stopped loading without any visible error.

Full package: github.com/Esl1h/ai-md-stack.


Share this post on:

Previous Post
RTK 1.0: atualizando e economizando milhões de tokens
Next Post
From WAF to API contract: Coraza, nginx and CRS in a testable lab