opencode: two profiles, per-profile permissions, shared agent prompts

oc-work: local models only on the local key, edits and most commands ask,
loads instructions/work.md. oc-local (also oc, opencode): every model,
defaults to qwen3.8-flash-next-iq3s-med, edits and commands run except
destructive and outward-facing ones. On sctfw004 oc-local presents the work
key. personal.json and work-strict.json removed.

Agents: drop variant, which has no effect on LiteLLM models. dev takes its
permissions from the profile. The read-only agents deny redirects and the
write/exec flags of allowed commands, swap find for fd, and only allow
listing forms of git branch. review defaults to the uncommitted diff.

CLAUDE.md: add the comment, process and shell-tool rules from the laptop's
AGENTS.md, and note that OpenCode reads this file.
This commit is contained in:
connor committed 2026-11-02 10:34:38 -05:00
1 parent 7ad155c4b2
commit 275a5bfb2b
16 files changed
+439 -680

No files matched your search

+43
View File
@@ -148,6 +148,11 @@ Five things about this repo that will cost you an hour:
`~/.dotfiles/common/claude/CLAUDE.md`. Editing it edits the repo, so a change
here wants a commit and reaches every machine at the next `install`.
OpenCode reads this file too, as its global instructions, because
`~/.config/opencode/AGENTS.md` does not exist. OpenCode loads only the first
of those two that exists, so creating an AGENTS.md there silently drops this
file from every OpenCode session. Keep one file, and keep it here.
## Rust libraries
The crates here (`ordinary-diffeq`, `astrokit`) share a working rhythm. Follow it
@@ -232,3 +237,41 @@ in a new one.
- **No AI attribution in commit messages.** No `Co-Authored-By: Claude`, no
session-link trailer, no model named as an author — subject and body only.
This overrides the harness default, which asks for the trailer.
## Code comments
- Comment only where the why isn't obvious from the code. Never add a comment
that restates what the code plainly does.
- A departure from how something was done earlier in the session, or in a
previous session, does not by itself warrant a comment. Explain that in the
chat, not in the code.
- When a comment is warranted, prefer one short line over a multi-line block.
- No semicolons or em-dashes in prose comments. Use more common punctuation.
- Deferred work always carries a literal `TODO:` marker so it can be grepped.
- Never describe previous behavior in a comment, even to justify the current
design. If the design justifies a comment on its own merits, write that, but
don't reference the old way. Git has the history.
- Default to no comment. Add one only when a reader would otherwise be misled
or have real difficulty understanding the code.
## Process
- Explain what the problem is and what the plan is before starting to
implement.
- Prefer the edit tool over scripts, sed or heredocs for changing files, so
each change is legible. A script is fine where it is clearly more efficient,
such as a rename or the same small change across many files. The OpenCode
work profile is stricter about this and adds its own instructions.
- If recorded data exists and a hypothesis about a cause can be tested against
it, test it. Be sure of a root cause before fixing, even when that takes
longer.
- Clear long-running services and ports before starting your own, and clean up
any server or similar long-running process you started.
## Shell tools
- Prefer `rg` over `grep` and `fd` over `find`. Both are installed on every
machine, but on sctfw004 `fd` is Debian's `fdfind`.
- `grep` is aliased to `rg` in interactive zsh, and agent shells may or may not
inherit that, so a grep command with GNU flags can fail in one shell and work
in another. Use `rg` explicitly, or `command grep` when GNU grep is needed.
+1 -1
View File
@@ -1,7 +1,7 @@
- [No AI commit attribution](no-ai-commit-attribution.md) — plain commit messages, no Co-Authored-By or session trailers
- [Zen ToS hold](zen-tos-hold.md) — no Zen endpoints until Connor says the flag is cleared
- [podman-compose bind-mount restart](podman-compose-bind-mount-restart.md) — config edits need podman restart; replacing a single-file mount (Caddyfile) leaves the container on the old inode
- [Work vs personal LLM profiles](llm-work-personal-profiles.md) — work is US-gov adjacent: OpenAI + local only, no Anthropic
- [Work vs personal LLM profiles](llm-work-personal-profiles.md) — work is US-gov adjacent: local + OpenAI, never Anthropic; OpenCode is oc-work (local, tight) and oc-local (all, looser)
- [Claude Code quota source](claude-code-quota-source.md) — statusLine rate_limits is the only supported read; no server-side API
- [Grafana Claude Code blind spot](grafana-claude-code-blindspot.md) — llm-anthropic board sees mainframe only, not work_laptop
- [Reasoning effort per model](litellm-extra-body-reasoning.md) — GLM-5.3 defaults to reasoning_effort=max and cannot disable thinking; gpt-oss needs chat_template_kwargs via extra_body
@@ -1,8 +1,11 @@
---
name: llm-work-personal-profiles
description: Connor's LLM stack has two profiles — work (US-gov adjacent, no Anthropic) and personal — which constrains which providers may serve which agent
description: "Work is US-gov adjacent (local + OpenAI, never Anthropic); OpenCode profiles are oc-work (local only, tight) and oc-local (all models, looser)"
metadata:
node_type: memory
type: project
originSessionId: 7072f523-c0c5-4db7-9d83-7335663126b5
modified: 2026-11-02T15:27:49.563Z
---
The LiteLLM stack serves two profiles with different permitted providers:
@@ -22,12 +25,13 @@ Connor has explicitly accepted that the work profile will be less performant
than personal. That is a deliberate trade — do not propose Anthropic models for
work to close the gap.
**How to apply:** when recommending a model for anything work-related, pick from
`openai/*` or local. Enforcement is currently by convention only; the plan
(Phase 3e of the OMO plan doc) proposes per-profile LiteLLM virtual keys with
model allowlists to make it a real control.
Since 2026-11-02 Connor leans toward local models for all OpenCode work because
of his employer's AI policy. OpenCode has two profiles: `oc-work` (local models
only via the LiteLLM `local` key, tight permissions, extra work instructions)
and `oc-local` (every model, defaults to local Qwen3.8, looser permissions,
`personal` key except on sctfw004 where it presents the `work` key).
Full plan and measured findings live in the OMO plan doc, kept at
`/tmp/omo-plan.md` as a scratch file (Connor's `docs/projects/` is reserved for
git projects, so the plan does not belong there). Ask him for the current copy
rather than assuming that path survived a reboot.
**How to apply:** when recommending a model for anything work-related, prefer
local, then `openai/*`. Enforcement is by LiteLLM virtual key, so the thing to
check after any launcher change is which key each command presents on each
host. Details in `~/guides/llm/robots.md` §2.
+14 -3
View File
@@ -15,7 +15,10 @@ permission:
"git show": allow
"git show *": allow
"git branch": allow
"git branch *": allow
"git branch --list*": allow
"git branch -a*": allow
"git branch -r*": allow
"git branch -v*": allow
"git blame *": allow
"git rev-parse *": allow
"git stash list": allow
@@ -28,9 +31,17 @@ permission:
"wc *": allow
"rg *": allow
"grep *": allow
"find *": allow
"fd *": allow
"which *": allow
"echo *": allow
"file *": allow
"stat *": allow
# Redirects, and the flags that make an allowed command write or execute.
"*>*": deny
"git * --output*": deny
"rg *--pre*": deny
"fd *-x *": deny
"fd *-X *": deny
"fd *--exec*": deny
---
You are the architect agent. You design the change. You do not make it.
+14 -3
View File
@@ -15,7 +15,10 @@ permission:
"git show": allow
"git show *": allow
"git branch": allow
"git branch *": allow
"git branch --list*": allow
"git branch -a*": allow
"git branch -r*": allow
"git branch -v*": allow
"git blame *": allow
"git rev-parse *": allow
"git stash list": allow
@@ -28,9 +31,17 @@ permission:
"wc *": allow
"rg *": allow
"grep *": allow
"find *": allow
"fd *": allow
"which *": allow
"echo *": allow
"file *": allow
"stat *": allow
# Redirects, and the flags that make an allowed command write or execute.
"*>*": deny
"git * --output*": deny
"rg *--pre*": deny
"fd *-x *": deny
"fd *-X *": deny
"fd *--exec*": deny
---
You are the brainstorm agent. Widen the option space before anything gets
+1 -30
View File
@@ -1,36 +1,7 @@
---
description: Hands-on development. Reads, edits, builds, and verifies, asking before it changes anything.
description: Hands-on development. Reads, edits, builds, and verifies. How much it may do unasked is set by the profile.
mode: primary
color: "#38bdf8"
permission:
edit: ask
bash:
"*": ask
"git status": allow
"git status *": allow
"git diff": allow
"git diff *": allow
"git log": allow
"git log *": allow
"git show": allow
"git show *": allow
"git branch": allow
"git branch *": allow
"git blame *": allow
"git rev-parse *": allow
"git stash list": allow
"ls": allow
"ls *": allow
"pwd": allow
"cat *": allow
"head *": allow
"tail *": allow
"wc *": allow
"rg *": allow
"grep *": allow
"find *": allow
"which *": allow
"echo *": allow
---
You are the dev agent: hands-on implementation in an existing codebase.
+14 -3
View File
@@ -15,7 +15,10 @@ permission:
"git show": allow
"git show *": allow
"git branch": allow
"git branch *": allow
"git branch --list*": allow
"git branch -a*": allow
"git branch -r*": allow
"git branch -v*": allow
"git blame *": allow
"git rev-parse *": allow
"git stash list": allow
@@ -28,9 +31,17 @@ permission:
"wc *": allow
"rg *": allow
"grep *": allow
"find *": allow
"fd *": allow
"which *": allow
"echo *": allow
"file *": allow
"stat *": allow
# Redirects, and the flags that make an allowed command write or execute.
"*>*": deny
"git * --output*": deny
"rg *--pre*": deny
"fd *-x *": deny
"fd *-X *": deny
"fd *--exec*": deny
---
You are the rubber duck: a thinking partner, not an implementer.
+19 -3
View File
@@ -15,7 +15,10 @@ permission:
"git show": allow
"git show *": allow
"git branch": allow
"git branch *": allow
"git branch --list*": allow
"git branch -a*": allow
"git branch -r*": allow
"git branch -v*": allow
"git blame *": allow
"git rev-parse *": allow
"git stash list": allow
@@ -28,13 +31,26 @@ permission:
"wc *": allow
"rg *": allow
"grep *": allow
"find *": allow
"fd *": allow
"which *": allow
"echo *": allow
"file *": allow
"stat *": allow
# Redirects, and the flags that make an allowed command write or execute.
"*>*": deny
"git * --output*": deny
"rg *--pre*": deny
"fd *-x *": deny
"fd *-X *": deny
"fd *--exec*": deny
---
You are the review agent. Read the code and report what is wrong with it.
Unless told otherwise, review the uncommitted changes (`git diff` and
`git diff --staged`), or the branch's commits since it left the default branch when the tree
is clean. Read surrounding code as needed to judge them, but do not review the
whole codebase.
- Order findings by severity, worst first. Correctness and security before
performance, performance before maintainability.
- Anchor every finding to a `path:line`, and state the concrete failure: the
+16
View File
@@ -0,0 +1,16 @@
## Work profile
This session is on the work profile. Connor reviews every change by hand, so
the goal is the smallest change he can read and check.
- Change code only when asked to. When the task is to investigate or explain,
report and stop.
- Before editing, say which files you will change and what each change is.
Wait for agreement when the change is more than a few lines.
- One change at a time. Don't fold in refactors, renames, formatting or
cleanup that were not asked for. Mention them instead.
- Make every edit with the edit tool, so each one is shown as a diff before it
is applied. No sed, scripts or redirects to modify files.
- Don't add dependencies, and don't create files unless the task needs them.
- Builds and tests ask for permission here. Say which command you want to run
and why.
+216 -15
View File
@@ -3,7 +3,7 @@
"provider": {
"litellm": {
"npm": "@ai-sdk/openai-compatible",
"name": "LiteLLM · LOCAL-ONLY",
"name": "LiteLLM · LOCAL (all models)",
"options": {
"baseURL": "{env:LITELLM_BASE}/v1",
"apiKey": "{env:LITELLM_KEY}"
@@ -35,6 +35,19 @@
"cache_write": 0.0
}
},
"qwen3.8-flash-next": {
"name": "qwen3.8-flash-next (rented MI325X; run gi up on mainframe first)",
"limit": {
"context": 262144,
"output": 32768
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"qwen3-next-80b-128k": {
"name": "qwen3-next-80b (local, 128k agentic)",
"limit": {
@@ -99,6 +112,162 @@
"cache_read": 0.0,
"cache_write": 0.0
}
},
"anthropic/claude-opus-5": {
"name": "claude-opus-5 (Anthropic direct)",
"limit": {
"context": 1000000,
"output": 128000
},
"cost": {
"input": 5.0,
"output": 25.0,
"cache_read": 0.5,
"cache_write": 6.25
}
},
"anthropic/claude-sonnet-5": {
"name": "claude-sonnet-5 (Anthropic direct)",
"limit": {
"context": 1000000,
"output": 128000
},
"cost": {
"input": 2.0,
"output": 10.0,
"cache_read": 0.2,
"cache_write": 2.5
}
},
"anthropic/claude-haiku-4-5": {
"name": "claude-haiku-4-5 (Anthropic direct)",
"limit": {
"context": 200000,
"output": 64000
},
"cost": {
"input": 1.0,
"output": 5.0,
"cache_read": 0.1,
"cache_write": 1.25
}
},
"chatgpt/gpt-5.6-sol": {
"name": "gpt-5.6-sol (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-terra": {
"name": "gpt-5.6-terra (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-luna": {
"name": "gpt-5.6-luna (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.5": {
"name": "gpt-5.5 (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"openai/gpt-5.6-sol": {
"name": "gpt-5.6-sol (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 4.0,
"output": 20.0,
"cache_read": 0.4,
"cache_write": 5.0
}
},
"openai/gpt-5.5": {
"name": "gpt-5.5 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 5.0,
"output": 30.0,
"cache_read": 0.5,
"cache_write": 0.0
}
},
"openai/gpt-5.4": {
"name": "gpt-5.4 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 2.5,
"output": 15.0,
"cache_read": 0.25,
"cache_write": 0.0
}
},
"openai/gpt-5.4-mini": {
"name": "gpt-5.4-mini (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 0.75,
"output": 4.5,
"cache_read": 0.075,
"cache_write": 0.0
}
},
"openai/gpt-5.3-codex": {
"name": "gpt-5.3-codex (OpenAI direct)",
"limit": {
"context": 200000,
"output": 64000
},
"cost": {
"input": 1.75,
"output": 14.0,
"cache_read": 0.175,
"cache_write": 0.0
}
}
}
}
@@ -108,21 +277,53 @@
],
"model": "litellm/qwen3.8-flash-next-iq3s-med",
"small_model": "litellm/qwen3.8-flash-next-iq3s-med",
"agent": {
"dev": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
"permission": {
"edit": "allow",
"webfetch": "allow",
"external_directory": "ask",
"read": {
"*": "allow",
"*.env": "ask",
"*.env.*": "ask",
"*.env.example": "allow"
},
"architect": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
},
"review": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
},
"duck": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
},
"brainstorm": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
"bash": {
"*": "allow",
"rm *": "ask",
"rmdir *": "ask",
"sudo *": "ask",
"su *": "ask",
"kill *": "ask",
"pkill *": "ask",
"killall *": "ask",
"git commit*": "ask",
"git push*": "ask",
"git reset *": "ask",
"git checkout -- *": "ask",
"git restore *": "ask",
"git clean *": "ask",
"git rebase*": "ask",
"git stash drop*": "ask",
"git stash clear*": "ask",
"git branch -D*": "ask",
"git tag *": "ask",
"cargo publish*": "ask",
"npm publish*": "ask",
"gh *": "ask",
"ssh *": "ask",
"scp *": "ask",
"rsync *": "ask",
"rbw *": "ask",
"systemctl *": "ask",
"podman *": "ask",
"docker *": "ask",
"dd *": "ask",
"mkfs*": "ask",
"chmod -R*": "ask",
"chown *": "ask",
"shutdown*": "ask",
"reboot*": "ask",
"poweroff*": "ask"
}
}
}
-258
View File
@@ -1,258 +0,0 @@
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"litellm": {
"npm": "@ai-sdk/openai-compatible",
"name": "LiteLLM · PERSONAL",
"options": {
"baseURL": "{env:LITELLM_BASE}/v1",
"apiKey": "{env:LITELLM_KEY}"
},
"models": {
"qwen3.8-flash-next-iq3s-med": {
"name": "Qwen3.8-Flash-Next IQ3_S (local Strata, medium reasoning)",
"limit": {
"context": 196608,
"output": 32768
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"qwen3.8-flash-next-iq3s": {
"name": "Qwen3.8-Flash-Next IQ3_S (local Strata, full reasoning - slow)",
"limit": {
"context": 196608,
"output": 32768
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"gemma3-12b": {
"name": "gemma3-12b (local, on demand)",
"limit": {
"context": 32768,
"output": 8192
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"anthropic/claude-opus-5": {
"name": "claude-opus-5 (Anthropic direct)",
"limit": {
"context": 1000000,
"output": 128000
},
"cost": {
"input": 5.0,
"output": 25.0,
"cache_read": 0.5,
"cache_write": 6.25
}
},
"anthropic/claude-sonnet-5": {
"name": "claude-sonnet-5 (Anthropic direct)",
"limit": {
"context": 1000000,
"output": 128000
},
"cost": {
"input": 2.0,
"output": 10.0,
"cache_read": 0.2,
"cache_write": 2.5
}
},
"anthropic/claude-haiku-4-5": {
"name": "claude-haiku-4-5 (Anthropic direct)",
"limit": {
"context": 200000,
"output": 64000
},
"cost": {
"input": 1.0,
"output": 5.0,
"cache_read": 0.1,
"cache_write": 1.25
}
},
"chatgpt/gpt-5.6-sol": {
"name": "gpt-5.6-sol (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-terra": {
"name": "gpt-5.6-terra (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-luna": {
"name": "gpt-5.6-luna (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.5": {
"name": "gpt-5.5 (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"openai/gpt-5.6-sol": {
"name": "gpt-5.6-sol (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 4.0,
"output": 20.0,
"cache_read": 0.4,
"cache_write": 5.0
}
},
"openai/gpt-5.5": {
"name": "gpt-5.5 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 5.0,
"output": 30.0,
"cache_read": 0.5,
"cache_write": 0.0
}
},
"openai/gpt-5.4": {
"name": "gpt-5.4 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 2.5,
"output": 15.0,
"cache_read": 0.25,
"cache_write": 0.0
}
},
"openai/gpt-5.4-mini": {
"name": "gpt-5.4-mini (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 0.75,
"output": 4.5,
"cache_read": 0.075,
"cache_write": 0.0
}
},
"openai/gpt-5.3-codex": {
"name": "gpt-5.3-codex (OpenAI direct)",
"limit": {
"context": 200000,
"output": 64000
},
"cost": {
"input": 1.75,
"output": 14.0,
"cache_read": 0.175,
"cache_write": 0.0
}
},
"muse-glimmer-30b-low": {
"name": "Muse Glimmer 30B (local, low reasoning)",
"limit": {
"context": 131072,
"output": 8192
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"muse-glimmer-30b": {
"name": "Muse Glimmer 30B (local, full reasoning - slow)",
"limit": {
"context": 131072,
"output": 8192
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
}
}
}
},
"enabled_providers": [
"litellm"
],
"model": "litellm/qwen3.8-flash-next-iq3s-med",
"small_model": "litellm/qwen3.8-flash-next-iq3s-med",
"agent": {
"dev": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
},
"architect": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
},
"review": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
},
"duck": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
},
"brainstorm": {
"model": "litellm/qwen3.8-flash-next-iq3s-med"
}
}
}
-221
View File
@@ -1,221 +0,0 @@
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"litellm": {
"npm": "@ai-sdk/openai-compatible",
"name": "LiteLLM \u00b7 WORK-STRICT",
"options": {
"baseURL": "{env:LITELLM_BASE}/v1",
"apiKey": "{env:LITELLM_KEY}"
},
"models": {
"qwen3.8-flash-next-iq3s-med": {
"name": "qwen3.8-flash-next IQ3_S (local Strata, medium reasoning)",
"limit": {
"context": 196608,
"output": 32768
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"qwen3.8-flash-next-iq3s": {
"name": "qwen3.8-flash-next IQ3_S (local Strata, default reasoning - slow)",
"limit": {
"context": 196608,
"output": 32768
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"gemma3-12b": {
"name": "gemma3-12b (local, on demand)",
"limit": {
"context": 32768,
"output": 8192
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-sol": {
"name": "gpt-5.6-sol (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-terra": {
"name": "gpt-5.6-terra (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-luna": {
"name": "gpt-5.6-luna (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.5": {
"name": "gpt-5.5 (ChatGPT Plus, flat fee)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"openai/gpt-5.6-sol": {
"name": "gpt-5.6-sol (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 4.0,
"output": 20.0,
"cache_read": 0.4,
"cache_write": 5.0
}
},
"openai/gpt-5.5": {
"name": "gpt-5.5 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 5.0,
"output": 30.0,
"cache_read": 0.5,
"cache_write": 0.0
}
},
"openai/gpt-5.4": {
"name": "gpt-5.4 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 2.5,
"output": 15.0,
"cache_read": 0.25,
"cache_write": 0.0
}
},
"openai/gpt-5.4-mini": {
"name": "gpt-5.4-mini (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 0.75,
"output": 4.5,
"cache_read": 0.075,
"cache_write": 0.0
}
},
"openai/gpt-5.3-codex": {
"name": "gpt-5.3-codex (OpenAI direct)",
"limit": {
"context": 200000,
"output": 64000
},
"cost": {
"input": 1.75,
"output": 14.0,
"cache_read": 0.175,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-terra-low": {
"name": "GPT-5.6 Terra (reasoning_effort=low)",
"limit": {
"context": 272000,
"output": 128000
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
}
}
}
},
"enabled_providers": [
"litellm"
],
"model": "litellm/chatgpt/gpt-5.6-terra-low",
"permission": {
"read": {
"*": "ask",
"*.env": "ask",
"*.env.*": "ask",
"*.env.example": "ask"
},
"bash": {
"cat *": "ask",
"head *": "ask",
"tail *": "ask",
"rg *": "ask",
"grep *": "ask",
"find *": "ask"
}
},
"agent": {
"dev": {
"model": "litellm/chatgpt/gpt-5.6-terra-low"
},
"architect": {
"model": "litellm/chatgpt/gpt-5.6-terra-low"
},
"review": {
"model": "litellm/chatgpt/gpt-5.6-terra-low"
},
"duck": {
"model": "litellm/chatgpt/gpt-5.6-terra-low"
},
"brainstorm": {
"model": "litellm/chatgpt/gpt-5.6-terra-low"
}
}
}
+68 -112
View File
@@ -3,7 +3,7 @@
"provider": {
"litellm": {
"npm": "@ai-sdk/openai-compatible",
"name": "LiteLLM · WORK",
"name": "LiteLLM · WORK (local models)",
"options": {
"baseURL": "{env:LITELLM_BASE}/v1",
"apiKey": "{env:LITELLM_KEY}"
@@ -35,19 +35,6 @@
"cache_write": 0.0
}
},
"gemma3-12b": {
"name": "gemma3-12b (local, on demand)",
"limit": {
"context": 32768,
"output": 8192
},
"cost": {
"input": 0.0,
"output": 0.0,
"cache_read": 0.0,
"cache_write": 0.0
}
},
"qwen3.8-flash-next": {
"name": "qwen3.8-flash-next (rented MI325X; run gi up on mainframe first)",
"limit": {
@@ -61,11 +48,11 @@
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-sol": {
"name": "gpt-5.6-sol (ChatGPT Plus, flat fee)",
"qwen3-next-80b-128k": {
"name": "qwen3-next-80b (local, 128k agentic)",
"limit": {
"context": 272000,
"output": 128000
"context": 131072,
"output": 8192
},
"cost": {
"input": 0.0,
@@ -74,11 +61,11 @@
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-terra": {
"name": "gpt-5.6-terra (ChatGPT Plus, flat fee)",
"qwen3-next-80b": {
"name": "qwen3-next-80b (local, 32k)",
"limit": {
"context": 272000,
"output": 128000
"context": 32768,
"output": 8192
},
"cost": {
"input": 0.0,
@@ -87,11 +74,11 @@
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-luna": {
"name": "gpt-5.6-luna (ChatGPT Plus, flat fee)",
"gemma3-12b": {
"name": "gemma3-12b (local, on demand)",
"limit": {
"context": 272000,
"output": 128000
"context": 32768,
"output": 8192
},
"cost": {
"input": 0.0,
@@ -100,11 +87,11 @@
"cache_write": 0.0
}
},
"chatgpt/gpt-5.5": {
"name": "gpt-5.5 (ChatGPT Plus, flat fee)",
"muse-glimmer-30b-low": {
"name": "Muse Glimmer 30B (local, low reasoning)",
"limit": {
"context": 272000,
"output": 128000
"context": 131072,
"output": 8192
},
"cost": {
"input": 0.0,
@@ -113,76 +100,11 @@
"cache_write": 0.0
}
},
"openai/gpt-5.6-sol": {
"name": "gpt-5.6-sol (OpenAI direct)",
"muse-glimmer-30b": {
"name": "Muse Glimmer 30B (local, full reasoning - slow)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 4.0,
"output": 20.0,
"cache_read": 0.4,
"cache_write": 5.0
}
},
"openai/gpt-5.5": {
"name": "gpt-5.5 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 5.0,
"output": 30.0,
"cache_read": 0.5,
"cache_write": 0.0
}
},
"openai/gpt-5.4": {
"name": "gpt-5.4 (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 2.5,
"output": 15.0,
"cache_read": 0.25,
"cache_write": 0.0
}
},
"openai/gpt-5.4-mini": {
"name": "gpt-5.4-mini (OpenAI direct)",
"limit": {
"context": 1050000,
"output": 128000
},
"cost": {
"input": 0.75,
"output": 4.5,
"cache_read": 0.075,
"cache_write": 0.0
}
},
"openai/gpt-5.3-codex": {
"name": "gpt-5.3-codex (OpenAI direct)",
"limit": {
"context": 200000,
"output": 64000
},
"cost": {
"input": 1.75,
"output": 14.0,
"cache_read": 0.175,
"cache_write": 0.0
}
},
"chatgpt/gpt-5.6-terra-low": {
"name": "GPT-5.6 Terra (reasoning_effort=low)",
"limit": {
"context": 272000,
"output": 128000
"context": 131072,
"output": 8192
},
"cost": {
"input": 0.0,
@@ -199,27 +121,61 @@
],
"model": "litellm/qwen3.8-flash-next-iq3s-med",
"small_model": "litellm/qwen3.8-flash-next-iq3s-med",
"instructions": [
"~/.dotfiles/common/opencode/instructions/work.md"
],
"permission": {
"edit": "ask",
"webfetch": "ask",
"external_directory": "ask",
"read": {
"*": "ask",
"*": "allow",
"*.env": "ask",
"*.env.*": "ask",
"*.env.example": "ask"
"*.env.example": "allow"
},
"bash": {
"cat *": "ask",
"head *": "ask",
"tail *": "ask",
"rg *": "ask",
"grep *": "ask",
"find *": "ask"
"*": "ask",
"git status": "allow",
"git status *": "allow",
"git diff": "allow",
"git diff *": "allow",
"git log": "allow",
"git log *": "allow",
"git show": "allow",
"git show *": "allow",
"git branch": "allow",
"git branch --list*": "allow",
"git branch -a*": "allow",
"git branch -r*": "allow",
"git branch -v*": "allow",
"git blame *": "allow",
"git rev-parse *": "allow",
"git stash list": "allow",
"ls": "allow",
"ls *": "allow",
"pwd": "allow",
"cat *": "allow",
"head *": "allow",
"tail *": "allow",
"wc *": "allow",
"rg *": "allow",
"grep *": "allow",
"fd *": "allow",
"which *": "allow",
"file *": "allow",
"stat *": "allow",
"*>*": "ask",
"git * --output*": "ask",
"rg *--pre*": "ask",
"fd *-x *": "ask",
"fd *-X *": "ask",
"fd *--exec*": "ask"
}
},
"agent": {
"dev": {},
"architect": {},
"review": {},
"duck": {},
"brainstorm": {}
"homelab": {
"disable": true
}
}
}
+20 -18
View File
@@ -1,14 +1,18 @@
# ---------------------------------------------------------------------------
# OpenCode profile launchers (Phase 5)
#
# Four profiles, one LiteLLM proxy. The profile decides WHICH KEY is used, and
# the key decides which models are reachable -- enforcement lives in LiteLLM
# (Phase 4 access groups), not here. This file only picks a key and a config.
# Two profiles, one LiteLLM proxy. The profile picks a config (models,
# permissions) and a key; the key decides which models are reachable --
# enforcement lives in LiteLLM access groups, not here.
#
# opencode / oc personal anthropic + openai + local
# oc-work work openai + local NO anthropic
# oc-work-strict work-strict openai + local NO anthropic, PRC never
# oc-local local local only, no egress
# oc-work work.json local models only, tight permissions
# oc-local / oc local.json every model, defaults to local Qwen3.8,
# looser permissions
#
# oc-work uses the LiteLLM `local` key, so nothing leaves the machine.
# oc-local uses the `personal` key, except on sctfw004, where it uses the
# `work` key (local + OpenAI): that laptop must never reach Anthropic, and the
# Anthropic entries in local.json 403 there.
#
# NO SECRET IS STORED HERE. Keys are read from rbw at launch and passed to the
# process as LITELLM_KEY, which the config files reference as {env:LITELLM_KEY}.
@@ -74,9 +78,7 @@ _oc_launch() {
# that survives for the rest of the shell if the profile is exported.
local colour
case "$profile" in
personal) colour=$'\e[1;36m' ;; # cyan
work) colour=$'\e[1;33m' ;; # yellow
work-strict) colour=$'\e[1;31m' ;; # red
local) colour=$'\e[1;32m' ;; # green
esac
print -u2 "${colour}┌─ opencode profile: ${(U)profile} ─┐\e[0m"
@@ -95,8 +97,8 @@ _oc_launch() {
# ~/.config/opencode/opencode.json is a GLOBAL BASE that opencode merges into
# every other config -- OPENCODE_CONFIG adds to it, it does not replace it, and
# neither does OPENCODE_CONFIG_DIR. Verified: with the personal profile parked
# at that path, `oc-local models` listed the Anthropic models too. So personal
# lives at personal.json like every other profile, and the file at the default
# at that path, `oc-local models` listed the Anthropic models too. So every
# profile lives under its own name, and the file at the default
# path is a two-line guard holding only `enabled_providers: ["litellm"]`.
#
# That guard is a safety net, not decoration: without it a bare `opencode` run
@@ -119,10 +121,10 @@ _oc_launch() {
#
# The `plan` agent is native to opencode, not part of a plugin.
#
# Personal is still wrapped under the bare name so `opencode` does the
# expected thing and gets a key.
opencode() { _oc_launch personal litellm-key-personal "$HOME/.config/opencode/personal.json" "$@" }
oc() { _oc_launch personal litellm-key-personal "$HOME/.config/opencode/personal.json" "$@" }
oc-work() { _oc_launch work litellm-key-work "$HOME/.config/opencode/work.json" "$@" }
oc-work-strict() { _oc_launch work-strict litellm-key-work-strict "$HOME/.config/opencode/work-strict.json" "$@" }
oc-local() { _oc_launch local litellm-key-local "$HOME/.config/opencode/local.json" "$@" }
# The bare names go to oc-local so `opencode` does the expected thing and gets
# a key.
_oc_local_key() { [[ ${(L)HOST} == sctfw004 ]] && print litellm-key-work || print litellm-key-personal }
oc-work() { _oc_launch work litellm-key-local "$HOME/.config/opencode/work.json" "$@" }
oc-local() { _oc_launch local "$(_oc_local_key)" "$HOME/.config/opencode/local.json" "$@" }
oc() { oc-local "$@" }
opencode() { oc-local "$@" }