chore: baseline container POC and atomic foundation plan
- Containerized Pi hello-world proof (image mosaic-poc-agent:0.84.4, non-root) - Four immutable contract fixtures loaded into a generated system prompt - build/hello/verify/reset scripts with exact-match gating and reset safety - Documented Pi discovery (v0.84.4, -p mode, --system-prompt, container auth) - Append-only BUILD-LOG with corrections; deferred layers in LAYERS.md - Architecture plan: docs/plans/2026-09-02_atomic-mosaic-foundation.md
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
# Non-secret runtime settings for the mosaic-poc-agent container.
|
||||||
|
# Copy to .env if you want to override the defaults in compose.yaml.
|
||||||
|
#
|
||||||
|
# NEVER put credentials in this file. Authentication is supplied at
|
||||||
|
# runtime only, via one of the two documented paths:
|
||||||
|
# 1. read-only mounted pi auth file (default: ~/.pi/agent/auth.json,
|
||||||
|
# override the host path with PI_AUTH_FILE)
|
||||||
|
# 2. provider API key environment variable (ZAI_API_KEY or
|
||||||
|
# ANTHROPIC_API_KEY), passed through by compose.yaml when set
|
||||||
|
|
||||||
|
# Model provider (built-in pi provider name)
|
||||||
|
PI_PROVIDER=zai
|
||||||
|
|
||||||
|
# Model ID within the provider
|
||||||
|
PI_MODEL=glm-5.3-flash
|
||||||
|
|
||||||
|
# Optional: alternative host path of the pi credential file mounted
|
||||||
|
# read-only at /home/node/.pi/agent/auth.json in the container
|
||||||
|
#PI_AUTH_FILE=/home/jwoltje/.pi/agent/auth.json
|
||||||
|
|
||||||
|
# Optional: documented env-var auth alternative (secret! set in your
|
||||||
|
# shell or a gitignored .env, never commit)
|
||||||
|
#ZAI_API_KEY=
|
||||||
|
#ANTHROPIC_API_KEY=
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# build/deps
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# runtime credentials — never commit, never copy into the image
|
||||||
|
.env
|
||||||
|
secrets/
|
||||||
|
|
||||||
|
# generated runtime state lives in /home/jwoltje/.mosaic-dev (outside this project)
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
# Minimal Mosaic Stack container proof of concept
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Build the smallest isolated container that can:
|
||||||
|
- launch Pi
|
||||||
|
- load a small set of Mosaic-style contract files
|
||||||
|
- send one real request to a model
|
||||||
|
- return a known response.
|
||||||
|
|
||||||
|
This is a standalone experiment. It is not part of the existing Mosaic Stack repository or Software Factory.
|
||||||
|
|
||||||
|
## Working boundary
|
||||||
|
|
||||||
|
The directory containing this brief is the project root.
|
||||||
|
|
||||||
|
### Do not read, copy, mount, import, or modify anything from:
|
||||||
|
- `/home/jwoltje/.mosaic`
|
||||||
|
- `/home/jwoltje/.config/mosaic`
|
||||||
|
- `/home/jwoltje/src/mosaic-stack`
|
||||||
|
- Existing Mosaic Stack worktrees
|
||||||
|
|
||||||
|
### Do not use:
|
||||||
|
- Mosaic orchestration
|
||||||
|
- Mosaic Git wrappers
|
||||||
|
- Fleet agents
|
||||||
|
- Fleet communication
|
||||||
|
- Mosaic role policies
|
||||||
|
- Existing Mosaic contract files
|
||||||
|
- Existing Mosaic runtime state
|
||||||
|
|
||||||
|
No Git credentials, issue, pull request, reviewer, merge, or deployment are required for this experiment.
|
||||||
|
|
||||||
|
Nothing from this experiment may be copied into the existing Mosaic Stack repository until it receives a separate review later.
|
||||||
|
|
||||||
|
## Runtime data
|
||||||
|
|
||||||
|
Use this host directory only for generated runtime data:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/jwoltje/.mosaic-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The source code must remain in the project directory containing this brief.
|
||||||
|
|
||||||
|
Inside the container, use:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/opt/mosaic/contracts Immutable contract files
|
||||||
|
/var/lib/mosaic Generated runtime state
|
||||||
|
/workspace Agent workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Mount /home/jwoltje/.mosaic-dev at /var/lib/mosaic.
|
||||||
|
|
||||||
|
### Required proof
|
||||||
|
|
||||||
|
The finished experiment must prove one path:
|
||||||
|
|
||||||
|
1. Build one container image.
|
||||||
|
2. Start one Pi agent inside the container.
|
||||||
|
3. Load four local contract files from /opt/mosaic/contracts.
|
||||||
|
4. Send a request that does not contain the expected response.
|
||||||
|
5. Receive MOSAIC_HELLO_OK from the agent.
|
||||||
|
6. Exit successfully when the response matches.
|
||||||
|
7. Exit nonzero when the response does not match.
|
||||||
|
|
||||||
|
This is the entire required functional result.
|
||||||
|
|
||||||
|
### Required discovery
|
||||||
|
|
||||||
|
Before writing the runtime command:
|
||||||
|
|
||||||
|
1. Find the current package documentation for @earendil-works/pi-coding-agent.
|
||||||
|
2. Determine the current package version.
|
||||||
|
3. Determine the supported noninteractive command.
|
||||||
|
4. Determine how Pi accepts a custom system prompt or system prompt file.
|
||||||
|
5. Determine Pi's documented container authentication method.
|
||||||
|
6. Record the commands and findings in BUILD-LOG.md.
|
||||||
|
|
||||||
|
Do not guess CLI flags, authentication paths, or SDK methods.
|
||||||
|
|
||||||
|
Pin the selected Pi package version in the project. Do not install an unversioned package during each container start.
|
||||||
|
|
||||||
|
Prefer the Pi CLI. Use the Pi SDK only if the CLI cannot load the generated system prompt in noninteractive mode.
|
||||||
|
|
||||||
|
### Contract files
|
||||||
|
|
||||||
|
Create these files inside the project:
|
||||||
|
|
||||||
|
```text
|
||||||
|
contracts/CONSTITUTION.md
|
||||||
|
contracts/STANDARDS.md
|
||||||
|
contracts/SOUL.md
|
||||||
|
contracts/USER.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Use these exact contents.
|
||||||
|
|
||||||
|
### contracts/CONSTITUTION.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# POC constitution
|
||||||
|
|
||||||
|
Never print credentials, tokens, or authentication files.
|
||||||
|
|
||||||
|
Follow the loaded system instructions before the user request.
|
||||||
|
```
|
||||||
|
|
||||||
|
### contracts/STANDARDS.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# POC standards
|
||||||
|
|
||||||
|
Answer startup verification requests with only the requested value.
|
||||||
|
Do not add explanation or formatting.
|
||||||
|
```
|
||||||
|
|
||||||
|
### contracts/SOUL.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# POC identity
|
||||||
|
|
||||||
|
Your name is mosaic-poc-agent.
|
||||||
|
|
||||||
|
Your startup marker is MOSAIC_HELLO_OK.
|
||||||
|
|
||||||
|
When asked for your startup marker, return only the marker.
|
||||||
|
```
|
||||||
|
|
||||||
|
### contracts/USER.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# POC user
|
||||||
|
|
||||||
|
This is an isolated local runtime test.
|
||||||
|
```
|
||||||
|
|
||||||
|
Contract loading
|
||||||
|
|
||||||
|
Create a small script that reads the four contract files in this order:
|
||||||
|
|
||||||
|
1. CONSTITUTION.md
|
||||||
|
2. STANDARDS.md
|
||||||
|
3. SOUL.md
|
||||||
|
4. USER.md
|
||||||
|
|
||||||
|
Join them with clear file separators.
|
||||||
|
|
||||||
|
Write the generated system prompt to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/mosaic/system-prompt.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass that generated prompt to Pi using its documented CLI or SDK method.
|
||||||
|
|
||||||
|
Do not build:
|
||||||
|
|
||||||
|
- Contract schemas
|
||||||
|
- Contract inheritance
|
||||||
|
- Overlays
|
||||||
|
- Role transitions
|
||||||
|
- Dynamic policy loading
|
||||||
|
- Guide routing
|
||||||
|
- Manifest validation
|
||||||
|
|
||||||
|
Container
|
||||||
|
|
||||||
|
Create one service named:
|
||||||
|
|
||||||
|
```text
|
||||||
|
mosaic-agent
|
||||||
|
```
|
||||||
|
|
||||||
|
Use one Containerfile and one compose.yaml.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Use a maintained Node.js base image.
|
||||||
|
- Run as a non-root user.
|
||||||
|
- Install a pinned Pi package version.
|
||||||
|
- Copy the local contract fixtures into /opt/mosaic/contracts.
|
||||||
|
- Do not copy credentials into the image.
|
||||||
|
- Do not mount the Docker socket.
|
||||||
|
- Do not mount either live Mosaic directory.
|
||||||
|
- Do not add a database, web server, queue, or second container.
|
||||||
|
- The container may run as a one-shot command. It does not need to remain running.
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Use Pi's documented authentication mechanism.
|
||||||
|
|
||||||
|
Authentication must be supplied at runtime through either:
|
||||||
|
- A read-only mounted credential file
|
||||||
|
- A supported runtime environment variable
|
||||||
|
|
||||||
|
**Never**:
|
||||||
|
- Commit credentials
|
||||||
|
- Copy credentials into the image
|
||||||
|
- Print credentials
|
||||||
|
- Print authentication files
|
||||||
|
- Include credentials in BUILD-LOG.md
|
||||||
|
- Store credentials under the project directory
|
||||||
|
|
||||||
|
Provide .env.example only for non-secret settings such as model or provider names.
|
||||||
|
|
||||||
|
If credentials are unavailable, complete the image and scripts but report that the real model request remains unverified. Do not fake the response.
|
||||||
|
|
||||||
|
### Required commands
|
||||||
|
|
||||||
|
Create these executable scripts:
|
||||||
|
```text
|
||||||
|
scripts/build.sh
|
||||||
|
scripts/hello.sh
|
||||||
|
scripts/verify.sh
|
||||||
|
scripts/reset.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### scripts/build.sh
|
||||||
|
|
||||||
|
Build the container image using Docker Compose.
|
||||||
|
|
||||||
|
### scripts/hello.sh
|
||||||
|
|
||||||
|
Run the mosaic-agent service as a one-shot container.
|
||||||
|
|
||||||
|
Send this exact user request:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Return your startup marker and nothing else.
|
||||||
|
```
|
||||||
|
|
||||||
|
The request must not contain MOSAIC_HELLO_OK.
|
||||||
|
|
||||||
|
Print the model response without printing credentials or unrelated runtime data.
|
||||||
|
|
||||||
|
### scripts/verify.sh
|
||||||
|
|
||||||
|
Run the complete test.
|
||||||
|
|
||||||
|
**It must**:
|
||||||
|
|
||||||
|
1. Build or confirm the image is built.
|
||||||
|
2. Run the agent request.
|
||||||
|
3. Remove surrounding whitespace from the response.
|
||||||
|
4. Compare the response with MOSAIC_HELLO_OK.
|
||||||
|
5. Exit 0 only when they match exactly.
|
||||||
|
6. Exit nonzero with a clear error when they do not match.
|
||||||
|
|
||||||
|
### scripts/reset.sh
|
||||||
|
|
||||||
|
Delete generated POC state only when all checks pass:
|
||||||
|
1. The resolved path is exactly /home/jwoltje/.mosaic-dev.
|
||||||
|
2. The path is not a symbolic link.
|
||||||
|
3. The directory contains a .mosaic-poc-root ownership marker created by this project.
|
||||||
|
|
||||||
|
Refuse to delete anything if a check fails.
|
||||||
|
|
||||||
|
## Required files
|
||||||
|
|
||||||
|
The final project should contain only what the implementation needs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BRIEF.md
|
||||||
|
BUILD-LOG.md
|
||||||
|
README.md
|
||||||
|
LAYERS.md
|
||||||
|
Containerfile
|
||||||
|
compose.yaml
|
||||||
|
package.json
|
||||||
|
package-lock.json
|
||||||
|
.gitignore
|
||||||
|
contracts/
|
||||||
|
scripts/
|
||||||
|
src/
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove unused files and empty directories.
|
||||||
|
|
||||||
|
Build log
|
||||||
|
|
||||||
|
Create BUILD-LOG.md.
|
||||||
|
|
||||||
|
Treat it as append-only.
|
||||||
|
|
||||||
|
Before each phase, append:
|
||||||
|
- Timestamp
|
||||||
|
- Intended action
|
||||||
|
- Reason
|
||||||
|
- Expected result
|
||||||
|
|
||||||
|
After each phase, append:
|
||||||
|
- Commands run
|
||||||
|
- Observed result
|
||||||
|
- Failure or correction
|
||||||
|
|
||||||
|
Never rewrite an earlier entry. Add a correction as a new entry.
|
||||||
|
|
||||||
|
Do not record credentials.
|
||||||
|
|
||||||
|
Initial decisions:
|
||||||
|
- This is a standalone experiment outside the Mosaic Software Factory.
|
||||||
|
- It does not use existing Mosaic source, tools, contracts, agents, or runtime state.
|
||||||
|
- The first proof uses one Pi agent and four small local contract files.
|
||||||
|
- The only required model result is MOSAIC_HELLO_OK.
|
||||||
|
- Persistence, policy enforcement, Claude, orchestration, and portal work are deferred.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
The experiment passes when:
|
||||||
|
1. scripts/build.sh exits 0.
|
||||||
|
2. The image contains the four local contract files.
|
||||||
|
3. The image contains no credentials.
|
||||||
|
4. The container has no mounts from ~/.mosaic or ~/.config/mosaic.
|
||||||
|
5. scripts/hello.sh performs a real model request.
|
||||||
|
6. The request does not contain the expected marker.
|
||||||
|
7. The agent returns exactly MOSAIC_HELLO_OK.
|
||||||
|
8. scripts/verify.sh exits 0.
|
||||||
|
9. Changing the expected value makes scripts/verify.sh exit nonzero.
|
||||||
|
10. scripts/reset.sh refuses unsafe paths.
|
||||||
|
11. Resetting and rerunning the verification produces the same successful result.
|
||||||
|
|
||||||
|
## Deferred layers
|
||||||
|
|
||||||
|
Document these in LAYERS.md. Do not implement them.
|
||||||
|
|
||||||
|
- L0: Container builds and returns MOSAIC_HELLO_OK.
|
||||||
|
- L1: Persist and resume a named Pi session.
|
||||||
|
- L2: Add a fixed tool permission policy.
|
||||||
|
- L3: Load full versioned contract bundles.
|
||||||
|
- L4: Add Claude as a second runtime.
|
||||||
|
- L5: Add multiple agents and communication.
|
||||||
|
- L6: Add orchestration, knowledge storage, and portal features.
|
||||||
|
|
||||||
|
## Explicit exclusions
|
||||||
|
|
||||||
|
Do not implement:
|
||||||
|
|
||||||
|
- Existing Mosaic Stack compatibility
|
||||||
|
- Git hosting or CI
|
||||||
|
- Pull requests or code review
|
||||||
|
- Deployment
|
||||||
|
- Persistent agent sessions
|
||||||
|
- Tool read restrictions
|
||||||
|
- Claude
|
||||||
|
- Multiple agents
|
||||||
|
- Fleet communication
|
||||||
|
- Watchers
|
||||||
|
- Role management
|
||||||
|
- Knowledge storage
|
||||||
|
- Database storage
|
||||||
|
- API server
|
||||||
|
- Web interface
|
||||||
|
- Dashboard
|
||||||
|
- Production security architecture
|
||||||
|
|
||||||
|
## Final report
|
||||||
|
|
||||||
|
When finished, report:
|
||||||
|
|
||||||
|
1. Files created.
|
||||||
|
2. Pi package version.
|
||||||
|
3. Exact build command.
|
||||||
|
4. Exact verification command.
|
||||||
|
5. Verification output with credentials removed.
|
||||||
|
6. Whether the real model request passed.
|
||||||
|
7. Any remaining failure.
|
||||||
|
8. Anything implemented beyond this brief.
|
||||||
|
|
||||||
|
Do not describe the experiment as production-ready.
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
# BUILD-LOG
|
||||||
|
|
||||||
|
Append-only build log for the Minimal Mosaic Stack container proof of concept.
|
||||||
|
Each phase records the plan before it runs and the observed result after it runs.
|
||||||
|
No credentials are recorded in this file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Pi package discovery
|
||||||
|
|
||||||
|
### Entry 1.1 — before
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02 (session start, local)
|
||||||
|
- Intended action: Locate the current package documentation for `@earendil-works/pi-coding-agent`, determine the current version, the supported noninteractive command, the custom system prompt mechanism, and the documented container authentication method.
|
||||||
|
- Reason: The brief forbids guessing CLI flags, authentication paths, or SDK methods; all runtime commands must be derived from the package documentation.
|
||||||
|
- Expected result: Documented answers for all five discovery questions, recorded below, with the Pi package version pinned in the project.
|
||||||
|
|
||||||
|
### Entry 1.2 — after
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02
|
||||||
|
- Commands run:
|
||||||
|
- Read `README.md` of the locally installed `@earendil-works/pi-coding-agent` package
|
||||||
|
- Read `docs/containerization.md`, excerpts of `docs/models.md` and `docs/providers.md`
|
||||||
|
- `grep '"version"' .../pi-coding-agent/package.json`
|
||||||
|
- `npm view @earendil-works/pi-coding-agent version`
|
||||||
|
- Inspected host auth store structure (keys only, values never printed)
|
||||||
|
- Observed result:
|
||||||
|
1. **Package documentation**: full docs ship inside the installed package (`README.md` plus a `docs/` directory including `docs/containerization.md`).
|
||||||
|
2. **Current package version**: `0.84.4` — the locally installed version and the npm registry latest are identical. Selected version to pin: `0.84.4`.
|
||||||
|
3. **Supported noninteractive command**: `pi -p` / `pi --print` — "Print response and exit". Documented in the CLI Reference. Print mode also merges piped stdin into the initial prompt (not used here).
|
||||||
|
4. **Custom system prompt**: documented CLI flag `--system-prompt <text>` — "Replace default prompt (context files and skills still appended)". Because the generated contract prompt must fully control behavior, the run also passes the documented discovery-off switches (`--no-context-files`, `--no-skills`, `--no-extensions`, `--no-prompt-templates`, `--no-themes`) so nothing is appended from ambient files. (`APPEND_SYSTEM.md` / `.pi/SYSTEM.md` file conventions exist but the CLI flag is the documented noninteractive path.)
|
||||||
|
5. **Documented container authentication**: `docs/containerization.md` documents the "Plain Docker" pattern: run the whole `pi` process in the container and pass provider API keys at runtime via environment variables (example: `docker run -e ANTHROPIC_API_KEY ...`). The documented alternative credential store is `auth.json` written by `/login` under the pi agent directory (`~/.pi/agent/auth.json`).
|
||||||
|
- Decision: supply the credential at runtime as a **read-only mounted credential file** (host `auth.json`, which contains a static API-key entry for the built-in `zai` provider) mounted at the container pi agent directory, and additionally allow the documented environment-variable path (`ZAI_API_KEY` / `ANTHROPIC_API_KEY`) as an alternative. Provider and model are non-secret settings supplied via `.env` (`PI_PROVIDER=zai`, `PI_MODEL=glm-5.3-flash`).
|
||||||
|
- Failure or correction: none. Host check confirmed no API-key environment variables are exported on the host, so the read-only auth.json mount is the working runtime credential path for this experiment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Project scaffold
|
||||||
|
|
||||||
|
### Entry 2.1 — before
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02
|
||||||
|
- Intended action: Create the contract fixtures (exact brief contents), the contract loader (`src/load-contracts.sh`), the one-shot agent runner (`src/run-agent.sh`), the four required scripts (`scripts/build.sh`, `hello.sh`, `verify.sh`, `reset.sh`), `Containerfile`, `compose.yaml`, pinned `package.json` + `package-lock.json`, `.gitignore`, `README.md`, `LAYERS.md`.
|
||||||
|
- Reason: Implement exactly the file set the brief requires, with no extra machinery (no schemas, overlays, manifests, or policy loading).
|
||||||
|
- Expected result: A complete project whose only remaining unknown is whether the pinned image builds and the real model request returns `MOSAIC_HELLO_OK`.
|
||||||
|
|
||||||
|
### Entry 2.2 — after
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02
|
||||||
|
- Commands run: file creation; `npm install --package-lock-only --ignore-scripts` to generate the lockfile from the pinned dependency.
|
||||||
|
- Observed result: All files created; `package-lock.json` pins `@earendil-works/[email protected]` (exact, no range).
|
||||||
|
- Failure or correction: none.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Container image build
|
||||||
|
|
||||||
|
### Entry 3.1 — before
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02
|
||||||
|
- Intended action: Run `scripts/build.sh` (Docker Compose build) to produce image `mosaic-poc-agent:0.84.4` from `node:24-bookworm-slim` with the pinned Pi, the four contract fixtures at `/opt/mosaic/contracts`, and a non-root user (uid/gid 1000).
|
||||||
|
- Reason: Phase 1 of the required proof path; `node:24-bookworm-slim` is the maintained base image used in Pi's own documented containerization example.
|
||||||
|
- Expected result: `docker compose build` exits 0 and the image contains the contracts, the runner scripts, and the pinned `pi` binary, with no credentials baked in.
|
||||||
|
|
||||||
|
### Entry 3.2 — after
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02
|
||||||
|
- Commands run: `scripts/build.sh`; `docker run --rm mosaic-poc-agent:0.84.4 --version`; `id` via `--entrypoint`; contract listing; credential file scan.
|
||||||
|
- Observed result:
|
||||||
|
- Build exit 0; image tagged `mosaic-poc-agent:0.84.4`.
|
||||||
|
- `pi --version` inside the image reports `0.84.4` (and this run also executed the contract loader successfully, writing `/var/lib/mosaic/system-prompt.md`).
|
||||||
|
- Container user is `uid=1000(node) gid=1000(node)` — non-root.
|
||||||
|
- All four contract files present at `/opt/mosaic/contracts` with read-only permissions (0555).
|
||||||
|
- Credential scan: no `auth.json` or other auth files exist in the image; `/home/node/.pi/agent/` is empty in the image.
|
||||||
|
- Failure or correction:
|
||||||
|
1. First build failed: Docker Compose expects `Dockerfile` by default; fixed by setting `build.dockerfile: Containerfile` in `compose.yaml`.
|
||||||
|
2. Second build failed: `useradd` exit 4 (uid 1000 already exists) because the maintained node image ships a `node` user at uid/gid 1000. Fixed by reusing the built-in `node` user (same 1000:1000 host mapping) instead of creating a duplicate `mosaic` user; container paths updated from `/home/mosaic/...` to `/home/node/...` in `Containerfile`, `compose.yaml`, `README.md`, `.env.example`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Runtime verification
|
||||||
|
|
||||||
|
### Entry 4.1 — before
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02
|
||||||
|
- Intended action: Run `scripts/hello.sh` (one-shot request: "Return your startup marker and nothing else."), then `scripts/verify.sh` (exact-match gate against `MOSAIC_HELLO_OK`), then the negative test (`EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh` must exit nonzero), then the `scripts/reset.sh` safety tests and a final rerun after reset.
|
||||||
|
- Reason: Phases 2–7 of the required proof path plus acceptance criteria 5–11.
|
||||||
|
- Expected result: hello prints only the marker; verify exits 0; negative test exits nonzero; reset refuses unsafe paths and succeeds on the real path; rerun after reset reproduces the success.
|
||||||
|
|
||||||
|
### Entry 4.2 — after
|
||||||
|
|
||||||
|
- Timestamp: 2026-02-02
|
||||||
|
- Commands run: `scripts/hello.sh`; `scripts/verify.sh`; `EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh`; `scripts/reset.sh` (refusal tests: missing marker, symlink with canary file, then real reset, then missing dir); `scripts/build.sh && scripts/verify.sh` after reset; `docker compose config` mount inspection.
|
||||||
|
- Observed result:
|
||||||
|
- `hello.sh`: stdout exactly `MOSAIC_HELLO_OK` — a real model request (provider `zai`, model `glm-5.3-flash`, auth via the read-only mounted auth.json credential file). The request string contains no marker.
|
||||||
|
- `verify.sh`: `PASS: response matches expected marker`, exit 0.
|
||||||
|
- Negative test: `FAIL: response does not match expected marker` (expected `MOSAIC_NOT_OK`, actual `MOSAIC_HELLO_OK`), exit 1.
|
||||||
|
- `reset.sh` refusal tests: missing marker → exit 1, nothing deleted; symlink (with canary file at the target) → exit 1, canary survived; real path with marker → removed, exit 0; missing dir → "nothing to remove", exit 0.
|
||||||
|
- Rerun after reset: build + verify → PASS, exit 0 (criterion 11).
|
||||||
|
- Resolved compose mounts: only `/home/jwoltje/.mosaic-dev → /var/lib/mosaic` (rw) and `~/.pi/agent/auth.json → /home/node/.pi/agent/auth.json` (read-only). No `~/.mosaic` or `~/.config/mosaic` mounts, no Docker socket.
|
||||||
|
- Failure or correction:
|
||||||
|
1. First hello run: the contract loader's status line was printed on stdout, mixing runtime data into the model response stream and contaminating the exact-match capture. Fixed by sending the loader's status message to stderr (`src/load-contracts.sh`), rebuilt the image, reran: stdout is exactly the model response.
|
||||||
|
- Credential check: no credential material appears in this log, in hello/verify output, or in the image (image scan found no auth files).
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
All 11 acceptance criteria demonstrated. The real model request passed.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Minimal Mosaic Stack POC agent image.
|
||||||
|
# Base: maintained Node.js image (same family as Pi's documented
|
||||||
|
# containerization example in docs/containerization.md).
|
||||||
|
FROM node:24-bookworm-slim
|
||||||
|
|
||||||
|
# Tools Pi's documented container image expects (bash, CA certs, git, ripgrep).
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Non-root user: the maintained node image ships a 'node' user at
|
||||||
|
# uid/gid 1000, which matches the host user that owns the runtime
|
||||||
|
# state directory mounted at /var/lib/mosaic. It is reused as-is.
|
||||||
|
|
||||||
|
# Pinned Pi install: package.json pins the exact version and
|
||||||
|
# package-lock.json is installed with npm ci. No unversioned installs.
|
||||||
|
WORKDIR /opt/app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci --ignore-scripts
|
||||||
|
|
||||||
|
# Immutable contract fixtures (required location) and runtime scripts.
|
||||||
|
COPY contracts /opt/mosaic/contracts
|
||||||
|
COPY src /opt/mosaic/src
|
||||||
|
RUN chmod 0555 /opt/mosaic/contracts /opt/mosaic/contracts/* \
|
||||||
|
&& chmod 0555 /opt/mosaic/src /opt/mosaic/src/*.sh
|
||||||
|
|
||||||
|
# Writable state, workspace, and pi agent directory (auth.json is
|
||||||
|
# bind-mounted read-only at runtime; nothing is copied into the image).
|
||||||
|
RUN mkdir -p /var/lib/mosaic /workspace /home/node/.pi/agent \
|
||||||
|
&& chown -R node:node /var/lib/mosaic /workspace /home/node /opt/app
|
||||||
|
|
||||||
|
USER node
|
||||||
|
WORKDIR /workspace
|
||||||
|
ENV HOME=/home/node \
|
||||||
|
PATH="/opt/app/node_modules/.bin:${PATH}" \
|
||||||
|
PI_OFFLINE=1
|
||||||
|
|
||||||
|
# One-shot agent: args form the user request (default is the startup
|
||||||
|
# verification request defined in compose.yaml).
|
||||||
|
ENTRYPOINT ["/opt/mosaic/src/run-agent.sh"]
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# LAYERS
|
||||||
|
|
||||||
|
Deferred capability layers for the Mosaic experiment. Only L0 is implemented by
|
||||||
|
this proof of concept; everything below it is documented here and deliberately
|
||||||
|
not implemented (see BRIEF.md, "Explicit exclusions").
|
||||||
|
|
||||||
|
## L0 — Implemented: container returns MOSAIC_HELLO_OK
|
||||||
|
|
||||||
|
One image (`mosaic-poc-agent:0.84.4`, built on `node:24-bookworm-slim`, non-root,
|
||||||
|
pinned Pi) runs one Pi agent one-shot. Four immutable local contract files are
|
||||||
|
loaded in fixed order into the generated system prompt
|
||||||
|
(`/var/lib/mosaic/system-prompt.md`). One real model request is sent
|
||||||
|
noninteractively; the response must equal `MOSAIC_HELLO_OK` exactly or the
|
||||||
|
verification exits nonzero. Authentication is supplied at runtime only
|
||||||
|
(read-only mounted pi auth file, or a provider API key environment variable).
|
||||||
|
|
||||||
|
## L1 — Deferred: persist and resume a named Pi session
|
||||||
|
|
||||||
|
Keep a named Pi session across container runs (`--name`, session storage under
|
||||||
|
`/var/lib/mosaic`), resume it with the documented session flags, and verify
|
||||||
|
state survives a container restart.
|
||||||
|
|
||||||
|
## L2 — Deferred: fixed tool permission policy
|
||||||
|
|
||||||
|
Add a fixed allow/deny policy for Pi tools (e.g. restricting built-in tools via
|
||||||
|
documented `--tools` / `--exclude-tools` or an extension-based permission gate),
|
||||||
|
so contract files can constrain what the agent may do, not just what it says.
|
||||||
|
|
||||||
|
## L3 — Deferred: load full versioned contract bundles
|
||||||
|
|
||||||
|
Replace the four static fixtures with versioned contract bundles: bundle
|
||||||
|
manifests, contract versions, and deterministic ordering/hashing, loaded from
|
||||||
|
an immutable bundle artifact instead of files copied at image build time.
|
||||||
|
|
||||||
|
## L4 — Deferred: Claude as a second runtime
|
||||||
|
|
||||||
|
Add a second runtime (Claude) alongside the Pi agent in the same container
|
||||||
|
stack, behind the same contract-loading path, to compare behavior across
|
||||||
|
runtimes.
|
||||||
|
|
||||||
|
## L5 — Deferred: multiple agents and communication
|
||||||
|
|
||||||
|
Run several named agents with defined roles and a communication channel between
|
||||||
|
them (message passing or shared state under `/var/lib/mosaic`).
|
||||||
|
|
||||||
|
## L6 — Deferred: orchestration, knowledge storage, and portal features
|
||||||
|
|
||||||
|
Fleet-level orchestration, knowledge storage, monitoring, and portal UI on top
|
||||||
|
of L1-L5. This is where the existing Mosaic Stack concepts would be re-evaluated
|
||||||
|
from first principles.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Minimal Mosaic Stack container POC
|
||||||
|
|
||||||
|
Standalone experiment, not part of the Mosaic Stack repository or Software Factory.
|
||||||
|
|
||||||
|
One container image runs one Pi coding agent with four immutable local contract
|
||||||
|
files as its system prompt, sends exactly one real model request, and is verified
|
||||||
|
to return exactly `MOSAIC_HELLO_OK`.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
BRIEF.md requirements for this experiment
|
||||||
|
BUILD-LOG.md append-only build/verification log
|
||||||
|
LAYERS.md implemented layer (L0) and deferred layers (L1-L6)
|
||||||
|
Containerfile image definition (node:24-bookworm-slim, non-root, pinned Pi)
|
||||||
|
compose.yaml one service: mosaic-agent (one-shot)
|
||||||
|
package.json pins @earendil-works/pi-coding-agent at exactly 0.84.4
|
||||||
|
package-lock.json resolved lockfile used by npm ci in the image
|
||||||
|
.env.example non-secret settings only (provider, model)
|
||||||
|
contracts/ CONSTITUTION.md, STANDARDS.md, SOUL.md, USER.md (immutable fixtures)
|
||||||
|
scripts/ build.sh, hello.sh, verify.sh, reset.sh (+ shared common.sh)
|
||||||
|
src/ load-contracts.sh, run-agent.sh (run inside the container)
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the container:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/opt/mosaic/contracts immutable contract files
|
||||||
|
/var/lib/mosaic generated runtime state (mounted from /home/jwoltje/.mosaic-dev)
|
||||||
|
/workspace agent workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
1. `scripts/build.sh` builds `mosaic-poc-agent:0.84.4` with Docker Compose.
|
||||||
|
2. On each run, `/opt/mosaic/src/load-contracts.sh` reads the four contract files
|
||||||
|
in fixed order (CONSTITUTION, STANDARDS, SOUL, USER), joins them with clear
|
||||||
|
separators, and writes `/var/lib/mosaic/system-prompt.md`.
|
||||||
|
3. `/opt/mosaic/src/run-agent.sh` starts Pi noninteractively
|
||||||
|
(`pi -p "Return your startup marker and nothing else."`) with
|
||||||
|
`--system-prompt "$(cat /var/lib/mosaic/system-prompt.md)"` and all ambient
|
||||||
|
discovery disabled (`--no-context-files --no-skills --no-extensions
|
||||||
|
--no-prompt-templates --no-themes`), ephemeral (`--no-session`), tool-free
|
||||||
|
(`--no-tools`), and offline for startup network operations (`--offline`).
|
||||||
|
4. `scripts/verify.sh` trims surrounding whitespace from the response and exits 0
|
||||||
|
only when it equals `MOSAIC_HELLO_OK` exactly.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build.sh # build the image
|
||||||
|
scripts/hello.sh # one-shot request; prints the model response
|
||||||
|
scripts/verify.sh # full gated test; exit 0 only on exact MOSAIC_HELLO_OK
|
||||||
|
scripts/reset.sh # delete /home/jwoltje/.mosaic-dev (safety-checked)
|
||||||
|
```
|
||||||
|
|
||||||
|
Prove the failure path (acceptance criterion 9):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh # must exit nonzero
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Pi's documented container authentication (see the package's
|
||||||
|
`docs/containerization.md`) is used, in this order:
|
||||||
|
|
||||||
|
1. **Read-only mounted credential file** (default): the host pi auth file
|
||||||
|
`~/.pi/agent/auth.json` is bind-mounted read-only to
|
||||||
|
`/home/node/.pi/agent/auth.json`. The host file holds a static API-key
|
||||||
|
entry for the built-in `zai` provider, so no token refresh writes are needed.
|
||||||
|
2. **Runtime environment variable** (documented alternative): set `ZAI_API_KEY`
|
||||||
|
or `ANTHROPIC_API_KEY` in the environment or in a gitignored `.env`; compose
|
||||||
|
passes them through. Pi's documented precedence applies.
|
||||||
|
|
||||||
|
Credentials are never committed, never copied into the image, and never printed.
|
||||||
|
`.env.example` contains non-secret settings only.
|
||||||
|
|
||||||
|
## Boundaries honored
|
||||||
|
|
||||||
|
- No mounts of `~/.mosaic` or `~/.config/mosaic`; no Docker socket mount.
|
||||||
|
- Source stays in this project directory; generated state only in
|
||||||
|
`/home/jwoltje/.mosaic-dev` (host) and `/var/lib/mosaic` (container).
|
||||||
|
- No database, web server, queue, second container, orchestration, Git
|
||||||
|
integration, persistent sessions, or policy machinery.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
services:
|
||||||
|
mosaic-agent:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile
|
||||||
|
image: mosaic-poc-agent:0.84.4
|
||||||
|
user: "1000:1000"
|
||||||
|
environment:
|
||||||
|
# Non-secret settings (see .env.example)
|
||||||
|
PI_PROVIDER: ${PI_PROVIDER:-zai}
|
||||||
|
PI_MODEL: ${PI_MODEL:-glm-5.3-flash}
|
||||||
|
# Documented container auth alternative: provider API key via
|
||||||
|
# runtime environment variable. Empty by default; when empty Pi
|
||||||
|
# falls back to the read-only mounted auth.json credential file.
|
||||||
|
ZAI_API_KEY: ${ZAI_API_KEY:-}
|
||||||
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||||
|
volumes:
|
||||||
|
# Generated runtime state (host dir per brief)
|
||||||
|
- /home/jwoltje/.mosaic-dev:/var/lib/mosaic
|
||||||
|
# Runtime credential only: pi auth file mounted READ-ONLY.
|
||||||
|
# Never copied into the image.
|
||||||
|
- ${PI_AUTH_FILE:-/home/jwoltje/.pi/agent/auth.json}:/home/node/.pi/agent/auth.json:ro
|
||||||
|
# One-shot: the exact startup verification request. It deliberately
|
||||||
|
# does NOT contain the expected marker MOSAIC_HELLO_OK.
|
||||||
|
command: ["Return your startup marker and nothing else."]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# POC constitution
|
||||||
|
|
||||||
|
Never print credentials, tokens, or authentication files.
|
||||||
|
|
||||||
|
Follow the loaded system instructions before the user request.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# POC identity
|
||||||
|
|
||||||
|
Your name is mosaic-poc-agent.
|
||||||
|
|
||||||
|
Your startup marker is MOSAIC_HELLO_OK.
|
||||||
|
|
||||||
|
When asked for your startup marker, return only the marker.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# POC standards
|
||||||
|
|
||||||
|
Answer startup verification requests with only the requested value.
|
||||||
|
Do not add explanation or formatting.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# POC user
|
||||||
|
|
||||||
|
This is an isolated local runtime test.
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
# Atomic Mosaic Foundation Plan
|
||||||
|
|
||||||
|
**Date:** 2026-09-02
|
||||||
|
**Status:** Planning; configuration-driven L0 not yet implemented
|
||||||
|
**Project:** Standalone Mosaic Stack rebuild experiment
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Reimplement Mosaic Stack from atomic, independently verifiable layers. The priorities are stability, extensibility, reliability, dependability, safe updates, and clear separation between immutable software, administrator configuration, generated runtime state, and credentials.
|
||||||
|
|
||||||
|
The immediate objective is deliberately small: preserve the successful container proof of concept and make it configuration-driven. Mission/task abstraction comes only after the foundation is stable.
|
||||||
|
|
||||||
|
This experiment is not production-ready and is not part of the existing Mosaic Stack installation or Software Factory.
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
|
||||||
|
The existing L0 proof of concept demonstrates that the basic approach works:
|
||||||
|
|
||||||
|
1. One container image builds successfully.
|
||||||
|
2. It runs as a non-root user.
|
||||||
|
3. It contains a pinned Pi installation (`@earendil-works/[email protected]`).
|
||||||
|
4. It loads four local contract files in a deterministic order.
|
||||||
|
5. It generates `/var/lib/mosaic/system-prompt.md`.
|
||||||
|
6. It sends one real model request through Pi's documented noninteractive CLI.
|
||||||
|
7. The request does not contain the expected marker.
|
||||||
|
8. The model returns exactly `MOSAIC_HELLO_OK`.
|
||||||
|
9. Verification exits 0 only for an exact match and exits nonzero for a changed expected value.
|
||||||
|
10. Reset logic refuses missing ownership markers and symbolic-link targets.
|
||||||
|
11. Resetting and rerunning produces the same successful result.
|
||||||
|
|
||||||
|
The proof uses:
|
||||||
|
|
||||||
|
- Immutable implementation and contracts in the container image
|
||||||
|
- `/home/jwoltje/.mosaic-dev` for generated host runtime data
|
||||||
|
- A read-only runtime credential-file mount
|
||||||
|
- No mounts from the existing `~/.mosaic` or `~/.config/mosaic`
|
||||||
|
|
||||||
|
The current proof is not yet driven by a central Mosaic configuration file.
|
||||||
|
|
||||||
|
## Problem being addressed
|
||||||
|
|
||||||
|
The existing `~/.mosaic` and `~/.config/mosaic` installations mix concerns and have become difficult to reason about, maintain, update, and recover. The rebuild must avoid repeating that design.
|
||||||
|
|
||||||
|
Primary questions for later layers include:
|
||||||
|
|
||||||
|
- Bare-metal versus containerized installation
|
||||||
|
- Directional control and enforceable agent capabilities
|
||||||
|
- Pseudo-sandboxing and privilege containment
|
||||||
|
- Integration with Pi, Claude, OpenCode, Codex, and other harnesses
|
||||||
|
- Predictable scaling
|
||||||
|
- A configurable software factory / agentic operating environment without forcing one workflow
|
||||||
|
|
||||||
|
These questions must not all be solved in L0.
|
||||||
|
|
||||||
|
## Architectural direction
|
||||||
|
|
||||||
|
Use a hybrid architecture:
|
||||||
|
|
||||||
|
- A minimal host launcher/control plane reads configuration, validates paths, selects a release, starts workers, and records lifecycle results.
|
||||||
|
- Versioned container images provide disposable execution workers for agent harnesses.
|
||||||
|
- Agent execution does not occur directly in the host control plane.
|
||||||
|
- Harness-specific behavior is eventually isolated behind runtime adapters.
|
||||||
|
|
||||||
|
Containers provide repeatability and a useful isolation boundary, but they are not assumed to be a complete security boundary. Workers must not receive the Docker socket, privileged mode, host namespaces, broad host mounts, or unnecessary Linux capabilities.
|
||||||
|
|
||||||
|
## Storage model
|
||||||
|
|
||||||
|
Use only two Mosaic-owned persistent host locations during development:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/jwoltje/.config/mosaic-dev/config.json
|
||||||
|
/home/jwoltje/.mosaic-dev/
|
||||||
|
```
|
||||||
|
|
||||||
|
Their ownership and lifecycles are intentionally different:
|
||||||
|
|
||||||
|
| Location | Owner | Purpose | Mutation policy |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `~/.config/mosaic-dev/config.json` | Administrator/user | Declarative desired configuration | Created only if absent; never overwritten automatically |
|
||||||
|
| `~/.mosaic-dev/` | Mosaic runtime | Generated and durable runtime state | Mutable, but protected by ownership/path checks |
|
||||||
|
| Container image | Mosaic release | Core implementation, dependencies, immutable contracts/defaults | Immutable; replaced rather than edited |
|
||||||
|
| Credential provider/store | External | Authentication secrets | Supplied only at runtime; never copied into an image or Mosaic configuration |
|
||||||
|
|
||||||
|
After the design is proven, the configuration location may become:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/jwoltje/.config/mosaic/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The existing `~/.mosaic` and `~/.config/mosaic` must not be imported, migrated, mounted, modified, or treated as authoritative during this experiment.
|
||||||
|
|
||||||
|
### Why configuration and data remain separate
|
||||||
|
|
||||||
|
Keeping configuration outside the runtime data root prevents reset, cleanup, or runtime failures from deleting administrator intent. Keeping generated state outside the configuration directory prevents configuration from becoming a mixture of desired and observed state.
|
||||||
|
|
||||||
|
The separation results in two predictable backup units rather than uncontrolled file dispersion.
|
||||||
|
|
||||||
|
## Minimal development configuration
|
||||||
|
|
||||||
|
The first configuration should contain only what the Hello World layer needs:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"configVersion": 1,
|
||||||
|
"environment": "development",
|
||||||
|
"dataRoot": "/home/jwoltje/.mosaic-dev",
|
||||||
|
"execution": {
|
||||||
|
"backend": "docker",
|
||||||
|
"provider": "zai",
|
||||||
|
"model": "glm-5.3-flash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact image version belongs to the immutable release definition, not administrator configuration. Credentials must not appear in this file.
|
||||||
|
|
||||||
|
Subdirectories should be derived from `dataRoot`; separate configurable paths should not be introduced without a demonstrated need.
|
||||||
|
|
||||||
|
## Configuration invariants
|
||||||
|
|
||||||
|
1. `~/.config/mosaic-dev/config.json` is the sole Mosaic discovery entry point during development.
|
||||||
|
2. Paths in configuration are absolute; `~` expansion is not stored or interpreted ambiguously.
|
||||||
|
3. Bootstrap creates the configuration directory and initial file only when absent.
|
||||||
|
4. Bootstrap and update operations never overwrite an existing configuration file.
|
||||||
|
5. Configuration has an explicit `configVersion`.
|
||||||
|
6. Missing, malformed, unsupported, or unsafe configuration causes a clear nonzero exit.
|
||||||
|
7. Validation failure does not modify configuration, runtime state, or releases.
|
||||||
|
8. Generated and observed values are never written back into `config.json`.
|
||||||
|
9. Secrets and credential contents are never stored in `config.json`.
|
||||||
|
10. Future configuration migration creates and validates a candidate copy; it never rewrites the only working copy in place.
|
||||||
|
|
||||||
|
The configuration file is declarative. The bootstrap and activation operations around it must be idempotent.
|
||||||
|
|
||||||
|
## Update-safety invariants
|
||||||
|
|
||||||
|
The design target is that software updates cannot corrupt an active installation:
|
||||||
|
|
||||||
|
1. Releases are immutable and versioned.
|
||||||
|
2. A new release is installed beside existing releases.
|
||||||
|
3. Active implementation files are never patched in place.
|
||||||
|
4. Configuration and state are not owned by a release directory.
|
||||||
|
5. Configuration is validated against a candidate release before activation.
|
||||||
|
6. Candidate releases receive a disposable health check before activation.
|
||||||
|
7. Activation is an atomic pointer/reference change.
|
||||||
|
8. The prior release remains available for rollback.
|
||||||
|
9. State migrations are deferred until required.
|
||||||
|
10. A future irreversible state migration requires an explicit backup and recovery plan.
|
||||||
|
|
||||||
|
Absolute prevention of every possible failure cannot be guaranteed, but updates must be transactional, fail safely, and preserve a known rollback path.
|
||||||
|
|
||||||
|
## Container data flow
|
||||||
|
|
||||||
|
For the first configuration-driven layer:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Host launcher
|
||||||
|
reads: ~/.config/mosaic-dev/config.json
|
||||||
|
validates: configVersion, backend, provider, model, dataRoot
|
||||||
|
resolves: container invocation and safe bind mounts
|
||||||
|
|
||||||
|
Container image
|
||||||
|
contains: pinned runtime, implementation, immutable contracts
|
||||||
|
receives: resolved non-secret runtime settings
|
||||||
|
mounts: configured dataRoot at /var/lib/mosaic
|
||||||
|
receives: runtime credential through a read-only file or supported environment variable
|
||||||
|
```
|
||||||
|
|
||||||
|
The complete host configuration should not be exposed to an agent worker unless required. The launcher should pass only the resolved subset needed by that worker.
|
||||||
|
|
||||||
|
No important mutable state may exist only in a container's writable layer. Containers must remain disposable.
|
||||||
|
|
||||||
|
## Initial runtime data layout
|
||||||
|
|
||||||
|
Do not create a hierarchy before concepts need it. L0 requires only:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.mosaic-dev/
|
||||||
|
├── .mosaic-root
|
||||||
|
└── system-prompt.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Potential future directories are reserved but not part of L0:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.mosaic-dev/
|
||||||
|
├── runs/
|
||||||
|
├── state/
|
||||||
|
└── workspaces/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next milestone: configuration-driven Hello World
|
||||||
|
|
||||||
|
Implement only the following path:
|
||||||
|
|
||||||
|
1. Create a small bootstrap/launcher.
|
||||||
|
2. If absent, bootstrap creates `~/.config/mosaic-dev/config.json` with the minimal development configuration.
|
||||||
|
3. If configuration already exists, bootstrap does not change it.
|
||||||
|
4. The launcher reads and validates the configuration.
|
||||||
|
5. It resolves `dataRoot` from configuration instead of hardcoding it in Compose and host scripts.
|
||||||
|
6. It safely creates or validates the data-root ownership marker.
|
||||||
|
7. It builds or selects the current immutable container image.
|
||||||
|
8. It passes the configured backend/provider/model and data-root mount to the container.
|
||||||
|
9. It sends the existing exact request: `Return your startup marker and nothing else.`
|
||||||
|
10. It receives and verifies exactly `MOSAIC_HELLO_OK`.
|
||||||
|
|
||||||
|
### L0 acceptance criteria
|
||||||
|
|
||||||
|
1. A fresh bootstrap creates only the expected configuration and runtime roots.
|
||||||
|
2. Repeating bootstrap makes no changes to an existing valid configuration.
|
||||||
|
3. Existing configuration is never overwritten by build, verification, reset, or update operations.
|
||||||
|
4. Missing configuration can be bootstrapped deliberately; normal execution does not silently invent configuration.
|
||||||
|
5. Malformed JSON exits nonzero without modifying files.
|
||||||
|
6. Unsupported `configVersion` exits nonzero without modifying files.
|
||||||
|
7. A relative or unsafe `dataRoot` exits nonzero without modifying files.
|
||||||
|
8. The container receives the configured data root at `/var/lib/mosaic`.
|
||||||
|
9. The image and container contain no credentials.
|
||||||
|
10. The real model request returns exactly `MOSAIC_HELLO_OK`.
|
||||||
|
11. Changing the expected marker produces a nonzero verification exit.
|
||||||
|
12. Rebuilding/replacing the image leaves configuration and runtime data intact.
|
||||||
|
13. Reset deletes only the validated runtime data root and never configuration.
|
||||||
|
14. Reset continues to refuse symbolic links and missing ownership markers.
|
||||||
|
|
||||||
|
## Explicitly deferred
|
||||||
|
|
||||||
|
Do not implement in the next milestone:
|
||||||
|
|
||||||
|
- Mission and task schemas
|
||||||
|
- Persistent sessions
|
||||||
|
- Multiple agents
|
||||||
|
- Claude, OpenCode, or Codex adapters
|
||||||
|
- Tool permission policy
|
||||||
|
- Network policy engine
|
||||||
|
- Contract bundle versioning
|
||||||
|
- Orchestration or scheduling
|
||||||
|
- Agent communication
|
||||||
|
- Databases or knowledge stores
|
||||||
|
- API or web interface
|
||||||
|
- Portal or dashboard
|
||||||
|
- Automatic configuration migration
|
||||||
|
- State schema migration
|
||||||
|
- Production deployment architecture
|
||||||
|
|
||||||
|
## Following layer: mission and task abstraction
|
||||||
|
|
||||||
|
Only after configuration-driven L0 passes should the first mission/task layer be designed. Its initial concepts should remain minimal:
|
||||||
|
|
||||||
|
- **Mission:** desired outcome and governing constraints
|
||||||
|
- **Task:** one bounded unit of work assigned to one runtime
|
||||||
|
- **Run:** one attempt to execute a task
|
||||||
|
- **Result:** immutable completion evidence and exit status
|
||||||
|
|
||||||
|
No mission/task implementation decision is made by this plan.
|
||||||
|
|
||||||
|
## Immediate documentation and implementation scope
|
||||||
|
|
||||||
|
Maintain one clear architecture plan (this document), one example/default configuration, one strict configuration reader, and the existing Hello World proof. Avoid new services, generalized frameworks, and abstractions until a passing acceptance test requires them.
|
||||||
Generated
+1729
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "mosaic-stack-dev-test",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Minimal Mosaic Stack container proof of concept: one Pi agent, four local contract files, one real model request returning MOSAIC_HELLO_OK.",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"dependencies": {
|
||||||
|
"@earendil-works/pi-coding-agent": "0.84.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the mosaic-agent container image using Docker Compose.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
bootstrap_runtime_dir
|
||||||
|
|
||||||
|
docker compose build
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Shared helpers for the POC host scripts. Not a documented entry point.
|
||||||
|
|
||||||
|
MOSAIC_DEV_DIR="/home/jwoltje/.mosaic-dev"
|
||||||
|
POC_ROOT_MARKER=".mosaic-poc-root"
|
||||||
|
|
||||||
|
# Ensure the runtime state directory exists and carries this project's
|
||||||
|
# ownership marker. The marker is what scripts/reset.sh requires before
|
||||||
|
# it will delete anything.
|
||||||
|
bootstrap_runtime_dir() {
|
||||||
|
if [ ! -d "$MOSAIC_DEV_DIR" ]; then
|
||||||
|
mkdir -p "$MOSAIC_DEV_DIR"
|
||||||
|
echo "bootstrap: created $MOSAIC_DEV_DIR"
|
||||||
|
fi
|
||||||
|
touch "$MOSAIC_DEV_DIR/$POC_ROOT_MARKER"
|
||||||
|
}
|
||||||
Executable
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Repository-local Git credential helper for git.mosaicstack.dev.
|
||||||
|
*
|
||||||
|
* Git invokes this helper with "get", "store", or "erase" and consumes its
|
||||||
|
* stdout directly. Never invoke it manually, enable shell tracing around it,
|
||||||
|
* or add credential values to logs.
|
||||||
|
*
|
||||||
|
* The credential file is intentionally not part of Git and must remain mode
|
||||||
|
* 0600. Override its location with MOSAIC_GITEA_CREDENTIAL_FILE if needed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import process from "node:process";
|
||||||
|
|
||||||
|
const operation = process.argv[2] ?? "";
|
||||||
|
|
||||||
|
// Git may offer credentials back through stdin for store/erase. This helper is
|
||||||
|
// read-only: ignore those operations and never persist or print their input.
|
||||||
|
if (operation !== "get") {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCredentialFile = path.join(
|
||||||
|
os.homedir(),
|
||||||
|
"secrets",
|
||||||
|
"mosaic.gitea.json",
|
||||||
|
);
|
||||||
|
const credentialFile =
|
||||||
|
process.env.MOSAIC_GITEA_CREDENTIAL_FILE ?? defaultCredentialFile;
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
process.stderr.write(`git-credential-mosaic: ${message}\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stat;
|
||||||
|
try {
|
||||||
|
stat = fs.lstatSync(credentialFile);
|
||||||
|
} catch {
|
||||||
|
fail("credential file is unavailable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
||||||
|
fail("credential path must be a regular, non-symbolic-link file");
|
||||||
|
}
|
||||||
|
if ((stat.mode & 0o077) !== 0) {
|
||||||
|
fail("credential file permissions must be 0600 or stricter");
|
||||||
|
}
|
||||||
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
||||||
|
fail("credential file must be owned by the current user");
|
||||||
|
}
|
||||||
|
|
||||||
|
let document;
|
||||||
|
try {
|
||||||
|
document = JSON.parse(fs.readFileSync(credentialFile, "utf8"));
|
||||||
|
} catch {
|
||||||
|
fail("credential file is not valid JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = document?.mosaicstack;
|
||||||
|
const configuredUrl = entry?.url;
|
||||||
|
const username = entry?.user;
|
||||||
|
const token = entry?.api_token;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof configuredUrl !== "string" ||
|
||||||
|
typeof username !== "string" ||
|
||||||
|
typeof token !== "string" ||
|
||||||
|
username.length === 0 ||
|
||||||
|
token.length === 0 ||
|
||||||
|
/[\r\n]/.test(username) ||
|
||||||
|
/[\r\n]/.test(token)
|
||||||
|
) {
|
||||||
|
fail("credential file is missing valid mosaicstack url/user/api_token fields");
|
||||||
|
}
|
||||||
|
|
||||||
|
let credentialUrl;
|
||||||
|
try {
|
||||||
|
credentialUrl = new URL(configuredUrl);
|
||||||
|
} catch {
|
||||||
|
fail("configured credential URL is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
credentialUrl.protocol !== "https:" ||
|
||||||
|
credentialUrl.hostname !== "git.mosaicstack.dev"
|
||||||
|
) {
|
||||||
|
fail("credential URL is not the approved HTTPS Gitea host");
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = {};
|
||||||
|
for (const line of fs.readFileSync(0, "utf8").split("\n")) {
|
||||||
|
const separator = line.indexOf("=");
|
||||||
|
if (separator > 0) {
|
||||||
|
request[line.slice(0, separator)] = line.slice(separator + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail closed: emit credentials only for the approved HTTPS host. A host may
|
||||||
|
// include an explicit port; it must match the configured URL exactly.
|
||||||
|
if (
|
||||||
|
request.protocol !== "https" ||
|
||||||
|
request.host !== credentialUrl.host
|
||||||
|
) {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdout is the Git credential-helper protocol channel, consumed directly by
|
||||||
|
// Git. Do not add status messages here.
|
||||||
|
process.stdout.write(`username=${username}\npassword=${token}\n`);
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the mosaic-agent service as a one-shot container and print the
|
||||||
|
# model response for the exact startup verification request.
|
||||||
|
#
|
||||||
|
# The request deliberately does NOT contain the expected marker
|
||||||
|
# MOSAIC_HELLO_OK. Only the model response is printed to stdout; no
|
||||||
|
# credentials or unrelated runtime data are printed.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
bootstrap_runtime_dir
|
||||||
|
|
||||||
|
# -T: no pseudo-TTY, so stdout is clean model output.
|
||||||
|
# Errors, if any, go to stderr for diagnostics.
|
||||||
|
docker compose run --rm -T mosaic-agent
|
||||||
Executable
+42
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Delete the generated POC runtime state at /home/jwoltje/.mosaic-dev,
|
||||||
|
# but ONLY when every safety check passes:
|
||||||
|
# 1. The resolved path is exactly /home/jwoltje/.mosaic-dev.
|
||||||
|
# 2. The path is not a symbolic link.
|
||||||
|
# 3. The directory contains the .mosaic-poc-root ownership marker
|
||||||
|
# created by this project.
|
||||||
|
# Any failed check aborts with nothing deleted.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TARGET="/home/jwoltje/.mosaic-dev"
|
||||||
|
MARKER=".mosaic-poc-root"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "reset: refusing to delete: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Nothing to do when the directory does not exist.
|
||||||
|
if [ ! -e "$TARGET" ]; then
|
||||||
|
echo "reset: $TARGET does not exist; nothing to remove"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 2 (before resolution): the path itself must not be a symlink.
|
||||||
|
if [ -L "$TARGET" ]; then
|
||||||
|
fail "$TARGET is a symbolic link"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 1: resolved path must be exactly the POC runtime directory.
|
||||||
|
RESOLVED="$(realpath "$TARGET")"
|
||||||
|
if [ "$RESOLVED" != "$TARGET" ]; then
|
||||||
|
fail "resolved path $RESOLVED is not $TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 3: ownership marker created by this project must be present.
|
||||||
|
if [ ! -f "$TARGET/$MARKER" ]; then
|
||||||
|
fail "missing $MARKER ownership marker in $TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf -- "$TARGET"
|
||||||
|
echo "reset: removed $TARGET"
|
||||||
Executable
+53
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Complete verification test:
|
||||||
|
# 1. Build or confirm the image is built.
|
||||||
|
# 2. Run the agent request.
|
||||||
|
# 3. Remove surrounding whitespace from the response.
|
||||||
|
# 4. Compare with the expected marker (default MOSAIC_HELLO_OK).
|
||||||
|
# 5. Exit 0 only on exact match; nonzero otherwise.
|
||||||
|
#
|
||||||
|
# EXPECTED_MARKER may be overridden to prove the failure path
|
||||||
|
# (acceptance criterion 9), e.g.:
|
||||||
|
# EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
IMAGE="mosaic-poc-agent:0.84.4"
|
||||||
|
EXPECTED="${EXPECTED_MARKER:-MOSAIC_HELLO_OK}"
|
||||||
|
|
||||||
|
# 1. Build the image only if it is not already present.
|
||||||
|
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
|
||||||
|
echo "verify: image $IMAGE not found, building..." >&2
|
||||||
|
bootstrap_runtime_dir
|
||||||
|
docker compose build
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Run the agent request (stdout only = model response).
|
||||||
|
set +e
|
||||||
|
RESPONSE="$(docker compose run --rm -T mosaic-agent 2>/tmp/mosaic-poc-stderr.$$)"
|
||||||
|
RC=$?
|
||||||
|
set -e
|
||||||
|
STDERR_FILE="/tmp/mosaic-poc-stderr.$$"
|
||||||
|
if [ $RC -ne 0 ]; then
|
||||||
|
echo "verify: agent run failed (exit $RC):" >&2
|
||||||
|
cat "$STDERR_FILE" >&2
|
||||||
|
rm -f "$STDERR_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -f "$STDERR_FILE"
|
||||||
|
|
||||||
|
# 3. Remove surrounding whitespace.
|
||||||
|
TRIMMED="$(printf '%s' "$RESPONSE" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
||||||
|
|
||||||
|
# 4-6. Exact comparison gate.
|
||||||
|
if [ "$TRIMMED" = "$EXPECTED" ]; then
|
||||||
|
echo "PASS: response matches expected marker"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "FAIL: response does not match expected marker" >&2
|
||||||
|
printf 'expected: %s\n' "$EXPECTED" >&2
|
||||||
|
printf 'actual : %s\n' "$TRIMMED" >&2
|
||||||
|
exit 1
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Load the four immutable contract files in fixed order and write the
|
||||||
|
# generated system prompt to /var/lib/mosaic/system-prompt.md.
|
||||||
|
#
|
||||||
|
# Order is normative: CONSTITUTION.md, STANDARDS.md, SOUL.md, USER.md.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
CONTRACT_DIR="${1:-/opt/mosaic/contracts}"
|
||||||
|
OUT="${2:-/var/lib/mosaic/system-prompt.md}"
|
||||||
|
|
||||||
|
FILES="CONSTITUTION.md STANDARDS.md SOUL.md USER.md"
|
||||||
|
|
||||||
|
if [ ! -d "$CONTRACT_DIR" ]; then
|
||||||
|
echo "load-contracts: contract directory not found: $CONTRACT_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PARENT="$(dirname "$OUT")"
|
||||||
|
mkdir -p "$PARENT"
|
||||||
|
|
||||||
|
TEMP="$OUT.partial"
|
||||||
|
: > "$TEMP"
|
||||||
|
|
||||||
|
for f in $FILES; do
|
||||||
|
path="$CONTRACT_DIR/$f"
|
||||||
|
if [ ! -r "$path" ]; then
|
||||||
|
echo "load-contracts: missing contract file: $path" >&2
|
||||||
|
rm -f "$TEMP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf '===== CONTRACT: %s =====\n' "$f" >> "$TEMP"
|
||||||
|
cat "$path" >> "$TEMP"
|
||||||
|
printf '\n' >> "$TEMP"
|
||||||
|
done
|
||||||
|
|
||||||
|
mv "$TEMP" "$OUT"
|
||||||
|
echo "load-contracts: wrote $OUT from $CONTRACT_DIR" >&2
|
||||||
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# One-shot Pi agent runner inside the container.
|
||||||
|
# Loads the contract-generated system prompt, then sends exactly one
|
||||||
|
# user request through Pi's documented noninteractive mode and prints
|
||||||
|
# the model response on stdout.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
: "${PI_PROVIDER:=zai}"
|
||||||
|
: "${PI_MODEL:=glm-5.3-flash}"
|
||||||
|
export PI_PROVIDER PI_MODEL
|
||||||
|
|
||||||
|
REQUEST="${*:-Return your startup marker and nothing else.}"
|
||||||
|
|
||||||
|
/opt/mosaic/src/load-contracts.sh /opt/mosaic/contracts /var/lib/mosaic/system-prompt.md
|
||||||
|
|
||||||
|
# All flags are documented in the package README (CLI Reference):
|
||||||
|
# -p / --print noninteractive: print the response and exit
|
||||||
|
# --system-prompt replace the default system prompt with the
|
||||||
|
# contract-generated prompt
|
||||||
|
# --no-* switches prevent ambient context files, skills, extensions,
|
||||||
|
# prompt templates, and themes from being appended
|
||||||
|
# --no-session ephemeral: no persistent agent session
|
||||||
|
# --no-tools the startup request needs no tool execution
|
||||||
|
# --offline disable startup network operations (update checks,
|
||||||
|
# package update checks, install/update telemetry)
|
||||||
|
exec pi \
|
||||||
|
--offline \
|
||||||
|
--no-session \
|
||||||
|
--no-extensions \
|
||||||
|
--no-skills \
|
||||||
|
--no-prompt-templates \
|
||||||
|
--no-themes \
|
||||||
|
--no-context-files \
|
||||||
|
--no-tools \
|
||||||
|
--provider "$PI_PROVIDER" \
|
||||||
|
--model "$PI_MODEL" \
|
||||||
|
--system-prompt "$(cat /var/lib/mosaic/system-prompt.md)" \
|
||||||
|
-p "$REQUEST"
|
||||||
Reference in New Issue
Block a user