A Go and htmx web app on :6001 that edits ~/.claude/settings.json and shows what the loop produces.
It reads the nightly's open change commits (claude-routines/smine-nightly-*-proposals branches, via agentwt unplaced) with run-report verdicts and task records, rejected and dropped refs (History tab), session records and skills. Applicability: one agentwt probe per refresh, cached behind a ref fingerprint. Feedback-loop diagram: overview; setup: Install.
Every request needs the access token as HTTP Basic password; only its SHA-256 digest is stored, in ~/.claude/configserver/credential.
Mined session records, never the transcript.
| Route | Shows |
|---|---|
| /sessions | Mined weeks per repo; live list from GET /sessions-context. |
| /sessions/<scope>/<week> | A week's session cards from sessions/<scope>/<week>/*.json. |
| /sessions-report/<id> | One session's context report. |
| /sessions-archive | Archived scopes. |
| Parameter or anchor | Effect |
|---|---|
| session=<id>[,<id>] | Only those sessions, opened. |
| open=<id> | Opens one card. |
| dimension=<name> | Only that dimension's findings. |
| #session-<id> | Scrolls to a card. |
| #f-<id>-<n> | Opens the card and its n-th finding, 0-based in record order. |
A finding row shows headline; records mined before format 2, the first sentence of summary. A quote's turn is the 1-based transcript turn in peek's order.
GET /sessions-context lists peek's sessions of every registered repo, or the picked one, by ISO week (newest open) and local day (each week's newest open, others closed until their label is clicked), three rows per day until "show all". Empty while peek is disabled; retries every 15 s while peek does not answer. A row's title opens /sessions-report/<id>.
| Action | Effect |
|---|---|
| Archive: POST /sessions-archive | git mv of a scope's folder, committing only the moved paths: records stay tracked. A folder git does not track (archived before these actions used git) is renamed plainly. |
| Unarchive: POST /sessions-archive/{name}/unarchive | |
| Delete: POST /sessions-archive/{name}/delete | Moves the archived folder to the trash; commits its removal from the index. |
Actions are refused unless the sessions dir is a git checkout; the page redirects only on success.
The server also manages multi-repo worktrees, scheduled routines (launchd, Task Scheduler on Windows) and per-skill analytics. Its command-line flags: Development § Config server flags.
The Context page (/context) browses the docs and sets each entry's and rule's reach, per detail page or in bulk (reach row's bulk reach picker, set reach button). It builds a registered repo's context overlay, picked by registry name; Write to Repo writes it into the repository. Engine: Go deploy engine in internal/contextdocs (Install § Deployment).
~/.claude/smine/repos.json lists the registered repos. env holds the repo's skill values: SKILL_<SKILL>_<KEY> keys only, none empty. Each skill page's Environment section writes its own keys.
{
"repos": [
{
"name": "smine",
"path": "/absolute/path/to/smine",
"env": { "SKILL_FIMPLEMENT_CHECK": "make audit" }
}
]
}
Live session data comes from peek, which the server spawns and talks to over MCP. Peek down: the session column degrades, pages still render. Details: Peek.
Five tabs: Smine, Hooks, Permissions, Claude, Codex. /config lands on Smine.
/config/smine configures the hooks and the heartbeat daemon. A tile grid, three per row, has one tile per catalog component with settings: title, settings count, on/off, overridden count. Below: one component's settings, the tile ?component= names or else the first (active tile highlighted).
| Part | Behaviour |
|---|---|
| Rows | One per setting: key, kind, help, control, default, sink. Reference: Settings. |
| List kinds | strings, globs, paths, models: list editor, never a JSON textarea. One item per line with Remove, an Add field, the default list folded below. Add, Remove and Reset edit the form only; a text left in the Add field is folded in by Save. A yellow pending badge in the panel title shows while the form differs from what was saved. |
| Hidden rows | Rows the catalog marks hidden: off the page, kept in the CLI and the reference: the gate's deny-cause, allow-reason and steering texts; the hook groups' matcher and async fields. |
| Save | POST /config/smine/{component} is the only write: it validates rows against the schema, stores overrides in the install record (empty field or default resets a row), applies the component under the one sync lock, re-renders the panel with the apply report under the Save button. The report reads per component changes applied, changes applied: daemon restarted when the heartbeat LaunchAgent was bootstrapped again, or saved, nothing to apply when the deployed files already held every value; it names changed restart rows whose consumer needs a restart. |
/config/hooks shows only hook groups: a collapsed card per component whose fragment owns live or parked groups (first open), then Other hooks with every live or parked group no component fragment declares, idless ones included.
| Part | Behaviour |
|---|---|
| Group row | smineName; a toggle parking it in hooks.disabled.json or bringing it back; Remove, which keeps the file's previous bytes in the trash and journals the group. |
| Parked by the engine | Component off: smineReason: "component off", live again once the component is reselected. Parked here: no reason. |
| Card toggle | Parks or restores all the component's groups in one write (POST /config/hooks/{component}/groups/toggle?enabled=). Mixed: N of M off. |
| Filter bar | Name, event, matcher or command (?q=); state (?state=all|disabled|local|changed). Every card a filter leaves is open. |
| Group routes | POST /config/hooks/{card}/groups/{event}/{index}/toggle, DELETE /config/hooks/{card}/groups/{event}/{index}; {card}: a component id or other. Addressed by event and position, never smineId. |
| Parked file | hooks.disabled.json, read per request, never cached: a toggle after an apply sees the engine's result. The Overview's Hooks tile counts it too and links the tab. |
| Render-owned fields | Every apply copies timeout, matcher and async onto an equal-version live group, so a cleared hook-group override reverts at once. |
| Global context | The Context page's global-context toggle parks and unparks the two global-context hook groups through the same store. |
/config/permissions shows the live permission lists of ~/.claude/settings.json under the top-level headings Allow, Ask and Deny. Each rule head is a collapsed section (first open); its rows toggle a rule into settings.disabled.json or remove it. Each list has an add form.
Above sits the Claude settings' Sync, Force sync and Persist overrides widget (overridden count at its right end), to write live rules back into the repo fragment. Counts and state badges sit at the right edge of their heading or head row.
Edits the live ~/.claude/settings.json key by key. Hook groups and permissions have their own tabs, so those five keys never render as generic rows.
| Part | Behaviour |
|---|---|
| Generic row | Bool, enum, number, string, array or map key (skillOverrides: one select per skill). Other objects: read-only JSON plus Remove. |
| Mutations | Hook, permission, env, MCP, model and generic-row mutations answer with their own fragment, section and group badges swapped out of band. A generic row that changes state keeps its place until the next page load. Only Sync, Force sync and Persist overrides reload the page body. |
| Button | Direction | Effect |
|---|---|---|
| Sync | repo → live | The install engine's settings step over the stored selection. Adds absent base keys (present values stay) and missing declared permission rules and env keys (live extras stay). Parks unselected components' hook groups with smineReason; removes only groups the repo's retire list names. |
| Force sync | repo → live | Previews every key, hook group, env key, permission entry and parked group it drops. On your confirm, rebuilds the live file from the template alone (base plus selected components' fragments), empties the parked sidecar; both previous files go to the trash. ~/.claude.json keeps its own MCP servers; only the fragments' are reconciled. |
| Persist overrides | live → repo | Writes the live file into the template by ownership: env keys a component fragment declares land there, the rest in settings/claude_code/base.json, which then holds exactly the live keys no fragment owns. Hook groups never write back (their component's fragment owns them by smineId). Install markers ({{HOME}}, ports) collapse on the way back. |
The Skills page's Environment tab (/scripts/skills/environment) shows the install's tools, the global defaults and one skill's Environment widget. Value resolution: Install § Per-repo values.
| Section | Behaviour |
|---|---|
| Tools | A card per provider the selected skills bind through an install-scoped variable, with state: current, stale (missing version, "rebuild and install the <component> component") or missing ("install the <component> component"). Per-provider program override in a folded section. POST /scripts/skills/environment writes the install record's PROVIDER_ rows (skills.providers) and syncs the skills: every deployed copy runs the program in effect. |
| Global defaults | One card, a row per repo-scoped kind a selected skill reads (test, check, build, …): interface id, summary, a field with the example as placeholder, the skills using it. Its one Save, POST /scripts/skills/environment/defaults, checks each value against its interface and writes the record's skills.defaults (empty field clears one), deploying nothing. Every skill on every repo reads it at next invocation unless a skill default or repo value overrides it. The card swaps itself with a notice. |
| Skill widget | A select shows one skill's Environment widget, as on its skill page. GET /scripts/skills/environment/widget?skill=<name>&repo=<name> renders it alone; ?skill= and ?repo= on the tab open it there. |
The widget lists every variable the skill declares, install- and repo-scoped (build, check and test commands, the acdsl program, the plans archive among them). Each row shows interface, required or optional, scope and state. Chips select the default or one registered repo and swap the widget in place, never the page.
| View | Edits | Save |
|---|---|---|
| Default | The record's skills.variables rows, each listing the repos overriding it. An empty repo-scoped row shows the global default beneath (global badge, value as placeholder). | POST /scripts/skills/{origin}/{name}/env redeploys the skill only when an install-scoped value changed. |
| Repo | That repo's values; an empty field falls back to the default, then the global default. | POST /scripts/skills/{origin}/{name}/env/{repo} checks every value against its interface (a relative program resolved inside the repo), writes the skill's keys into the registry row, keeps other skills' keys. Deploys nothing: the skillenv hook exports the row into the repo's sessions. |
| Rule | Detail |
|---|---|
| One Save | Its target is named beside it (· default, · <repo>). Both forms answer with the re-rendered widget and a notice line; a refused value answers 400, shown in the error banner. |
| Unset required | ⚠ unset on its row; ⚠ n unset, naming the variables, on the skill head and index card, any scope. Install-scoped: the deploy skips the skill. Repo-scoped: invocation stops until it is set. Not counted when a global default fills it. |
| Unset optional | The row names the entries, grants and anchors it drops. |
| Layout | The default view names the skill's tools with their state above the rows and links the Environment tab (hidden on that tab). Rows and Save share one card. The contract and per-capability overrides follow in an advanced disclosure behind a divider, a second Save at its end: one form, one widget. |
| Widget alone | GET /scripts/skills/{origin}/{name}/env?repo=<name>, what the chips fetch. |
A skill's Graph tab (GET /scripts/skills/{origin}/{name}/graph) draws its model; GET /scripts/skills/graph?skill=<value> is the same view with a skill picker, defaulting to the first listed skill. It has a row per entry by section, the invocation column on the left (env tiles above args and session tiles) and the all-warnings panel.
| Element | Behaviour |
|---|---|
| Env tile | One $SKILL_ variable with its state mark: ● set, ⚠ unset required, ○ unset optional, ↯ deferred to invocation. Focus draws edges to the entries and params reading it and to the enforce card's anchor rows. Its card: interface, scope, source, value or cause, every reader (grants and checks included). |
| Arg tile | Edges to the entries its Args Effect cell links; an Args row linking none is a structure warning. |
| Warnings panel | Structure warnings and battery findings, each entry named by its title. |
| Endpoint | Does |
|---|---|
| …/graph/scenario | Evaluates the invocation scenario as JSON for the inputs switched on (on=, repeated) and one flipped pred (flip=). |
| …/graph/findings | Runs the model battery. |
| …/graph/diff | Diffs the skill's model files against HEAD. |
Spend history lives in the usage store ~/.claude/smine/usage, filled by the routine smine-budget: an hourly launchd job (minute 7) that starts no agent. Each run:
| # | Step |
|---|---|
| 1 | Mirrors every Claude Code transcript and its subagent files into usage/archive/. The archive only grows: only a larger source replaces a copy, never an equal, truncated or emptied one. History outlives Claude Code's transcript cleanup and peek's watch window. |
| 2 | Captures each session's peek usage (root, skill runs, subagents) and session-report context breakdown. |
| 3 | Runs ccusage (v20.0.20 or newer, on PATH) over that archive and Codex's store: the independent price check. |
Peek and ccusage, the two cost sources, are each an installation component. One off: smine-budget skips that side, the page offers the other alone. Both off, or Routines off (nothing else fills the store): no Usage page, no Overview cost tile.
/usage reads only that store: one source, or both with agreement verdicts, plus an error card per source that failed in the last run. Cost-per-day chart: dollars or plan percent (tokens weighted per model against the one measured Team Premium 5-hour window) for the plan picked on the page, Team Premium or Max 20x.
Skill and tool efficacy still come from Claude Code's native OpenTelemetry (OTel) events, received by peek (Peek § Claude Code telemetry).
Routines wrap claude -p in launchd jobs; the routine binary runs every job and holds its locks and timeout. Operations manual: Routines.
To add one, copy routines/_templates/ into routines/<name>/, rename the plist to the label and edit the marked spots, or use /skillroutine-create. First real routine: routines/smine-nightly/.
| Aspect | smine-nightly |
|---|---|
| Schedule and cell | 03:00, one dispatcher cell: claude-opus-5-5[1m], effort high, budget $300. |
| Stages | style (when a taste source changed), batch, memory, context, permissions, acdsl, problems, implement, consolidate, judge. |
| Output | Change commits plus one ledger commit, on a night branch cut from main. |
| Environment | Non-bare, against the shared ~/.claude (skills, peek-mcp, allowlist). |
| Auth | CLAUDE_CODE_OAUTH_TOKEN from ~/.config/claude-routine/token (0600, written from claude setup-token, never committed). |
| Plist | Ships as .plist.template; the Routines component (bin/configserver -apply) materializes the real, gitignored plist. |
Nothing smine removes or overwrites is lost: previous bytes go to ~/.claude/smine/trash/<stamp>/<original path>, each event appends a line to ~/.claude/smine/journal.jsonl (Trash).
/trash opens from the trash icon at the Overview's bottom right; no nav tab. It lists stashes newest first, each file with size and a Restore button, then the newest 200 journal lines.
| Rule | Detail |
|---|---|
| Restore | POST /trash/{stamp}/restore puts the file back at its original path. Refused over an existing file unless "replace the current file" is ticked, which stashes that file first. |
| Retention | config-server.trash_retention_days (default 0, keep forever): every stash and server boot removes stamp dirs older than that many days. |
| Journal | Never truncated. |