A coding agent harness for the supported Solar models solar-open2 and
solar-pro4.
One design premise: measure the model's behavior instead of guessing, then pin it down as constants. All measured values live in one place, src/sol/model.py, with the evidence recorded next to each constant. No other module second-guesses the model.
Three properties shaped the harness.
Reasoning eats the output budget. reasoning arrives as a separate
field from content, but they share the same max_tokens. Skimp on the
budget and you get an empty response. On practical tasks, 512 and 1024
returned content=""; answers only appeared at 2048. So the default is
the model's ceiling, 131072. Unused budget is not billed.
Tool specs are part of the cache prefix. Changing a single tool drops
cached_tokens from 13056 to 0. So the prefix is frozen, changes are
allowed only through invalidate(), and when the cache breaks the ledger
points at the segment responsible.
Broken tool pairs mean HTTP 400. A tool_calls message without its
responses, or an orphaned tool message, gets the request rejected. So
compaction only ever cuts at pair boundaries.
| Item | Value |
|---|---|
| Max output | 131,072 tokens |
| Context | 1,000,000-token solar-open2 baseline |
reasoning_effort |
CLI off/on mapped to API none/high |
| Cache chunk and minimum prefix | 1,088 tokens |
| Cache warmup | 3 requests |
| Korean token density | 1.85 chars/token (English 6.48) |
| Vision | unsupported (Image input is not allowed) |
logprobs |
accepted, but always returns null |
The CLI exposes only off/on for reasoning and maps them to the API's
none/high values. solar-open2 is the measured model; solar-pro4 is
supported through the same request path but remains a separate baseline.
Sampling parameters are fixed at temperature=0, top_p=1,
presence_penalty=0, and frequency_penalty=0.
sol is a Python runtime. npm and Bun expose it through a small Node launcher;
curl, Homebrew, and Git use the Python launcher directly. The runtime has no
third-party Python or npm dependencies.
- Python 3.10+ is required for every installation method.
- Node.js 18+ is additionally required for npm and Bun installations.
- Internet access and
UPSTAGE_API_KEYare required for model calls. - An ANSI-compatible interactive terminal is required for the full-screen TUI. Non-interactive CLI execution does not require it.
git, gh, rg, LSP servers, and MCP servers are optional integrations, not
base installation requirements. Choose one installation method below; every
method provides the same sol command.
After the package is published:
npm install --global @bytonylee/sol
sol --helpbun add --global @bytonylee/sol
sol --helpThis installs the current main branch under ~/.local/share/solar-code and
links sol into ~/.local/bin:
curl -fsSL https://raw.githubusercontent.com/bytonylee/solar-code/main/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
sol --helpThe repository includes a formula that tracks the current main branch:
brew install --HEAD https://raw.githubusercontent.com/bytonylee/solar-code/main/Formula/sol.rb
sol --helpgit clone https://github.com/bytonylee/solar-code.git \
"$HOME/.local/share/solar-code"
mkdir -p "$HOME/.local/bin"
ln -sfn "$HOME/.local/share/solar-code/bin/sol" "$HOME/.local/bin/sol"
export PATH="$HOME/.local/bin:$PATH"
sol --helpAsk an agent to use this prompt:
Install the
solCLI/TUI for this repository. Use the first available method in this order: npm, Bun, curl, Homebrew, then git. Confirm Python 3.10+ is available. For npm or Bun, also confirm Node.js 18+. Install only the runtime and its launcher, and do not install tests, caches, experiments, ordiagnose_solar.sh. On macOS, runsol --set-api-keyso I can enter the required$UPSTAGE_API_KEYthrough the hidden Keychain prompt. Offersol --set-tinyfish-api-keyonly as an option. Never ask me to send, print, log, or commit an API key. Refer to keys only as$UPSTAGE_API_KEYand$TINYFISH_API_KEY. Verifysol --help, then report the installation method, executable path, and version.solwithout arguments opens the TUI;sol -p "..."runs the CLI.
sol resolves each key in this order: process environment, project .env,
macOS Keychain, then the installation directory. Key values are never printed;
messages and records use only $UPSTAGE_API_KEY or $TINYFISH_API_KEY.
Use the CLI prompts. Input is hidden and saved in the login Keychain, not in shell history or a plaintext Solar configuration file:
sol --set-api-key
sol --set-tinyfish-api-key # optionalThe same prompts are available inside the TUI:
/api-key
/tinyfish-key
To import values that are already exported, pipe the variable expansion rather than writing the literal key in the command. Neither command prints the value:
echo -n "$UPSTAGE_API_KEY" | sol --set-api-key
echo -n "$TINYFISH_API_KEY" | sol --set-tinyfish-api-keysol reads the Keychain directly. To expose the same global values to other
shell programs, add Keychain lookups to ~/.zshrc; the recorded lines contain
only command substitutions, not literal keys:
echo 'export UPSTAGE_API_KEY="$(security find-generic-password -a "$USER" -s solar-code:UPSTAGE_API_KEY -w)"' >> ~/.zshrc
echo 'export TINYFISH_API_KEY="$(security find-generic-password -a "$USER" -s solar-code:TINYFISH_API_KEY -w)"' >> ~/.zshrc
source ~/.zshrcOn systems without macOS Keychain, provide environment variables through the
OS secret manager or use a project-local .env protected with mode 600.
For persistent project configuration, create .env beside the directory where
you run sol. When using a Git checkout, start from the included template:
cp .env.example .env
chmod 600 .envFor npm, Bun, curl, or Homebrew installations, create the same .env file
manually. Enter each value after = without quotes:
# Required for Solar model calls
UPSTAGE_API_KEY=
# Optional; leave empty when TinyFish is not used
TINYFISH_API_KEY=Never commit .env, paste keys into prompts, or include them in logs. You can
instead provide the same names through your shell or a secret manager. An
environment variable takes precedence over .env.
When TINYFISH_API_KEY is empty or absent, the default search chain tries the
keyless Naver and Mojeek providers. Use
SOL_SEARCH_PROVIDER=auto|tinyfish|naver|mojeek to force a provider.
The default runtime model is solar-pro4. Set SOL_MODEL=solar-open2 to use
the measured baseline explicitly, or set it to another supported model for an
experiment. The TUI marks solar-pro4 as unverified because the output,
context, and cache constants are measured for solar-open2.
Maintainers can inspect the npm payload before publishing with:
npm pack --dry-run
npm publish --access publicThe command line exposes the prompt, automatic approval, thinking, model
selection, session continuation, and Git branch selection. --help is the single
entry point for the complete command reference. In the full-screen TUI,
/model opens the read-only model contract and current runtime settings.
sol # full-screen TUI
sol -p "summarize this repository" # CLI, approve each action
sol -p "fix and test the bug" --yolo # CLI, automatic approval
sol -p "review this design" --think on
sol -p "quick review"
sol --continue 20260803-120000-a1b2c3d4
sol --branch feature/simple-command| Option | Meaning |
|---|---|
-p PROMPT |
Request to run in the CLI. Omit it to open the TUI |
--yolo |
Approve file edits and shell commands automatically |
| `--think on | off` |
| `--model solar-open2 | solar-pro4` |
--set-api-key |
Securely prompt for and globally store $UPSTAGE_API_KEY |
--set-tinyfish-api-key |
Securely prompt for and globally store optional $TINYFISH_API_KEY |
--continue SESSION_ID |
Continue the exact saved session |
--branch BRANCH_NAME |
Switch to the branch, creating it when absent |
--help |
Show the complete reference and examples |
Without --yolo, file edits and shell commands are available but require
approval each time. --yolo does not remove workspace containment,
read-before-write, checkpoints, or the dangerous-command blocklist. The
CLI summary and the TUI's exit message or /status command show the session ID.
A session is written only once it records something. A run that ends without
any input leaves no file and therefore shows no session ID. Every other session
is saved and can be reopened with --continue SESSION_ID.
Solar keeps user-scoped state and reusable configuration under ~/.solar,
following the same home-directory pattern as Codex. Set SOLAR_HOME to use a
different location. The layout is:
~/.solar/
├── AGENTS.md # user-wide instructions, loaded before project rules
├── skills/ # reusable skills, one <name>/SKILL.md per skill
└── sessions/ # append-only session JSONL files
Project AGENTS.md files and project skill directories remain supported and
override user defaults where names overlap.
The TUI has twelve commands: /help, /think on|off, /yolo on|off,
/plan on|off, /goal, /model, /api-key, /tinyfish-key, /status,
/undo, /clear, and /quit.
/model separates the values that
can change during a session from the measured values pinned for solar-open2.
Use /think on|off to change reasoning; the model ID, output ceiling, and
sampling parameters stay fixed. API keys are never shown. Calling /yolo
without an argument toggles the current mode, and Shift+Tab toggles the
same mode from the keyboard. Complete commands with Tab or Enter. Ctrl+Z undoes
the latest input edit and Ctrl+R restores the latest workspace checkpoint.
Terminals supporting the kitty keyboard protocol on macOS also accept
Command+Z and Command+R.
solar-pro4 is accepted by the same OpenAI-compatible request path, but its
output, context, and cache behavior has not been measured in this repository.
The /model panel labels those values as a solar-open2 baseline rather than
presenting them as verified solar-pro4 limits.
The regular CLI streams progress to stderr and answers to stdout. Tool results appear as soon as they finish; shell output streams line by line.
Multi-step work is tracked outside the conversation. A final answer cannot
pass while a task is open or doing; the retry forces a real tool call so
the agent must continue the work or mark a concrete blocker. After a file
write, the final report must name a changed path and state what validation was
run or why it was not run. A reported blocked task may stop execution, but
the episode remains incomplete rather than becoming a successful run.
The normal loop has no fixed round limit, so long work is not cut off by a
round count. Instead a stall detector ends runs that stop making progress:
identical repeated tool calls, a run of consecutive tool failures, or rounds
without any new successful work. An absolute cap remains as a safety net.
Stalling, exhausting a completion gate, receiving an empty answer, or losing
the model request never produces a silent answer="". The harness makes one
tool-free wrap-up request and falls back to a deterministic status report if
that request also fails. Stalled and gate-exhausted runs remain incomplete,
are visible in CLI/TUI status with the detected reason, and return a
non-zero CLI exit code even when a wrap-up message exists. Candidate answer
text is buffered until output guards accept it, so a rejected completion is
not leaked to stdout before its correction.
Dependencies flow one way with no cycles. model.py imports nothing; the
loop only delegates to the layers below.
model.py measured constants. If a value is wrong, fix this file,
not the harness
client.py API boundary. Preserves the whole response envelope
credentials.py global macOS Keychain storage and $API_KEY-only display markers
prefix.py cache stability. StablePrefix, AppendOnlyLog, CacheLedger
compaction.py 3-stage compression that honors tool pairs
(snip -> prune -> summary)
tools.py registry. Always returns a tool message, even on failure
loop.py turn lifecycle. Includes starved retry, tool-call nudge,
incomplete-state preservation, and forced final wrap-up
guards.py measurement-based detectors: degenerate repetition cutoff,
unfinished-task gate, unbacked verification claims, and
final-report completeness
spec.py input specification: turns vague instructions into a
scoped plan with done-criteria before the loop runs
goal.py completion criteria (Goal), the GoalGate processor, and the
outer Ralph loop that re-issues unmet criteria
interact.py ask_user tool. Auto-selection in non-interactive runs is
always recorded, never a silent assumption
websearch.py TinyFish -> Naver -> Mojeek search chain.
Failure is an explicit error, never fabricated results
cacheprobe.py cache hit-rate probe. Measures only the API's
usage.prompt_tokens_details.cached_tokens, excluding warmup
cacheprofile.py model-specific cache chunk and promotion profiles
codesearch.py AST/declaration-based outline and symbol search
lsp.py optional LSP definitions/references when a server is configured
mcpclient.py optional stdio MCP servers with fixed tool schemas
workspace.py file read/write/search/exec. Enforces read-before-write
checkpoint.py undo. The core is external-change conflict detection
worktree.py isolates parallel implementations via git worktree
session.py JSONL tree. Rewind and fork never destroy the past
tui.py full-screen TUI. cfonts title, SSE token streaming,
4-dot-height 8-frame spinner in Upstage lavender. View logic
is terminal-free and tested
progress.py live CLI spinner, mode footer, and tool-result display
context.py AGENTS.md / SKILL.md assembly. Deterministic, or no cache
processors.py hooks around the loop. Guardrails and secret redaction
hooks.py external command hooks from .sol/hooks.json
subagent.py 4-role delegation (explorer/implementer/reviewer/tester)
tracker.py plans and progress. Session-scoped to prevent context bleed
git.py learns repo conventions to write commit messages
github.py PRs/issues via gh CLI. Tokens stay in gh
cassette.py traffic record and replay
determinism.py Korean number/unit/null normalization
recall.py BM25 search over past sessions. Hangul bigram tokenizer
status.py cache and cost visibility
permissions.py permission presets and legacy flag normalization
The distribution intentionally excludes test sources, cache files, and
experiment records. Maintainers validate the source tree before publishing;
the runtime itself uses only the Python standard library. package.json and
package-lock.json contain npm packaging metadata only; the Node launcher has
no runtime dependencies. .env.example contains empty placeholders only.
With tools registered, the model tends to call them even when unneeded.
For pure computation, pass an empty Registry().
Tool-call omission at effort=off was originally observed but has not
reproduced in 60 follow-up runs (single-turn and multi-turn). The
tool_choice=required nudge remains as zero-cost insurance; its evidence
status is "awaiting reproduction".
No image input, so screenshot-based work is impossible. Verification must go through text observations (test output, hashes, diffs) instead.
Web search tries TinyFish first when TINYFISH_API_KEY is configured, then
keyless Naver and Mojeek providers. SOL_SEARCH_PROVIDER can force one
provider. Provider errors, bot challenges, parse failures, and responses with
no valid HTTP(S) sources are not treated as successful searches. When a task
explicitly requires external references, mutating tools remain blocked until a
source is observed; without one, the run ends incomplete. Image search is not
registered yet; direct page-body fetching is available through web_fetch.
The TinyFish integration follows the official TinyFish Search API contract. Keyless HTML providers are guarded by rate limits and explicit blocked/invalid-response envelopes rather than fabricated results.
LSP tools are optional and require an available server configured in
.sol/lsp.json. MCP tools are also optional, use stdio servers configured in
.sol/mcp.json, and pass only the environment keys declared by each server.
Neither integration is required for basic CLI or TUI startup.
Credentials are read from environment variables or a local .env file and
are not part of the repository. Keep .env, generated session records,
dependency directories, and build output untracked. Do not paste credentials
into prompts, issues, pull requests, or diagnostic logs.