Compare commits

..

1 Commits

Author SHA1 Message Date
Jason Woltje
764ef1be58 feat(appservice): mosaic-as daemon host + container (M4a deploy prep, agent-comms#9)
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
apps/appservice: framework-agnostic node:http daemon hosting
@mosaicstack/appservice — Synapse transactions endpoint passthrough,
internal bridge API v1 (messages/typing) with HMAC-digest timing-safe
bearer tokens, 1MiB request body cap, explicit 405 inside the
authenticated bridge block (no fall-through around auth), health endpoint,
env config, registration YAML printer bin. docker/appservice.Dockerfile
mirrors the gateway multi-stage pnpm/turbo pattern, runs as USER node
with container healthcheck. 9 vitest tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:02:10 -05:00
15 changed files with 522 additions and 1555 deletions

View File

@@ -114,31 +114,6 @@ steps:
depends_on: depends_on:
- build - build
build-appservice:
image: gcr.io/kaniko-project/executor:debug
environment:
REGISTRY_USER:
from_secret: gitea_username
REGISTRY_PASS:
from_secret: gitea_password
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
commands:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/appservice:sha-${CI_COMMIT_SHA:0:7}"
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:latest"
fi
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:$CI_COMMIT_TAG"
fi
/kaniko/executor --context . --dockerfile docker/appservice.Dockerfile $DESTINATIONS
depends_on:
- build
build-web: build-web:
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
environment: environment:

View File

@@ -39,9 +39,3 @@ Active workstream is **W1 — Federation v1**. Workers should:
1. Read [docs/federation/MISSION-MANIFEST.md](./federation/MISSION-MANIFEST.md) for workstream scope 1. Read [docs/federation/MISSION-MANIFEST.md](./federation/MISSION-MANIFEST.md) for workstream scope
2. Read [docs/federation/TASKS.md](./federation/TASKS.md) for the next pending task 2. Read [docs/federation/TASKS.md](./federation/TASKS.md) for the next pending task
3. Follow per-task agent + tier guidance from the workstream manifest 3. Follow per-task agent + tier guidance from the workstream manifest
## Thin-core prompt diet (#528) — feat/contract-thin-core
- Status: PR open, awaiting maintainer merge ratification (fleet-governing change).
- Cut always-injected contract AGENTS+TOOLS+RUNTIME 8,827→4,122 tok (53%); all 12 hard gates intact.
- Validation: deterministic gate-checklist PASS; headless A/B thin 7/9 vs monolith 5/9. Detail: scratchpads/contract-thin-core.md.

View File

@@ -453,26 +453,6 @@ Initialize standard labels and the first pre-MVP milestone:
--- ---
## Secrets Bootstrap (Required for Every New App)
Every new application MUST complete the following secrets bootstrap before deploying to any non-local environment. This is a hard gate — deployment without completed secrets bootstrap is forbidden.
### Secrets bootstrap checklist
- [ ] Vault path created: `vault kv put secret/k3s/<app>/ ...` with all required secret fields
- [ ] Required secrets listed in project README under a "Secrets architecture" section, including:
- Vault path(s) used
- All required secret keys and their purpose
- Whether the app uses ESO bridge (default) or Direct-Vault (opt-in, with justification)
- [ ] `external-secret.yaml` manifest committed to repo's `deploy/` or `k8s/` directory
- [ ] Deployment YAML references the synced k8s Secret via `secretKeyRef` (not raw env vars or `.env` files)
- [ ] App startup has schema-based validation for all required env vars (zod / pydantic / envconfig equivalent) that exits non-zero on missing required values
- [ ] Direct-Vault opt-in (if applicable): justification documented in README + AppRole provisioned + bootstrap credentials stored in Vault and synced via a separate `ExternalSecret`
See `~/.config/mosaic/guides/VAULT-SECRETS.md` for full worked examples of the ESO bridge pattern, the Direct-Vault opt-in pattern, and the forbidden antipatterns.
---
## Checklist ## Checklist
After bootstrapping, verify: After bootstrapping, verify:

View File

@@ -203,374 +203,3 @@ Error: token expired
3. **Audit logging** - All access is logged; act accordingly 3. **Audit logging** - All access is logged; act accordingly
4. **No local copies** - Don't store secrets in files or env vars long-term 4. **No local copies** - Don't store secrets in files or env vars long-term
5. **Rotate on compromise** - Immediately rotate any exposed secrets 5. **Rotate on compromise** - Immediately rotate any exposed secrets
---
## Secrets Architecture Decision Matrix
Use this table to choose between the ESO bridge (default) and Direct-Vault (opt-in) patterns for every new app or integration.
| Factor | ESO Bridge (default) | Direct-Vault (opt-in) |
| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Use-case** | All static secrets (DB creds, API keys, signing keys, OAuth secrets) | Dynamic creds with short TTLs (DB rotation, AWS STS, PKI), per-request audit trails, or lease renewal mid-pod-lifecycle |
| **App code change** | None — reads standard env vars via `secretKeyRef` | Requires Vault client (`hvac`, `node-vault`, `vault/api`) in application code |
| **Secret rotation** | ESO re-syncs on Vault write; pod restart or secret refresh picks up new value | App manages lease renewal or re-auth within the running process |
| **Audit granularity** | Access logged at Vault when ESO syncs; no per-request app audit | Every app request to Vault is a separate audit log entry |
| **Operational burden** | Low — ESO handles polling, sync, and k8s Secret lifecycle | Higher — app must handle auth, lease renewal, error paths, and token rotation |
| **Justification required?** | No — this is the default | Yes — document in project README under "Secrets architecture" |
| **Example use cases** | Web app DB password, OAuth client secret, JWT signing key, API token | HashiCorp DB secrets engine with 15-min TTL leases, AWS STS assume-role, Vault PKI short-lived certs |
**Decision rule:** If you are unsure, use ESO. Only justify Direct-Vault when the secret cannot be safely stored in a k8s Secret (too short-lived, per-request TTL required, or mid-lifecycle renewal needed).
---
## ESO Bridge Pattern (Default)
This is the required default for all k8s workloads. Follow this exact pattern unless a documented dynamic-secrets requirement justifies Direct-Vault.
### 1. Provision Vault path
```bash
# Write the secrets for the app (run once; use IaC/Terraform for repeatable provisioning)
vault kv put secret/k3s/<app> \
db_password="..." \
api_key="..." \
jwt_secret="..."
```
Use the canonical path structure: `secret/k3s/<app>` for k3s cluster workloads.
### 2. ExternalSecret manifest
Commit this to the repo's `deploy/` or `k8s/` directory:
```yaml
# deploy/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: <app>-secrets
namespace: <namespace>
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend # ClusterSecretStore name — verify with cluster admin
kind: ClusterSecretStore
target:
name: <app>-secrets # k8s Secret name that will be created
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD # key in the k8s Secret
remoteRef:
key: secret/k3s/<app> # Vault path
property: db_password # field within the Vault secret
- secretKey: API_KEY
remoteRef:
key: secret/k3s/<app>
property: api_key
- secretKey: JWT_SECRET
remoteRef:
key: secret/k3s/<app>
property: jwt_secret
```
### 3. Deployment manifest — reference synced k8s Secret
```yaml
# deploy/deployment.yaml (env section)
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: <app>-secrets # matches ExternalSecret target.name
key: DB_PASSWORD
- name: API_KEY
valueFrom:
secretKeyRef:
name: <app>-secrets
key: API_KEY
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: <app>-secrets
key: JWT_SECRET
- name: PORT
value: '3000' # safe-default: non-secret, no Vault needed
```
### 4. App-side schema validation — TypeScript (zod)
Validate all required env vars at startup. Exit non-zero on missing values.
```typescript
// src/env.ts
import { z } from 'zod';
const envSchema = z.object({
DB_PASSWORD: z.string().min(1, 'DB_PASSWORD is required'),
API_KEY: z.string().min(1, 'API_KEY is required'),
JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 chars'),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']).default('production'),
});
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('Missing or invalid environment variables:');
console.error(result.error.flatten().fieldErrors);
process.exit(1);
}
export const env = result.data;
```
### 4b. App-side schema validation — Python (pydantic)
```python
# src/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
db_password: str
api_key: str
jwt_secret: str
port: int = 3000
node_env: str = "production"
model_config = SettingsConfigDict(env_file=None) # no .env in prod
try:
settings = Settings()
except Exception as e:
import sys
print(f"Missing or invalid environment variables: {e}", file=sys.stderr)
sys.exit(1)
```
### 4c. App-side schema validation — Go (envconfig)
```go
// config/config.go
package config
import (
"fmt"
"github.com/kelseyhightower/envconfig"
)
type Config struct {
DBPassword string `envconfig:"DB_PASSWORD" required:"true"`
APIKey string `envconfig:"API_KEY" required:"true"`
JWTSecret string `envconfig:"JWT_SECRET" required:"true"`
Port int `envconfig:"PORT" default:"3000"`
}
func Load() (*Config, error) {
var cfg Config
if err := envconfig.Process("", &cfg); err != nil {
return nil, fmt.Errorf("invalid environment: %w", err)
}
return &cfg, nil
}
```
In your `main.go`:
```go
cfg, err := config.Load()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
```
---
## Direct-Vault Opt-In Pattern
Use this pattern ONLY when a documented dynamic-secrets requirement applies (DB rotation with short TTLs, AWS STS, PKI, per-request audit). Document the justification in the project README under "Secrets architecture" before implementing.
### When it is justified
- Vault DB secrets engine with lease TTLs shorter than a typical pod lifecycle (< 1 hour)
- AWS STS assume-role tokens generated per-request
- Vault PKI short-lived certificates (< 24 hours) that must be renewed within a running pod
- Per-request audit trail requirement (each app call must appear separately in Vault audit log)
### Provision an AppRole for the app
```bash
# Enable AppRole auth (if not already enabled)
vault auth enable approle
# Create a Vault policy for the app
# Note: KV v2 paths require both the exact path (for the top-level secret) and the
# wildcard (for sub-paths). Always include both to avoid permission denied errors.
vault policy write <app>-policy - <<EOF
path "secret/data/k3s/<app>" {
capabilities = ["read"]
}
path "secret/data/k3s/<app>/*" {
capabilities = ["read"]
}
path "database/creds/<app>-role" {
capabilities = ["read"]
}
EOF
# Create the AppRole
vault write auth/approle/role/<app>-role \
token_policies="<app>-policy" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=0
# Retrieve role-id and secret-id
vault read auth/approle/role/<app>-role/role-id
vault write -f auth/approle/role/<app>-role/secret-id
```
### Bootstrap AppRole credentials via ESO (solving the chicken-and-egg problem)
The AppRole `role-id` and `secret-id` are themselves secrets. Store them in Vault at a bootstrap path, then use ESO to sync them into a k8s Secret. The app reads that k8s Secret at startup to authenticate with Vault directly.
```bash
# Store the bootstrap credentials in Vault
vault kv put secret/k3s/<app>-bootstrap \
role_id="<role-id>" \
secret_id="<secret-id>"
```
```yaml
# deploy/external-secret-bootstrap.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: <app>-vault-auth
namespace: <namespace>
spec:
refreshInterval: 24h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: <app>-vault-auth
creationPolicy: Owner
data:
- secretKey: VAULT_ROLE_ID
remoteRef:
key: secret/k3s/<app>-bootstrap
property: role_id
- secretKey: VAULT_SECRET_ID
remoteRef:
key: secret/k3s/<app>-bootstrap
property: secret_id
```
```yaml
# deploy/deployment.yaml (env section for Direct-Vault app)
env:
- name: VAULT_ADDR
value: 'https://vault.example.com' # safe-default: non-secret cluster address
- name: VAULT_ROLE_ID
valueFrom:
secretKeyRef:
name: <app>-vault-auth
key: VAULT_ROLE_ID
- name: VAULT_SECRET_ID
valueFrom:
secretKeyRef:
name: <app>-vault-auth
key: VAULT_SECRET_ID
```
### App-side Vault client pattern
```typescript
// src/vault-client.ts — only exists in Direct-Vault apps
import vault from 'node-vault';
import { z } from 'zod';
const bootstrapSchema = z.object({
VAULT_ADDR: z.string().url(),
VAULT_ROLE_ID: z.string().min(1),
VAULT_SECRET_ID: z.string().min(1),
});
const bootstrap = bootstrapSchema.parse(process.env);
const client = vault({ endpoint: bootstrap.VAULT_ADDR });
export async function getVaultClient() {
const { auth } = await client.approleLogin({
role_id: bootstrap.VAULT_ROLE_ID,
secret_id: bootstrap.VAULT_SECRET_ID,
});
client.token = auth.client_token;
return client;
}
```
Document in README under "Secrets architecture": the Vault path, why Direct-Vault is required, and the lease/renewal strategy.
---
## Forbidden Patterns (CI Lint Targets)
The following patterns are forbidden in all Mosaic projects. CI lint SHOULD catch these automatically (implementation tracked separately). Agents MUST NOT introduce these patterns.
### 1. Untagged fallback defaults for required values
```yaml
# FORBIDDEN — required secret with silent fallback
environment:
- DB_PASSWORD=${DB_PASSWORD:-changeme}
- API_KEY=${API_KEY:-}
# REQUIRED — fast-fail on missing required values
environment:
- DB_PASSWORD=${DB_PASSWORD:?DB_PASSWORD is required}
- API_KEY=${API_KEY:?API_KEY is required}
# ALLOWED — true convenience default, tagged
environment:
- PORT=${PORT:-3000} # safe-default: non-secret, app works at any port
```
This applies to: `docker-compose.yml`, k8s manifests, Helm `values.yaml`, any env file committed to git.
### 2. Vault KV calls in application source code (ESO-default projects)
```python
# FORBIDDEN in ESO-default apps — direct Vault client in app source
import hvac
client = hvac.Client(url=os.environ['VAULT_ADDR'])
secret = client.secrets.kv.v2.read_secret_version(path='myapp/db')
```
ESO-default apps read env vars only. Direct-Vault clients belong only in apps with a documented dynamic-secrets justification in README.
### 3. Hardcoded secrets or API keys in committed files
```python
# FORBIDDEN — hardcoded credential
DB_PASSWORD = "supersecret123"
API_KEY = "sk-live-abc123"
```
No exceptions. CI lint must flag any string matching common secret patterns (`password`, `secret`, `api_key`, `token` assigned a literal non-env-var value).
### 4. `.env` files in production deployment paths
```
# FORBIDDEN — .env file in a production deploy path
deploy/.env
k8s/.env
docker/.env
# ALLOWED — local dev only
.env.example # template only, no real values
.env # local dev, must be in .gitignore
```
`.env` files are acceptable in local-dev contexts only and MUST be in `.gitignore`. They are forbidden in any path that a CI pipeline or production deployment process reads directly.

View File

@@ -1,24 +1,28 @@
# Mosaic Global Agent Contract # Mosaic Global Agent Contract
Canonical file: `~/.config/mosaic/AGENTS.md`. Mandatory behavior for all Mosaic agent runtimes. Canonical file: `~/.config/mosaic/AGENTS.md`
This is the THIN CORE — the launcher injects it (plus USER.md, the TOOLS index, and the runtime This file defines the mandatory behavior for all Mosaic agent runtimes.
contract) into every session. It carries only what must be resident to avoid violating a gate.
Depth lives in guides, read on demand (see Conditional Guide Loading).
## Session Start — Load Order ## MANDATORY Load Order (No Exceptions)
The core contract is ALREADY in your context (injected by `mosaic` launch). Do not re-read it. Before responding to any user message, you MUST read these files in order:
At session start, additionally:
1. Read `~/.config/mosaic/SOUL.md` (agent identity — small, once). 1. `~/.config/mosaic/SOUL.md`
2. Read project-local `AGENTS.md` / `CLAUDE.md` if present. 2. `~/.config/mosaic/USER.md`
3. Read guides ONLY as triggered by the Conditional Guide Loading table below. Do NOT pre-load 3. `~/.config/mosaic/STANDARDS.md`
guides you do not need — role-relevant detail is pulled on demand, not up front. 4. `~/.config/mosaic/AGENTS.md`
4. When you begin implementation work, read `~/.config/mosaic/guides/E2E-DELIVERY.md` (the full 5. `~/.config/mosaic/TOOLS.md`
delivery procedure: PRD/tracking gates, execution cycle, testing, review, completion). 6. `~/.config/mosaic/guides/E2E-DELIVERY.md`
5. `~/.config/mosaic/STANDARDS.md` is available for reference; load it only if the task requires 7. `~/.config/mosaic/guides/MEMORY.md`
standards validation (do NOT halt if missing). 8. Project-local `AGENTS.md` (if present)
9. Runtime-specific reference:
- Pi: `~/.config/mosaic/runtime/pi/RUNTIME.md`
- Claude: `~/.config/mosaic/runtime/claude/RUNTIME.md`
- Codex: `~/.config/mosaic/runtime/codex/RUNTIME.md`
- OpenCode: `~/.config/mosaic/runtime/opencode/RUNTIME.md`
If any required file is missing, you MUST stop and report the missing file.
## CRITICAL HARD GATES (Read First) ## CRITICAL HARD GATES (Read First)
@@ -33,39 +37,56 @@ At session start, additionally:
9. Do NOT stop at "PR created". Do NOT ask "should I merge?" Do NOT ask "should I close the issue?". 9. Do NOT stop at "PR created". Do NOT ask "should I merge?" Do NOT ask "should I close the issue?".
10. Manual `docker build` / `docker push` for deployment is FORBIDDEN when CI/CD pipelines exist in the repository. CI is the ONLY canonical build path for container images. 10. Manual `docker build` / `docker push` for deployment is FORBIDDEN when CI/CD pipelines exist in the repository. CI is the ONLY canonical build path for container images.
11. Before ANY build or deployment action, you MUST check for existing CI/CD pipeline configuration (`.woodpecker/`, `.woodpecker.yml`, `.github/workflows/`, etc.). If pipelines exist, use them — do not build locally. 11. Before ANY build or deployment action, you MUST check for existing CI/CD pipeline configuration (`.woodpecker/`, `.woodpecker.yml`, `.github/workflows/`, etc.). If pipelines exist, use them — do not build locally.
12. The mandatory intake procedure is NOT conditional on perceived task complexity. A "simple" commit-push-deploy task has the same procedural requirements as a multi-file feature. Skipping intake because a task "seems simple" is the most common framework violation. 12. The mandatory load order and intake procedure are NOT conditional on perceived task complexity. A "simple" commit-push-deploy task has the same procedural requirements as a multi-file feature. Skipping intake because a task "seems simple" is the most common framework violation.
## Non-Negotiable Operating Rules (condensed — full detail in `guides/E2E-DELIVERY.md`) ## Non-Negotiable Operating Rules
- **Source of requirements:** `docs/PRD.md`/`docs/PRD.json` MUST exist before coding. In steered autonomy, make best-guess PRD decisions, mark each `ASSUMPTION:` with rationale, continue. (`guides/PRD.md`) 1. You MUST create and maintain a task-specific scratchpad for every non-trivial task.
- **Tracking:** create/maintain a scratchpad and `docs/TASKS.md` for every non-trivial task; keep current through completion. 2. You MUST follow the end-to-end procedure in `E2E-DELIVERY.md`.
- **Execution cycle:** `plan code test review remediate review commit push greenfield situational test repeat`. On failure, remediate and re-run from the failed step. 3. You MUST execute this cycle for implementation work: `plan -> code -> test -> review -> remediate -> review -> commit -> push -> greenfield situational test -> repeat`.
- **Testing:** run baseline tests before any completion claim. Situational testing is the PRIMARY gate. Risk-based TDD is REQUIRED for bug fixes, security/auth/permission logic, and critical data mutations. (`guides/QA-TESTING.md`) 4. Before coding begins, `docs/PRD.md` or `docs/PRD.json` MUST exist and be treated as the source of requirements.
- **Review:** if you modify source code, an independent code review MUST pass before completion. (`guides/CODE-REVIEW.md`) 5. The main agent MUST prepare or update the PRD using user objectives, constraints, and available project context before implementation starts.
- **Evidence:** provide explicit verification evidence before any completion claim. Never use workarounds that bypass quality gates. 6. In steered autonomy mode, the agent MUST make best-guess PRD decisions when needed, mark each with `ASSUMPTION:` and rationale, and continue without waiting for routine user approval.
- **Secrets & deps:** never hardcode secrets (`guides/VAULT-SECRETS.md`); never use deprecated/unsupported dependencies. 7. You MUST run baseline tests before claiming completion.
- **Git strategy:** trunk-based — branch from `main`, merge to `main` via PR only (squash merge), never push directly to `main`. 8. Situational testing is the PRIMARY validation gate. You MUST run situational tests based on the change surface.
- **Provider work:** detect platform first, then use `~/.config/mosaic/tools/git/*.sh` wrappers before any raw `gh`/`tea`/`glab`. Create/link issue(s) in `docs/TASKS.md` before coding; if no provider, use `TASKS:<id>` refs. 9. TDD is risk-based and REQUIRED for bug fixes, security/auth/permission logic, and critical business logic/data mutations (see `~/.config/mosaic/guides/QA-TESTING.md`).
- **Deployment:** own it when in scope and access is configured. Use immutable image tags (`sha-*`, `vX.Y.Z-rc.N`) with digest-first promotion; `latest` is forbidden as a deployment reference. (`guides/INFRASTRUCTURE.md`) 10. If you modify source code, you MUST run an independent code review before completion.
- **Release:** on milestone completion, create + push a release tag and publish a repository release. 11. You MUST update required documentation for code/API/auth/infra changes per `~/.config/mosaic/guides/DOCUMENTATION.md`.
- **Documentation:** update required docs for code/API/auth/infra changes; keep `docs/` root clean (scoped folders). (`guides/DOCUMENTATION.md`) 12. You MUST provide verification evidence before completion claims.
- **TypeScript:** DTO files (`*.dto.ts`) REQUIRED for module/API boundaries. (`guides/TYPESCRIPT.md`) 13. You MUST NOT use workarounds that bypass quality gates.
- **Ownership:** own execution end-to-end (plan→deploy). Human intervention is escalation-only — do not ask the human to do routine coding, review, or repo work. 14. You MUST NOT hardcode secrets.
- **Budget:** honor user plan/token budgets; adjust execution strategy to stay within limits. 15. You MUST NOT use deprecated or unsupported dependencies.
16. When a milestone is completed, you MUST create and push a release tag and publish a repository release.
17. For every non-trivial implementation task, you MUST create or update `docs/TASKS.md` before coding and keep it current through completion.
18. You MUST keep `docs/` root clean and place reports/artifacts in scoped folders per `~/.config/mosaic/guides/DOCUMENTATION.md`.
19. For TypeScript codebases, DTO files are REQUIRED for module/API boundaries (`*.dto.ts`).
20. You MUST honor user plan/token budgets: monitor estimated vs used tokens and adjust execution strategy to stay within limits.
21. You MUST use trunk merge strategy: branch from `main`, merge to `main` via PR only, never push directly to `main`, and use squash merge only.
22. You MUST own project execution end-to-end: planning, coding, testing, review, remediation, PR/repo operations, release/tag, and deployment when in scope.
23. Human intervention is escalation-only; do not ask the human to perform routine coding, review, or repository management work.
24. Deployment ownership is REQUIRED when deployment is in scope and target access is configured.
25. For container deployments, you MUST use immutable image tags (`sha-*`, `vX.Y.Z-rc.N`) with digest-first promotion; `latest` is forbidden as a deployment reference.
26. If an external git provider is available (Gitea/GitHub/GitLab), you MUST create or update issue(s) and link them in `docs/TASKS.md` before coding; if unavailable, use `TASKS:<id>` internal refs in `docs/TASKS.md`.
27. For provider operations (issue/PR/milestone), you MUST detect platform first and use `~/.config/mosaic/tools/git/*.sh` wrappers before any raw provider CLI/API calls.
28. Direct `gh`/`tea`/`glab` commands are forbidden as first choice when a Mosaic wrapper exists; use raw commands only as documented fallback.
29. If the mission is orchestration-oriented (contains "orchestrate", issue/milestone coordination, or multi-task execution), you MUST load and follow `~/.config/mosaic/guides/ORCHESTRATOR.md` before taking action.
30. At session start, you MUST declare the operating mode in your first response before any tool calls or implementation steps.
31. For orchestration-oriented missions, the first line MUST be exactly: `Now initiating Orchestrator mode...`
32. For non-orchestrator implementation missions, the first line MUST be exactly: `Now initiating Delivery mode...`
33. For explicit review-only missions, the first line MUST be exactly: `Now initiating Review mode...`
34. For source-code delivery through PR workflow, completion is forbidden until the PR is merged to `main`, CI/pipeline status is terminal green, and linked issue/internal task is closed.
35. If merge/CI/issue-closure operations fail, you MUST report a blocker with the exact failed wrapper command and stop instead of declaring completion.
36. Before push or PR merge, you MUST run CI queue guard and wait if the project has running/queued pipelines: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge`.
37. When an active mission is detected at session start (MISSION-MANIFEST.md, TASKS.md, or scratchpads/ present), you MUST load `~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md` and follow the Session Resume Protocol before taking any action.
## Mode Declaration Protocol (Hard Rule) ## Mode Declaration Protocol (Hard Rule)
At session start, declare exactly one mode as the first line, before any tool call or step: At session start, declare one mode before any actions:
1. Orchestration mission: `Now initiating Orchestrator mode...` 1. Orchestration mission: `Now initiating Orchestrator mode...`
2. Implementation mission: `Now initiating Delivery mode...` 2. Implementation mission: `Now initiating Delivery mode...`
3. Review-only mission: `Now initiating Review mode...` 3. Review-only mission: `Now initiating Review mode...`
Orchestration-oriented = contains "orchestrate", issue/milestone coordination, or multi-task
execution → also load `guides/ORCHESTRATOR.md` before acting. If an active mission is detected at
session start (MISSION-MANIFEST.md, TASKS.md, or scratchpads/ present) → load
`guides/ORCHESTRATOR-PROTOCOL.md` and follow the Session Resume Protocol before any action.
## Steered Autonomy Escalation Triggers ## Steered Autonomy Escalation Triggers
Only interrupt the human when one of these is true: Only interrupt the human when one of these is true:
@@ -76,69 +97,136 @@ Only interrupt the human when one of these is true:
4. Legal/compliance/security constraints are unknown and materially affect delivery. 4. Legal/compliance/security constraints are unknown and materially affect delivery.
5. Objectives are mutually conflicting and cannot be resolved from PRD, repo, or prior decisions. 5. Objectives are mutually conflicting and cannot be resolved from PRD, repo, or prior decisions.
## Conditional Guide Loading (role/task-driven — load only what the task needs) ## Conditional Guide Loading
| Task | Guide | Load additional guides when the task requires them.
| -------------------------------------------------- | ---------------------------------- |
| Project bootstrap | `guides/BOOTSTRAP.md` |
| PRD creation / requirements | `guides/PRD.md` |
| Orchestration flow | `guides/ORCHESTRATOR.md` |
| Mission lifecycle / multi-session orchestration | `guides/ORCHESTRATOR-PROTOCOL.md` |
| Orchestrator estimation heuristics | `guides/ORCHESTRATOR-LEARNINGS.md` |
| Frontend changes | `guides/FRONTEND.md` |
| Backend/API changes | `guides/BACKEND.md` |
| Auth/authorization | `guides/AUTHENTICATION.md` |
| CI/CD changes | `guides/CI-CD-PIPELINES.md` |
| Infrastructure/DevOps/deployment | `guides/INFRASTRUCTURE.md` |
| Code review work | `guides/CODE-REVIEW.md` |
| TypeScript strict typing | `guides/TYPESCRIPT.md` |
| QA / test strategy | `guides/QA-TESTING.md` |
| Documentation (any code/API/auth/infra change) | `guides/DOCUMENTATION.md` |
| Secrets / vault usage | `guides/VAULT-SECRETS.md` |
| Tool/credential reference (service CLIs, wrappers) | `guides/TOOLS-REFERENCE.md` |
| Memory protocol (OpenBrain capture/recall) | `guides/MEMORY.md` |
## Subagent Model Selection (Cost — Hard Rule) | Task | Required Guide |
| ------------------------------------------------------- | --------------------------------------------------- |
| Project bootstrap | `~/.config/mosaic/guides/BOOTSTRAP.md` |
| PRD creation and requirements definition | `~/.config/mosaic/guides/PRD.md` |
| Orchestration flow | `~/.config/mosaic/guides/ORCHESTRATOR.md` |
| Frontend changes | `~/.config/mosaic/guides/FRONTEND.md` |
| Backend/API changes | `~/.config/mosaic/guides/BACKEND.md` |
| Documentation changes or any code/API/auth/infra change | `~/.config/mosaic/guides/DOCUMENTATION.md` |
| Authentication/authorization | `~/.config/mosaic/guides/AUTHENTICATION.md` |
| CI/CD changes | `~/.config/mosaic/guides/CI-CD-PIPELINES.md` |
| Infrastructure/DevOps | `~/.config/mosaic/guides/INFRASTRUCTURE.md` |
| Code review work | `~/.config/mosaic/guides/CODE-REVIEW.md` |
| TypeScript strict typing | `~/.config/mosaic/guides/TYPESCRIPT.md` |
| QA and test strategy | `~/.config/mosaic/guides/QA-TESTING.md` |
| Secrets and vault usage | `~/.config/mosaic/guides/VAULT-SECRETS.md` |
| Orchestrator estimation heuristics | `~/.config/mosaic/guides/ORCHESTRATOR-LEARNINGS.md` |
| Mission lifecycle / multi-session orchestration | `~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md` |
Select the cheapest model capable of the task; do NOT default to the most expensive. Omitting the ## Embedded Delivery Cycle (Hard Rule)
tier defaults to the parent (usually opus) and wastes budget.
- **haiku** — search/grep/glob, codebase exploration, status/health checks, one-line mechanical fixes. - Implementation work MUST follow the embedded execution cycle:
- **sonnet** — code review, lint, test writing/fixing, standard feature implementation. - `plan -> code -> test -> review -> remediate -> review -> commit -> push -> greenfield situational test -> repeat`
- **opus** — complex architecture / multi-file refactors, security/auth logic, ambiguous design decisions. - If a step fails, you MUST remediate and re-run from the relevant step before proceeding.
Start cheapest; escalate only when the task genuinely needs deeper reasoning. Runtime syntax for ## Sequential-Thinking MCP (Hard Requirement)
specifying tier is in the runtime contract.
- `sequential-thinking` MCP server is REQUIRED for Mosaic operation.
- Installation and configuration are managed by Mosaic bootstrap and runtime linking.
- If sequential-thinking is unavailable, you MUST report the failure and stop planning-intensive execution.
## Subagent Model Selection (Cost Optimization — Hard Rule)
When delegating work to subagents, you MUST select the cheapest model capable of completing the task. Do NOT default to the most expensive model for every delegation.
| Task Type | Model Tier | Rationale |
| --------------------------------------------- | ---------- | ------------------------------------------------------- |
| File search, grep, glob, codebase exploration | **haiku** | Read-only, pattern matching, no reasoning depth needed |
| Status checks, health monitoring, heartbeat | **haiku** | Structured API calls, pass/fail output |
| Simple code fixes (typos, rename, one-liner) | **haiku** | Minimal reasoning, mechanical changes |
| Code review, lint, style checks | **sonnet** | Needs judgment but not deep architectural reasoning |
| Test writing, test fixes | **sonnet** | Pattern-based, moderate complexity |
| Standard feature implementation | **sonnet** | Good balance of capability and cost for most coding |
| Complex architecture, multi-file refactors | **opus** | Requires deep reasoning, large context, design judgment |
| Security review, auth logic | **opus** | High-stakes reasoning where mistakes are costly |
| Ambiguous requirements, design decisions | **opus** | Needs nuanced judgment and tradeoff analysis |
**Decision rule**: Start with the cheapest viable tier. Only escalate if the task genuinely requires deeper reasoning — not as a safety default. Most coding tasks are sonnet-tier. Reserve opus for work where wrong answers are expensive.
**Runtime-specific syntax**: See the runtime reference for how to specify model tier when spawning subagents (e.g., Claude Code Task tool `model` parameter).
## Superpowers Enforcement (Hard Rule) ## Superpowers Enforcement (Hard Rule)
Skills, hooks, MCP tools, and plugins are force multipliers you MUST use when applicable; Mosaic provides capabilities beyond basic code editing: **skills**, **hooks**, **MCP tools**, and **plugins**. These are not optional extras — they are force multipliers that agents MUST actively use when applicable. Under-utilization of superpowers is a framework violation.
under-utilization is a framework violation.
- **Skills:** before implementation, scan `~/.config/mosaic/skills/` and load any matching the task ### Skills
domain (e.g. `nestjs-best-practices` for NestJS). Include skill loading in worker kickstarts. Do
not load unrelated skills.
- **Hooks:** never bypass or suppress hook output; treat hook failures like failing tests and fix
them. If a hook is wrong, report it as a framework issue — do not work around it.
- **MCP:** sequential-thinking is REQUIRED for planning/architecture/multi-step reasoning. OpenBrain
(`capture`/`search`/`recent`) is the cross-agent memory layer — search at session start, capture
what you learn. Use web/browser/research MCP tools instead of asking the user to look things up.
- **Plugins:** use code-review / pr-review / architecture plugins proactively after significant
changes and before opening a PR — do not wait to be asked.
- **Self-evolution:** capture recurring patterns (`framework-improvement`), missing tooling
(`tooling-gap`), and value-less friction (`framework-friction`) to OpenBrain.
## Other Hard Rules Skills are domain-specific instruction sets in `~/.config/mosaic/skills/` that encode best practices, patterns, and guardrails. They are loaded into agents via the runtime's skill mechanism (e.g., Claude Code slash commands, Pi `--skill` flag).
- **Sequential-thinking MCP** is REQUIRED. If unavailable, report the failure and stop planning-intensive execution. **Rules:**
- **Missing core file:** if `AGENTS.md`, `SOUL.md`, or the runtime contract is missing, stop and report it.
## Session Closure 1. Before starting implementation, scan available skills (`ls ~/.config/mosaic/skills/`) and load any that match the task domain.
2. When a skill exists for the technology being used (e.g., `nestjs-best-practices` for NestJS work), you MUST load it.
3. When spawning workers, include skill loading in the kickstart prompt.
4. If you complete a task without loading a relevant available skill, that is a quality gap.
Before closing an implementation task, confirm: required + situational tests passed (primary gate); ### Hooks
aligned to `docs/PRD.md`; acceptance criteria mapped to evidence; independent code review passed (if
code changed); required docs updated; scratchpad updated with decisions/results/risks; explicit Hooks provide automated quality gates (lint, format, typecheck) that fire on file edits. They are configured in the runtime settings and run automatically.
completion evidence provided. For PR-workflow delivery: confirm merged PR number + merge commit on
`main`, terminal-green CI, and linked issue closed (or `docs/TASKS.md` equivalent). If any of those **Rules:**
are blocked by access/tooling failure, return `blocked` with the exact failed wrapper command — do
not claim completion. Full checklist: `guides/E2E-DELIVERY.md`. 1. Do NOT bypass or suppress hook output. If a hook reports errors, fix them before proceeding.
2. Hook failures are immediate feedback — treat them like failing tests.
3. If a hook is consistently failing on valid code, report it as a framework issue rather than working around it.
### MCP Tools
MCP servers extend agent capabilities with external integrations (sequential-thinking, web search, memory, browser automation, etc.). Available MCP tools are listed at session start.
**Rules:**
1. **sequential-thinking** is REQUIRED for planning, architecture, and multi-step reasoning. Use it — do not skip structured thinking for complex decisions.
2. **OpenBrain** (`capture`, `search`, `recent`) is the cross-agent memory layer. Capture discoveries and search for prior context at session start.
3. When a task involves web research, browser testing, or external data, use the available MCP tools (web-search, chrome-devtools, web-reader) rather than asking the user to look things up.
4. Check available MCP tools at session start and use them proactively throughout the session.
### Plugins (Runtime-Specific)
Runtime plugins (e.g., Claude Code's `feature-dev`, `pr-review-toolkit`, `code-review`) provide specialized agent capabilities like code review, architecture analysis, and test coverage analysis.
**Rules:**
1. After completing a significant code change, use code review plugins proactively — do not wait for the user to ask.
2. Before creating a PR, use PR review plugins to catch issues early.
3. When designing architecture, use planning/architecture plugins for structured analysis.
### Self-Evolution
The Mosaic framework should improve over time based on usage patterns:
1. When you discover a recurring pattern that should be codified, capture it to OpenBrain with `type: "framework-improvement"`.
2. When a hook, skill, or tool is missing for a common task, capture the gap to OpenBrain with `type: "tooling-gap"`.
3. When a framework rule causes friction without adding value, capture the observation to OpenBrain with `type: "framework-friction"`.
These captures feed the framework's continuous improvement cycle.
## Skills Policy
- Load skills that match the active task domain before starting implementation.
- Do not load unrelated skills.
- Follow skill trigger rules from the active runtime instruction layer.
- Actively check `~/.config/mosaic/skills/` for applicable skills rather than passively waiting for them to be mentioned.
## Session Closure Requirement
Before closing any implementation task:
1. Confirm required tests passed.
2. Confirm situational tests passed (primary gate).
3. Confirm implementation is aligned to the active `docs/PRD.md` or `docs/PRD.json`.
4. Confirm acceptance criteria are mapped to verification evidence.
5. If source code changed, confirm independent code review passed.
6. Confirm required documentation updates were completed and reviewed.
7. Update scratchpad with decisions, results, and open risks.
8. Provide explicit completion evidence.
9. If source code changed and external provider is available, confirm merged PR number and merge commit on `main`.
10. Confirm CI/pipeline status is terminal green for the merged change (or merged PR head when equivalent).
11. Confirm linked issue is closed (or internal `docs/TASKS.md` equivalent is closed when no provider exists).
12. If any of items 9-11 are blocked by access/tooling failure, return `blocked` status with exact failed wrapper command and do not claim completion.

View File

@@ -27,16 +27,6 @@ Master/slave model:
- Do not perform destructive git/file actions without explicit instruction. - Do not perform destructive git/file actions without explicit instruction.
- Browser automation (Playwright, Cypress, Puppeteer) MUST run in headless mode. Never launch a visible browser — it collides with the user's display and active session. - Browser automation (Playwright, Cypress, Puppeteer) MUST run in headless mode. Never launch a visible browser — it collides with the user's display and active session.
### Secrets handling (HARD RULE)
- Vault is the canonical source-of-truth for every secret in every environment. No exceptions.
- For k8s workloads, the default read path is **External Secrets Operator → k8s Secret → env var** (`secretKeyRef`). The app reads standard env vars; no Vault client in app code.
- Direct-Vault clients in application code are **opt-in only**, justified per-app by a documented dynamic-secrets requirement (e.g., DB rotation, AWS STS). Default to ESO. Document the justification in the project's README under "Secrets architecture".
- `${VAR:-default}` fallback syntax in any deployment configuration (compose, k8s manifests, Helm values, env files committed to git) is **forbidden** for required values. Use `${VAR:?VAR is required}` to fast-fail. Defaults are allowed only for true conveniences (e.g. `${PORT:-3000}`) and MUST be tagged `# safe-default: <reason>` so a reviewer can confirm the intent.
- `.env` files in production deployment paths are **forbidden**. `.env.example` and `.env` in local-dev paths are fine.
- App startup MUST validate required secrets against a schema (zod / pydantic / equivalent) and exit non-zero on missing required values. Never run with defaulted weak fallbacks.
- New apps: bootstrap checklist (see `~/.config/mosaic/guides/BOOTSTRAP.md`) MUST include Vault path provisioning + `ExternalSecret` manifest + README declaring the Vault path and required keys.
## Session Lifecycle Contract ## Session Lifecycle Contract
- Start: `scripts/agent/session-start.sh` - Start: `scripts/agent/session-start.sh`

View File

@@ -1,58 +1,257 @@
# Machine Tools — Index # Machine-Level Tool Reference
Tool suites live at `~/.config/mosaic/tools/<suite>/`. This is the index only. Centralized reference for tools, credentials, and CLI patterns available across all projects.
**Full CLI signatures, flags, and examples: `~/.config/mosaic/guides/TOOLS-REFERENCE.md`**
read it (or the relevant service guide) when your task actually touches that service.
Project-specific tooling belongs in the project's `AGENTS.md`, not here. Project-specific tooling belongs in the project's `AGENTS.md`, not here.
## Suites (use wrappers first) All tool suites are located at `~/.config/mosaic/tools/`.
| Suite | Path | Purpose | ## Tool Suites
| ---------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
| git | `tools/git/*.sh` | issues, PRs, milestones, CI queue guard (platform-auto-detected) |
| woodpecker | `tools/woodpecker/*.sh` | CI pipelines (`-a mosaic`\|`usc`; match git remote host) |
| portainer | `tools/portainer/*.sh` | Docker Swarm stacks (status/redeploy/list) |
| coolify | `tools/coolify/*.sh` | **DEPRECATED** — superseded by Portainer; do not use for new deployments |
| authentik | `tools/authentik/*.sh` | identity (users/groups/apps/flows) |
| cloudflare | `tools/cloudflare/*.sh` | DNS (zones/records; `-a` instance) |
| glpi | `tools/glpi/*.sh` | IT tickets/computers/users |
| health | `tools/health/stack-health.sh` | service health checks |
| codex | `tools/codex/*.sh` | code/security review (`--uncommitted`) |
| openbrain | `tools/openbrain/*`, `tools/openbrain_client.py` | semantic memory (see below) |
| excalidraw | MCP `mcp__excalidraw__*` | diagram export/generation |
Git wrappers are MANDATORY-first for issue/PR/milestone ops (see AGENTS.md hard gates 68). ### Git Wrappers (Use First)
Queue guard before push/merge: `tools/git/ci-queue-wait.sh --purpose push|merge`.
## Credentials Mosaic wrappers at `~/.config/mosaic/tools/git/*.sh` handle platform detection and edge cases. Always use these before raw CLI commands.
`source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials <service>` ```bash
Supported: portainer, coolify (deprecated), authentik, glpi, github, gitea-mosaicstack, # Issues
gitea-usc, woodpecker, cloudflare, turbo-cache, openbrain. Never expose or commit values. ~/.config/mosaic/tools/git/issue-create.sh
~/.config/mosaic/tools/git/issue-close.sh
## OpenBrain — Semantic Memory (PRIMARY) — capture when you LEARN, never when you DO # PRs
~/.config/mosaic/tools/git/pr-create.sh
~/.config/mosaic/tools/git/pr-merge.sh
Primary cross-agent memory (pgvector). Capture decisions/gotchas/preferences/patterns; never task # Milestones
starts, commits, PRs, test results, or file edits. At session start, `search` + `recent` to load ~/.config/mosaic/tools/git/milestone-create.sh
prior context. MCP (`mcp__openbrain__capture/search/recent/stats`) preferred when connected; else
REST/`tools/openbrain_client.py`. Full protocol: `guides/MEMORY.md`.
**MANDATORY jarvis-brain rule:** when working in `~/src/jarvis-brain`, NEVER capture project data, # CI queue guard (required before push/merge)
meeting notes, status, timelines, or task completions to OpenBrain — the flat files ~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge
(`data/projects/*.json`, `data/tasks/*.json`) are the SSOT (use `tools/brain.py` + direct JSON ```
edits). OpenBrain there is for agent meta-observations ONLY (tooling gotchas, framework learnings,
cross-project patterns). Violating this creates duplicate, divergent data. ### Code Review (Codex)
```bash
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
```
### Infrastructure — Portainer
```bash
~/.config/mosaic/tools/portainer/stack-status.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-redeploy.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-list.sh
~/.config/mosaic/tools/portainer/endpoint-list.sh
```
### Infrastructure — Coolify (DEPRECATED)
> Coolify has been superseded by Portainer Docker Swarm in this stack.
> Tools remain for reference but should not be used for new deployments.
```bash
# DEPRECATED — do not use for new deployments
~/.config/mosaic/tools/coolify/project-list.sh
~/.config/mosaic/tools/coolify/service-list.sh
~/.config/mosaic/tools/coolify/service-status.sh -u <uuid>
~/.config/mosaic/tools/coolify/deploy.sh -u <uuid>
~/.config/mosaic/tools/coolify/env-set.sh -u <uuid> -k KEY -v VALUE
```
### Identity — Authentik
```bash
~/.config/mosaic/tools/authentik/user-list.sh
~/.config/mosaic/tools/authentik/user-create.sh -u <username> -n <name> -e <email>
~/.config/mosaic/tools/authentik/group-list.sh
~/.config/mosaic/tools/authentik/app-list.sh
~/.config/mosaic/tools/authentik/flow-list.sh
~/.config/mosaic/tools/authentik/admin-status.sh
```
### CI/CD — Woodpecker
Multi-instance support: `-a <instance>` selects a named instance. Omit `-a` to use the default from `woodpecker.default` in credentials.json.
| Instance | URL | Serves |
| ------------------ | ------------------ | ---------------------------------- |
| `mosaic` (default) | ci.mosaicstack.dev | Mosaic repos (git.mosaicstack.dev) |
| `usc` | ci.uscllc.com | USC repos (git.uscllc.com) |
```bash
# List recent pipelines
~/.config/mosaic/tools/woodpecker/pipeline-list.sh [-r owner/repo] [-a instance]
# Check latest or specific pipeline status
~/.config/mosaic/tools/woodpecker/pipeline-status.sh [-r owner/repo] [-n number] [-a instance]
# Trigger a build
~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh [-r owner/repo] [-b branch] [-a instance]
```
Instance selection rule: match `-a` to the git remote host of the target repo. If the repo is on `git.uscllc.com`, use `-a usc`. If on `git.mosaicstack.dev`, use `-a mosaic` (or omit, since it's the default).
### DNS — Cloudflare
Multi-instance support: `-a <instance>` selects a named instance (e.g. `personal`, `work`). Omit `-a` to use the default from `cloudflare.default` in credentials.json.
```bash
# List zones (domains)
~/.config/mosaic/tools/cloudflare/zone-list.sh [-a instance]
# List DNS records (zone by name or ID)
~/.config/mosaic/tools/cloudflare/record-list.sh -z <zone> [-a instance] [-t type] [-n name]
# Create DNS record
~/.config/mosaic/tools/cloudflare/record-create.sh -z <zone> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl] [-P priority]
# Update DNS record
~/.config/mosaic/tools/cloudflare/record-update.sh -z <zone> -r <record-id> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl]
# Delete DNS record
~/.config/mosaic/tools/cloudflare/record-delete.sh -z <zone> -r <record-id> [-a instance]
```
### IT Service — GLPI
```bash
~/.config/mosaic/tools/glpi/ticket-list.sh
~/.config/mosaic/tools/glpi/ticket-create.sh -t <title> -c <content>
~/.config/mosaic/tools/glpi/computer-list.sh
~/.config/mosaic/tools/glpi/user-list.sh
```
### Health Check
```bash
# Check all configured services
~/.config/mosaic/tools/health/stack-health.sh
# Check a specific service
~/.config/mosaic/tools/health/stack-health.sh -s portainer
# JSON output for automation
~/.config/mosaic/tools/health/stack-health.sh -f json
```
### Shared Credential Loader
```bash
# Source in any script to load service credentials
source ~/.config/mosaic/tools/_lib/credentials.sh
load_credentials <service-name>
# Supported: portainer, coolify, authentik, glpi, github, gitea-mosaicstack, gitea-usc, woodpecker, cloudflare, turbo-cache, openbrain
```
### OpenBrain — Semantic Memory (PRIMARY)
Self-hosted semantic brain backed by pgvector. Primary shared memory layer for all agents across all sessions and harnesses. Stores and retrieves decisions, context, and observations via semantic search.
**MANDATORY jarvis-brain rule:** When working in `~/src/jarvis-brain`, NEVER capture project data, meeting notes, status updates, timeline decisions, or task completions to OpenBrain. The flat files (`data/projects/*.json`, `data/tasks/*.json`) are the SSOT — use `tools/brain.py` and direct JSON edits. OpenBrain is for agent meta-observations ONLY (tooling gotchas, framework learnings, cross-project patterns). Violating this creates duplicate, divergent data.
**Credentials:** `load_credentials openbrain` → exports `OPENBRAIN_URL`, `OPENBRAIN_TOKEN`
Configure in your credentials.json:
```json
"openbrain": {
"url": "https://<your-openbrain-host>",
"api_key": "<your-api-key>"
}
```
**REST API** (any language, any harness):
```bash
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials openbrain
# Search by meaning
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
-d '{"query": "your search", "limit": 5}' "$OPENBRAIN_URL/v1/search"
# Capture a thought
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
-d '{"content": "...", "source": "agent-name", "metadata": {}}' "$OPENBRAIN_URL/v1/thoughts"
# Recent activity
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/thoughts/recent?limit=5"
# Stats
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/stats"
```
**Python client** (if jarvis-brain is available on PYTHONPATH):
```bash
python tools/openbrain_client.py search "topic"
python tools/openbrain_client.py capture "decision or observation" --source agent-name
python tools/openbrain_client.py recent --limit 5
python tools/openbrain_client.py stats
```
**MCP (Claude Code sessions):** When connected, `mcp__openbrain__capture/search/recent/stats` tools are available natively — prefer those over CLI when in a Claude session.
**Rule: capture when you LEARN something. Never when you DO something.**
| Trigger | Action | Retention |
| ----------------------------------------- | ----------------------------------------- | --------------------- |
| Session start | `search` + `recent` to load prior context | — |
| Architectural or tooling decision made | Capture with rationale | `long` or `permanent` |
| Gotcha or non-obvious behavior discovered | Capture immediately | `medium` |
| User preference stated or confirmed | Capture | `permanent` |
| Cross-project pattern identified | Capture | `permanent` |
| Prior decision superseded | UPDATE existing thought | (keep tier) |
**Never capture:** task started, commit pushed, PR opened, test results, file edits, CI status.
Full protocol and cleanup tools: `~/.config/mosaic/guides/MEMORY.md`
Smart capture wrapper (enforces schema + dedup): `~/.config/mosaic/tools/openbrain/capture.sh`
### Excalidraw — Diagram Export (MCP)
Headless `.excalidraw` → SVG export via `@excalidraw/excalidraw`. Available as MCP tools in Claude Code sessions.
**MCP tools (when connected):**
| Tool | Input | Output |
| ----------------------------------------- | --------------------------------------------- | ---------------------------------------------------- |
| `mcp__excalidraw__excalidraw_to_svg` | `elements` JSON string + optional `app_state` | SVG string |
| `mcp__excalidraw__excalidraw_file_to_svg` | `file_path` to `.excalidraw` | SVG string + writes `.svg` alongside |
| `mcp__excalidraw__list_diagrams` | (none) | Available templates (requires `EXCALIDRAW_GEN_PATH`) |
| `mcp__excalidraw__generate_diagram` | `name`, optional `output_path` | Path to generated `.excalidraw` |
| `mcp__excalidraw__generate_and_export` | `name`, optional `output_path` | Paths to `.excalidraw` and `.svg` |
**Diagram generation** (`list_diagrams`, `generate_diagram`, `generate_and_export`) requires `EXCALIDRAW_GEN_PATH` env var pointing to `excalidraw_gen.py`. Set in environment or shell profile:
```bash
export EXCALIDRAW_GEN_PATH="$HOME/src/jarvis-brain/tools/excalidraw_export/excalidraw_gen.py"
```
**Manual registration:**
```bash
mosaic-ensure-excalidraw # install deps + register with Claude
mosaic-ensure-excalidraw --check # verify registration
```
## Git Providers ## Git Providers
| Host | Instance | CI | | Instance | URL | CLI | Purpose |
| ------------------- | ---------------- | -------------------------------- | | ----------------------------- | --- | --- | ------- |
| git.mosaicstack.dev | mosaic (default) | ci.mosaicstack.dev (`-a mosaic`) | | (add your git providers here) | | | |
| git.uscllc.com | usc | ci.uscllc.com (`-a usc`) |
Match Woodpecker `-a` and credential instance to the target repo's git remote host. ## Credentials
**Location:** (configure your credential file path)
**Loader:** `source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials <service>`
**Never expose actual values. Never commit credential files.**
## CLI Gotchas
(Add platform-specific CLI gotchas as you discover them.)
## Safety Defaults ## Safety Defaults
- Prefer `trash` over `rm` when available — recoverable beats gone forever. - Prefer `trash` over `rm` when available — recoverable beats gone forever
- Never run destructive commands without explicit instruction. - Never run destructive commands without explicit instruction
- Write it down — "mental notes" don't survive session restarts; files do

View File

@@ -453,26 +453,6 @@ Initialize standard labels and the first pre-MVP milestone:
--- ---
## Secrets Bootstrap (Required for Every New App)
Every new application MUST complete the following secrets bootstrap before deploying to any non-local environment. This is a hard gate — deployment without completed secrets bootstrap is forbidden.
### Secrets bootstrap checklist
- [ ] Vault path created: `vault kv put secret/k3s/<app>/ ...` with all required secret fields
- [ ] Required secrets listed in project README under a "Secrets architecture" section, including:
- Vault path(s) used
- All required secret keys and their purpose
- Whether the app uses ESO bridge (default) or Direct-Vault (opt-in, with justification)
- [ ] `external-secret.yaml` manifest committed to repo's `deploy/` or `k8s/` directory
- [ ] Deployment YAML references the synced k8s Secret via `secretKeyRef` (not raw env vars or `.env` files)
- [ ] App startup has schema-based validation for all required env vars (zod / pydantic / envconfig equivalent) that exits non-zero on missing required values
- [ ] Direct-Vault opt-in (if applicable): justification documented in README + AppRole provisioned + bootstrap credentials stored in Vault and synced via a separate `ExternalSecret`
See `~/.config/mosaic/guides/VAULT-SECRETS.md` for full worked examples of the ESO bridge pattern, the Direct-Vault opt-in pattern, and the forbidden antipatterns.
---
## Checklist ## Checklist
After bootstrapping, verify: After bootstrapping, verify:

View File

@@ -1,257 +0,0 @@
# Machine-Level Tool Reference
Centralized reference for tools, credentials, and CLI patterns available across all projects.
Project-specific tooling belongs in the project's `AGENTS.md`, not here.
All tool suites are located at `~/.config/mosaic/tools/`.
## Tool Suites
### Git Wrappers (Use First)
Mosaic wrappers at `~/.config/mosaic/tools/git/*.sh` handle platform detection and edge cases. Always use these before raw CLI commands.
```bash
# Issues
~/.config/mosaic/tools/git/issue-create.sh
~/.config/mosaic/tools/git/issue-close.sh
# PRs
~/.config/mosaic/tools/git/pr-create.sh
~/.config/mosaic/tools/git/pr-merge.sh
# Milestones
~/.config/mosaic/tools/git/milestone-create.sh
# CI queue guard (required before push/merge)
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge
```
### Code Review (Codex)
```bash
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
```
### Infrastructure — Portainer
```bash
~/.config/mosaic/tools/portainer/stack-status.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-redeploy.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-list.sh
~/.config/mosaic/tools/portainer/endpoint-list.sh
```
### Infrastructure — Coolify (DEPRECATED)
> Coolify has been superseded by Portainer Docker Swarm in this stack.
> Tools remain for reference but should not be used for new deployments.
```bash
# DEPRECATED — do not use for new deployments
~/.config/mosaic/tools/coolify/project-list.sh
~/.config/mosaic/tools/coolify/service-list.sh
~/.config/mosaic/tools/coolify/service-status.sh -u <uuid>
~/.config/mosaic/tools/coolify/deploy.sh -u <uuid>
~/.config/mosaic/tools/coolify/env-set.sh -u <uuid> -k KEY -v VALUE
```
### Identity — Authentik
```bash
~/.config/mosaic/tools/authentik/user-list.sh
~/.config/mosaic/tools/authentik/user-create.sh -u <username> -n <name> -e <email>
~/.config/mosaic/tools/authentik/group-list.sh
~/.config/mosaic/tools/authentik/app-list.sh
~/.config/mosaic/tools/authentik/flow-list.sh
~/.config/mosaic/tools/authentik/admin-status.sh
```
### CI/CD — Woodpecker
Multi-instance support: `-a <instance>` selects a named instance. Omit `-a` to use the default from `woodpecker.default` in credentials.json.
| Instance | URL | Serves |
| ------------------ | ------------------ | ---------------------------------- |
| `mosaic` (default) | ci.mosaicstack.dev | Mosaic repos (git.mosaicstack.dev) |
| `usc` | ci.uscllc.com | USC repos (git.uscllc.com) |
```bash
# List recent pipelines
~/.config/mosaic/tools/woodpecker/pipeline-list.sh [-r owner/repo] [-a instance]
# Check latest or specific pipeline status
~/.config/mosaic/tools/woodpecker/pipeline-status.sh [-r owner/repo] [-n number] [-a instance]
# Trigger a build
~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh [-r owner/repo] [-b branch] [-a instance]
```
Instance selection rule: match `-a` to the git remote host of the target repo. If the repo is on `git.uscllc.com`, use `-a usc`. If on `git.mosaicstack.dev`, use `-a mosaic` (or omit, since it's the default).
### DNS — Cloudflare
Multi-instance support: `-a <instance>` selects a named instance (e.g. `personal`, `work`). Omit `-a` to use the default from `cloudflare.default` in credentials.json.
```bash
# List zones (domains)
~/.config/mosaic/tools/cloudflare/zone-list.sh [-a instance]
# List DNS records (zone by name or ID)
~/.config/mosaic/tools/cloudflare/record-list.sh -z <zone> [-a instance] [-t type] [-n name]
# Create DNS record
~/.config/mosaic/tools/cloudflare/record-create.sh -z <zone> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl] [-P priority]
# Update DNS record
~/.config/mosaic/tools/cloudflare/record-update.sh -z <zone> -r <record-id> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl]
# Delete DNS record
~/.config/mosaic/tools/cloudflare/record-delete.sh -z <zone> -r <record-id> [-a instance]
```
### IT Service — GLPI
```bash
~/.config/mosaic/tools/glpi/ticket-list.sh
~/.config/mosaic/tools/glpi/ticket-create.sh -t <title> -c <content>
~/.config/mosaic/tools/glpi/computer-list.sh
~/.config/mosaic/tools/glpi/user-list.sh
```
### Health Check
```bash
# Check all configured services
~/.config/mosaic/tools/health/stack-health.sh
# Check a specific service
~/.config/mosaic/tools/health/stack-health.sh -s portainer
# JSON output for automation
~/.config/mosaic/tools/health/stack-health.sh -f json
```
### Shared Credential Loader
```bash
# Source in any script to load service credentials
source ~/.config/mosaic/tools/_lib/credentials.sh
load_credentials <service-name>
# Supported: portainer, coolify, authentik, glpi, github, gitea-mosaicstack, gitea-usc, woodpecker, cloudflare, turbo-cache, openbrain
```
### OpenBrain — Semantic Memory (PRIMARY)
Self-hosted semantic brain backed by pgvector. Primary shared memory layer for all agents across all sessions and harnesses. Stores and retrieves decisions, context, and observations via semantic search.
**MANDATORY jarvis-brain rule:** When working in `~/src/jarvis-brain`, NEVER capture project data, meeting notes, status updates, timeline decisions, or task completions to OpenBrain. The flat files (`data/projects/*.json`, `data/tasks/*.json`) are the SSOT — use `tools/brain.py` and direct JSON edits. OpenBrain is for agent meta-observations ONLY (tooling gotchas, framework learnings, cross-project patterns). Violating this creates duplicate, divergent data.
**Credentials:** `load_credentials openbrain` → exports `OPENBRAIN_URL`, `OPENBRAIN_TOKEN`
Configure in your credentials.json:
```json
"openbrain": {
"url": "https://<your-openbrain-host>",
"api_key": "<your-api-key>"
}
```
**REST API** (any language, any harness):
```bash
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials openbrain
# Search by meaning
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
-d '{"query": "your search", "limit": 5}' "$OPENBRAIN_URL/v1/search"
# Capture a thought
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
-d '{"content": "...", "source": "agent-name", "metadata": {}}' "$OPENBRAIN_URL/v1/thoughts"
# Recent activity
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/thoughts/recent?limit=5"
# Stats
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/stats"
```
**Python client** (if jarvis-brain is available on PYTHONPATH):
```bash
python tools/openbrain_client.py search "topic"
python tools/openbrain_client.py capture "decision or observation" --source agent-name
python tools/openbrain_client.py recent --limit 5
python tools/openbrain_client.py stats
```
**MCP (Claude Code sessions):** When connected, `mcp__openbrain__capture/search/recent/stats` tools are available natively — prefer those over CLI when in a Claude session.
**Rule: capture when you LEARN something. Never when you DO something.**
| Trigger | Action | Retention |
| ----------------------------------------- | ----------------------------------------- | --------------------- |
| Session start | `search` + `recent` to load prior context | — |
| Architectural or tooling decision made | Capture with rationale | `long` or `permanent` |
| Gotcha or non-obvious behavior discovered | Capture immediately | `medium` |
| User preference stated or confirmed | Capture | `permanent` |
| Cross-project pattern identified | Capture | `permanent` |
| Prior decision superseded | UPDATE existing thought | (keep tier) |
**Never capture:** task started, commit pushed, PR opened, test results, file edits, CI status.
Full protocol and cleanup tools: `~/.config/mosaic/guides/MEMORY.md`
Smart capture wrapper (enforces schema + dedup): `~/.config/mosaic/tools/openbrain/capture.sh`
### Excalidraw — Diagram Export (MCP)
Headless `.excalidraw` → SVG export via `@excalidraw/excalidraw`. Available as MCP tools in Claude Code sessions.
**MCP tools (when connected):**
| Tool | Input | Output |
| ----------------------------------------- | --------------------------------------------- | ---------------------------------------------------- |
| `mcp__excalidraw__excalidraw_to_svg` | `elements` JSON string + optional `app_state` | SVG string |
| `mcp__excalidraw__excalidraw_file_to_svg` | `file_path` to `.excalidraw` | SVG string + writes `.svg` alongside |
| `mcp__excalidraw__list_diagrams` | (none) | Available templates (requires `EXCALIDRAW_GEN_PATH`) |
| `mcp__excalidraw__generate_diagram` | `name`, optional `output_path` | Path to generated `.excalidraw` |
| `mcp__excalidraw__generate_and_export` | `name`, optional `output_path` | Paths to `.excalidraw` and `.svg` |
**Diagram generation** (`list_diagrams`, `generate_diagram`, `generate_and_export`) requires `EXCALIDRAW_GEN_PATH` env var pointing to `excalidraw_gen.py`. Set in environment or shell profile:
```bash
export EXCALIDRAW_GEN_PATH="$HOME/src/jarvis-brain/tools/excalidraw_export/excalidraw_gen.py"
```
**Manual registration:**
```bash
mosaic-ensure-excalidraw # install deps + register with Claude
mosaic-ensure-excalidraw --check # verify registration
```
## Git Providers
| Instance | URL | CLI | Purpose |
| ----------------------------- | --- | --- | ------- |
| (add your git providers here) | | | |
## Credentials
**Location:** (configure your credential file path)
**Loader:** `source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials <service>`
**Never expose actual values. Never commit credential files.**
## CLI Gotchas
(Add platform-specific CLI gotchas as you discover them.)
## Safety Defaults
- Prefer `trash` over `rm` when available — recoverable beats gone forever
- Never run destructive commands without explicit instruction
- Write it down — "mental notes" don't survive session restarts; files do

View File

@@ -203,374 +203,3 @@ Error: token expired
3. **Audit logging** - All access is logged; act accordingly 3. **Audit logging** - All access is logged; act accordingly
4. **No local copies** - Don't store secrets in files or env vars long-term 4. **No local copies** - Don't store secrets in files or env vars long-term
5. **Rotate on compromise** - Immediately rotate any exposed secrets 5. **Rotate on compromise** - Immediately rotate any exposed secrets
---
## Secrets Architecture Decision Matrix
Use this table to choose between the ESO bridge (default) and Direct-Vault (opt-in) patterns for every new app or integration.
| Factor | ESO Bridge (default) | Direct-Vault (opt-in) |
| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Use-case** | All static secrets (DB creds, API keys, signing keys, OAuth secrets) | Dynamic creds with short TTLs (DB rotation, AWS STS, PKI), per-request audit trails, or lease renewal mid-pod-lifecycle |
| **App code change** | None — reads standard env vars via `secretKeyRef` | Requires Vault client (`hvac`, `node-vault`, `vault/api`) in application code |
| **Secret rotation** | ESO re-syncs on Vault write; pod restart or secret refresh picks up new value | App manages lease renewal or re-auth within the running process |
| **Audit granularity** | Access logged at Vault when ESO syncs; no per-request app audit | Every app request to Vault is a separate audit log entry |
| **Operational burden** | Low — ESO handles polling, sync, and k8s Secret lifecycle | Higher — app must handle auth, lease renewal, error paths, and token rotation |
| **Justification required?** | No — this is the default | Yes — document in project README under "Secrets architecture" |
| **Example use cases** | Web app DB password, OAuth client secret, JWT signing key, API token | HashiCorp DB secrets engine with 15-min TTL leases, AWS STS assume-role, Vault PKI short-lived certs |
**Decision rule:** If you are unsure, use ESO. Only justify Direct-Vault when the secret cannot be safely stored in a k8s Secret (too short-lived, per-request TTL required, or mid-lifecycle renewal needed).
---
## ESO Bridge Pattern (Default)
This is the required default for all k8s workloads. Follow this exact pattern unless a documented dynamic-secrets requirement justifies Direct-Vault.
### 1. Provision Vault path
```bash
# Write the secrets for the app (run once; use IaC/Terraform for repeatable provisioning)
vault kv put secret/k3s/<app> \
db_password="..." \
api_key="..." \
jwt_secret="..."
```
Use the canonical path structure: `secret/k3s/<app>` for k3s cluster workloads.
### 2. ExternalSecret manifest
Commit this to the repo's `deploy/` or `k8s/` directory:
```yaml
# deploy/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: <app>-secrets
namespace: <namespace>
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend # ClusterSecretStore name — verify with cluster admin
kind: ClusterSecretStore
target:
name: <app>-secrets # k8s Secret name that will be created
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD # key in the k8s Secret
remoteRef:
key: secret/k3s/<app> # Vault path
property: db_password # field within the Vault secret
- secretKey: API_KEY
remoteRef:
key: secret/k3s/<app>
property: api_key
- secretKey: JWT_SECRET
remoteRef:
key: secret/k3s/<app>
property: jwt_secret
```
### 3. Deployment manifest — reference synced k8s Secret
```yaml
# deploy/deployment.yaml (env section)
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: <app>-secrets # matches ExternalSecret target.name
key: DB_PASSWORD
- name: API_KEY
valueFrom:
secretKeyRef:
name: <app>-secrets
key: API_KEY
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: <app>-secrets
key: JWT_SECRET
- name: PORT
value: '3000' # safe-default: non-secret, no Vault needed
```
### 4. App-side schema validation — TypeScript (zod)
Validate all required env vars at startup. Exit non-zero on missing values.
```typescript
// src/env.ts
import { z } from 'zod';
const envSchema = z.object({
DB_PASSWORD: z.string().min(1, 'DB_PASSWORD is required'),
API_KEY: z.string().min(1, 'API_KEY is required'),
JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 chars'),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']).default('production'),
});
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('Missing or invalid environment variables:');
console.error(result.error.flatten().fieldErrors);
process.exit(1);
}
export const env = result.data;
```
### 4b. App-side schema validation — Python (pydantic)
```python
# src/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
db_password: str
api_key: str
jwt_secret: str
port: int = 3000
node_env: str = "production"
model_config = SettingsConfigDict(env_file=None) # no .env in prod
try:
settings = Settings()
except Exception as e:
import sys
print(f"Missing or invalid environment variables: {e}", file=sys.stderr)
sys.exit(1)
```
### 4c. App-side schema validation — Go (envconfig)
```go
// config/config.go
package config
import (
"fmt"
"github.com/kelseyhightower/envconfig"
)
type Config struct {
DBPassword string `envconfig:"DB_PASSWORD" required:"true"`
APIKey string `envconfig:"API_KEY" required:"true"`
JWTSecret string `envconfig:"JWT_SECRET" required:"true"`
Port int `envconfig:"PORT" default:"3000"`
}
func Load() (*Config, error) {
var cfg Config
if err := envconfig.Process("", &cfg); err != nil {
return nil, fmt.Errorf("invalid environment: %w", err)
}
return &cfg, nil
}
```
In your `main.go`:
```go
cfg, err := config.Load()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
```
---
## Direct-Vault Opt-In Pattern
Use this pattern ONLY when a documented dynamic-secrets requirement applies (DB rotation with short TTLs, AWS STS, PKI, per-request audit). Document the justification in the project README under "Secrets architecture" before implementing.
### When it is justified
- Vault DB secrets engine with lease TTLs shorter than a typical pod lifecycle (< 1 hour)
- AWS STS assume-role tokens generated per-request
- Vault PKI short-lived certificates (< 24 hours) that must be renewed within a running pod
- Per-request audit trail requirement (each app call must appear separately in Vault audit log)
### Provision an AppRole for the app
```bash
# Enable AppRole auth (if not already enabled)
vault auth enable approle
# Create a Vault policy for the app
# Note: KV v2 paths require both the exact path (for the top-level secret) and the
# wildcard (for sub-paths). Always include both to avoid permission denied errors.
vault policy write <app>-policy - <<EOF
path "secret/data/k3s/<app>" {
capabilities = ["read"]
}
path "secret/data/k3s/<app>/*" {
capabilities = ["read"]
}
path "database/creds/<app>-role" {
capabilities = ["read"]
}
EOF
# Create the AppRole
vault write auth/approle/role/<app>-role \
token_policies="<app>-policy" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=0
# Retrieve role-id and secret-id
vault read auth/approle/role/<app>-role/role-id
vault write -f auth/approle/role/<app>-role/secret-id
```
### Bootstrap AppRole credentials via ESO (solving the chicken-and-egg problem)
The AppRole `role-id` and `secret-id` are themselves secrets. Store them in Vault at a bootstrap path, then use ESO to sync them into a k8s Secret. The app reads that k8s Secret at startup to authenticate with Vault directly.
```bash
# Store the bootstrap credentials in Vault
vault kv put secret/k3s/<app>-bootstrap \
role_id="<role-id>" \
secret_id="<secret-id>"
```
```yaml
# deploy/external-secret-bootstrap.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: <app>-vault-auth
namespace: <namespace>
spec:
refreshInterval: 24h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: <app>-vault-auth
creationPolicy: Owner
data:
- secretKey: VAULT_ROLE_ID
remoteRef:
key: secret/k3s/<app>-bootstrap
property: role_id
- secretKey: VAULT_SECRET_ID
remoteRef:
key: secret/k3s/<app>-bootstrap
property: secret_id
```
```yaml
# deploy/deployment.yaml (env section for Direct-Vault app)
env:
- name: VAULT_ADDR
value: 'https://vault.example.com' # safe-default: non-secret cluster address
- name: VAULT_ROLE_ID
valueFrom:
secretKeyRef:
name: <app>-vault-auth
key: VAULT_ROLE_ID
- name: VAULT_SECRET_ID
valueFrom:
secretKeyRef:
name: <app>-vault-auth
key: VAULT_SECRET_ID
```
### App-side Vault client pattern
```typescript
// src/vault-client.ts — only exists in Direct-Vault apps
import vault from 'node-vault';
import { z } from 'zod';
const bootstrapSchema = z.object({
VAULT_ADDR: z.string().url(),
VAULT_ROLE_ID: z.string().min(1),
VAULT_SECRET_ID: z.string().min(1),
});
const bootstrap = bootstrapSchema.parse(process.env);
const client = vault({ endpoint: bootstrap.VAULT_ADDR });
export async function getVaultClient() {
const { auth } = await client.approleLogin({
role_id: bootstrap.VAULT_ROLE_ID,
secret_id: bootstrap.VAULT_SECRET_ID,
});
client.token = auth.client_token;
return client;
}
```
Document in README under "Secrets architecture": the Vault path, why Direct-Vault is required, and the lease/renewal strategy.
---
## Forbidden Patterns (CI Lint Targets)
The following patterns are forbidden in all Mosaic projects. CI lint SHOULD catch these automatically (implementation tracked separately). Agents MUST NOT introduce these patterns.
### 1. Untagged fallback defaults for required values
```yaml
# FORBIDDEN — required secret with silent fallback
environment:
- DB_PASSWORD=${DB_PASSWORD:-changeme}
- API_KEY=${API_KEY:-}
# REQUIRED — fast-fail on missing required values
environment:
- DB_PASSWORD=${DB_PASSWORD:?DB_PASSWORD is required}
- API_KEY=${API_KEY:?API_KEY is required}
# ALLOWED — true convenience default, tagged
environment:
- PORT=${PORT:-3000} # safe-default: non-secret, app works at any port
```
This applies to: `docker-compose.yml`, k8s manifests, Helm `values.yaml`, any env file committed to git.
### 2. Vault KV calls in application source code (ESO-default projects)
```python
# FORBIDDEN in ESO-default apps — direct Vault client in app source
import hvac
client = hvac.Client(url=os.environ['VAULT_ADDR'])
secret = client.secrets.kv.v2.read_secret_version(path='myapp/db')
```
ESO-default apps read env vars only. Direct-Vault clients belong only in apps with a documented dynamic-secrets justification in README.
### 3. Hardcoded secrets or API keys in committed files
```python
# FORBIDDEN — hardcoded credential
DB_PASSWORD = "supersecret123"
API_KEY = "sk-live-abc123"
```
No exceptions. CI lint must flag any string matching common secret patterns (`password`, `secret`, `api_key`, `token` assigned a literal non-env-var value).
### 4. `.env` files in production deployment paths
```
# FORBIDDEN — .env file in a production deploy path
deploy/.env
k8s/.env
docker/.env
# ALLOWED — local dev only
.env.example # template only, no real values
.env # local dev, must be in .gitignore
```
`.env` files are acceptable in local-dev contexts only and MUST be in `.gitignore`. They are forbidden in any path that a CI pipeline or production deployment process reads directly.

View File

@@ -1,61 +1,131 @@
# Claude Runtime Reference # Claude Runtime Reference
Claude-runtime behavior only. Global rules win if anything here conflicts. ## Runtime Scope
This file applies only to Claude runtime behavior.
## Required Actions ## Required Actions
1. Follow the Session Start load order in `~/.config/mosaic/AGENTS.md`. 1. Follow global load order in `~/.config/mosaic/AGENTS.md`.
2. Runtime config lives in `~/.claude/settings.json` (hooks, model, plugins, permissions) and 2. Use `~/.claude/settings.json` and `~/.claude/hooks-config.json` as runtime config sources.
`~/.claude/hooks-config.json`. 3. Treat sequential-thinking MCP as required.
3. sequential-thinking MCP is required. 4. If runtime config conflicts with global rules, global rules win.
4. First response MUST declare mode per the global contract. 5. Documentation rules are inherited from `~/.config/mosaic/AGENTS.md` and `~/.config/mosaic/guides/DOCUMENTATION.md`.
5. Git wrappers first for issue/PR/milestone ops; runtime-default confirmation prompts do NOT 6. For issue/PR/milestone actions, run Mosaic git wrappers first (`~/.config/mosaic/tools/git/*.sh`) and do not call raw `gh`/`tea`/`glab` first.
override Mosaic hard gates (push/merge/issue-close without routine confirmation). 7. For orchestration-oriented missions, load `~/.config/mosaic/guides/ORCHESTRATOR.md` before acting.
8. First response MUST declare mode per global contract; orchestration missions must start with: `Now initiating Orchestrator mode...`
9. Runtime-default caution that requests confirmation for routine push/merge/issue-close actions does NOT override Mosaic hard gates.
## Subagent Model Selection (Claude Code syntax) ## Subagent Model Selection (Claude Code Syntax)
The Task tool takes `model`: `"haiku"` | `"sonnet"` | `"opus"`. You MUST set it per the tier rule Claude Code's Task tool accepts a `model` parameter: `"haiku"`, `"sonnet"`, or `"opus"`.
in AGENTS.md — omitting it defaults to the parent (usually opus) and wastes budget.
You MUST set this parameter according to the model selection table in `~/.config/mosaic/AGENTS.md`. Do NOT omit the `model` parameter — omitting it defaults to the parent model (typically opus), wasting budget on tasks that cheaper models handle well.
**Examples:**
``` ```
# Codebase exploration — haiku
Task(subagent_type="Explore", model="haiku", prompt="Find all API route handlers") Task(subagent_type="Explore", model="haiku", prompt="Find all API route handlers")
Task(subagent_type="feature-dev:code-reviewer", model="sonnet", prompt="Review src/auth/ changes")
# Code review — sonnet
Task(subagent_type="feature-dev:code-reviewer", model="sonnet", prompt="Review the changes in src/auth/")
# Standard feature work — sonnet
Task(subagent_type="general-purpose", model="sonnet", prompt="Add validation to the user input form")
# Complex architecture — opus (only when justified)
Task(subagent_type="Plan", model="opus", prompt="Design the multi-tenant isolation strategy") Task(subagent_type="Plan", model="opus", prompt="Design the multi-tenant isolation strategy")
``` ```
**Quick reference (from global AGENTS.md):**
| haiku | sonnet | opus |
| ---------------------- | ----------------- | -------------------------- |
| Search, grep, glob | Code review | Complex architecture |
| Status/health checks | Test writing | Security/auth logic |
| Simple one-liner fixes | Standard features | Ambiguous design decisions |
## Memory Policy (Hard Gate) ## Memory Policy (Hard Gate)
OpenBrain is the primary cross-agent memory layer — capture learnings/gotchas/decisions there **OpenBrain is the primary cross-agent memory layer.** All agent learnings, gotchas, decisions, and project state MUST be captured to OpenBrain via the `capture` MCP tool or REST API.
(`capture` MCP tool or REST). `~/.claude/projects/*/memory/MEMORY.md` is **write-blocked** by the
`prevent-memory-write.sh` PreToolUse hook (the rule alone proved insufficient — the hook is the
hard gate). At session start, `search(topic)` + `recent()` to load prior context. Full protocol:
`~/.config/mosaic/guides/MEMORY.md`.
Quick placement: discoveries/decisions → OpenBrain; active task state → `docs/TASKS.md` or `~/.claude/projects/*/memory/MEMORY.md` files are **write-blocked by PreToolUse hook** (`prevent-memory-write.sh`). Any attempt to write agent learnings there will be rejected with an error directing you to OpenBrain.
`docs/scratchpads/`; Mosaic framework notes → `~/.config/mosaic/memory/`.
### What belongs where
| Content | Location |
| ----------------------------------------------- | ---------------------------------------------------------------------- |
| Discoveries, gotchas, decisions, observations | OpenBrain `capture` — searchable by all agents |
| Active task state | `docs/TASKS.md` or `docs/scratchpads/` |
| Behavioral guardrails that MUST be in load-path | `MEMORY.md` (read-mostly; write only for genuine behavioral overrides) |
| Mosaic framework technical notes | `~/.config/mosaic/memory/` |
### Using OpenBrain
At session start, load prior context:
```
search("topic or project name") # semantic search
recent(limit=5) # what's been happening
```
When you discover something:
```
capture("The thing you learned", source="project/context", metadata={"type": "gotcha", ...})
```
### Why the hook exists
Instructions in RUNTIME.md, CLAUDE.md, and MEMORY.md are insufficient — agents default to writing local MEMORY.md regardless of written rules. The PreToolUse hook is a hard technical gate that makes the correct behavior the only possible behavior.
## MCP Configuration ## MCP Configuration
MCP servers are configured in `~/.claude.json` (key `mcpServers`) — NOT `~/.claude/settings.json`, **MCPs are configured in `~/.claude.json` — NOT `~/.claude/settings.json`.**
where that key is ignored. `settings.json` controls hooks/model/plugins/permissions.
`settings.json` controls hooks, model, plugins, and allowed commands.
`~/.claude.json` is the global Claude Code state file where `mcpServers` lives.
To register an MCP server that persists across all sessions:
```bash ```bash
# HTTP MCP (e.g. OpenBrain)
claude mcp add --scope user --transport http <name> <url> --header "Authorization: Bearer <token>" claude mcp add --scope user --transport http <name> <url> --header "Authorization: Bearer <token>"
claude mcp add --scope user <name> -- npx -y <package> # stdio
# stdio MCP
claude mcp add --scope user <name> -- npx -y <package>
``` ```
`--scope user` `~/.claude.json` (global); `project``.claude/settings.json`; `local` (default) `--scope user` = writes to `~/.claude.json` (global, all projects).
→ not committed. `--scope project` = writes to `.claude/settings.json` in project root.
`--scope local` = default, local-only (not committed).
## Required Settings (launcher-audited, advisory) Do NOT add `mcpServers` to `~/.claude/settings.json` — that key is ignored for MCP loading.
`mosaic claude` warns if `~/.claude/settings.json` is missing these (session still launches): ## Required Claude Code Settings (Enforced by Launcher)
- **Hooks** — PreToolUse `prevent-memory-write.sh` (Write|Edit|MultiEdit); PostToolUse The `mosaic claude` launcher validates that `~/.claude/settings.json` contains the required Mosaic configuration. Missing or outdated settings trigger a warning at launch.
`qa-hook-stdin.sh` + `typecheck-hook.sh` (Edit|MultiEdit|Write).
- **Plugins** — `feature-dev`, `pr-review-toolkit`, `code-review`.
- **Settings** — `enableAllMcpTools: true`; `model: "opus"` (orchestrator default; workers use
tiered models via the Task `model` param).
Note: PostToolUse hook plain stdout on exit 0 goes to the debug log, not model context — only **Required hooks:**
`hookSpecificOutput.additionalContext` (or exit-2 stderr) enters context.
| Event | Matcher | Script | Purpose |
| ----------- | ------------------------ | ------------------------- | ---------------------------------------------- |
| PreToolUse | `Write\|Edit\|MultiEdit` | `prevent-memory-write.sh` | Block writes to `~/.claude/projects/*/memory/` |
| PostToolUse | `Edit\|MultiEdit\|Write` | `qa-hook-stdin.sh` | QA report generation after code edits |
| PostToolUse | `Edit\|MultiEdit\|Write` | `typecheck-hook.sh` | Inline TypeScript type checking |
**Required plugins:**
| Plugin | Purpose |
| ------------------- | -------------------------------------------------------------------------------------------------------- |
| `feature-dev` | Subagent architecture: code-reviewer, code-architect, code-explorer |
| `pr-review-toolkit` | PR review: code-simplifier, comment-analyzer, test-analyzer, silent-failure-hunter, type-design-analyzer |
| `code-review` | Standalone code review capabilities |
**Required settings:**
- `enableAllMcpTools: true` — Allow all configured MCP tools without per-tool approval
- `model: "opus"` — Default to opus for orchestrator-level sessions (workers use tiered models via Task tool)
If `mosaic claude` detects missing hooks or plugins, it will print a warning with the exact settings to add. The session will still launch — enforcement is advisory, not blocking — but agents operating without these settings are running degraded.

View File

@@ -1,78 +0,0 @@
# Inter-Agent tmux Comms — Standard & Tooling
Reliable, self-identifying messaging between Mosaic agents running in tmux panes
(Claude Code / Codex / OpenCode REPLs), across hosts.
## The addressing standard (required)
Every cross-agent tmux message MUST begin with an addressing preamble:
```
[<src_host>:<src_session> -> <dst_host>:<dst_session>] <message>
```
- `host` = `hostname -s` of the machine the agent runs on (e.g. `web1`, `sb-it-mgr-0-lt`).
- `session` = the tmux session name (e.g. `mos-claude`, `rev0-4`, `installer-1`).
- **Replies FLIP the preamble**: the recipient answers with `[<dst> -> <src>] ...`.
Why: a fresh or context-wiped agent always knows who sent a message and to whom.
No ambiguity about origin or lane after a tmux wipe / session restart.
Example exchange:
```
[web1:mos-claude -> sb-it-mgr-0-lt:installer-1] status on #29?
[sb-it-mgr-0-lt:installer-1 -> web1:mos-claude] Q2 done, opening PR #34.
```
## The helper: `agent-send.sh`
Prepends the preamble automatically (auto-detecting your own `host:session`) and
delivers reliably to local OR remote panes.
```bash
# Local target (same host)
agent-send.sh -s <dst_session> -m "message"
# Remote target (over ssh)
agent-send.sh -H user@host -s <dst_session> -m "message"
# From a file / stdin
agent-send.sh -H user@host -s <dst_session> -f msg.txt
echo "msg" | agent-send.sh -s <dst_session>
```
Key flags: `-s` dst session (required) · `-H` ssh target for remote · `-n` dst
hostname for the preamble (else auto-resolved) · `-m`/`-f`/stdin body · `-S`
override source label · `-v` verbose · `-r N` Enter-flush attempts.
## Why a helper exists (the submission gotcha)
Pasting into an interactive REPL via raw `tmux send-keys` is unreliable: a
trailing `Enter` is frequently swallowed and the message sits as an **unsubmitted
draft** ("Press up to edit queued messages"). Over an `ssh -> nested tmux` hop the
plain `Enter` keyname often does not register at all — `C-m` is needed.
`send-message.sh` solves this for a **local** pane: bracketed-paste the body
(so multi-line content doesn't submit early), pause, then send `Enter` as its own
keystroke and flush with a second, verifying against a draft heuristic.
`agent-send.sh` solves the **remote** case by _shipping `send-message.sh` over ssh_
(`ssh host bash -s -- ... < send-message.sh`) and running it local to the target
pane — so the reliable send-keys always happens on the pane's own host. The remote
needs only `bash` + `tmux` + `base64`; **no mosaic install required there**. The
message crosses the wire as base64 (`-b`) to avoid all shell-quoting hazards.
## Files
- `agent-send.sh` — inter-agent wrapper (preamble + local/remote dispatch).
- `send-message.sh` — low-level reliable single-pane submitter (`-b` base64 input).
## Distribution
These live in the installed framework copy at
`~/.config/mosaic/tools/tmux/`. `install.sh` rsyncs the framework **source tree**
to each host, so to propagate permanently, land both files in the framework
source repo and re-run the installer on each host. Until then, `agent-send.sh`
already works against any reachable host because it ships `send-message.sh` over
ssh per-send — no pre-install on the target host is needed to _send to_ it.

View File

@@ -1,100 +0,0 @@
#!/usr/bin/env bash
# agent-send.sh — standard inter-agent tmux messaging for the Mosaic stack.
#
# WHAT IT DOES
# Sends a message to another agent's tmux pane (local or on a remote host)
# with the canonical addressing preamble prepended:
#
# [<src_host>:<src_session> -> <dst_host>:<dst_session>] <message>
#
# The preamble makes every inter-agent message self-identifying, so a fresh
# or context-wiped agent always knows who sent a message and to whom — no
# ambiguity about lanes or origin. Recipients replying should FLIP the
# preamble: [<dst> -> <src>] ... (this tool sends; it does not auto-reply).
#
# WHY A WRAPPER
# Reliable submission into an interactive REPL (Claude Code / Codex) is fiddly:
# a trailing Enter is often swallowed and the message sits as an unsubmitted
# DRAFT. tools/tmux/send-message.sh already solves that for a LOCAL pane via
# bracketed-paste + Enter-flush + draft-detection. For REMOTE targets this
# wrapper SHIPS send-message.sh over ssh (stdin) and runs it there, so the
# reliable send-keys happens local to the target pane — sidestepping the
# ssh->nested-tmux Enter/C-m swallow entirely. No mosaic install needed on
# the remote host; only bash + tmux + base64 (standard).
#
# USAGE
# agent-send.sh -s <dst_session> -m "message" # local target
# agent-send.sh -H user@host -s <dst_session> -m "message" # remote target
# agent-send.sh -H user@host -n <dst_hostname> -s <sess> -f msg.txt
# echo "msg" | agent-send.sh -H user@host -s <dst_session>
#
# OPTIONS
# -s DST_SESSION target tmux session (or session:window.pane) [required]
# -H SSH_TARGET ssh target (user@host) for a remote pane; omit for local
# -n DST_HOST hostname to show in the preamble for the target.
# Default: local hostname, or (remote) resolved via one ssh.
# -m MESSAGE message text (single- or multi-line)
# -f FILE read message from FILE instead of -m
# -S SRC_LABEL override source label "<host>:<session>" (default: auto)
# -r N Enter-flush attempts passed through (default 2)
# -v verbose: print pane tail after delivery
# -h help
#
# EXIT CODES (passed through from send-message.sh)
# 0 delivered/queued · 1 target not found · 2 still draft · 3 usage error
set -uo pipefail
SELF_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
SENDER="$SELF_DIR/send-message.sh"
DST_SESSION=""; SSH_TARGET=""; DST_HOST=""; MSG=""; FILE=""
SRC_LABEL=""; RETRIES=2; VERBOSE=0
usage() { sed -n '2,44p' "$0"; exit "${1:-3}"; }
while getopts "s:H:n:m:f:S:r:vh" o; do
case "$o" in
s) DST_SESSION=$OPTARG ;; H) SSH_TARGET=$OPTARG ;; n) DST_HOST=$OPTARG ;;
m) MSG=$OPTARG ;; f) FILE=$OPTARG ;; S) SRC_LABEL=$OPTARG ;;
r) RETRIES=$OPTARG ;; v) VERBOSE=1 ;; h) usage 0 ;; *) usage 3 ;;
esac
done
[ -n "$DST_SESSION" ] || { echo "ERROR: -s DST_SESSION is required" >&2; usage 3; }
[ -x "$SENDER" ] || { echo "ERROR: send-message.sh not found beside this script" >&2; exit 3; }
# Message body from -f / -m / stdin.
if [ -n "$FILE" ]; then [ -r "$FILE" ] || { echo "ERROR: cannot read $FILE" >&2; exit 3; }; MSG=$(cat -- "$FILE")
elif [ -z "$MSG" ] && [ ! -t 0 ]; then MSG=$(cat)
fi
[ -n "$MSG" ] || { echo "ERROR: empty message (use -m, -f, or stdin)" >&2; exit 3; }
# Source label: this agent's host:session (auto-detected, overridable).
if [ -z "$SRC_LABEL" ]; then
src_host=$(hostname -s 2>/dev/null || echo "?")
src_sess=$(tmux display-message -p '#S' 2>/dev/null || echo "?")
SRC_LABEL="${src_host}:${src_sess}"
fi
# Destination host label for the preamble.
if [ -z "$DST_HOST" ]; then
if [ -n "$SSH_TARGET" ]; then
DST_HOST=$(ssh -o ConnectTimeout=8 -o BatchMode=yes "$SSH_TARGET" 'hostname -s' 2>/dev/null || echo "${SSH_TARGET#*@}")
else
DST_HOST=$(hostname -s 2>/dev/null || echo "local")
fi
fi
PREAMBLE="[${SRC_LABEL} -> ${DST_HOST}:${DST_SESSION}]"
FULL="${PREAMBLE} ${MSG}"
B64=$(printf '%s' "$FULL" | base64 -w0)
vflag=""; [ "$VERBOSE" = 1 ] && vflag="-v"
if [ -z "$SSH_TARGET" ]; then
# Local pane: call the canonical sender directly.
exec "$SENDER" -t "$DST_SESSION" -b "$B64" -r "$RETRIES" $vflag
else
# Remote pane: ship the sender over ssh and run it local to the target.
ssh -o ConnectTimeout=10 "$SSH_TARGET" \
"bash -s -- -t '$DST_SESSION' -b '$B64' -r '$RETRIES' $vflag" < "$SENDER"
fi

View File

@@ -1,97 +0,0 @@
#!/usr/bin/env bash
# send-message.sh — reliably deliver a message to a tmux pane running an
# interactive REPL (e.g. a Claude Code / Codex agent).
#
# WHY THIS EXISTS
# Pasting multi-line text into an interactive agent REPL via `tmux send-keys`
# is unreliable: the text lands in the input box but a single trailing Enter
# in the same keystroke stream is frequently swallowed, so the message sits as
# an UNSUBMITTED DRAFT ("Press up to edit queued messages") and the agent never
# sees it. The mechanical fix is: paste as a bracketed paste (so embedded
# newlines don't submit early), pause, then send Enter as its OWN keystroke,
# pause, and send Enter again to flush. An extra Enter on an empty prompt is a
# no-op in Claude Code, so the double-Enter is safe.
#
# USAGE
# send-message.sh -t <target> -m "message"
# send-message.sh -t <target> -f <file>
# echo "message" | send-message.sh -t <target>
# ssh host bash -s -- -t <target> -b "$(base64 -w0 <<<msg)" < send-message.sh
#
# OPTIONS
# -t TARGET tmux target: session, or session:window.pane [required]
# -m MESSAGE message text (single- or multi-line)
# -f FILE read message from FILE instead of -m
# -b BASE64 message as base64 (ssh-safe transport; decoded internally)
# -r N Enter-flush attempts (default 2)
# -v verbose: print a short tail of the pane after delivery
# -h help
#
# EXIT CODES
# 0 delivered (submitted) or queued (agent busy; will process when free)
# 1 tmux target not found
# 2 message still appears to be an unsubmitted draft after retries
# 3 usage error
set -uo pipefail
TARGET=""; MSG=""; FILE=""; B64=""; RETRIES=2; VERBOSE=0
usage() { sed -n '2,34p' "$0"; exit "${1:-3}"; }
while getopts "t:m:f:b:r:vh" o; do
case "$o" in
t) TARGET=$OPTARG ;; m) MSG=$OPTARG ;; f) FILE=$OPTARG ;; b) B64=$OPTARG ;;
r) RETRIES=$OPTARG ;; v) VERBOSE=1 ;; h) usage 0 ;; *) usage 3 ;;
esac
done
[ -n "$TARGET" ] || { echo "ERROR: -t TARGET is required" >&2; usage 3; }
if [ -n "$B64" ]; then MSG=$(printf '%s' "$B64" | base64 -d) || { echo "ERROR: bad -b base64" >&2; exit 3; }
elif [ -n "$FILE" ]; then [ -r "$FILE" ] || { echo "ERROR: cannot read $FILE" >&2; exit 3; }; MSG=$(cat -- "$FILE")
elif [ -z "$MSG" ] && [ ! -t 0 ]; then MSG=$(cat)
fi
[ -n "$MSG" ] || { echo "ERROR: empty message (use -m, -f, or stdin)" >&2; exit 3; }
# Target must resolve to a live pane.
if ! tmux list-panes -t "$TARGET" >/dev/null 2>&1; then
echo "ERROR: tmux target not found: $TARGET" >&2; exit 1
fi
QUEUED_RE='Press up to edit queued messages'
# A distinctive tail of the message to spot an unsubmitted draft on the input line.
snippet=$(printf '%s' "$MSG" | tr '\n' ' ' | tr -s ' ' | sed 's/[^[:print:]]//g' | tail -c 32)
# 1) Paste the body as a bracketed paste so multi-line content does not submit
# line-by-line. load-buffer/paste-buffer is far safer than `send-keys -l`.
printf '%s' "$MSG" | tmux load-buffer -b __mosaic_send -
# -p = bracketed paste when the client supports it; fall back if not.
tmux paste-buffer -d -p -b __mosaic_send -t "$TARGET" 2>/dev/null \
|| tmux paste-buffer -d -b __mosaic_send -t "$TARGET"
sleep 0.5
# 2) Submit, then verify; flush with another Enter if it is still a draft.
status="sent"
for attempt in $(seq 1 $((RETRIES + 1))); do
tmux send-keys -t "$TARGET" Enter
sleep 1.2
pane=$(tmux capture-pane -t "$TARGET" -p 2>/dev/null)
if printf '%s' "$pane" | grep -qF "$QUEUED_RE"; then
status="queued"; break
fi
# Draft heuristic: the prompt glyph line still carries our message tail.
# (Submitted messages scroll up into history; a draft stays on the line.)
promptline=$(printf '%s' "$pane" | grep -E '|^>|│ >' | tail -1)
if [ -n "$snippet" ] && printf '%s' "$promptline" | grep -qF "$snippet"; then
status="draft"; continue
fi
status="delivered"; break
done
[ "$VERBOSE" = 1 ] && { echo "--- pane tail ($TARGET) ---"; printf '%s\n' "$pane" | tail -4; echo "---"; }
case "$status" in
delivered) echo "✓ delivered to $TARGET"; exit 0 ;;
queued) echo "✓ queued to $TARGET (agent busy — will process when it returns to prompt)"; exit 0 ;;
draft) echo "✗ still an unsubmitted draft on $TARGET after $RETRIES flush attempts" >&2; exit 2 ;;
*) echo "✓ sent to $TARGET (submission state indeterminate; verify with -v)"; exit 0 ;;
esac

View File

@@ -1,35 +0,0 @@
# Scratchpad — Thin-core prompt diet (#528)
**Branch:** `feat/contract-thin-core` · **Issue:** #528 · **Mode:** Delivery
## Objective
Cut the always-injected contract (`defaults/AGENTS.md` + `defaults/TOOLS.md` + `runtime/claude/RUNTIME.md`, inlined every turn by the launcher) without losing any hard gate. Restore the original "thin core + on-demand guides" intent.
## Change
- `defaults/AGENTS.md` → thin core: 12 hard gates verbatim, 37 operating rules condensed to ~15 bullets (detail already in `guides/E2E-DELIVERY.md`), Superpowers condensed, load order made on-demand (no halt-on-missing for STANDARDS), conditional guide-loading index retained.
- `defaults/TOOLS.md` → index; full catalog moved to new `guides/TOOLS-REFERENCE.md` (read on demand).
- `runtime/claude/RUNTIME.md` → slimmed (dedup tier table, terser pointers).
## Method (autoresearch-style validation)
1. Built a 9-probe role battery (backend/deploy/review/orchestrate/secrets/docs/simple-trap/no-stop-at-PR/agent-work) + a deterministic 18-signature gate-checklist.
2. Headless interactive runs (Claude Max **subscription**, tmux — no API), scored by per-probe rubric.
3. Keep-or-discard hill-climb (token cost gated by per-probe fidelity) proved the method; final design re-derived against THIS repo's content (diet-only, no drifted-deployment content imported).
## Validation evidence
- Gate-checklist: ALL gates + critical rules + mode lines + sequential-thinking + OpenBrain + Superpowers present.
- A/B on real repo content: **thin 7/9 vs monolith 5/9** probes; strictly better on deploy/review/simple-task; composed **8,827 → 4,122 tok (53%)**.
- p11 (don't-stop-at-PR): 3→2/3 on one rubric line — verified a scorer/phrasing artifact (answer correctly cites gates §5/§9 + close-issue; gate verbatim-present). Variance: thin 2/2/3, v0 3/3/3.
## Decisions / risks
- **Diet-only** vs repo content (user decision). Did NOT import web1's Gate 13-15 / federated memory / OpenViking — canonical repo is behind those deployments; flagged for separate reconciliation.
- AGENTS/TOOLS are shared across runtimes → diet benefits codex/pi/opencode too; RUNTIME change is claude-only.
- p11 accepted as-is (user decision) — not gaming the rubric.
## Status
PR open, paused for maintainer merge ratification (fleet-governing change). `mosaic upgrade` will propagate on merge.