Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,44 @@ base_image:
nvidia/cuda:12.6.3-devel-ubuntu24.04.
default: "ubuntu:24.04"

# ---------------------------------------------------------------------------
# Coding agents
# rq-44a7f2bd rq-46f6180d
# ---------------------------------------------------------------------------

agents:
type: str
multiselect: true
help: >-
Which command-line coding agents should the development image install? Choose any
subset; the launcher installs, refreshes, and pins exactly the agents you select.
Selecting none builds a container with the toolchain but no agent.
choices:
- Claude Code
- Codex
- OpenCode
default:
- Claude Code
- Codex
- OpenCode

# Derived, never asked: single flags that switch each agent's installation on and off in the
# container definitions and launcher scripts.
install_claude:
type: bool
default: "{{ 'Claude Code' in agents }}"
when: false

install_codex:
type: bool
default: "{{ 'Codex' in agents }}"
when: false

install_opencode:
type: bool
default: "{{ 'OpenCode' in agents }}"
when: false

# ---------------------------------------------------------------------------
# Language
# ---------------------------------------------------------------------------
Expand Down
169 changes: 128 additions & 41 deletions rqm/development-container.md

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions rqm/generated-project-variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ contains the notices and complete license texts needed to communicate the select
contributor guidance distinguishes commands supplied by a rendered skeleton from commands that an
existing project must define for itself.

## Agent Selection <!-- rq-46f6180d -->

- A generated project installs any subset of the supported command-line agents -- Claude, Codex,
and OpenCode. The choice is presented as a single multiple-choice question whose answer is the set
of agents to install, so a project selects the agents together rather than answering a separate
question for each one.
- The selection may be every agent, a proper subset, or none. The default selection is every
supported agent, so a project that expresses no preference installs all of them.
- Every selection renders a coherent project. Generated documentation that tells a reader which
agents the container provides, and how to start them, names exactly the selected agents and does
not instruct a reader to run an agent the project did not install. A project that selects no agent
describes a container without an agent rather than claiming an absent one.

## Python Documentation <!-- rq-51db1fdd -->

- A Python documentation skeleton is self-contained and buildable when it is rendered into an empty
Expand Down Expand Up @@ -100,4 +113,24 @@ Feature: Render coherent project variants
When the project is rendered
Then its contributor guidance does not prescribe a language test command supplied by Riprap
And it directs contributors to the project's own test instructions

@rq-bcbeb1ed
Scenario: Generated guidance names only the selected agents
Given a project selects Claude and OpenCode but not Codex
When the project is rendered
Then its generated documentation explains how to start Claude and OpenCode
And its generated documentation does not instruct the reader to run Codex

@rq-9ebdd1a7
Scenario: A project selecting every agent documents all of them
Given a project selects all supported agents
When the project is rendered
Then its generated documentation explains how to start Claude, Codex, and OpenCode

