212 lines
12 KiB
Markdown
212 lines
12 KiB
Markdown
# MOPAC harness — design spec v0 (2026-08-28)
|
|
|
|
A Go headless agent harness for the RCEO/TSG org. Replaces bash/screen/file-hack
|
|
inner loop. All patterns established 2026-08-28 STAY; only the substrate
|
|
changes. References: ../reference/{crush,maki}; siblings to port:
|
|
../siblings/{KNEL-AIMiddleware, KNELSecretsManager}.
|
|
|
|
## What it is NOT
|
|
- Not a TUI. Inner loop = pull Redmine -> run turn -> write Discourse/Redmine
|
|
back -> chain next turn. Humans observe via files/Redmine/Discourse, not by
|
|
attaching to the loop.
|
|
- Not a daemon. Bounded turns chaining (drip), same as dispatch-turn.sh today.
|
|
|
|
## Core loop (from DESIGN-24x7-execution.md, carried over)
|
|
1. INTAKE: poll released scope (Redmine query) + local inbox files.
|
|
2. GATE: semaphore (LLM slots, class-aware: llm-heavy|compute-long|hybrid),
|
|
budget from LiteLLM usage API (http://192.168.3.78:4001) — virtual key
|
|
per stack, hard caps enforced at proxy.
|
|
3. EXECUTE: bounded turn, fresh small context, tools below.
|
|
4. WRITEBACK: REPORT file + Redmine issue note (SoR). Discourse for docs.
|
|
5. CHAIN: completion triggers next conductor iteration. P1 first, grinders
|
|
chunked, compute-long tasks run in non-LLM slots.
|
|
6. ESCALATE: anything outside standing-release scope -> TASK file -> founder
|
|
gate. Never auto-executes.
|
|
|
|
## Tools (port from crush/KNEL, Go)
|
|
- bash exec, file edit (tree-sitter-aware parses; see maki's approach),
|
|
redmine-cli, discourse-cli, tea (gitea), docker, bitwarden (replace
|
|
KNELSecretsManager with Go bwraper around `bw` CLI; secrets never in logs).
|
|
- MCP client (crush's mcp plumbing is the reference; wrappers exist in
|
|
siblings/KNEL-AIMiddleware — port config format for compat).
|
|
- LSP: port lsp-* wrappers or embed go.lsp devkit.
|
|
- Permission model: per-tool allow/deny, tree-sitter bash parsing (maki
|
|
reference) — subshells/pipes handled; org preset: no sudo/ssh/network
|
|
exfil; writes confined to declared roots.
|
|
|
|
## Providers
|
|
- OpenAI-compatible only, pointed at LiteLLM (base_url + virtual key).
|
|
OAuth flows (crush-style) OUT of scope for v1 — proxy key is enough.
|
|
- Retry/backoff on stream truncation (z.ai "Message content shorter than
|
|
read bytes" seen 2x today) with turn resume.
|
|
|
|
## Cross-host (5 onstage + 3 offstage)
|
|
- Each host runs harness instances; coordination via _crossfeed files (v1)
|
|
and/or LiteLLM keys as the global budget broker. Event receiver (T1) later.
|
|
|
|
## Build order (go slow, grinder TASKs, quota-aware)
|
|
1. Reference studies: crush oauth/mcp/session code; maki permission parsing +
|
|
token-reduction (index/tool_search). Deliverable: PORTING-NOTES.md each.
|
|
2. harness skeleton: config, loop, redmine-pull, file writeback, exec tool.
|
|
3. Permission layer + bitwarden wrapper.
|
|
4. LiteLLM integration + semaphore/budget gates.
|
|
5. MCP/LSP ports.
|
|
6. Pilot: one RCEO stack on trivial released scope; compare vs drip loop.
|
|
|
|
## Constraints
|
|
- Quota: grinder turns only, no idle polling loops, compact aggressively.
|
|
- Never log secret values; keys from bitwarden at runtime.
|
|
- Everything it does lands in git (tooling repos) or Redmine (work records).
|
|
|
|
## Parity target (Charles, 2026-08-28 ~18:45)
|
|
Harness aims for crush FEATURE PARITY on the agent core: tool calling,
|
|
subagents (weak/medium/strong tiers), MCP client, sessions/resume, skills.
|
|
v1 skeleton (tonight) = the headless subset; parity items land in ordered
|
|
phases after the pilot. TUI explicitly stays out of scope.
|
|
|
|
## Org model + interaction layer (Charles, 2026-08-28 evening)
|
|
- VERTICALS are first-class config: each vertical/stack = identity + LiteLLM
|
|
virtual key + budget + scope query + allowed tools. The harness natively
|
|
models the whole org (5 onstage + 3 offstage), not one loop.
|
|
- INTERACTION LAYER = Redmine (work/SoR), Discourse (docs/specs), Gitea (code,
|
|
PRs). Humans and other agents talk to a stack ONLY through these + git.
|
|
- _crossfeed files/doorbells/screens = internal implementation detail of the
|
|
legacy bash stack; harness keeps file state locally but never requires a
|
|
human to touch it. Chaining is in-process, not screen-stuffing.
|
|
|
|
## Toolchain policy (Charles, 2026-08-28 evening) — HARD RULE
|
|
100% Go for the harness ecosystem: core, CLIs, tools, MCP replacements.
|
|
No Python/Rust/Node in the toolchain (CMMC L3/TS, SCI/ITAR supply-chain
|
|
posture). Exceptions only when unavoidable (e.g. Playwright-class browser
|
|
automation) and each requires explicit founder sign-off, isolated + vendored.
|
|
- Deps: minimal, vendored, reproducible builds, go vet + staticcheck clean.
|
|
- Existing Go assets to REUSE, not rewrite: tea (gitea), LiteLLM (service,
|
|
not ours). Bitwarden: Go wrapper replaces Node bw CLI dependency.
|
|
|
|
## Tooling architecture (open question -> PMO recommendation)
|
|
Recommendation: SEPARATE Go executables (MOPAC/tools/: redmine-cli,
|
|
discourse-cli, bitwarden-go, ...) driven by the harness via exec with a
|
|
JSON in/out contract + thin in-process adapter for hot paths.
|
|
Why: (1) auditable per-binary supply chain; (2) CLIs stay usable standalone
|
|
by humans/legacy stacks — interaction layer must not require the harness;
|
|
(3) Unix plumbing/porcelain split, same model as git; (4) in-process plugin
|
|
interface can come later without redesign. MCP servers we run get replaced
|
|
by these Go CLIs behind a minimal MCP adapter, killing the docker/python
|
|
MCP fleet.
|
|
|
|
## MVP demo bar (Charles, 2026-08-28 ~19:00)
|
|
Fire up the harness (screen or docker) and run a turn whose prompt is
|
|
"tell me about yourself" routed through LiteLLM; the GLM 5.3 self-
|
|
description comes back and lands as the REPORT. That is the MVP. The
|
|
0800 checklist (compile, dry-run, real single-turn, LSP wired, studies)
|
|
builds toward it; the demo itself is this one exchange.
|
|
|
|
## Model selection layer (Charles, 2026-08-28 ~19:00)
|
|
Claude-Code-style model picking for the harness and its subagents:
|
|
the harness chooses the right model per body of work (overall + per-task).
|
|
GLM fleet is wide; use it deliberately.
|
|
- Inventory on proxy today (12): glm-4.5{,-air,v}, 4.6{,v}, 4.7{,-flash},
|
|
5, 5-turbo, 5.1, 5.2, 5.3. No glm-5.3-flash configured yet; flash-class
|
|
today = glm-4.7-flash. Adding 5.3-flash later = one LiteLLM model_list
|
|
entry, zero code change (see aliases).
|
|
- v0 (tonight, skeleton): static TASK CLASS -> MODEL TIER map in
|
|
harness.toml. Classes: study/read -> flash tier; code/architecture ->
|
|
flagship (glm-5.2/5.3); review/summarize/writeback -> mid (5-turbo/5.1);
|
|
vision when needed (4.6v/4.5v). Config-only, no heuristic code.
|
|
- Aliases are the contract: harness requests `mopac-study`, `mopac-code`,
|
|
`mopac-review` ... LiteLLM resolves alias -> concrete model. Swapping
|
|
models (e.g. 4.7-flash -> 5.3-flash when live) = proxy config edit only.
|
|
- v1 (post-pilot): auto-selection — cheap flash model classifies each
|
|
incoming TASK (Redmine issue text) and picks tier + model before the
|
|
main turn spends anything; subagent spawns inherit class-based routing
|
|
with per-tier budget caps (LiteLLM vkeys already enforce spend).
|
|
- Telemetry: every REPORT logs model used + tokens + cost via LiteLLM
|
|
spend APIs, so routing decisions are auditable and tunable.
|
|
|
|
## Key proxy: placeholders only, material never leaves the vault (Charles, 2026-08-28 evening)
|
|
- PROBLEM with existing gateways: they HOLD real keys in a webui/api and
|
|
serve placeholders. We invert that: key material lives ONLY in Bitwarden
|
|
(Secrets Manager) + HashiCorp Vault. A Go proxy (MOPAC/tools/keyproxy or
|
|
bitwarden-go evolution) resolves placeholder -> real at the wire, holds
|
|
material in memory only (fetch-on-demand, short TTL, never persisted,
|
|
never in any admin UI; there is no admin surface — config only).
|
|
- FRONTEND: consumers (harness, CLIs, containers) receive placeholder keys
|
|
(mpk_... opaque refs). If one leaks, it's a placeholder: revoke/re-map in
|
|
vault, zero upstream exposure. Defense in depth.
|
|
- BACKEND connectors: Bitwarden Secrets Manager REST (machine accounts;
|
|
AVOID the official SDK — source-available license conflicts with AGPL;
|
|
plain REST via stdlib keeps AGPLv3 clean) + HashiCorp Vault KV v2 +
|
|
AppRole (official Go api pkg is MPL-2.0, AGPL-compatible, vendor it).
|
|
License: AGPLv3; MIT fallback only if a conflict still arises.
|
|
- ADOPT crush's exposure-minimization protocol everywhere (per
|
|
docs/PORTING-NOTES-crush.md + house practice): keys via os.environ/VAR
|
|
indirection in configs (never literals), 0600 on secret files, redaction
|
|
of key material in all logs/output, canonical-source regeneration
|
|
(crush.json -> providers.json pattern), never exec env files inside
|
|
scripting languages, session scrubbing.
|
|
- Also feeds: study-secrets TASK (KNELSecretsManager notes) in flight.
|
|
LiteLLM stays a sanctioned service exception (its z.ai key can later be
|
|
injected at container start from the vault; not tonight).
|
|
|
|
## Tooling = standalone public FLOSS repos, loosely coupled (Charles, 2026-08-28 evening)
|
|
- Dedicated CLIs/services each get their OWN public Gitea repo under the
|
|
UKRRS org (org for ALL LLM work; AGPLv3), composable by anyone:
|
|
ukrrs/mopac-keyproxy (created, seeded), later mopac-redmine-cli,
|
|
mopac-discourse-cli, mopac-bitwarden-go, ... ukrrs/MOPAC (transferred
|
|
2026-08-28) is the harness core. MOPAC references them; it does not
|
|
contain them.
|
|
- LOOSE COUPLING RULES (FLOSS without support burden): config-driven —
|
|
no org-specific hosts/paths/defaults baked into code; contracts are
|
|
exec + JSON in/out (Unix plumbing); each tool standalone-usable by a
|
|
human; TSYS-specific policy/policy repos (agent-stack) live OUTSIDE the
|
|
tools. We build for our needs first; genericness comes from clean
|
|
contracts, not from supporting foreign requirements.
|
|
|
|
## ALL dev work in Docker (Charles, 2026-08-28 evening) — BIG RULE
|
|
- No dev toolchains on the host: compiling, vetting, testing, hacking all
|
|
happen inside containers (e.g. digest-pinned golang:1.26 builder with the
|
|
repo bind-mounted, `go build/vet/test` inside, artifacts out via the
|
|
mount). Host runs containers + screens; nothing else.
|
|
- docker pull of anything needed is PRE-AUTHORIZED, no asking.
|
|
- Exception cleanup: the harness-skeleton turn installed go1.26.7 to
|
|
host ~/.local before this rule landed — remove it (follow-up TASK) once
|
|
that turn completes; all future builds go through the builder container.
|
|
- Rationale: reproducible, supply-chain-clean (CMMC posture), host stays
|
|
pristine; same pattern as the LSP stack (digest-pinned containers).
|
|
|
|
## Events are V1 scope: webhook receiver now (Charles, 2026-08-28 evening)
|
|
- NOT deferred. `mopac events` (Go HTTP receiver, in ukrrs/MOPAC) ships with
|
|
the first build. The interaction layer (Redmine/Discourse/Gitea) must be
|
|
able to PUNCH the harness: when Charles replies on Discourse, updates a
|
|
Redmine ticket, or approves a Gitea PR, the harness knows and acts.
|
|
- Trigger -> action semantics (dedup + replay, at-least-once):
|
|
Redmine issue update/note on an active stack's issue -> dispatch/redirect
|
|
turn for that stack; Discourse post reply in a stack's category/topic ->
|
|
context update + response turn; Gitea PR approved/merged -> next pipeline
|
|
step (build/test/deploy per repo config).
|
|
- Signatures: Gitea HMAC-SHA256 secret header; Discourse webhook secret
|
|
header; Redmine shared-secret header (verify, reject unsigned). Normalize
|
|
to internal event record -> Redmine issue (SoR) -> conductor picks up.
|
|
- Runs containerized (dev-in-docker rule), port published on host LAN;
|
|
Cloudron apps get the webhook URL configured per-app.
|
|
|
|
## Hermes killed; OpenWebUI is the interactive front door (Charles, 2026-08-28 evening)
|
|
- Hermes was Node — removed (dashboard/gateway/searxng, 2026-08-28 ~19:10).
|
|
No Node middleware anywhere in MOPAC's path.
|
|
- OWUI (Cloudron + SSO) becomes the ONLY interactive surface: MOPAC serves
|
|
an OpenAI-compatible /v1/chat/completions endpoint (Go stdlib HTTP, in
|
|
this repo, e.g. `mopac serve`). OWUI adds it as a connection; each stack/
|
|
vertical registers as a "model". OWUI admin UI + Cloudron SSO = RBAC we
|
|
do not build. Keys minted per OWUI connection (LiteLLM vkeys pattern).
|
|
- Event path unchanged: Redmine/Discourse/Gitea webhooks -> `mopac events`
|
|
(Go T1 receiver). OWUI chat and webhooks are two doors into the same loop.
|
|
|
|
## Budget + quota reality (Charles, 2026-08-28 evening)
|
|
- Until 2026-09-22: legacy z.ai coding plan = FLAT RATE. Real budget = plan
|
|
quota, not USD. Smaller/flash models stretch quota ~3x. Routing policy:
|
|
grind/study/summarize -> flash tier (glm-5.3-flash now live on proxy,
|
|
13 models); flagship (glm-5.3/5.2) only when the work needs it. Maximize
|
|
usage of what we already pay for.
|
|
- After 2026-09-22: org-wide AI cap $200/mo across all 8 stacks:
|
|
2x $80 z.ai max plans + $40 OpenRouter. Design all budgets/vkeys to fit.
|