@rq-18962eb4
Scenario: A project with no agents documents an agent-less container
Given a project selects none of the supported agents
When the project is rendered
Then its generated documentation does not instruct the reader to run any agent
And it does not claim any agent is installed in the container
```
120 changes: 120 additions & 0 deletions rqm/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@
"title": "An empty pin stops the launch before any image is built",
"type": "scenario"
},
"rq-0ac07b19": {
"decl": "Scenario: A pin that names an unselected agent stops the launch before any image is built",
"file": "development-container",
"refs": [
{
"file": "tests/test_credential_isolation.sh",
"kind": "code"
},
{
"file": "tests/test_windows_launcher.ps1",
"kind": "code"
}
],
"title": "A pin that names an unselected agent stops the launch before any image is built",
"type": "scenario"
},
"rq-0b49d1fa": {
"decl": "Scenario: An empty pin value stops the launch before any image is built",
"file": "development-container",
Expand Down Expand Up @@ -279,6 +295,18 @@
"title": "Out of Scope",
"type": "section"
},
"rq-18962eb4": {
"decl": "Scenario: A project with no agents documents an agent-less container",
"file": "generated-project-variants",
"refs": [
{
"file": "tests/test_generated_project_variants.sh",
"kind": "code"
}
],
"title": "A project with no agents documents an agent-less container",
"type": "scenario"
},
"rq-197626f6": {
"decl": "Scenario: LGPL distribution includes its complete license basis",
"file": "generated-project-variants",
Expand Down Expand Up @@ -658,13 +686,41 @@
"title": "Export produces a project-scoped image without launching",
"type": "scenario"
},
"rq-44a7f2bd": {
"decl": "## Agent Selection",
"file": "development-container",
"level": 2,
"refs": [],
"title": "Agent Selection",
"type": "section"
},
"rq-4543a5d9": {
"decl": "Scenario: A relocated managed component leaves nothing at its former path",
"file": "template-ownership-layout",
"refs": [],
"title": "A relocated managed component leaves nothing at its former path",
"type": "scenario"
},
"rq-46f6180d": {
"decl": "## Agent Selection",
"file": "generated-project-variants",
"level": 2,
"refs": [],
"title": "Agent Selection",
"type": "section"
},
"rq-487e0817": {
"decl": "Scenario: A project installs only its selected agents",
"file": "development-container",
"refs": [
{
"file": "tests/test_development_container.sh",
"kind": "code"
}
],
"title": "A project installs only its selected agents",
"type": "scenario"
},
"rq-4a16eccf": {
"decl": "Scenario: The Windows launcher reports that export is unavailable",
"file": "portable-development-image",
Expand Down Expand Up @@ -1175,6 +1231,18 @@
"title": "A generated project enables no additional run options",
"type": "scenario"
},
"rq-849762d2": {
"decl": "Scenario: An unselected agent is recorded as absent without version verification",
"file": "development-container",
"refs": [
{
"file": "tests/test_credential_isolation.sh",
"kind": "code"
}
],
"title": "An unselected agent is recorded as absent without version verification",
"type": "scenario"
},
"rq-860c43c8": {
"decl": "Scenario: A generated project enables no execution-host run options",
"file": "portable-development-image",
Expand Down Expand Up @@ -1341,6 +1409,18 @@
"title": "Shipped defaults grant no interpreter, installer, or shell",
"type": "scenario"
},
"rq-944dc431": {
"decl": "Scenario: A project that selects no agent builds a runnable agent image",
"file": "development-container",
"refs": [
{
"file": "tests/test_development_container.sh",
"kind": "code"
}
],
"title": "A project that selects no agent builds a runnable agent image",
"type": "scenario"
},
"rq-96d0dd76": {
"decl": "## Image Roles",
"file": "portable-development-image",
Expand Down Expand Up @@ -1368,6 +1448,18 @@
"title": "First launch creates a stable project identity and credential volumes",
"type": "scenario"
},
"rq-9ebdd1a7": {
"decl": "Scenario: A project selecting every agent documents all of them",
"file": "generated-project-variants",
"refs": [
{
"file": "tests/test_generated_project_variants.sh",
"kind": "code"
}
],
"title": "A project selecting every agent documents all of them",
"type": "scenario"
},
"rq-a32974ac": {
"decl": "Scenario: Copier is available in a generated Rust development container",
"file": "development-container",
Expand Down Expand Up @@ -1596,6 +1688,18 @@
"title": "A duplicate pin name stops the launch before any image is built",
"type": "scenario"
},
"rq-bcbeb1ed": {
"decl": "Scenario: Generated guidance names only the selected agents",
"file": "generated-project-variants",
"refs": [
{
"file": "tests/test_generated_project_variants.sh",
"kind": "code"
}
],
"title": "Generated guidance names only the selected agents",
"type": "scenario"
},
"rq-c2cdf6d8": {
"decl": "Scenario: An unrecognized pin name outranks a non-exact pin value",
"file": "development-container",
Expand Down Expand Up @@ -2057,6 +2161,22 @@
"title": "The Windows launcher stops when no compatible agent image exists",
"type": "scenario"
},
"rq-f6cee9b5": {
"decl": "Scenario: A project with no agent to track suspends the weekly refresh",
"file": "development-container",
"refs": [
{
"file": "tests/test_credential_isolation.sh",
"kind": "code"
},
{
"file": "tests/test_windows_launcher.ps1",
"kind": "code"
}
],
"title": "A project with no agent to track suspends the weekly refresh",
"type": "scenario"
},
"rq-f75ca93e": {
"decl": "## Feature Interface",
"file": "agent-permission-defaults",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
ARG RIPRAP_TOOLING_IMAGE
FROM ${RIPRAP_TOOLING_IMAGE}

{% if install_claude %}
ARG CLAUDE_VERSION
{% endif %}
{% if install_codex %}
ARG CODEX_VERSION
{% endif %}
{% if install_opencode %}
ARG OPENCODE_VERSION
{% endif %}

# The installers place files beneath HOME, which the tooling image sets to an
# image-owned path outside /root. Each agent's own configuration home under that
# path is a credential mount point at run time, so the image leaves nothing there.
{% if install_claude %}
RUN set -eu; \
test -n "$CLAUDE_VERSION"; \
curl -fsSL https://claude.ai/install.sh | bash -s -- "$CLAUDE_VERSION"; \
rm -rf "$HOME/.claude.json" "$HOME/.claude"
{% endif %}

{% if install_codex %}
# The installer extracts a release tarball with a bare `tar -xzf`. Under a root-mapped runtime
# (Podman), GNU tar cannot restore the archive's runner ownership or perform its deferred directory
# mode pass. Keep the build user's ownership and apply the build umask instead; the archive already
Expand All @@ -24,7 +33,9 @@ RUN set -eu; \
TAR_OPTIONS='--no-same-owner --no-same-permissions'; \
export TAR_OPTIONS; \
curl -fsSL https://chatgpt.com/codex/install.sh | sh -s -- --release "$CODEX_VERSION"
{% endif %}

{% if install_opencode %}
RUN set -eu; \
test -n "$OPENCODE_VERSION"; \
if [ "$OPENCODE_VERSION" = latest ]; then version_args=; else version_args="--version $OPENCODE_VERSION"; fi; \
Expand All @@ -35,12 +46,15 @@ RUN set -eu; \

COPY opencode /usr/local/bin/opencode
RUN chmod 0755 /usr/local/bin/opencode
{% endif %}

{% if install_codex %}
ENV CODEX_HOME=/opt/riprap/home/.codex
{% endif %}
ENV DISABLE_AUTOUPDATER=1

# Reach the agent programs as any unprivileged user, on the same terms as the toolchain the tooling
# image installed. Every directory beneath the home directory is made sticky world-writable so any
# runtime user can create the scratch and configuration directories the agents write at startup --
# an agent installed as root leaves directories there that the runtime user must write into.
RUN chmod -R a+rX /opt/riprap /opt/codex /opt/opencode && find /opt/riprap/home -type d -exec chmod 1777 {} +
RUN chmod -R a+rX /opt/riprap{% if install_codex %} /opt/codex{% endif %}{% if install_opencode %} /opt/opencode{% endif %} && find /opt/riprap/home -type d -exec chmod 1777 {} +
15 changes: 0 additions & 15 deletions template/.riprap/managed/container/AgentLabels.Containerfile

This file was deleted.

22 changes: 22 additions & 0 deletions template/.riprap/managed/container/AgentLabels.Containerfile.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
ARG RIPRAP_AGENT_CANDIDATE_IMAGE
FROM ${RIPRAP_AGENT_CANDIDATE_IMAGE}
{% if install_claude %}
ARG CLAUDE_VERSION
{% endif %}
{% if install_codex %}
ARG CODEX_VERSION
{% endif %}
{% if install_opencode %}
ARG OPENCODE_VERSION
{% endif %}
ARG TOOLING_IMAGE_ID
# The project image is based on this image and inherits these labels, so an
# exported image reports its agent releases and the project it belongs to
# without reference to the machine that built it. An unselected agent carries no
# release label, because the image installs no such agent.
ARG RIPRAP_PROJECT_ID
LABEL {% if install_claude %}io.riprap.claude-version="${CLAUDE_VERSION}" \
{% endif %}{% if install_codex %}io.riprap.codex-version="${CODEX_VERSION}" \
{% endif %}{% if install_opencode %}io.riprap.opencode-version="${OPENCODE_VERSION}" \
{% endif %}io.riprap.tooling-image-id="${TOOLING_IMAGE_ID}" \
io.riprap.project-id="${RIPRAP_PROJECT_ID}"
Loading
Loading