From 749bc7043e96d97bbbddee5648237cae510ab9d9 Mon Sep 17 00:00:00 2001 From: eetoc Date: Wed, 15 Jul 2026 10:59:39 +0800 Subject: [PATCH 1/5] test: refresh agent-compose examples --- TESTING.md | 45 +++ Taskfile.yml | 57 ++++ cmd/agent-compose/examples_contract_test.go | 164 +++++++++ examples/agent-compose/README.md | 46 +-- examples/agent-compose/README.zh-CN.md | 43 +-- .../agent-compose/boxlite-minimal/README.md | 15 + .../boxlite-minimal/README.zh-CN.md | 15 + .../boxlite-minimal/agent-compose.yml | 8 + .../agent-compose/docker-build/Dockerfile | 4 + examples/agent-compose/docker-build/README.md | 18 + .../docker-build/README.zh-CN.md | 17 + .../docker-build/agent-compose.yml | 15 + .../docker-env-secrets/README.md | 17 + .../docker-env-secrets/README.zh-CN.md | 16 + .../docker-env-secrets/agent-compose.yml | 21 ++ .../docker-env-secrets/example.env | 1 + .../agent-compose/docker-minimal/README.md | 165 +-------- .../docker-minimal/README.zh-CN.md | 163 +-------- .../docker-minimal/agent-compose.yml | 2 +- .../docker-multi-agent/README.md | 20 ++ .../docker-multi-agent/README.zh-CN.md | 20 ++ .../docker-multi-agent/agent-compose.yml | 24 ++ .../docker-multi-agent/workspace/project.txt | 1 + .../docker-scheduler-cron/README.md | 197 +---------- .../docker-scheduler-cron/README.zh-CN.md | 192 +---------- .../docker-scheduler-cron/agent-compose.yml | 3 +- .../docker-scheduler-script-runtime/README.md | 19 ++ .../README.zh-CN.md | 18 + .../agent-compose.yml | 36 ++ .../docker-scheduler-script-url/README.md | 22 +- .../README.zh-CN.md | 18 +- .../agent-compose.yml | 3 +- .../docker-scheduler-script-url/scheduler.js | 6 +- .../docker-scheduler-timeout/README.md | 182 +--------- .../docker-scheduler-timeout/README.zh-CN.md | 179 +--------- .../agent-compose.yml | 3 +- .../docker-volume-persistence/README.md | 21 ++ .../docker-volume-persistence/README.zh-CN.md | 21 ++ .../agent-compose.yml | 17 + .../fixtures/readonly.txt | 1 + .../docker-workspace-lifecycle/README.md | 22 ++ .../README.zh-CN.md | 22 ++ .../agent-compose.yml | 15 + .../workspace/README.md | 4 + .../microsandbox-minimal/README.md | 15 + .../microsandbox-minimal/README.zh-CN.md | 15 + .../microsandbox-minimal/agent-compose.yml | 8 + examples/scheduler-script/README.md | 3 +- .../e2e/examples_docker_live_provider_test.go | 85 +++++ test/e2e/examples_docker_provider_test.go | 171 ++++++++++ test/e2e/examples_docker_test.go | 321 ++++++++++++++++++ test/e2e/host_daemon_helpers_test.go | 44 ++- 52 files changed, 1480 insertions(+), 1080 deletions(-) create mode 100644 cmd/agent-compose/examples_contract_test.go create mode 100644 examples/agent-compose/boxlite-minimal/README.md create mode 100644 examples/agent-compose/boxlite-minimal/README.zh-CN.md create mode 100644 examples/agent-compose/boxlite-minimal/agent-compose.yml create mode 100644 examples/agent-compose/docker-build/Dockerfile create mode 100644 examples/agent-compose/docker-build/README.md create mode 100644 examples/agent-compose/docker-build/README.zh-CN.md create mode 100644 examples/agent-compose/docker-build/agent-compose.yml create mode 100644 examples/agent-compose/docker-env-secrets/README.md create mode 100644 examples/agent-compose/docker-env-secrets/README.zh-CN.md create mode 100644 examples/agent-compose/docker-env-secrets/agent-compose.yml create mode 100644 examples/agent-compose/docker-env-secrets/example.env create mode 100644 examples/agent-compose/docker-multi-agent/README.md create mode 100644 examples/agent-compose/docker-multi-agent/README.zh-CN.md create mode 100644 examples/agent-compose/docker-multi-agent/agent-compose.yml create mode 100644 examples/agent-compose/docker-multi-agent/workspace/project.txt create mode 100644 examples/agent-compose/docker-scheduler-script-runtime/README.md create mode 100644 examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md create mode 100644 examples/agent-compose/docker-scheduler-script-runtime/agent-compose.yml create mode 100644 examples/agent-compose/docker-volume-persistence/README.md create mode 100644 examples/agent-compose/docker-volume-persistence/README.zh-CN.md create mode 100644 examples/agent-compose/docker-volume-persistence/agent-compose.yml create mode 100644 examples/agent-compose/docker-volume-persistence/fixtures/readonly.txt create mode 100644 examples/agent-compose/docker-workspace-lifecycle/README.md create mode 100644 examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md create mode 100644 examples/agent-compose/docker-workspace-lifecycle/agent-compose.yml create mode 100644 examples/agent-compose/docker-workspace-lifecycle/workspace/README.md create mode 100644 examples/agent-compose/microsandbox-minimal/README.md create mode 100644 examples/agent-compose/microsandbox-minimal/README.zh-CN.md create mode 100644 examples/agent-compose/microsandbox-minimal/agent-compose.yml create mode 100644 test/e2e/examples_docker_live_provider_test.go create mode 100644 test/e2e/examples_docker_provider_test.go create mode 100644 test/e2e/examples_docker_test.go diff --git a/TESTING.md b/TESTING.md index bf937837f..5cf6910fd 100644 --- a/TESTING.md +++ b/TESTING.md @@ -100,6 +100,51 @@ compiled only with the `docker_e2e` build tag, so the ordinary `task test` coverage gate does not include this scheduler Docker E2E or create its runtime containers. +Committed examples have two verification layers. The deterministic contract +test parses every compose file, resolves scheduler script URLs, validates all +QJS scripts, checks secret redaction, and is included in `task test`. Run it +directly with: + +```bash +task test:examples +``` + +The opt-in Docker examples lifecycle starts an isolated host daemon and runs +the committed Docker examples through the real CLI and guest image: + +```bash +task test:e2e:examples-docker +``` + +It covers command runs, workspace isolation, stop/resume, multiple agents, +environment and secret propagation, managed volumes, read-only bind mounts, +image builds, and scheduler shell scripts. A second task drives the declarative +cron and timeout examples through the guest Codex CLI and daemon LLM facade, +using a controlled local Chat Completions fixture instead of a public model: + +```bash +task test:e2e:examples-docker-provider +``` + +To verify the actual provider configured in the repository-local `.env`, run +the separate live-provider task. It requires non-empty `LLM_API_ENDPOINT`, +`LLM_API_KEY`, and `LLM_MODEL`, then sends a real prompt through the daemon, +Docker guest, runtime facade, and Codex CLI: + +```bash +task test:e2e:examples-docker-live-provider +``` + +This task is intentionally not part of deterministic test gates because it +uses external credentials, network access, and model quota. + +All three Docker example E2E tasks require the Docker daemon and +`ghcr.io/chaitin/agent-compose-guest:latest` locally. They use temporary daemon +state and unique build tags and remove owned containers, volumes, and images on +exit. BoxLite and Microsandbox example manifests receive deterministic config +validation only; runtime validation remains `task test:runtime-smoke` on a +prepared Linux/KVM host. + The full daemon image Docker lifecycle E2E is opt-in because it starts the daemon image and Docker sandbox containers through a local Docker socket. Run it after building both local images: diff --git a/Taskfile.yml b/Taskfile.yml index 1f06b4d2c..c7cfa7de9 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -128,6 +128,12 @@ tasks: - cd runtime/agent-compose-runtime-sdk && npm test - cd runtime/javascript && TEST_SHAPE=unit npm run test:unit + test:examples: + desc: Validate all committed compose and scheduler script examples + dir: '{{.TASKFILE_DIR}}' + cmds: + - GOCACHE={{.GO_BUILD_CACHE_DIR}} {{.GO_TOOLCHAIN_ENV}} go test ./cmd/agent-compose -run '^TestExampleFilesContract$' -count=1 + test:integration: desc: Run integration tests cmds: @@ -186,6 +192,57 @@ tasks: GOCACHE={{.GO_BUILD_CACHE_DIR}} \ {{.GO_TOOLCHAIN_ENV}} go test -tags docker_e2e ./cmd/agent-compose -run '^TestE2EDockerSchedulerScriptHelloWorldFlow$' -count=1 -v + test:e2e:examples-docker: + desc: Run committed Docker examples through an isolated local daemon + dir: '{{.TASKFILE_DIR}}' + cmds: + - | + image="${AGENT_COMPOSE_E2E_EXAMPLES_IMAGE:-ghcr.io/chaitin/agent-compose-guest:latest}" + if ! docker image inspect "$image" >/dev/null 2>&1; then + echo "Docker examples image $image is unavailable locally; pull it before running this task" >&2 + exit 1 + fi + AGENT_COMPOSE_E2E_EXAMPLES_IMAGE="$image" \ + GOCACHE={{.GO_BUILD_CACHE_DIR}} \ + {{.GO_TOOLCHAIN_ENV}} go test ./test/e2e -run '^TestE2EExamplesDocker$' -count=1 -v + + test:e2e:examples-docker-provider: + desc: Run cron and timeout through Codex with a controlled local provider + dir: '{{.TASKFILE_DIR}}' + cmds: + - | + image="${AGENT_COMPOSE_E2E_EXAMPLES_IMAGE:-ghcr.io/chaitin/agent-compose-guest:latest}" + if ! docker image inspect "$image" >/dev/null 2>&1; then + echo "Docker examples image $image is unavailable locally" >&2 + exit 1 + fi + AGENT_COMPOSE_E2E_EXAMPLES_IMAGE="$image" \ + AGENT_COMPOSE_E2E_EXAMPLES_PROVIDER=1 \ + GOCACHE={{.GO_BUILD_CACHE_DIR}} \ + {{.GO_TOOLCHAIN_ENV}} go test ./test/e2e -run '^TestE2EExamplesDockerProvider$' -count=1 -v + + test:e2e:examples-docker-live-provider: + desc: Run docker-minimal through Codex with the live provider configured in .env + dir: '{{.TASKFILE_DIR}}' + cmds: + - | + if [ ! -f .env ]; then + echo "A configured .env is required for the live-provider examples E2E" >&2 + exit 1 + fi + set -a + . ./.env + set +a + image="${AGENT_COMPOSE_E2E_EXAMPLES_IMAGE:-ghcr.io/chaitin/agent-compose-guest:latest}" + if ! docker image inspect "$image" >/dev/null 2>&1; then + echo "Docker examples image $image is unavailable locally" >&2 + exit 1 + fi + AGENT_COMPOSE_E2E_EXAMPLES_IMAGE="$image" \ + AGENT_COMPOSE_E2E_EXAMPLES_LIVE_PROVIDER=1 \ + GOCACHE={{.GO_BUILD_CACHE_DIR}} \ + {{.GO_TOOLCHAIN_ENV}} go test ./test/e2e -run '^TestE2EExamplesDockerLiveProvider$' -count=1 -v + test:e2e:image-docker: desc: Run the full daemon image without KVM and exercise a Docker sandbox lifecycle dir: '{{.TASKFILE_DIR}}' diff --git a/cmd/agent-compose/examples_contract_test.go b/cmd/agent-compose/examples_contract_test.go new file mode 100644 index 000000000..dadc316f2 --- /dev/null +++ b/cmd/agent-compose/examples_contract_test.go @@ -0,0 +1,164 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "testing" + + "agent-compose/pkg/loaders" +) + +type exampleContract struct { + driver string + agentCount int + schedulers int +} + +func TestExampleFilesContract(t *testing.T) { + root := repoRootForComposeEnvTest(t) + examplesRoot := filepath.Join(root, "examples", "agent-compose") + want := map[string]exampleContract{ + "boxlite-minimal": {driver: "boxlite", agentCount: 1}, + "docker-build": {driver: "docker", agentCount: 1}, + "docker-env-secrets": {driver: "docker", agentCount: 1}, + "docker-minimal": {driver: "docker", agentCount: 1}, + "docker-multi-agent": {driver: "docker", agentCount: 2}, + "docker-scheduler-cron": {driver: "docker", agentCount: 1, schedulers: 1}, + "docker-scheduler-script-runtime": {driver: "docker", agentCount: 1, schedulers: 1}, + "docker-scheduler-script-url": {driver: "docker", agentCount: 1, schedulers: 1}, + "docker-scheduler-timeout": {driver: "docker", agentCount: 1, schedulers: 1}, + "docker-volume-persistence": {driver: "docker", agentCount: 1}, + "docker-workspace-lifecycle": {driver: "docker", agentCount: 1}, + "microsandbox-minimal": {driver: "microsandbox", agentCount: 1}, + } + + entries, err := os.ReadDir(examplesRoot) + if err != nil { + t.Fatalf("read examples directory: %v", err) + } + var gotNames []string + for _, entry := range entries { + if !entry.IsDir() { + continue + } + gotNames = append(gotNames, entry.Name()) + } + sort.Strings(gotNames) + wantNames := make([]string, 0, len(want)) + for name := range want { + wantNames = append(wantNames, name) + } + sort.Strings(wantNames) + if strings.Join(gotNames, "\n") != strings.Join(wantNames, "\n") { + t.Fatalf("example directories = %v, want %v", gotNames, wantNames) + } + + engine := &loaders.QJSLoaderEngine{} + for _, name := range wantNames { + t.Run(name, func(t *testing.T) { + dir := filepath.Join(examplesRoot, name) + for _, readme := range []string{"README.md", "README.zh-CN.md"} { + assertExampleFileExists(t, filepath.Join(dir, readme)) + } + _, normalized, err := loadResolvedNormalizedCompose(context.Background(), cliOptions{ + ComposeFile: filepath.Join(dir, "agent-compose.yml"), + }) + if err != nil { + t.Fatalf("normalize example: %v", err) + } + contract := want[name] + if normalized.Name != name || len(normalized.Agents) != contract.agentCount { + t.Fatalf("normalized project name/agents = %q/%d, want %q/%d", normalized.Name, len(normalized.Agents), name, contract.agentCount) + } + schedulers := 0 + for _, agent := range normalized.Agents { + if agent.Driver == nil || agent.Driver.Name != contract.driver { + t.Fatalf("agent %s driver = %#v, want %s", agent.Name, agent.Driver, contract.driver) + } + if agent.Scheduler == nil { + continue + } + schedulers++ + if agent.Scheduler.SandboxPolicy != "new" { + t.Fatalf("agent %s scheduler sandbox policy = %q, want new", agent.Name, agent.Scheduler.SandboxPolicy) + } + if strings.TrimSpace(agent.Scheduler.Script) != "" { + if _, err := engine.Validate(context.Background(), "scheduler", agent.Scheduler.Script); err != nil { + t.Fatalf("validate scheduler script: %v", err) + } + } + } + if schedulers != contract.schedulers { + t.Fatalf("scheduler count = %d, want %d", schedulers, contract.schedulers) + } + if name == "docker-env-secrets" { + redacted, err := normalized.MarshalCanonicalYAML(true) + if err != nil { + t.Fatalf("marshal redacted config: %v", err) + } + if strings.Contains(string(redacted), "safe-example-secret") { + t.Fatalf("redacted config leaked example secret: %s", redacted) + } + } + }) + } + + validateStandaloneSchedulerExamples(t, root, engine) + assertExampleDocsDoNotUseStaleCLI(t, root) +} + +func validateStandaloneSchedulerExamples(t *testing.T, root string, engine *loaders.QJSLoaderEngine) { + t.Helper() + files, err := filepath.Glob(filepath.Join(root, "examples", "scheduler-script", "*.js")) + if err != nil { + t.Fatalf("glob scheduler examples: %v", err) + } + if len(files) != 6 { + t.Fatalf("standalone scheduler example count = %d, want 6", len(files)) + } + for _, file := range files { + script, err := os.ReadFile(file) + if err != nil { + t.Fatalf("read %s: %v", file, err) + } + if _, err := engine.Validate(context.Background(), "scheduler", string(script)); err != nil { + t.Fatalf("validate %s: %v", file, err) + } + } +} + +func assertExampleDocsDoNotUseStaleCLI(t *testing.T, root string) { + t.Helper() + err := filepath.WalkDir(filepath.Join(root, "examples"), func(file string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if entry.IsDir() || !strings.HasPrefix(entry.Name(), "README") || filepath.Ext(entry.Name()) != ".md" { + return nil + } + data, err := os.ReadFile(file) + if err != nil { + return err + } + for _, stale := range []string{"exec --agent", "SCHEDULER LATEST RUN", "RunLoaderNow"} { + if strings.Contains(string(data), stale) { + return fmt.Errorf("%s contains stale CLI/API text %q", file, stale) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} + +func assertExampleFileExists(t *testing.T, path string) { + t.Helper() + if info, err := os.Stat(path); err != nil || info.IsDir() { + t.Fatalf("required example file %s is unavailable: %v", path, err) + } +} diff --git a/examples/agent-compose/README.md b/examples/agent-compose/README.md index e29006c1f..17bfdbd0a 100644 --- a/examples/agent-compose/README.md +++ b/examples/agent-compose/README.md @@ -2,27 +2,29 @@ Languages: English | [中文](README.zh-CN.md) -Runnable examples for the `agent-compose` Docker runtime driver, ordered from -simplest to most complete. +## Locally verified Docker examples -| Example | What it shows | Needs provider auth | +| Example | Capability | Provider auth | | --- | --- | --- | -| [docker-minimal](docker-minimal/) | Smallest Docker-backed project: one agent, no scheduler. | No, for `config`/`up`/`ps` | -| [docker-scheduler-cron](docker-scheduler-cron/) | Managed cron scheduler control plane. | No, for `config`/`up`/`ps`/`down` | -| [docker-scheduler-script-url](docker-scheduler-script-url/) | A scheduler script loaded from a relative file URL source. | No, for `config`/`up`/`ps`/`down` | -| [docker-scheduler-timeout](docker-scheduler-timeout/) | End-to-end scheduled run that fires, executes the agent, and persists logs. | Yes, for the scheduled run | - -## Common prerequisites - -- Docker daemon is running. -- The `agent-compose` daemon is already running. -- The `agent-compose-guest:latest` image exists locally. - -From the repository root, build the guest image if needed: - -```bash -task image:agent-compose-guest -``` - -Each example has its own `README.md` with the exact commands and expected -output. +| [docker-minimal](docker-minimal/) | Minimal project and current sandbox-oriented CLI | No | +| [docker-workspace-lifecycle](docker-workspace-lifecycle/) | Local workspace, exec, stop, resume, and isolation | No | +| [docker-multi-agent](docker-multi-agent/) | Multiple agents sharing a workspace declaration | No for command runs | +| [docker-env-secrets](docker-env-secrets/) | Dotenv, project variables, agent env, and redaction | No | +| [docker-volume-persistence](docker-volume-persistence/) | Managed volume and read-only bind mount | No | +| [docker-build](docker-build/) | Compose-driven guest image build | No | +| [docker-scheduler-cron](docker-scheduler-cron/) | Declarative cron control plane | Only to run the trigger | +| [docker-scheduler-timeout](docker-scheduler-timeout/) | Automatic scheduled provider run | Yes | +| [docker-scheduler-script-url](docker-scheduler-script-url/) | Relative scheduler script URL snapshot | No | +| [docker-scheduler-script-runtime](docker-scheduler-script-runtime/) | State, logs, interval, and scheduler shell | No | + +Docker examples require a running agent-compose daemon, Docker daemon, and the +published guest image locally. Their READMEs use stable behavior rather than +dynamic IDs or complete output snapshots. + +## Configuration-only KVM templates + +- [boxlite-minimal](boxlite-minimal/) +- [microsandbox-minimal](microsandbox-minimal/) + +These manifests are parsed by automated tests, but runtime execution requires a +prepared Linux/KVM host and was not verified locally. diff --git a/examples/agent-compose/README.zh-CN.md b/examples/agent-compose/README.zh-CN.md index c908f5f33..0fee4ac0f 100644 --- a/examples/agent-compose/README.zh-CN.md +++ b/examples/agent-compose/README.zh-CN.md @@ -2,25 +2,28 @@ 语言:[English](README.md) | 中文 -`agent-compose` Docker runtime driver 的可运行示例,按从简单到完整的顺序排列。 +## 已在本地验证的 Docker 示例 -| 示例 | 演示内容 | 是否需要 provider 凭证 | +| 示例 | 能力 | Provider 认证 | | --- | --- | --- | -| [docker-minimal](docker-minimal/) | 最小的 Docker project:一个 agent,不启用 scheduler。 | `config`/`up`/`ps` 不需要 | -| [docker-scheduler-cron](docker-scheduler-cron/) | managed cron scheduler 的控制面流程。 | `config`/`up`/`ps`/`down` 不需要 | -| [docker-scheduler-script-url](docker-scheduler-script-url/) | 从相对文件 URL 来源加载 scheduler 脚本。 | `config`/`up`/`ps`/`down` 不需要 | -| [docker-scheduler-timeout](docker-scheduler-timeout/) | 端到端的定时运行:触发、执行 agent 并持久化日志。 | 定时运行需要 | - -## 通用前置条件 - -- Docker daemon 正在运行。 -- `agent-compose` daemon 已经启动。 -- 本地存在 `agent-compose-guest:latest` 镜像。 - -如需构建 guest 镜像,在仓库根目录执行: - -```bash -task image:agent-compose-guest -``` - -每个示例都有自己的 `README.md`,包含完整命令和预期输出。 +| [docker-minimal](docker-minimal/) | 最小 project 和当前 sandbox CLI | 不需要 | +| [docker-workspace-lifecycle](docker-workspace-lifecycle/) | Local workspace、exec、stop、resume 和隔离 | 不需要 | +| [docker-multi-agent](docker-multi-agent/) | 多个 agent 共享 workspace 声明 | command run 不需要 | +| [docker-env-secrets](docker-env-secrets/) | Dotenv、project variables、agent env 和隐藏 secret | 不需要 | +| [docker-volume-persistence](docker-volume-persistence/) | Managed volume 和只读 bind mount | 不需要 | +| [docker-build](docker-build/) | Compose 驱动的 guest image 构建 | 不需要 | +| [docker-scheduler-cron](docker-scheduler-cron/) | 声明式 cron 控制面 | 运行 trigger 时需要 | +| [docker-scheduler-timeout](docker-scheduler-timeout/) | 自动调度的 provider run | 需要 | +| [docker-scheduler-script-url](docker-scheduler-script-url/) | 相对 scheduler script URL 快照 | 不需要 | +| [docker-scheduler-script-runtime](docker-scheduler-script-runtime/) | State、日志、interval 和 scheduler shell | 不需要 | + +Docker 示例需要运行中的 agent-compose daemon、Docker daemon,以及本地已有 +发布版 guest image。README 描述稳定行为,不保存动态 ID 或完整输出快照。 + +## 仅做配置验证的 KVM 模板 + +- [boxlite-minimal](boxlite-minimal/) +- [microsandbox-minimal](microsandbox-minimal/) + +自动化测试会解析这些 manifest,但 runtime 执行需要准备好的 Linux/KVM 主机, +本地未做运行验证。 diff --git a/examples/agent-compose/boxlite-minimal/README.md b/examples/agent-compose/boxlite-minimal/README.md new file mode 100644 index 000000000..946fd6f7d --- /dev/null +++ b/examples/agent-compose/boxlite-minimal/README.md @@ -0,0 +1,15 @@ +# Minimal BoxLite project + +Languages: English | [中文](README.zh-CN.md) + +This is a configuration template for a BoxLite-backed agent. + +```bash +agent-compose config +# On a prepared Linux/KVM host with a BoxLite-enabled binary: +agent-compose up +``` + +The compose file is validated in normal tests. Runtime execution was not +verified locally and requires Linux, `/dev/kvm`, BoxLite runtime artifacts, and +a binary whose `compiled_drivers` includes `boxlite`. diff --git a/examples/agent-compose/boxlite-minimal/README.zh-CN.md b/examples/agent-compose/boxlite-minimal/README.zh-CN.md new file mode 100644 index 000000000..99aec88fe --- /dev/null +++ b/examples/agent-compose/boxlite-minimal/README.zh-CN.md @@ -0,0 +1,15 @@ +# 最小 BoxLite project + +语言:[English](README.md) | 中文 + +这是一个使用 BoxLite driver 的配置模板。 + +```bash +agent-compose config +# 在准备好的 Linux/KVM 主机和包含 BoxLite 的二进制上: +agent-compose up +``` + +compose 文件会进入常规测试,但本地未验证 runtime 执行。运行需要 Linux、 +`/dev/kvm`、BoxLite runtime artifacts,并且二进制的 `compiled_drivers` 包含 +`boxlite`。 diff --git a/examples/agent-compose/boxlite-minimal/agent-compose.yml b/examples/agent-compose/boxlite-minimal/agent-compose.yml new file mode 100644 index 000000000..026e652fb --- /dev/null +++ b/examples/agent-compose/boxlite-minimal/agent-compose.yml @@ -0,0 +1,8 @@ +name: boxlite-minimal + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + boxlite: {} diff --git a/examples/agent-compose/docker-build/Dockerfile b/examples/agent-compose/docker-build/Dockerfile new file mode 100644 index 000000000..2dec12c61 --- /dev/null +++ b/examples/agent-compose/docker-build/Dockerfile @@ -0,0 +1,4 @@ +ARG BASE_IMAGE=ghcr.io/chaitin/agent-compose-guest:latest +FROM ${BASE_IMAGE} +ARG EXAMPLE_MARKER=built-by-agent-compose +RUN printf '%s\n' "${EXAMPLE_MARKER}" > /opt/agent-compose-example.txt diff --git a/examples/agent-compose/docker-build/README.md b/examples/agent-compose/docker-build/README.md new file mode 100644 index 000000000..810ce759e --- /dev/null +++ b/examples/agent-compose/docker-build/README.md @@ -0,0 +1,18 @@ +# Build a Docker guest image + +Languages: English | [中文](README.zh-CN.md) + +This example builds a guest-derived image and verifies a build argument marker. + +```bash +agent-compose build +agent-compose up +agent-compose run worker --command "cat /opt/agent-compose-example.txt" +agent-compose down +agent-compose rmi agent-compose-example-build:latest --force +agent-compose rmi agent-compose-example-build:local --force +``` + +The expected marker is `built-by-agent-compose`. The build requires Docker and +the published guest base image locally. The example uses fixed local tags for +clarity; automation should copy the example and substitute unique tags. diff --git a/examples/agent-compose/docker-build/README.zh-CN.md b/examples/agent-compose/docker-build/README.zh-CN.md new file mode 100644 index 000000000..e46e1d259 --- /dev/null +++ b/examples/agent-compose/docker-build/README.zh-CN.md @@ -0,0 +1,17 @@ +# 构建 Docker guest image + +语言:[English](README.md) | 中文 + +该示例构建一个基于 guest image 的本地镜像,并验证 build argument marker。 + +```bash +agent-compose build +agent-compose up +agent-compose run worker --command "cat /opt/agent-compose-example.txt" +agent-compose down +agent-compose rmi agent-compose-example-build:latest --force +agent-compose rmi agent-compose-example-build:local --force +``` + +预期 marker 为 `built-by-agent-compose`。构建需要 Docker 和本地已有的发布版 +guest base image。示例使用固定 tag 以便阅读;自动化测试应复制示例并替换成唯一 tag。 diff --git a/examples/agent-compose/docker-build/agent-compose.yml b/examples/agent-compose/docker-build/agent-compose.yml new file mode 100644 index 000000000..4e5dcc75c --- /dev/null +++ b/examples/agent-compose/docker-build/agent-compose.yml @@ -0,0 +1,15 @@ +name: docker-build + +agents: + worker: + provider: codex + image: agent-compose-example-build:local + build: + context: . + dockerfile: Dockerfile + args: + EXAMPLE_MARKER: built-by-agent-compose + tags: + - agent-compose-example-build:latest + driver: + docker: {} diff --git a/examples/agent-compose/docker-env-secrets/README.md b/examples/agent-compose/docker-env-secrets/README.md new file mode 100644 index 000000000..906698501 --- /dev/null +++ b/examples/agent-compose/docker-env-secrets/README.md @@ -0,0 +1,17 @@ +# Project environment and secrets + +Languages: English | [中文](README.zh-CN.md) + +This example uses an explicit dotenv file, project variables, agent-specific +environment, and secret metadata. The committed value is intentionally fake. + +```bash +agent-compose config +agent-compose up +agent-compose run inspector --command 'test "$PROJECT_VALUE" = project-level && test "$AGENT_VALUE" = agent-level && test "$PROJECT_SECRET" = safe-example-secret && test "$AGENT_SECRET" = safe-example-secret && echo "environment ok"' +agent-compose down +``` + +`config` redacts values marked `secret: true`. Project variables are supplied to +runs, while agent env is scoped to that agent. Process environment values passed +to the CLI take precedence over `example.env`. diff --git a/examples/agent-compose/docker-env-secrets/README.zh-CN.md b/examples/agent-compose/docker-env-secrets/README.zh-CN.md new file mode 100644 index 000000000..b8977c7d1 --- /dev/null +++ b/examples/agent-compose/docker-env-secrets/README.zh-CN.md @@ -0,0 +1,16 @@ +# Project 环境变量和 secret + +语言:[English](README.md) | 中文 + +该示例使用显式 dotenv 文件、project variables、agent 专属环境变量和 secret +元数据。仓库中提交的值是刻意设置的假值。 + +```bash +agent-compose config +agent-compose up +agent-compose run inspector --command 'test "$PROJECT_VALUE" = project-level && test "$AGENT_VALUE" = agent-level && test "$PROJECT_SECRET" = safe-example-secret && test "$AGENT_SECRET" = safe-example-secret && echo "environment ok"' +agent-compose down +``` + +`config` 会隐藏标记为 `secret: true` 的值。Project variables 会传给 run,agent +env 只属于该 agent。启动 CLI 时的进程环境变量优先于 `example.env`。 diff --git a/examples/agent-compose/docker-env-secrets/agent-compose.yml b/examples/agent-compose/docker-env-secrets/agent-compose.yml new file mode 100644 index 000000000..5e7df1a77 --- /dev/null +++ b/examples/agent-compose/docker-env-secrets/agent-compose.yml @@ -0,0 +1,21 @@ +name: docker-env-secrets + +env_file: example.env + +variables: + PROJECT_VALUE: project-level + PROJECT_SECRET: + value: ${EXAMPLE_SECRET} + secret: true + +agents: + inspector: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + env: + AGENT_VALUE: agent-level + AGENT_SECRET: + value: ${EXAMPLE_SECRET} + secret: true diff --git a/examples/agent-compose/docker-env-secrets/example.env b/examples/agent-compose/docker-env-secrets/example.env new file mode 100644 index 000000000..9221d113d --- /dev/null +++ b/examples/agent-compose/docker-env-secrets/example.env @@ -0,0 +1 @@ +EXAMPLE_SECRET=safe-example-secret diff --git a/examples/agent-compose/docker-minimal/README.md b/examples/agent-compose/docker-minimal/README.md index 3b2e1e9df..7bd0a9734 100644 --- a/examples/agent-compose/docker-minimal/README.md +++ b/examples/agent-compose/docker-minimal/README.md @@ -1,168 +1,35 @@ -# agent-compose Docker minimal example +# Minimal Docker project Languages: English | [中文](README.zh-CN.md) -This example shows the smallest useful `agent-compose.yml` for running an -agent-compose project with the Docker runtime driver. - -It is intentionally minimal: - -- one project -- one agent -- Docker runtime driver -- explicit guest image -- no scheduler -- no model or API key requirement for `config`, `up`, and `ps` +This is the smallest Docker-backed project: one agent, an explicit guest image, +and no workspace or scheduler. ## Prerequisites -- Docker daemon is running. -- The `agent-compose` daemon is already running. -- The `agent-compose-guest:latest` image exists locally. - -From the repository root, build the guest image if needed: - -```bash -task image:agent-compose-guest -``` - -If you have an installed `agent-compose` binary in `PATH`, use: +- The agent-compose daemon and Docker daemon are running. +- `ghcr.io/chaitin/agent-compose-guest:latest` is available locally. -```bash -agent-compose status -``` - -When working from the source tree, you can run the CLI directly: - -```bash -go run ./cmd/agent-compose status -``` - -## Compose file - -This directory contains the minimal Docker-backed project: - -```yaml -name: docker-minimal - -agents: - reviewer: - provider: codex - image: agent-compose-guest:latest - driver: - docker: {} -``` - -The important part is: - -```yaml -driver: - docker: {} -``` - -If the agent omits `driver`, the compose normalizer defaults to `docker`. -This example sets `docker: {}` explicitly to document the intended runtime. - -## Run the example - -From this directory: +## Run ```bash agent-compose config agent-compose up +agent-compose inspect agent reviewer +agent-compose run reviewer --command "printf 'docker minimal ok\\n'" --keep-running agent-compose ps ``` -From the repository root without installing the binary: - -```bash -go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config -go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up -go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps -``` - -Expected result: - -- `config` prints a normalized project with `driver.name: docker`. -- `up` creates or updates the project and managed agent definition. -- `ps` shows the `reviewer` agent using Docker and `agent-compose-guest:latest`. - -## Optional run test - -To start a runtime session and keep it alive: - -```bash -agent-compose run reviewer --keep-running --prompt "hello from docker minimal example" -``` - -A real agent run requires a working guest runtime and provider authentication. -For `provider: codex`, configure the required Codex credentials or API key in -the guest environment before expecting model execution to succeed. - -If the runtime session is alive, you can run commands in it: - -```bash -agent-compose exec --agent reviewer -- pwd -agent-compose exec --agent reviewer -- env -``` - -Clean up running project sessions: +`ps` lists sandboxes, not project agents. Copy the sandbox ID from `run` or +`ps` when executing another command: ```bash +agent-compose exec -- pwd +agent-compose exec -- env +agent-compose logs reviewer agent-compose down ``` -## Verification output - -Output from a local verification run. - -### 1. Config normalization - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config -name: docker-minimal -agents: - - name: reviewer - provider: codex - image: agent-compose-guest:latest - driver: - name: docker - docker: {} -network: - mode: default -``` - -### 2. Apply project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up -Project: docker-minimal -ID: project-docker-minimal-ad604c8bf8d3 -Revision: 1 -Spec: sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd -Status: applied -Agents: 1 -Schedulers: 0 - -ACTION TYPE NAME ID -created project docker-minimal project-docker-minimal-ad604c8bf8d3 -created project_revision sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd project-docker-minimal-ad604c8bf8d3/1 -created project_agent reviewer agent-reviewer-a9f84de36227 -created agent_definition reviewer agent-reviewer-a9f84de36227 -``` - -### 3. Project status - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps -AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer disabled - - - docker agent-compose-guest:latest -``` - -### 4. Docker runtime container - -```console -$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' -NAMES IMAGE STATUS -agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds -``` +Expected behavior: `config` reports `driver.name: docker`, `up` applies one +agent, the command prints `docker minimal ok`, and `down` stops its sandbox and +removes the applied project. diff --git a/examples/agent-compose/docker-minimal/README.zh-CN.md b/examples/agent-compose/docker-minimal/README.zh-CN.md index 91e4298e3..205db2179 100644 --- a/examples/agent-compose/docker-minimal/README.zh-CN.md +++ b/examples/agent-compose/docker-minimal/README.zh-CN.md @@ -1,167 +1,34 @@ -# agent-compose Docker 最小示例 +# 最小 Docker project 语言:[English](README.md) | 中文 -本示例展示一个使用 Docker runtime driver 的最小可用 -`agent-compose.yml`。 - -它刻意保持最小化: - -- 一个 project -- 一个 agent -- Docker runtime driver -- 显式指定 guest image -- 不启用 scheduler -- `config`、`up` 和 `ps` 不要求配置模型或 API key +这是最小的 Docker project:一个 agent、显式 guest image,不配置 workspace +和 scheduler。 ## 前置条件 -- Docker daemon 正在运行。 -- `agent-compose` daemon 已经启动。 -- 本地存在 `agent-compose-guest:latest` 镜像。 - -如果还没有 guest image,可以在仓库根目录构建: - -```bash -task image:agent-compose-guest -``` - -如果 `agent-compose` 二进制已经在 `PATH` 中,可以直接检查 daemon: +- agent-compose daemon 和 Docker daemon 正在运行。 +- 本地已有 `ghcr.io/chaitin/agent-compose-guest:latest`。 -```bash -agent-compose status -``` - -如果是在源码仓库中调试,也可以直接运行 CLI: - -```bash -go run ./cmd/agent-compose status -``` - -## Compose 文件 - -本目录包含一个最小 Docker project: - -```yaml -name: docker-minimal - -agents: - reviewer: - provider: codex - image: agent-compose-guest:latest - driver: - docker: {} -``` - -关键配置是: - -```yaml -driver: - docker: {} -``` - -如果 agent 省略 `driver`,compose normalizer 会默认使用 `docker`。 -本示例显式设置 `docker: {}`,是为了明确说明预期的 runtime。 - -## 运行示例 - -在本目录执行: +## 运行 ```bash agent-compose config agent-compose up +agent-compose inspect agent reviewer +agent-compose run reviewer --command "printf 'docker minimal ok\\n'" --keep-running agent-compose ps ``` -如果没有安装二进制,也可以在仓库根目录执行: - -```bash -go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config -go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up -go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps -``` - -预期结果: - -- `config` 输出标准化后的 project,并显示 `driver.name: docker`。 -- `up` 创建或更新 project 和 managed agent definition。 -- `ps` 显示 `reviewer` agent 使用 Docker 和 `agent-compose-guest:latest`。 - -## 可选运行测试 - -启动一次 runtime session,并在运行结束后保留 session: - -```bash -agent-compose run reviewer --keep-running --prompt "hello from docker minimal example" -``` - -真正执行 agent 需要 guest runtime 可用,并且 provider 已完成认证。对于 -`provider: codex`,需要先在 guest 环境中配置 Codex 凭据或 API key。 - -如果 runtime session 仍在运行,可以在其中执行命令: - -```bash -agent-compose exec --agent reviewer -- pwd -agent-compose exec --agent reviewer -- env -``` - -清理正在运行的 project sessions: +`ps` 列出的是 sandbox,不是 project agent。需要继续执行命令时,从 `run` +或 `ps` 复制 sandbox ID: ```bash +agent-compose exec -- pwd +agent-compose exec -- env +agent-compose logs reviewer agent-compose down ``` -## 验证输出 - -以下为一次本地验证运行的输出。 - -### 1. 配置标准化 - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config -name: docker-minimal -agents: - - name: reviewer - provider: codex - image: agent-compose-guest:latest - driver: - name: docker - docker: {} -network: - mode: default -``` - -### 2. 应用 project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up -Project: docker-minimal -ID: project-docker-minimal-ad604c8bf8d3 -Revision: 1 -Spec: sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd -Status: applied -Agents: 1 -Schedulers: 0 - -ACTION TYPE NAME ID -created project docker-minimal project-docker-minimal-ad604c8bf8d3 -created project_revision sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd project-docker-minimal-ad604c8bf8d3/1 -created project_agent reviewer agent-reviewer-a9f84de36227 -created agent_definition reviewer agent-reviewer-a9f84de36227 -``` - -### 3. Project 状态 - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps -AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer disabled - - - docker agent-compose-guest:latest -``` - -### 4. Docker runtime 容器 - -```console -$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' -NAMES IMAGE STATUS -agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds -``` +预期行为:`config` 显示 `driver.name: docker`,`up` 应用一个 agent,command +输出 `docker minimal ok`,`down` 停止 sandbox 并移除已应用的 project。 diff --git a/examples/agent-compose/docker-minimal/agent-compose.yml b/examples/agent-compose/docker-minimal/agent-compose.yml index 5fe0e4d43..64d927494 100644 --- a/examples/agent-compose/docker-minimal/agent-compose.yml +++ b/examples/agent-compose/docker-minimal/agent-compose.yml @@ -3,6 +3,6 @@ name: docker-minimal agents: reviewer: provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: docker: {} diff --git a/examples/agent-compose/docker-multi-agent/README.md b/examples/agent-compose/docker-multi-agent/README.md new file mode 100644 index 000000000..aa37202b8 --- /dev/null +++ b/examples/agent-compose/docker-multi-agent/README.md @@ -0,0 +1,20 @@ +# Docker multi-agent project + +Languages: English | [中文](README.zh-CN.md) + +Two agents share one workspace declaration while receiving independent sandbox +copies and agent definitions. + +```bash +agent-compose up +agent-compose inspect agent reviewer +agent-compose inspect agent tester +agent-compose run reviewer --command "printf 'reviewer ok\\n'" +agent-compose run tester --command "printf 'tester ok\\n'" +agent-compose logs reviewer +agent-compose logs tester +agent-compose down +``` + +The command path does not invoke the configured provider. The distinct system +prompts apply when the agents are run with `--prompt`. diff --git a/examples/agent-compose/docker-multi-agent/README.zh-CN.md b/examples/agent-compose/docker-multi-agent/README.zh-CN.md new file mode 100644 index 000000000..814fdaca1 --- /dev/null +++ b/examples/agent-compose/docker-multi-agent/README.zh-CN.md @@ -0,0 +1,20 @@ +# Docker 多 agent project + +语言:[English](README.md) | 中文 + +两个 agent 引用同一个 workspace 声明,但会获得独立的 sandbox workspace 副本 +和 agent definition。 + +```bash +agent-compose up +agent-compose inspect agent reviewer +agent-compose inspect agent tester +agent-compose run reviewer --command "printf 'reviewer ok\\n'" +agent-compose run tester --command "printf 'tester ok\\n'" +agent-compose logs reviewer +agent-compose logs tester +agent-compose down +``` + +command 路径不会调用 provider;使用 `--prompt` 时才会应用两个 agent 各自的 +system prompt。 diff --git a/examples/agent-compose/docker-multi-agent/agent-compose.yml b/examples/agent-compose/docker-multi-agent/agent-compose.yml new file mode 100644 index 000000000..e31fc8f6f --- /dev/null +++ b/examples/agent-compose/docker-multi-agent/agent-compose.yml @@ -0,0 +1,24 @@ +name: docker-multi-agent + +workspaces: + source: + provider: local + path: ./workspace + +agents: + reviewer: + provider: codex + system_prompt: Review changes and report risks. + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + workspace: + name: source + tester: + provider: codex + system_prompt: Run focused checks and report failures. + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + workspace: + name: source diff --git a/examples/agent-compose/docker-multi-agent/workspace/project.txt b/examples/agent-compose/docker-multi-agent/workspace/project.txt new file mode 100644 index 000000000..f0d07b762 --- /dev/null +++ b/examples/agent-compose/docker-multi-agent/workspace/project.txt @@ -0,0 +1 @@ +shared source fixture diff --git a/examples/agent-compose/docker-scheduler-cron/README.md b/examples/agent-compose/docker-scheduler-cron/README.md index 15899efc5..ee3fbf262 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.md +++ b/examples/agent-compose/docker-scheduler-cron/README.md @@ -1,200 +1,21 @@ -# agent-compose Docker cron scheduler example +# Declarative cron scheduler Languages: English | [中文](README.zh-CN.md) -This example shows a Docker-backed agent-compose project with a managed cron -scheduler. - -It verifies the scheduler control-plane flow: - -- parse a cron trigger from `agent-compose.yml` -- apply the project to the daemon -- create a managed project scheduler and loader -- show the scheduler as enabled -- disable the scheduler with `agent-compose down` - -The example does not require a model call for `config`, `up`, `ps`, or `down`. -The scheduled run itself still requires a working guest runtime and provider -authentication. - -## Prerequisites - -- Docker daemon is running. -- The `agent-compose` daemon is already running. -- The `agent-compose-guest:latest` image exists locally. - -From the repository root, build the guest image if needed: - -```bash -task image:agent-compose-guest -``` - -## Compose file - -```yaml -name: docker-scheduler-cron - -agents: - reviewer: - provider: codex - image: agent-compose-guest:latest - driver: - docker: {} - scheduler: - enabled: true - triggers: - - name: hourly-review - cron: "0 * * * *" - prompt: "Review the current project state and summarize any important changes." -``` - -The trigger uses standard cron syntax. The expression below runs at the top of -every hour: - -```yaml -cron: "0 * * * *" -``` - -## Run the example - -From this directory: +This project declares an hourly cron trigger. Scheduler state is inspected with +the scheduler commands; `ps` is reserved for sandboxes. ```bash agent-compose config agent-compose up -agent-compose ps +agent-compose scheduler ls reviewer +agent-compose scheduler inspect reviewer hourly-review agent-compose inspect project docker-scheduler-cron agent-compose down ``` -From the repository root without installing the binary: - -```bash -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down -``` - -Expected result: - -- `config` prints the trigger as `kind: cron`. -- `up` creates `project_scheduler` and `loader` resources. -- `ps` shows the scheduler as `enabled`. -- `inspect project` shows `scheduler_count: 1` and `trigger_count: 1`. -- `down` disables the managed scheduler and loader. - -## Making the trigger easier to observe - -For a local demo where you want the scheduler to fire soon, use an interval -trigger instead of cron: - -```yaml -scheduler: - enabled: true - triggers: - - name: every-minute - interval: 1m - prompt: "Say hello from the interval trigger." -``` - -Use cron when you want calendar-based scheduling. Use interval when you want -short local feedback while testing. - -## Verification output - -Output from a local verification run. - -### 1. Config normalization - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config -name: docker-scheduler-cron -agents: - - name: reviewer - provider: codex - image: agent-compose-guest:latest - driver: - name: docker - docker: {} - scheduler: - enabled: true - triggers: - - name: hourly-review - kind: cron - cron: 0 * * * * - prompt: Review the current project state and summarize any important changes. -network: - mode: default -``` - -### 2. Apply project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up -Project: docker-scheduler-cron -ID: project-docker-scheduler-cron-034aaf526f91 -Revision: 1 -Spec: sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 -Status: applied -Agents: 1 -Schedulers: 1 +The normalized scheduler includes `sandbox_policy: new` and the trigger includes +`kind: cron`. `up`, inspection, and `down` do not need provider credentials. +Waiting for or manually running the trigger does need a working Codex provider. -ACTION TYPE NAME ID -created project docker-scheduler-cron project-docker-scheduler-cron-034aaf526f91 -created project_revision sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 project-docker-scheduler-cron-034aaf526f91/1 -created project_agent reviewer agent-reviewer-4bff2fb6372a -created agent_definition reviewer agent-reviewer-4bff2fb6372a -created project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa -created loader docker-scheduler-cron/reviewer scheduler loader-reviewer-default-ed0b5bed0daa -``` - -### 3. Scheduler status - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps -AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled - - - docker agent-compose-guest:latest -``` - -### 4. Inspect project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron -{ - "project": { - "id": "project-docker-scheduler-cron-034aaf526f91", - "name": "docker-scheduler-cron", - "current_revision": 1, - "agent_count": 1, - "scheduler_count": 1 - }, - "agents": [ - { - "agent_name": "reviewer", - "provider": "codex", - "image": "agent-compose-guest:latest", - "driver": "docker", - "scheduler_enabled": true - } - ], - "schedulers": [ - { "agent_name": "reviewer", "enabled": true, "trigger_count": 1 } - ] -} -``` - -### 5. Disable scheduler - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down -Project: docker-scheduler-cron -ID: project-docker-scheduler-cron-034aaf526f91 -Status: down -Failed session stops: 0 - -ACTION TYPE NAME ID MESSAGE -updated project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa disabled by project down -updated loader reviewer loader-reviewer-default-ed0b5bed0daa disabled by project down -``` +For quicker experiments, replace the cron field with `interval: 1m`. diff --git a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md index 4a698da91..4d05ac3e9 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md @@ -1,196 +1,20 @@ -# agent-compose Docker cron scheduler 示例 +# 声明式 cron scheduler 语言:[English](README.md) | 中文 -本示例展示一个使用 Docker runtime 的 agent-compose project,并为它配置 -managed cron scheduler。 - -它验证 scheduler 控制面流程: - -- 从 `agent-compose.yml` 解析 cron trigger -- 将 project 应用到 daemon -- 创建 managed project scheduler 和 loader -- 确认 scheduler 处于 enabled 状态 -- 使用 `agent-compose down` 禁用 scheduler - -本示例的 `config`、`up`、`ps` 和 `down` 不要求真实调用模型。真正的定时 -运行仍然需要 guest runtime 可用,并且 provider 已完成认证。 - -## 前置条件 - -- Docker daemon 正在运行。 -- `agent-compose` daemon 已经启动。 -- 本地存在 `agent-compose-guest:latest` 镜像。 - -如果还没有 guest image,可以在仓库根目录构建: - -```bash -task image:agent-compose-guest -``` - -## Compose 文件 - -```yaml -name: docker-scheduler-cron - -agents: - reviewer: - provider: codex - image: agent-compose-guest:latest - driver: - docker: {} - scheduler: - enabled: true - triggers: - - name: hourly-review - cron: "0 * * * *" - prompt: "Review the current project state and summarize any important changes." -``` - -trigger 使用标准 cron 语法。下面的表达式表示每小时整点运行: - -```yaml -cron: "0 * * * *" -``` - -## 运行示例 - -在本目录执行: +该 project 声明一个每小时触发的 cron。scheduler 状态应通过 scheduler 命令 +查看;`ps` 只用于列出 sandbox。 ```bash agent-compose config agent-compose up -agent-compose ps +agent-compose scheduler ls reviewer +agent-compose scheduler inspect reviewer hourly-review agent-compose inspect project docker-scheduler-cron agent-compose down ``` -如果没有安装二进制,也可以在仓库根目录执行: - -```bash -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down -``` - -预期结果: - -- `config` 显示 trigger 为 `kind: cron`。 -- `up` 创建 `project_scheduler` 和 `loader` 资源。 -- `ps` 显示 scheduler 为 `enabled`。 -- `inspect project` 显示 `scheduler_count: 1` 和 `trigger_count: 1`。 -- `down` 禁用 managed scheduler 和 loader。 - -## 更容易观察触发的方法 - -如果本地演示时希望 scheduler 很快触发,可以使用 interval trigger 替代 cron: - -```yaml -scheduler: - enabled: true - triggers: - - name: every-minute - interval: 1m - prompt: "Say hello from the interval trigger." -``` - -需要基于日历时间调度时使用 cron;需要本地快速反馈时使用 interval。 - -## 验证输出 - -以下为一次本地验证运行的输出。 - -### 1. 配置标准化 - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config -name: docker-scheduler-cron -agents: - - name: reviewer - provider: codex - image: agent-compose-guest:latest - driver: - name: docker - docker: {} - scheduler: - enabled: true - triggers: - - name: hourly-review - kind: cron - cron: 0 * * * * - prompt: Review the current project state and summarize any important changes. -network: - mode: default -``` - -### 2. 应用 project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up -Project: docker-scheduler-cron -ID: project-docker-scheduler-cron-034aaf526f91 -Revision: 1 -Spec: sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 -Status: applied -Agents: 1 -Schedulers: 1 +标准化配置包含 `sandbox_policy: new`,trigger 包含 `kind: cron`。`up`、查询和 +`down` 不需要 provider 凭证;等待或手动运行 trigger 需要可用的 Codex provider。 -ACTION TYPE NAME ID -created project docker-scheduler-cron project-docker-scheduler-cron-034aaf526f91 -created project_revision sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 project-docker-scheduler-cron-034aaf526f91/1 -created project_agent reviewer agent-reviewer-4bff2fb6372a -created agent_definition reviewer agent-reviewer-4bff2fb6372a -created project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa -created loader docker-scheduler-cron/reviewer scheduler loader-reviewer-default-ed0b5bed0daa -``` - -### 3. Scheduler 状态 - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps -AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled - - - docker agent-compose-guest:latest -``` - -### 4. 查看 project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron -{ - "project": { - "id": "project-docker-scheduler-cron-034aaf526f91", - "name": "docker-scheduler-cron", - "current_revision": 1, - "agent_count": 1, - "scheduler_count": 1 - }, - "agents": [ - { - "agent_name": "reviewer", - "provider": "codex", - "image": "agent-compose-guest:latest", - "driver": "docker", - "scheduler_enabled": true - } - ], - "schedulers": [ - { "agent_name": "reviewer", "enabled": true, "trigger_count": 1 } - ] -} -``` - -### 5. 禁用 scheduler - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down -Project: docker-scheduler-cron -ID: project-docker-scheduler-cron-034aaf526f91 -Status: down -Failed session stops: 0 - -ACTION TYPE NAME ID MESSAGE -updated project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa disabled by project down -updated loader reviewer loader-reviewer-default-ed0b5bed0daa disabled by project down -``` +本地快速观察时,可以把 cron 字段替换为 `interval: 1m`。 diff --git a/examples/agent-compose/docker-scheduler-cron/agent-compose.yml b/examples/agent-compose/docker-scheduler-cron/agent-compose.yml index 079e9bc34..3cd265393 100644 --- a/examples/agent-compose/docker-scheduler-cron/agent-compose.yml +++ b/examples/agent-compose/docker-scheduler-cron/agent-compose.yml @@ -3,11 +3,12 @@ name: docker-scheduler-cron agents: reviewer: provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: docker: {} scheduler: enabled: true + sandbox_policy: new triggers: - name: hourly-review cron: "0 * * * *" diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.md b/examples/agent-compose/docker-scheduler-script-runtime/README.md new file mode 100644 index 000000000..fe754a3af --- /dev/null +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.md @@ -0,0 +1,19 @@ +# Scheduler script runtime + +Languages: English | [中文](README.zh-CN.md) + +This inline QJS script combines a stable interval trigger, persisted state, +loader logs, and a Docker-backed shell command. + +```bash +agent-compose up +agent-compose scheduler ls heartbeat +agent-compose scheduler inspect heartbeat warmup +agent-compose scheduler inspect heartbeat follow-up +# Wait until both timeout triggers report that they have fired. +agent-compose down +``` + +The two automatic timeout runs produce `heartbeat 1` and `heartbeat 2`, +demonstrating that loader state persists between runs. The interval remains as +the long-running schedule. No model provider is called. diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md new file mode 100644 index 000000000..979c7e28f --- /dev/null +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md @@ -0,0 +1,18 @@ +# Scheduler 脚本 runtime + +语言:[English](README.md) | 中文 + +该 inline QJS 脚本组合了稳定的 interval trigger、持久状态、Loader 日志和 +Docker-backed shell command。 + +```bash +agent-compose up +agent-compose scheduler ls heartbeat +agent-compose scheduler inspect heartbeat warmup +agent-compose scheduler inspect heartbeat follow-up +# 等待两个 timeout trigger 都显示已触发。 +agent-compose down +``` + +两个自动 timeout run 分别产生 `heartbeat 1` 和 `heartbeat 2`,证明 Loader +state 会在 run 之间持久化;interval 保留为长期调度。该流程不会调用模型 provider。 diff --git a/examples/agent-compose/docker-scheduler-script-runtime/agent-compose.yml b/examples/agent-compose/docker-scheduler-script-runtime/agent-compose.yml new file mode 100644 index 000000000..84a346c90 --- /dev/null +++ b/examples/agent-compose/docker-scheduler-script-runtime/agent-compose.yml @@ -0,0 +1,36 @@ +name: docker-scheduler-script-runtime + +agents: + heartbeat: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + scheduler: + enabled: true + sandbox_policy: new + script: | + function runHeartbeat() { + const previous = scheduler.state.get("heartbeat-count") ?? 0; + const count = Number(previous) + 1; + scheduler.state.set("heartbeat-count", count); + const command = scheduler.shell("printf 'heartbeat %s\\n' " + count); + scheduler.log("heartbeat completed", { count, output: command.output }); + return { count, output: command.output, success: command.success }; + } + + scheduler.timeout("warmup", function warmup() { + return runHeartbeat(); + }, 1000); + + scheduler.timeout("follow-up", function followUp() { + return runHeartbeat(); + }, 3000); + + scheduler.interval("heartbeat", function heartbeat() { + return runHeartbeat(); + }, 60000); + + function main() { + return runHeartbeat(); + } diff --git a/examples/agent-compose/docker-scheduler-script-url/README.md b/examples/agent-compose/docker-scheduler-script-url/README.md index faac9b615..65501b0a5 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.md @@ -1,21 +1,23 @@ -# Scheduler script URL example +# Scheduler script URL Languages: English | [中文](README.zh-CN.md) -This example keeps QJS in `scheduler.js` and references it from -`agent-compose.yml` with `scheduler.script.url`. +The QJS scheduler is stored in `scheduler.js` and referenced with +`scheduler.script.url`. ```bash agent-compose config agent-compose up -agent-compose ps +agent-compose scheduler ls reviewer +agent-compose scheduler inspect reviewer source-loaded +# Wait until scheduler inspect reports that source-loaded has fired. agent-compose down ``` -`config` prints the fetched script inline. `up` fetches it once more, hashes the -content snapshot, and sends only script text to the daemon. Editing -`scheduler.js` takes effect on the next `up`. The relative path is resolved from -the directory containing `agent-compose.yml`. +`config` and `up` resolve the relative path from the compose directory and send +an inline content snapshot to the daemon. Editing `scheduler.js` takes effect on +the next `up`; this is not a runtime import or background refresh. -The control-plane commands do not require provider authentication. A scheduled -run does require a working guest runtime and provider credentials. +The two-second timeout trigger runs a local shell command, so provider +authentication is not required. It is driven by the scheduler runtime rather +than `scheduler trigger`, whose project-run path is intended for agent prompts. diff --git a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md index 8cbaaeeae..3980c8330 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md @@ -1,20 +1,20 @@ -# Scheduler 脚本 URL 示例 +# Scheduler 脚本 URL 语言:[English](README.md) | 中文 -本示例把 QJS 保存在 `scheduler.js`,并在 `agent-compose.yml` 中通过 -`scheduler.script.url` 引用。 +QJS scheduler 保存在 `scheduler.js`,通过 `scheduler.script.url` 引用。 ```bash agent-compose config agent-compose up -agent-compose ps +agent-compose scheduler ls reviewer +agent-compose scheduler inspect reviewer source-loaded +# 等待 scheduler inspect 显示 source-loaded 已触发。 agent-compose down ``` -`config` 会把获取到的脚本以内联形式输出。`up` 再获取一次,基于内容快照计算 -hash,并且只把脚本文本发送给 daemon。修改 `scheduler.js` 后需再次执行 `up` -才会生效。相对路径以 `agent-compose.yml` 所在目录为基准。 +`config` 和 `up` 以 compose 目录为基准解析相对路径,并把内容快照内联发送给 +daemon。修改 `scheduler.js` 后需要再次 `up`;它不是运行时 import,也不会后台刷新。 -控制面命令不要求 provider 凭证;实际定时运行仍需要可用的 guest runtime 和 -provider 凭证。 +两秒 timeout trigger 执行本地 shell 命令,因此不需要 provider 认证。它由 +scheduler runtime 驱动;`scheduler trigger` 的 project-run 路径用于 agent prompt。 diff --git a/examples/agent-compose/docker-scheduler-script-url/agent-compose.yml b/examples/agent-compose/docker-scheduler-script-url/agent-compose.yml index 28d4ec9c5..c882ca5cc 100644 --- a/examples/agent-compose/docker-scheduler-script-url/agent-compose.yml +++ b/examples/agent-compose/docker-scheduler-script-url/agent-compose.yml @@ -3,10 +3,11 @@ name: docker-scheduler-script-url agents: reviewer: provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: docker: {} scheduler: enabled: true + sandbox_policy: new script: url: ./scheduler.js diff --git a/examples/agent-compose/docker-scheduler-script-url/scheduler.js b/examples/agent-compose/docker-scheduler-script-url/scheduler.js index f3572a2ee..2b87eaff9 100644 --- a/examples/agent-compose/docker-scheduler-script-url/scheduler.js +++ b/examples/agent-compose/docker-scheduler-script-url/scheduler.js @@ -1,6 +1,6 @@ -scheduler.cron("daily-review", "0 9 * * *", function dailyReview() { - return scheduler.agent("Review the current project state."); -}); +scheduler.timeout("source-loaded", function sourceLoaded() { + return scheduler.shell("printf 'scheduler script URL ok\\n'"); +}, 2000); function main(payload) { return { ok: true, payload }; diff --git a/examples/agent-compose/docker-scheduler-timeout/README.md b/examples/agent-compose/docker-scheduler-timeout/README.md index 08e97a278..df8e59584 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.md @@ -1,187 +1,27 @@ -# agent-compose Docker timeout scheduler example +# Declarative timeout scheduler Languages: English | [中文](README.zh-CN.md) -This example runs an end-to-end scheduled agent flow with the Docker runtime. - -It verifies that agent-compose can: - -- parse a timeout trigger from `agent-compose.yml` -- apply the project to the daemon -- create a managed scheduler and loader -- let the scheduler fire automatically -- start a Docker-backed agent runtime session -- run the configured agent prompt -- persist the successful project run and logs -- disable the scheduler with `agent-compose down` +This example fires once 15 seconds after the scheduler is applied and runs a +Codex prompt. ## Prerequisites -- Docker daemon is running. -- The `agent-compose` daemon is already running. -- The `agent-compose-guest:latest` image exists locally. -- The guest image has working Codex credentials or API access. - -From the repository root, build the guest image if needed: - -```bash -task image:agent-compose-guest -``` - -## Compose file - -```yaml -name: docker-scheduler-timeout - -agents: - reviewer: - provider: codex - image: agent-compose-guest:latest - driver: - docker: {} - scheduler: - enabled: true - triggers: - - name: run-once-after-15-seconds - timeout: 15s - prompt: "Reply with exactly: timeout scheduler ok" -``` - -The `timeout: 15s` trigger is intentionally short so the full flow can be -tested quickly. - -## Run the example - -From this directory: +Docker and the published guest image are required. The daemon must also have a +working Codex/OpenAI provider configuration. ```bash agent-compose config agent-compose up -sleep 35 -agent-compose ps +agent-compose scheduler ls reviewer +agent-compose --json logs reviewer agent-compose inspect run agent-compose logs --run agent-compose down ``` -From the repository root without installing the binary: - -```bash -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up -sleep 35 -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down -``` - -Replace `` with the run id shown in the `ps` output. - -Expected result: - -- `config` prints the trigger as `kind: timeout`. -- `up` creates or updates the managed scheduler and loader. -- After the timeout fires once, `ps` shows a scheduler-created run. -- `inspect run ` shows `source: scheduler`, `status: succeeded`, `driver: docker`, and output from the agent. -- `logs --run ` prints the agent output. -- `down` disables the managed scheduler and loader. - -## Verification output - -Output from a local verification run. The run id below is from that run; yours -will differ. - -### 1. Config normalization - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config -name: docker-scheduler-timeout -agents: - - name: reviewer - provider: codex - image: agent-compose-guest:latest - driver: - name: docker - docker: {} - scheduler: - enabled: true - triggers: - - name: run-once-after-15-seconds - kind: timeout - timeout: 15s - prompt: 'Reply with exactly: timeout scheduler ok' -network: - mode: default -``` - -### 2. Apply project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up -Project: docker-scheduler-timeout -ID: project-docker-scheduler-timeout-3a00cafbae27 -Revision: 1 -Spec: sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 -Status: applied -Agents: 1 -Schedulers: 1 - -ACTION TYPE NAME ID -created project docker-scheduler-timeout project-docker-scheduler-timeout-3a00cafbae27 -created project_revision sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 project-docker-scheduler-timeout-3a00cafbae27/1 -created project_agent reviewer agent-reviewer-a0befcb745b8 -created agent_definition reviewer agent-reviewer-a0befcb745b8 -created project_scheduler reviewer scheduler-reviewer-default-181247660dc1 -created loader docker-scheduler-timeout/reviewer scheduler loader-reviewer-default-181247660dc1 -``` - -### 3. Successful scheduled run - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps -AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker agent-compose-guest:latest -``` - -### 4. Inspect successful run - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run run-reviewer-28c0ef985c8d -{ - "run_id": "run-reviewer-28c0ef985c8d", - "project_name": "docker-scheduler-timeout", - "agent_name": "reviewer", - "source": "scheduler", - "status": "succeeded", - "session_id": "23a1ede4-3325-470d-99db-377e3296e7a2", - "exit_code": 0, - "duration_ms": 10917, - "prompt": "Reply with exactly: timeout scheduler ok", - "output": "timeout scheduler ok", - "result_json": "{\"agent\":\"codex\",\"exitCode\":0,\"stopReason\":\"completed\",\"success\":true}", - "driver": "docker", - "image_ref": "agent-compose-guest:latest" -} -``` - -### 5. Run logs +Poll `logs reviewer` until the run appears; do not assume a fixed completion +time. The JSON log response contains the run ID. A successful detail reports +`source: scheduler`, `status: succeeded`, and Docker as the driver. -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run run-reviewer-28c0ef985c8d -timeout scheduler ok -``` - -### 6. Disable scheduler - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down -Project: docker-scheduler-timeout -ID: project-docker-scheduler-timeout-3a00cafbae27 -Status: down -Failed session stops: 0 - -ACTION TYPE NAME ID MESSAGE -updated project_scheduler reviewer scheduler-reviewer-default-181247660dc1 disabled by project down -updated loader reviewer loader-reviewer-default-181247660dc1 disabled by project down -``` +The normalized scheduler includes `sandbox_policy: new` and `kind: timeout`. diff --git a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md index 7fdff74b8..5642f4818 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md @@ -1,185 +1,26 @@ -# agent-compose Docker timeout scheduler 示例 +# 声明式 timeout scheduler 语言:[English](README.md) | 中文 -本示例使用 Docker runtime 跑通一个端到端的 scheduled agent 流程。 - -它验证 agent-compose 可以完成: - -- 从 `agent-compose.yml` 解析 timeout trigger -- 将 project 应用到 daemon -- 创建 managed scheduler 和 loader -- 由 scheduler 自动触发运行 -- 启动 Docker-backed agent runtime session -- 执行配置的 agent prompt -- 持久化成功的 project run 和日志 -- 使用 `agent-compose down` 禁用 scheduler +该示例在 scheduler 应用 15 秒后触发一次 Codex prompt。 ## 前置条件 -- Docker daemon 正在运行。 -- `agent-compose` daemon 已经启动。 -- 本地存在 `agent-compose-guest:latest` 镜像。 -- guest image 中已经配置可用的 Codex 凭据或 API 访问能力。 - -如果还没有 guest image,可以在仓库根目录构建: - -```bash -task image:agent-compose-guest -``` - -## Compose 文件 - -```yaml -name: docker-scheduler-timeout - -agents: - reviewer: - provider: codex - image: agent-compose-guest:latest - driver: - docker: {} - scheduler: - enabled: true - triggers: - - name: run-once-after-15-seconds - timeout: 15s - prompt: "Reply with exactly: timeout scheduler ok" -``` - -`timeout: 15s` 刻意设置得较短,方便快速验证完整流程。 - -## 运行示例 - -在本目录执行: +需要 Docker、发布的 guest image,以及 daemon 侧可用的 Codex/OpenAI provider +配置。 ```bash agent-compose config agent-compose up -sleep 35 -agent-compose ps +agent-compose scheduler ls reviewer +agent-compose --json logs reviewer agent-compose inspect run agent-compose logs --run agent-compose down ``` -如果没有安装二进制,也可以在仓库根目录执行: - -```bash -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up -sleep 35 -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down -``` - -将 `` 替换为上一步 `ps` 输出中显示的 run id。 - -预期结果: - -- `config` 显示 trigger 为 `kind: timeout`。 -- `up` 创建或更新 managed scheduler 和 loader。 -- 等待 timeout 触发一次后,`ps` 显示 scheduler 创建的 run。 -- `inspect run ` 显示 `source: scheduler`、`status: succeeded`、`driver: docker`,并包含 agent 输出。 -- `logs --run ` 输出 agent 日志。 -- `down` 禁用 managed scheduler 和 loader。 - -## 验证输出 - -以下为一次本地验证运行的输出。其中的 run id 来自该次运行,你本地的会不同。 - -### 1. 配置标准化 - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config -name: docker-scheduler-timeout -agents: - - name: reviewer - provider: codex - image: agent-compose-guest:latest - driver: - name: docker - docker: {} - scheduler: - enabled: true - triggers: - - name: run-once-after-15-seconds - kind: timeout - timeout: 15s - prompt: 'Reply with exactly: timeout scheduler ok' -network: - mode: default -``` - -### 2. 应用 project - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up -Project: docker-scheduler-timeout -ID: project-docker-scheduler-timeout-3a00cafbae27 -Revision: 1 -Spec: sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 -Status: applied -Agents: 1 -Schedulers: 1 - -ACTION TYPE NAME ID -created project docker-scheduler-timeout project-docker-scheduler-timeout-3a00cafbae27 -created project_revision sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 project-docker-scheduler-timeout-3a00cafbae27/1 -created project_agent reviewer agent-reviewer-a0befcb745b8 -created agent_definition reviewer agent-reviewer-a0befcb745b8 -created project_scheduler reviewer scheduler-reviewer-default-181247660dc1 -created loader docker-scheduler-timeout/reviewer scheduler loader-reviewer-default-181247660dc1 -``` - -### 3. 成功的 scheduled run - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps -AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker agent-compose-guest:latest -``` - -### 4. 查看成功 run - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run run-reviewer-28c0ef985c8d -{ - "run_id": "run-reviewer-28c0ef985c8d", - "project_name": "docker-scheduler-timeout", - "agent_name": "reviewer", - "source": "scheduler", - "status": "succeeded", - "session_id": "23a1ede4-3325-470d-99db-377e3296e7a2", - "exit_code": 0, - "duration_ms": 10917, - "prompt": "Reply with exactly: timeout scheduler ok", - "output": "timeout scheduler ok", - "result_json": "{\"agent\":\"codex\",\"exitCode\":0,\"stopReason\":\"completed\",\"success\":true}", - "driver": "docker", - "image_ref": "agent-compose-guest:latest" -} -``` - -### 5. Run 日志 +轮询 `logs reviewer` 直到 run 出现,不要依赖固定完成时间。JSON 日志响应中包含 +run ID。成功的 detail 应显示 `source: scheduler`、`status: succeeded` 和 Docker +driver。 -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run run-reviewer-28c0ef985c8d -timeout scheduler ok -``` - -### 6. 禁用 scheduler - -```console -$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down -Project: docker-scheduler-timeout -ID: project-docker-scheduler-timeout-3a00cafbae27 -Status: down -Failed session stops: 0 - -ACTION TYPE NAME ID MESSAGE -updated project_scheduler reviewer scheduler-reviewer-default-181247660dc1 disabled by project down -updated loader reviewer loader-reviewer-default-181247660dc1 disabled by project down -``` +标准化 scheduler 包含 `sandbox_policy: new` 和 `kind: timeout`。 diff --git a/examples/agent-compose/docker-scheduler-timeout/agent-compose.yml b/examples/agent-compose/docker-scheduler-timeout/agent-compose.yml index 808a1456c..be455e529 100644 --- a/examples/agent-compose/docker-scheduler-timeout/agent-compose.yml +++ b/examples/agent-compose/docker-scheduler-timeout/agent-compose.yml @@ -3,11 +3,12 @@ name: docker-scheduler-timeout agents: reviewer: provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: docker: {} scheduler: enabled: true + sandbox_policy: new triggers: - name: run-once-after-15-seconds timeout: 15s diff --git a/examples/agent-compose/docker-volume-persistence/README.md b/examples/agent-compose/docker-volume-persistence/README.md new file mode 100644 index 000000000..4c4b5162c --- /dev/null +++ b/examples/agent-compose/docker-volume-persistence/README.md @@ -0,0 +1,21 @@ +# Docker volume persistence + +Languages: English | [中文](README.zh-CN.md) + +This project mounts a managed named volume at `/cache` and a read-only bind +fixture at `/fixtures`. + +```bash +agent-compose up +agent-compose run worker --command "cat /fixtures/readonly.txt && printf 'persistent\\n' > /cache/value" --keep-running +agent-compose stop +agent-compose resume +agent-compose exec -- cat /cache/value +agent-compose exec -- sh -c 'if touch /fixtures/unexpected 2>/dev/null; then exit 1; fi' +agent-compose stop +agent-compose rm +agent-compose down +``` + +The cache value survives sandbox stop/resume. `down` removes project-managed +volume ownership, so do not use this example as a backup mechanism. diff --git a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md new file mode 100644 index 000000000..3669e85d9 --- /dev/null +++ b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md @@ -0,0 +1,21 @@ +# Docker volume 持久化 + +语言:[English](README.md) | 中文 + +该 project 把 managed named volume 挂载到 `/cache`,并把只读 bind fixture +挂载到 `/fixtures`。 + +```bash +agent-compose up +agent-compose run worker --command "cat /fixtures/readonly.txt && printf 'persistent\\n' > /cache/value" --keep-running +agent-compose stop +agent-compose resume +agent-compose exec -- cat /cache/value +agent-compose exec -- sh -c 'if touch /fixtures/unexpected 2>/dev/null; then exit 1; fi' +agent-compose stop +agent-compose rm +agent-compose down +``` + +cache 值在 sandbox stop/resume 后仍存在。`down` 会移除 project-managed volume +的归属,不应把该示例当作备份机制。 diff --git a/examples/agent-compose/docker-volume-persistence/agent-compose.yml b/examples/agent-compose/docker-volume-persistence/agent-compose.yml new file mode 100644 index 000000000..f6a02a129 --- /dev/null +++ b/examples/agent-compose/docker-volume-persistence/agent-compose.yml @@ -0,0 +1,17 @@ +name: docker-volume-persistence + +volumes: + cache: + driver: local + labels: + purpose: agent-compose-example + +agents: + worker: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + volumes: + - cache:/cache + - ./fixtures:/fixtures:ro diff --git a/examples/agent-compose/docker-volume-persistence/fixtures/readonly.txt b/examples/agent-compose/docker-volume-persistence/fixtures/readonly.txt new file mode 100644 index 000000000..8dd1fefe6 --- /dev/null +++ b/examples/agent-compose/docker-volume-persistence/fixtures/readonly.txt @@ -0,0 +1 @@ +read-only bind fixture diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.md b/examples/agent-compose/docker-workspace-lifecycle/README.md new file mode 100644 index 000000000..98b14df80 --- /dev/null +++ b/examples/agent-compose/docker-workspace-lifecycle/README.md @@ -0,0 +1,22 @@ +# Docker workspace lifecycle + +Languages: English | [中文](README.zh-CN.md) + +This example copies `workspace/` into each new sandbox and demonstrates the +current sandbox lifecycle without model credentials. + +```bash +agent-compose up +agent-compose run worker --command "printf 'sandbox-only\\n' > generated.txt" --keep-running +agent-compose ps +agent-compose exec -- cat generated.txt +agent-compose stop +agent-compose resume +agent-compose exec -- cat generated.txt +agent-compose stop +agent-compose rm +agent-compose down +``` + +`generated.txt` survives stop/resume but is not written into the committed +`workspace/` source. A second new sandbox receives a fresh source copy. diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md new file mode 100644 index 000000000..62703d580 --- /dev/null +++ b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md @@ -0,0 +1,22 @@ +# Docker workspace 生命周期 + +语言:[English](README.md) | 中文 + +该示例把 `workspace/` 复制到每个新 sandbox,并在不需要模型凭证的情况下演示 +当前 sandbox 生命周期。 + +```bash +agent-compose up +agent-compose run worker --command "printf 'sandbox-only\\n' > generated.txt" --keep-running +agent-compose ps +agent-compose exec -- cat generated.txt +agent-compose stop +agent-compose resume +agent-compose exec -- cat generated.txt +agent-compose stop +agent-compose rm +agent-compose down +``` + +`generated.txt` 在 stop/resume 后仍存在,但不会写回仓库中的 `workspace/` source。 +新建第二个 sandbox 时会得到一份全新的 source 副本。 diff --git a/examples/agent-compose/docker-workspace-lifecycle/agent-compose.yml b/examples/agent-compose/docker-workspace-lifecycle/agent-compose.yml new file mode 100644 index 000000000..3502c901e --- /dev/null +++ b/examples/agent-compose/docker-workspace-lifecycle/agent-compose.yml @@ -0,0 +1,15 @@ +name: docker-workspace-lifecycle + +workspaces: + source: + provider: local + path: ./workspace + +agents: + worker: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + workspace: + name: source diff --git a/examples/agent-compose/docker-workspace-lifecycle/workspace/README.md b/examples/agent-compose/docker-workspace-lifecycle/workspace/README.md new file mode 100644 index 000000000..e1f702492 --- /dev/null +++ b/examples/agent-compose/docker-workspace-lifecycle/workspace/README.md @@ -0,0 +1,4 @@ +# Workspace lifecycle fixture + +This file is copied into each new sandbox workspace. Changes made in a sandbox +must not modify this source directory. diff --git a/examples/agent-compose/microsandbox-minimal/README.md b/examples/agent-compose/microsandbox-minimal/README.md new file mode 100644 index 000000000..95751d909 --- /dev/null +++ b/examples/agent-compose/microsandbox-minimal/README.md @@ -0,0 +1,15 @@ +# Minimal Microsandbox project + +Languages: English | [中文](README.zh-CN.md) + +This is a configuration template for a Microsandbox-backed agent. + +```bash +agent-compose config +# On a prepared Linux/KVM host with a Microsandbox-enabled binary: +agent-compose up +``` + +The compose file is validated in normal tests. Runtime execution was not +verified locally and requires Linux, `/dev/kvm`, Microsandbox runtime artifacts, +and a binary whose `compiled_drivers` includes `microsandbox`. diff --git a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md new file mode 100644 index 000000000..2a50955b8 --- /dev/null +++ b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md @@ -0,0 +1,15 @@ +# 最小 Microsandbox project + +语言:[English](README.md) | 中文 + +这是一个使用 Microsandbox driver 的配置模板。 + +```bash +agent-compose config +# 在准备好的 Linux/KVM 主机和包含 Microsandbox 的二进制上: +agent-compose up +``` + +compose 文件会进入常规测试,但本地未验证 runtime 执行。运行需要 Linux、 +`/dev/kvm`、Microsandbox runtime artifacts,并且二进制的 `compiled_drivers` +包含 `microsandbox`。 diff --git a/examples/agent-compose/microsandbox-minimal/agent-compose.yml b/examples/agent-compose/microsandbox-minimal/agent-compose.yml new file mode 100644 index 000000000..ec564f316 --- /dev/null +++ b/examples/agent-compose/microsandbox-minimal/agent-compose.yml @@ -0,0 +1,8 @@ +name: microsandbox-minimal + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + microsandbox: {} diff --git a/examples/scheduler-script/README.md b/examples/scheduler-script/README.md index e583a6cfb..308b1d116 100644 --- a/examples/scheduler-script/README.md +++ b/examples/scheduler-script/README.md @@ -111,7 +111,8 @@ scheduler.schedule(triggerId, expression, callback, options); 手动运行时,agent-compose 会优先调用全局 `main(payload)`。如果没有 `main()` 且脚本只注册了一个触发器,则会调用这个触发器的 callback;如果有多个触发器,必须显式选择触发器或定义 `main()`。 -- 手动运行的 `payload` 来自 `RunLoaderNow.payloadJson`。 +- 手动运行的 `payload` 来自统一 run 请求的 `payload_json`;CLI 对应 + `agent-compose scheduler trigger --payload '{...}'`。 - `scheduler.on(...)` handler 收到事件 envelope:`{ topic, createdAt, payload }`。 - interval、timeout、cron handler 默认收到 `undefined`,除非你在脚本里自己转发自定义上下文。 diff --git a/test/e2e/examples_docker_live_provider_test.go b/test/e2e/examples_docker_live_provider_test.go new file mode 100644 index 000000000..7cfa71f6f --- /dev/null +++ b/test/e2e/examples_docker_live_provider_test.go @@ -0,0 +1,85 @@ +package e2e + +import ( + "context" + "encoding/json" + "net" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestE2EExamplesDockerLiveProvider(t *testing.T) { + if strings.TrimSpace(os.Getenv("AGENT_COMPOSE_E2E_EXAMPLES_LIVE_PROVIDER")) != "1" { + t.Skip("set AGENT_COMPOSE_E2E_EXAMPLES_LIVE_PROVIDER=1 to use the configured live provider") + } + image := strings.TrimSpace(os.Getenv(examplesDockerImageEnv)) + if image == "" { + t.Fatalf("%s is required", examplesDockerImageEnv) + } + + providerEnv := liveProviderE2EEnv(t) + ctx, cancel := context.WithTimeout(context.Background(), 8*time.Minute) + defer cancel() + root := e2eRepoRoot(t) + testRoot, err := os.MkdirTemp(root, ".docker-examples-live-provider-e2e-") + if err != nil { + t.Fatalf("create live-provider E2E root: %v", err) + } + t.Cleanup(func() { _ = os.RemoveAll(testRoot) }) + dockerClient := newE2EDockerClient(t, ctx, image) + t.Cleanup(func() { removeE2EDockerContainersUnderRoot(t, dockerClient, testRoot) }) + binary := e2eDaemonBinary(t, ctx, root, testRoot) + listen := unusedLoopbackAddress(t) + baseURL := "http://" + listen + _, port, err := net.SplitHostPort(listen) + if err != nil { + t.Fatalf("split daemon listen address: %v", err) + } + gateway := dockerBridgeGateway(t, ctx, dockerClient) + providerEnv["AGENT_COMPOSE_RUNTIME_BASE_URL"] = "http://" + net.JoinHostPort(gateway, port) + providerEnv["DOCKER_HOST_SANDBOX_ROOT"] = "" + providerEnv["HTTP_LISTEN"] = net.JoinHostPort("0.0.0.0", port) + + daemon := startE2EDaemonWithOverrides(t, binary, root, testRoot, listen, image, providerEnv) + waitForE2EDaemon(t, ctx, daemon, baseURL) + t.Cleanup(func() { + if t.Failed() { + t.Logf("live-provider examples daemon log:\n%s", daemon.logs.String()) + } + }) + + file := filepath.Join(root, "examples", "agent-compose", "docker-minimal", "agent-compose.yml") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + const marker = "agent-compose live provider ok" + out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "--json", "run", "reviewer", "--prompt", "Reply with exactly: "+marker) + var run exampleRunOutput + if err := json.Unmarshal([]byte(out), &run); err != nil { + t.Fatalf("decode live-provider run output %q: %v", out, err) + } + if run.Status != "succeeded" || run.SandboxID == "" || !strings.Contains(run.Output, marker) { + t.Fatalf("live-provider run = %#v", run) + } +} + +func liveProviderE2EEnv(t *testing.T) map[string]string { + t.Helper() + required := []string{"LLM_API_ENDPOINT", "LLM_API_KEY", "LLM_MODEL"} + env := make(map[string]string, len(required)+3) + for _, name := range required { + value := strings.TrimSpace(os.Getenv(name)) + if value == "" { + t.Fatalf("%s is required for the live-provider E2E", name) + } + env[name] = value + } + for _, name := range []string{"LLM_API_PROTOCOL", "LLM_TIMEOUT", "AGENT_TIMEOUT"} { + if value := strings.TrimSpace(os.Getenv(name)); value != "" { + env[name] = value + } + } + return env +} diff --git a/test/e2e/examples_docker_provider_test.go b/test/e2e/examples_docker_provider_test.go new file mode 100644 index 000000000..c505a6db7 --- /dev/null +++ b/test/e2e/examples_docker_provider_test.go @@ -0,0 +1,171 @@ +package e2e + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "connectrpc.com/connect" + networkapi "github.com/docker/docker/api/types/network" + + agentcomposev2 "agent-compose/proto/agentcompose/v2" + "agent-compose/proto/agentcompose/v2/agentcomposev2connect" +) + +func TestE2EExamplesDockerProvider(t *testing.T) { + if strings.TrimSpace(os.Getenv("AGENT_COMPOSE_E2E_EXAMPLES_PROVIDER")) != "1" { + t.Skip("set AGENT_COMPOSE_E2E_EXAMPLES_PROVIDER=1 to run provider-backed examples") + } + image := strings.TrimSpace(os.Getenv(examplesDockerImageEnv)) + if image == "" { + t.Fatalf("%s is required", examplesDockerImageEnv) + } + + ctx, cancel := context.WithTimeout(context.Background(), 8*time.Minute) + defer cancel() + root := e2eRepoRoot(t) + testRoot, err := os.MkdirTemp(root, ".docker-examples-provider-e2e-") + if err != nil { + t.Fatalf("create provider E2E root: %v", err) + } + t.Cleanup(func() { _ = os.RemoveAll(testRoot) }) + dockerClient := newE2EDockerClient(t, ctx, image) + t.Cleanup(func() { removeE2EDockerContainersUnderRoot(t, dockerClient, testRoot) }) + binary := e2eDaemonBinary(t, ctx, root, testRoot) + listen := unusedLoopbackAddress(t) + baseURL := "http://" + listen + provider := newExampleOpenAIProvider(t) + _, port, err := net.SplitHostPort(listen) + if err != nil { + t.Fatalf("split daemon listen address: %v", err) + } + gateway := dockerBridgeGateway(t, ctx, dockerClient) + overrides := providerE2EEnv(provider.URL, "http://"+net.JoinHostPort(gateway, port), port) + daemon := startE2EDaemonWithOverrides(t, binary, root, testRoot, listen, image, overrides) + waitForE2EDaemon(t, ctx, daemon, baseURL) + t.Cleanup(func() { + if t.Failed() { + t.Logf("provider examples daemon log:\n%s", daemon.logs.String()) + } + }) + + examplesRoot := filepath.Join(root, "examples", "agent-compose") + t.Run("manual cron trigger", func(t *testing.T) { + file := filepath.Join(examplesRoot, "docker-scheduler-cron", "agent-compose.yml") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "--json", "scheduler", "trigger", "reviewer", "hourly-review", "--prompt", "Reply with exactly: cron scheduler ok") + if !strings.Contains(out, `"status": "succeeded"`) || !strings.Contains(out, "cron scheduler ok") { + t.Fatalf("cron trigger output = %q", out) + } + }) + + t.Run("automatic timeout trigger", func(t *testing.T) { + file := filepath.Join(examplesRoot, "docker-scheduler-timeout", "agent-compose.yml") + up := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "--json", "up") + defer downExampleProject(t, ctx, binary, root, baseURL, file) + var applied struct { + Project struct { + ID string `json:"id"` + } `json:"project"` + } + if err := json.Unmarshal([]byte(up), &applied); err != nil || applied.Project.ID == "" { + t.Fatalf("decode timeout up output %q: %v", up, err) + } + client := agentcomposev2connect.NewRunServiceClient(newE2EHTTPClient(), baseURL) + deadline := time.Now().Add(3 * time.Minute) + for time.Now().Before(deadline) { + resp, err := client.ListRuns(ctx, connect.NewRequest(&agentcomposev2.ListRunsRequest{ + ProjectId: applied.Project.ID, + AgentName: "reviewer", + Source: agentcomposev2.RunSource_RUN_SOURCE_SCHEDULER, + Limit: 20, + })) + if err == nil { + for _, run := range resp.Msg.GetRuns() { + if run.GetStatus() == agentcomposev2.RunStatus_RUN_STATUS_SUCCEEDED { + return + } + if run.GetStatus() == agentcomposev2.RunStatus_RUN_STATUS_FAILED { + t.Fatalf("timeout run failed: %s", run.GetError()) + } + } + } + time.Sleep(500 * time.Millisecond) + } + t.Fatal("timeout scheduler did not produce a successful run") + }) +} + +func providerE2EEnv(endpoint, runtimeBaseURL, daemonPort string) map[string]string { + return map[string]string{ + "AGENT_COMPOSE_RUNTIME_BASE_URL": runtimeBaseURL, + "AGENT_TIMEOUT": "1m", + "DOCKER_HOST_SANDBOX_ROOT": "", + "HTTP_LISTEN": net.JoinHostPort("0.0.0.0", daemonPort), + "LLM_API_ENDPOINT": endpoint, + "LLM_API_KEY": "example-provider-key", + "LLM_API_PROTOCOL": "chat_completions", + "LLM_MODEL": "gpt-5.4", + "OPENAI_API_KEY": "", + "OPENAI_BASE_URL": "", + } +} + +func dockerBridgeGateway(t *testing.T, ctx context.Context, dockerClient interface { + NetworkInspect(context.Context, string, networkapi.InspectOptions) (networkapi.Inspect, error) +}) string { + t.Helper() + network, err := dockerClient.NetworkInspect(ctx, "bridge", networkapi.InspectOptions{}) + if err != nil { + t.Fatalf("inspect Docker bridge network: %v", err) + } + for _, config := range network.IPAM.Config { + if gateway := strings.TrimSpace(config.Gateway); gateway != "" { + return gateway + } + } + t.Fatal("Docker bridge network has no gateway") + return "" +} + +func newExampleOpenAIProvider(t *testing.T) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { + t.Logf("controlled provider request %s %s", request.Method, request.URL.Path) + body, err := io.ReadAll(request.Body) + if err != nil { + t.Errorf("read provider request: %v", err) + http.Error(w, "read request", http.StatusBadRequest) + return + } + if !strings.HasSuffix(request.URL.Path, "/chat/completions") { + t.Errorf("provider path = %q", request.URL.Path) + http.NotFound(w, request) + return + } + text := "cron scheduler ok" + if strings.Contains(string(body), "timeout scheduler ok") { + text = "timeout scheduler ok" + } + if strings.Contains(string(body), `"stream":true`) { + w.Header().Set("Content-Type", "text/event-stream") + _, _ = fmt.Fprintf(w, "data: {\"id\":\"chatcmpl-example\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-5.4\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":%q},\"finish_reason\":null}]}\n\n", text) + _, _ = fmt.Fprint(w, "data: {\"id\":\"chatcmpl-example\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-5.4\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n") + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = fmt.Fprintf(w, "{\"id\":\"chatcmpl-example\",\"object\":\"chat.completion\",\"model\":\"gpt-5.4\",\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":%q},\"finish_reason\":\"stop\"}]}", text) + })) + t.Cleanup(server.Close) + return server +} diff --git a/test/e2e/examples_docker_test.go b/test/e2e/examples_docker_test.go new file mode 100644 index 000000000..6d1d54819 --- /dev/null +++ b/test/e2e/examples_docker_test.go @@ -0,0 +1,321 @@ +package e2e + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "connectrpc.com/connect" + + agentcomposev2 "agent-compose/proto/agentcompose/v2" + "agent-compose/proto/agentcompose/v2/agentcomposev2connect" +) + +const examplesDockerImageEnv = "AGENT_COMPOSE_E2E_EXAMPLES_IMAGE" + +type exampleRunOutput struct { + ID string `json:"id"` + AgentName string `json:"agent_name"` + Status string `json:"status"` + SandboxID string `json:"sandbox_id"` + Output string `json:"output"` + ResultJSON string `json:"result_json"` +} + +func TestE2EExamplesDocker(t *testing.T) { + image := strings.TrimSpace(os.Getenv(examplesDockerImageEnv)) + if image == "" { + t.Skipf("set %s to the locally available published guest image", examplesDockerImageEnv) + } + const manifestImage = "ghcr.io/chaitin/agent-compose-guest:latest" + if image != manifestImage { + t.Fatalf("%s = %q, examples use %q", examplesDockerImageEnv, image, manifestImage) + } + + ctx, cancel := context.WithTimeout(context.Background(), 12*time.Minute) + defer cancel() + root := e2eRepoRoot(t) + testRoot, err := os.MkdirTemp(root, ".docker-examples-e2e-") + if err != nil { + t.Fatalf("create Docker-visible E2E root: %v", err) + } + t.Cleanup(func() { _ = os.RemoveAll(testRoot) }) + dockerClient := newE2EDockerClient(t, ctx, image) + t.Cleanup(func() { removeE2EDockerContainersUnderRoot(t, dockerClient, testRoot) }) + binary := e2eDaemonBinary(t, ctx, root, testRoot) + listen := unusedLoopbackAddress(t) + baseURL := "http://" + listen + daemon := startE2EDaemonWithOverrides(t, binary, root, testRoot, listen, image, map[string]string{ + "DOCKER_HOST_SANDBOX_ROOT": "", + }) + waitForE2EDaemon(t, ctx, daemon, baseURL) + t.Cleanup(func() { + if t.Failed() { + t.Logf("examples daemon log:\n%s", daemon.logs.String()) + } + }) + + examplesRoot := filepath.Join(root, "examples", "agent-compose") + t.Run("minimal", func(t *testing.T) { + file := filepath.Join(examplesRoot, "docker-minimal", "agent-compose.yml") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + run := runExampleCommand(t, ctx, binary, root, baseURL, file, "reviewer", "printf 'docker minimal ok\\n'", true) + if run.Status != "succeeded" || !strings.Contains(run.Output, "docker minimal ok") || run.SandboxID == "" { + t.Fatalf("minimal run = %#v", run) + } + out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "exec", run.SandboxID, "--", "pwd") + if strings.TrimSpace(out) == "" { + t.Fatal("minimal exec returned no working directory") + } + }) + + t.Run("workspace lifecycle", func(t *testing.T) { + dir := filepath.Join(examplesRoot, "docker-workspace-lifecycle") + file := filepath.Join(dir, "agent-compose.yml") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + run := runExampleCommand(t, ctx, binary, root, baseURL, file, "worker", "test -f README.md && printf 'sandbox-only\\n' > generated.txt", true) + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "stop", run.SandboxID) + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "resume", run.SandboxID) + out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "exec", run.SandboxID, "--", "cat", "generated.txt") + if strings.TrimSpace(out) != "sandbox-only" { + t.Fatalf("resumed workspace output = %q", out) + } + if _, err := os.Stat(filepath.Join(dir, "workspace", "generated.txt")); !os.IsNotExist(err) { + t.Fatalf("sandbox change leaked to source: %v", err) + } + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "stop", run.SandboxID) + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "rm", run.SandboxID) + }) + + t.Run("multi agent", func(t *testing.T) { + file := filepath.Join(examplesRoot, "docker-multi-agent", "agent-compose.yml") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + for _, agent := range []string{"reviewer", "tester"} { + run := runExampleCommand(t, ctx, binary, root, baseURL, file, agent, "test -f project.txt && printf '"+agent+" ok\\n'", false) + if run.AgentName != agent || !strings.Contains(run.Output, agent+" ok") { + t.Fatalf("%s run = %#v", agent, run) + } + } + }) + + t.Run("environment and secrets", func(t *testing.T) { + file := filepath.Join(examplesRoot, "docker-env-secrets", "agent-compose.yml") + config := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "config") + if strings.Contains(config, "safe-example-secret") || !strings.Contains(config, "********") { + t.Fatalf("redacted config = %q", config) + } + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + command := `test "$PROJECT_VALUE" = project-level && test "$AGENT_VALUE" = agent-level && test "$PROJECT_SECRET" = safe-example-secret && test "$AGENT_SECRET" = safe-example-secret && printf 'environment ok\n'` + run := runExampleCommand(t, ctx, binary, root, baseURL, file, "inspector", command, false) + if !strings.Contains(run.Output, "environment ok") { + t.Fatalf("environment run = %#v", run) + } + }) + + t.Run("volume persistence", func(t *testing.T) { + file := filepath.Join(examplesRoot, "docker-volume-persistence", "agent-compose.yml") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + command := `grep -q 'read-only bind fixture' /fixtures/readonly.txt && printf 'persistent\n' > /cache/value` + run := runExampleCommand(t, ctx, binary, root, baseURL, file, "worker", command, true) + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "stop", run.SandboxID) + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "resume", run.SandboxID) + out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "exec", run.SandboxID, "--", "cat", "/cache/value") + if strings.TrimSpace(out) != "persistent" { + t.Fatalf("persistent volume output = %q", out) + } + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "exec", run.SandboxID, "--", "sh", "-c", "if touch /fixtures/unexpected 2>/dev/null; then exit 1; fi") + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "stop", run.SandboxID) + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "rm", run.SandboxID) + }) + + for _, tc := range []struct { + name string + dir string + agent string + trigger string + want string + }{ + {name: "script URL", dir: "docker-scheduler-script-url", agent: "reviewer", trigger: "source-loaded", want: "scheduler script URL ok"}, + {name: "script runtime", dir: "docker-scheduler-script-runtime", agent: "heartbeat", trigger: "follow-up", want: "heartbeat 2"}, + } { + t.Run(tc.name, func(t *testing.T) { + file := filepath.Join(examplesRoot, tc.dir, "agent-compose.yml") + projectID := applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "scheduler", "inspect", tc.agent, tc.trigger) + waitForExampleSchedulerEvent(t, ctx, baseURL, projectID, tc.agent, tc.want) + }) + } + + t.Run("cron control plane", func(t *testing.T) { + file := filepath.Join(examplesRoot, "docker-scheduler-cron", "agent-compose.yml") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "scheduler", "ls", "reviewer") + if !strings.Contains(out, "hourly-review") || !strings.Contains(out, "cron") { + t.Fatalf("cron scheduler list = %q", out) + } + }) + + t.Run("build", func(t *testing.T) { + file, imageRef := copyBuildExampleWithUniqueTag(t, examplesRoot, testRoot) + defer func() { + _ = exampleCLIErr(ctx, binary, root, baseURL, "--file", file, "rmi", imageRef, "--force") + }() + exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "build") + applyExampleProject(t, ctx, binary, root, baseURL, file) + defer downExampleProject(t, ctx, binary, root, baseURL, file) + run := runExampleCommand(t, ctx, binary, root, baseURL, file, "worker", "cat /opt/agent-compose-example.txt", false) + if !strings.Contains(run.Output, "built-by-agent-compose") { + t.Fatalf("build run = %#v", run) + } + }) +} + +func applyExampleProject(t *testing.T, ctx context.Context, binary, root, baseURL, file string) string { + t.Helper() + out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "--json", "up") + var applied struct { + Project struct { + ID string `json:"id"` + } `json:"project"` + } + if err := json.Unmarshal([]byte(out), &applied); err != nil || applied.Project.ID == "" { + t.Fatalf("decode up output %q: %v", out, err) + } + return applied.Project.ID +} + +func downExampleProject(t *testing.T, ctx context.Context, binary, root, baseURL, file string) { + t.Helper() + var ps struct { + Sandboxes []struct { + ID string `json:"sandbox_id"` + } `json:"sandboxes"` + } + if out, err := exampleCLIOutput(ctx, binary, root, baseURL, "--file", file, "--json", "ps", "--all"); err == nil { + if err := json.Unmarshal([]byte(out), &ps); err != nil { + t.Errorf("decode ps output for %s: %v", file, err) + } else { + for _, sandbox := range ps.Sandboxes { + if err := exampleCLIErr(ctx, binary, root, baseURL, "--file", file, "rm", sandbox.ID, "--force"); err != nil { + t.Errorf("remove example sandbox %s: %v", sandbox.ID, err) + } + } + } + } + if err := exampleCLIErr(ctx, binary, root, baseURL, "--file", file, "down"); err != nil { + t.Errorf("down example %s: %v", file, err) + } +} + +func runExampleCommand(t *testing.T, ctx context.Context, binary, root, baseURL, file, agent, command string, keep bool) exampleRunOutput { + t.Helper() + args := []string{"--file", file, "--json", "run", agent, "--command", command} + if keep { + args = append(args, "--keep-running") + } + out := exampleCLI(t, ctx, binary, root, baseURL, args...) + var run exampleRunOutput + if err := json.Unmarshal([]byte(out), &run); err != nil { + t.Fatalf("decode run output %q: %v", out, err) + } + if run.ID == "" || run.Status != "succeeded" { + t.Fatalf("run did not succeed: %#v", run) + } + return run +} + +func exampleCLI(t *testing.T, ctx context.Context, binary, root, baseURL string, args ...string) string { + t.Helper() + cmdArgs := append([]string{"--host", baseURL}, args...) + cmd := exec.CommandContext(ctx, binary, cmdArgs...) + cmd.Dir = root + cmd.Env = os.Environ() + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("agent-compose %s: %v\n%s", strings.Join(cmdArgs, " "), err, out) + } + return string(out) +} + +func exampleCLIErr(ctx context.Context, binary, root, baseURL string, args ...string) error { + _, err := exampleCLIOutput(ctx, binary, root, baseURL, args...) + return err +} + +func exampleCLIOutput(ctx context.Context, binary, root, baseURL string, args ...string) (string, error) { + cmdArgs := append([]string{"--host", baseURL}, args...) + cmd := exec.CommandContext(ctx, binary, cmdArgs...) + cmd.Dir = root + cmd.Env = os.Environ() + out, err := cmd.CombinedOutput() + if err != nil { + return string(out), fmt.Errorf("agent-compose %s: %w: %s", strings.Join(cmdArgs, " "), err, out) + } + return string(out), nil +} + +func waitForExampleSchedulerEvent(t *testing.T, ctx context.Context, baseURL, projectID, agentName, want string) { + t.Helper() + client := agentcomposev2connect.NewProjectServiceClient(newE2EHTTPClient(), baseURL) + deadline := time.Now().Add(2 * time.Minute) + for time.Now().Before(deadline) { + resp, err := client.ListSchedulerEvents(ctx, connect.NewRequest(&agentcomposev2.ListSchedulerEventsRequest{ + Project: &agentcomposev2.ProjectRef{ProjectId: projectID}, + AgentName: agentName, + Limit: 100, + })) + if err == nil { + for _, event := range resp.Msg.GetEvents() { + if event.GetType() == "loader.run.failed" { + t.Fatalf("scheduler run failed: %s", event.GetMessage()) + } + if strings.Contains(event.GetMessage(), want) || strings.Contains(event.GetPayloadJson(), want) { + return + } + } + } + time.Sleep(250 * time.Millisecond) + } + t.Fatalf("scheduler event did not contain %q", want) +} + +func copyBuildExampleWithUniqueTag(t *testing.T, examplesRoot, testRoot string) (string, string) { + t.Helper() + source := filepath.Join(examplesRoot, "docker-build") + destination := filepath.Join(testRoot, "docker-build") + if err := os.MkdirAll(destination, 0o755); err != nil { + t.Fatalf("create build example copy: %v", err) + } + dockerfile, err := os.ReadFile(filepath.Join(source, "Dockerfile")) + if err != nil { + t.Fatalf("read build Dockerfile: %v", err) + } + if err := os.WriteFile(filepath.Join(destination, "Dockerfile"), dockerfile, 0o600); err != nil { + t.Fatalf("write build Dockerfile: %v", err) + } + composeData, err := os.ReadFile(filepath.Join(source, "agent-compose.yml")) + if err != nil { + t.Fatalf("read build compose: %v", err) + } + imageRef := "agent-compose-example-e2e-" + fmt.Sprint(time.Now().UnixNano()) + ":local" + content := strings.ReplaceAll(string(composeData), "agent-compose-example-build:local", imageRef) + content = strings.ReplaceAll(content, "agent-compose-example-build:latest", imageRef) + file := filepath.Join(destination, "agent-compose.yml") + if err := os.WriteFile(file, []byte(content), 0o600); err != nil { + t.Fatalf("write build compose: %v", err) + } + return file, imageRef +} diff --git a/test/e2e/host_daemon_helpers_test.go b/test/e2e/host_daemon_helpers_test.go index 1db8108b2..0111c9567 100644 --- a/test/e2e/host_daemon_helpers_test.go +++ b/test/e2e/host_daemon_helpers_test.go @@ -44,11 +44,15 @@ type e2eDaemonProcess struct { } func startE2EDaemon(t *testing.T, binary, repoRoot, testRoot, listenAddress, image string) *e2eDaemonProcess { + return startE2EDaemonWithOverrides(t, binary, repoRoot, testRoot, listenAddress, image, nil) +} + +func startE2EDaemonWithOverrides(t *testing.T, binary, repoRoot, testRoot, listenAddress, image string, overrides map[string]string) *e2eDaemonProcess { t.Helper() process := &e2eDaemonProcess{done: make(chan error, 1)} process.cmd = exec.Command(binary, "daemon") process.cmd.Dir = repoRoot - process.cmd.Env = overrideE2EEnv(os.Environ(), map[string]string{ + env := map[string]string{ "AGENT_COMPOSE_SOCKET": filepath.Join(testRoot, "agent-compose.sock"), "AUTH_PASSWORD": "", "AUTH_USERNAME": "", @@ -67,7 +71,11 @@ func startE2EDaemon(t *testing.T, binary, repoRoot, testRoot, listenAddress, ima "RUNTIME_DRIVER": "docker", "SANDBOX_ROOT": filepath.Join(testRoot, "sandboxes"), "SANDBOX_START_TIMEOUT": "3m", - }) + } + for name, value := range overrides { + env[name] = value + } + process.cmd.Env = overrideE2EEnv(os.Environ(), env) process.cmd.Stdout = &process.logs process.cmd.Stderr = &process.logs if err := process.cmd.Start(); err != nil { @@ -210,6 +218,38 @@ func removeE2EDockerSandboxFallback(t *testing.T, ctx context.Context, dockerCli } } +func removeE2EDockerContainersUnderRoot(t *testing.T, dockerClient *client.Client, root string) { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + args := filters.NewArgs(filters.Arg("label", "agent-compose.driver=docker")) + containers, err := dockerClient.ContainerList(ctx, containerapi.ListOptions{All: true, Filters: args}) + if err != nil { + t.Errorf("list Docker containers for root cleanup: %v", err) + return + } + root = filepath.Clean(root) + string(filepath.Separator) + for _, item := range containers { + info, err := dockerClient.ContainerInspect(ctx, item.ID) + if err != nil { + t.Errorf("inspect Docker container %s for root cleanup: %v", item.ID, err) + continue + } + owned := false + for _, mount := range info.Mounts { + if strings.HasPrefix(filepath.Clean(mount.Source)+string(filepath.Separator), root) { + owned = true + break + } + } + if owned { + if err := dockerClient.ContainerRemove(ctx, item.ID, containerapi.RemoveOptions{Force: true}); err != nil { + t.Errorf("remove Docker container %s for root cleanup: %v", item.ID, err) + } + } + } +} + func unusedLoopbackAddress(t *testing.T) string { t.Helper() listener, err := net.Listen("tcp", "127.0.0.1:0") From ca7257b42eaaf7b3cba0e5b48035f24055204ee3 Mon Sep 17 00:00:00 2001 From: eetoc Date: Wed, 15 Jul 2026 11:30:11 +0800 Subject: [PATCH 2/5] docs: restore detailed example tutorials --- .../agent-compose/boxlite-minimal/README.md | 17 +++ .../boxlite-minimal/README.zh-CN.md | 16 +++ examples/agent-compose/docker-build/README.md | 18 ++- .../docker-build/README.zh-CN.md | 18 ++- .../docker-env-secrets/README.md | 15 ++ .../docker-env-secrets/README.zh-CN.md | 14 ++ .../agent-compose/docker-minimal/README.md | 125 +++++++++++++++-- .../docker-minimal/README.zh-CN.md | 122 +++++++++++++++-- .../docker-multi-agent/README.md | 19 ++- .../docker-multi-agent/README.zh-CN.md | 18 ++- .../docker-scheduler-cron/README.md | 128 +++++++++++++++++- .../docker-scheduler-cron/README.zh-CN.md | 123 ++++++++++++++++- .../docker-scheduler-script-runtime/README.md | 15 ++ .../README.zh-CN.md | 14 ++ .../docker-scheduler-script-url/README.md | 71 ++++++++-- .../README.zh-CN.md | 63 ++++++++- .../docker-scheduler-script-url/scheduler.js | 4 + .../docker-scheduler-timeout/README.md | 104 ++++++++++++-- .../docker-scheduler-timeout/README.zh-CN.md | 101 ++++++++++++-- .../docker-volume-persistence/README.md | 15 ++ .../docker-volume-persistence/README.zh-CN.md | 13 ++ .../docker-workspace-lifecycle/README.md | 19 +++ .../README.zh-CN.md | 13 ++ .../microsandbox-minimal/README.md | 17 +++ .../microsandbox-minimal/README.zh-CN.md | 16 +++ 25 files changed, 1013 insertions(+), 85 deletions(-) diff --git a/examples/agent-compose/boxlite-minimal/README.md b/examples/agent-compose/boxlite-minimal/README.md index 946fd6f7d..256a9f2bf 100644 --- a/examples/agent-compose/boxlite-minimal/README.md +++ b/examples/agent-compose/boxlite-minimal/README.md @@ -4,6 +4,18 @@ Languages: English | [中文](README.zh-CN.md) This is a configuration template for a BoxLite-backed agent. +## Requirements + +Runtime use requires Linux, KVM access, BoxLite artifacts, and a binary whose +`compiled_drivers` contains `boxlite`. Check the binary before applying: + +```bash +agent-compose --json version +test -r /dev/kvm && test -w /dev/kvm +``` + +## Run the tutorial + ```bash agent-compose config # On a prepared Linux/KVM host with a BoxLite-enabled binary: @@ -13,3 +25,8 @@ agent-compose up The compose file is validated in normal tests. Runtime execution was not verified locally and requires Linux, `/dev/kvm`, BoxLite runtime artifacts, and a binary whose `compiled_drivers` includes `boxlite`. + +`config` is safe on any platform and should normalize `driver.name: boxlite`. +On a prepared host, continue with `run reviewer --command "uname -a"`, inspect +the returned sandbox, and finish with `down`. Runtime success is deliberately +not claimed by this repository-local validation. diff --git a/examples/agent-compose/boxlite-minimal/README.zh-CN.md b/examples/agent-compose/boxlite-minimal/README.zh-CN.md index 99aec88fe..a15d99c58 100644 --- a/examples/agent-compose/boxlite-minimal/README.zh-CN.md +++ b/examples/agent-compose/boxlite-minimal/README.zh-CN.md @@ -4,6 +4,18 @@ 这是一个使用 BoxLite driver 的配置模板。 +## 环境要求 + +实际运行要求 Linux、KVM 权限、BoxLite artifacts,以及 `compiled_drivers` 包含 +`boxlite` 的二进制。应用前检查: + +```bash +agent-compose --json version +test -r /dev/kvm && test -w /dev/kvm +``` + +## 运行教程 + ```bash agent-compose config # 在准备好的 Linux/KVM 主机和包含 BoxLite 的二进制上: @@ -13,3 +25,7 @@ agent-compose up compose 文件会进入常规测试,但本地未验证 runtime 执行。运行需要 Linux、 `/dev/kvm`、BoxLite runtime artifacts,并且二进制的 `compiled_drivers` 包含 `boxlite`。 + +`config` 可在任意平台安全执行,应归一化出 `driver.name: boxlite`。在准备好的 host +上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后执行 +`down`。仓库本地验证不会声称 BoxLite runtime 已成功运行。 diff --git a/examples/agent-compose/docker-build/README.md b/examples/agent-compose/docker-build/README.md index 810ce759e..71d624045 100644 --- a/examples/agent-compose/docker-build/README.md +++ b/examples/agent-compose/docker-build/README.md @@ -4,6 +4,15 @@ Languages: English | [中文](README.zh-CN.md) This example builds a guest-derived image and verifies a build argument marker. +## Prerequisites and configuration + +Docker and the daemon must be running, and Docker must be able to obtain the +published guest base image. `build.context` is this directory, `dockerfile` +selects `Dockerfile`, `args` supplies the marker, and `tags` adds a second local +tag to the primary `image` reference. + +## Run the tutorial + ```bash agent-compose build agent-compose up @@ -14,5 +23,12 @@ agent-compose rmi agent-compose-example-build:local --force ``` The expected marker is `built-by-agent-compose`. The build requires Docker and -the published guest base image locally. The example uses fixed local tags for +access to the published guest base image. The example uses fixed local tags for clarity; automation should copy the example and substitute unique tags. + +## What to verify + +`build` must complete and create both local tags. The worker command reads the +file written during the image build and must print `built-by-agent-compose`. +After `down`, both `rmi` commands remove the tutorial images. Avoid these fixed +tags in concurrent automation; the E2E creates unique tags for that reason. diff --git a/examples/agent-compose/docker-build/README.zh-CN.md b/examples/agent-compose/docker-build/README.zh-CN.md index e46e1d259..a94cf995a 100644 --- a/examples/agent-compose/docker-build/README.zh-CN.md +++ b/examples/agent-compose/docker-build/README.zh-CN.md @@ -4,6 +4,14 @@ 该示例构建一个基于 guest image 的本地镜像,并验证 build argument marker。 +## 前置条件与配置 + +Docker 和 daemon 必须已启动,Docker 还需能获得发布版 guest 基础镜像。 +`build.context` 指向本目录,`dockerfile` 选择 Dockerfile,`args` 提供 marker, +`tags` 为主 `image` 引用增加第二个本地 tag。 + +## 运行教程 + ```bash agent-compose build agent-compose up @@ -13,5 +21,11 @@ agent-compose rmi agent-compose-example-build:latest --force agent-compose rmi agent-compose-example-build:local --force ``` -预期 marker 为 `built-by-agent-compose`。构建需要 Docker 和本地已有的发布版 -guest base image。示例使用固定 tag 以便阅读;自动化测试应复制示例并替换成唯一 tag。 +预期 marker 为 `built-by-agent-compose`。构建需要 Docker 能访问发布版 guest base +image。示例使用固定 tag 以便阅读;自动化测试应复制示例并替换成唯一 tag。 + +## 验证要点 + +`build` 必须完成并创建两个本地 tag。worker 命令读取镜像构建阶段写入的文件,输出 +必须为 `built-by-agent-compose`。`down` 后两个 `rmi` 命令删除教程镜像。并发自动化 +不要复用这些固定 tag;E2E 会生成唯一 tag。 diff --git a/examples/agent-compose/docker-env-secrets/README.md b/examples/agent-compose/docker-env-secrets/README.md index 906698501..2b69f13bb 100644 --- a/examples/agent-compose/docker-env-secrets/README.md +++ b/examples/agent-compose/docker-env-secrets/README.md @@ -5,6 +5,14 @@ Languages: English | [中文](README.zh-CN.md) This example uses an explicit dotenv file, project variables, agent-specific environment, and secret metadata. The committed value is intentionally fake. +## Prerequisites and configuration + +Docker and the daemon must be running. `env_file: example.env` supplies +`EXAMPLE_SECRET`; project `variables` apply to every agent, while `agents.*.env` +is agent-scoped. `secret: true` marks values for redaction in rendered config. + +## Run the tutorial + ```bash agent-compose config agent-compose up @@ -15,3 +23,10 @@ agent-compose down `config` redacts values marked `secret: true`. Project variables are supplied to runs, while agent env is scoped to that agent. Process environment values passed to the CLI take precedence over `example.env`. + +## What to verify + +Before `up`, confirm `agent-compose config` contains `********` and never prints +`safe-example-secret`. The command run must print `environment ok`, proving the +real guest received both scopes. The value is a non-sensitive fixture; do not +commit production secrets or use this pattern as a secret manager. diff --git a/examples/agent-compose/docker-env-secrets/README.zh-CN.md b/examples/agent-compose/docker-env-secrets/README.zh-CN.md index b8977c7d1..4875d59d8 100644 --- a/examples/agent-compose/docker-env-secrets/README.zh-CN.md +++ b/examples/agent-compose/docker-env-secrets/README.zh-CN.md @@ -5,6 +5,14 @@ 该示例使用显式 dotenv 文件、project variables、agent 专属环境变量和 secret 元数据。仓库中提交的值是刻意设置的假值。 +## 前置条件与配置 + +Docker 和 daemon 必须已启动。`env_file: example.env` 提供 `EXAMPLE_SECRET`; +project `variables` 作用于所有 agent,`agents.*.env` 只作用于对应 agent。 +`secret: true` 要求渲染配置时隐藏该值。 + +## 运行教程 + ```bash agent-compose config agent-compose up @@ -14,3 +22,9 @@ agent-compose down `config` 会隐藏标记为 `secret: true` 的值。Project variables 会传给 run,agent env 只属于该 agent。启动 CLI 时的进程环境变量优先于 `example.env`。 + +## 验证要点 + +执行 `up` 前,确认 `agent-compose config` 包含 `********`,且不输出 +`safe-example-secret`。command run 必须输出 `environment ok`,证明真实 guest 收到 +两个作用域的变量。该值只是非敏感 fixture;不要提交生产 secret。 diff --git a/examples/agent-compose/docker-minimal/README.md b/examples/agent-compose/docker-minimal/README.md index 7bd0a9734..a7126ea6a 100644 --- a/examples/agent-compose/docker-minimal/README.md +++ b/examples/agent-compose/docker-minimal/README.md @@ -1,35 +1,132 @@ -# Minimal Docker project +# agent-compose Docker minimal example Languages: English | [中文](README.zh-CN.md) -This is the smallest Docker-backed project: one agent, an explicit guest image, -and no workspace or scheduler. +This example shows the smallest useful `agent-compose.yml` for running an +agent-compose project with the Docker runtime driver. + +It is intentionally minimal: + +- one project +- one agent +- Docker runtime driver +- explicit guest image +- no scheduler +- no model or API key requirement for `config`, `up`, and `ps` ## Prerequisites -- The agent-compose daemon and Docker daemon are running. -- `ghcr.io/chaitin/agent-compose-guest:latest` is available locally. +- Docker daemon is running. +- The `agent-compose` daemon is already running. +- Docker can pull `ghcr.io/chaitin/agent-compose-guest:latest`, or it already exists locally. + +Pull the exact image referenced by this example if needed: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +If you have an installed `agent-compose` binary in `PATH`, use: + +```bash +agent-compose status +``` + +When working from the source tree, you can run the CLI directly: + +```bash +go run ./cmd/agent-compose status +``` + +## Compose file + +This directory contains the minimal Docker-backed project: + +```yaml +name: docker-minimal + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} +``` + +The important part is: + +```yaml +driver: + docker: {} +``` + +If the agent omits `driver`, the compose normalizer defaults to `docker`. +This example sets `docker: {}` explicitly to document the intended runtime. + +## Run the example -## Run +From this directory: ```bash agent-compose config agent-compose up -agent-compose inspect agent reviewer -agent-compose run reviewer --command "printf 'docker minimal ok\\n'" --keep-running agent-compose ps ``` -`ps` lists sandboxes, not project agents. Copy the sandbox ID from `run` or -`ps` when executing another command: +From the repository root without installing the binary: + +```bash +go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config +go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up +go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps +``` + +Expected result: + +- `config` prints a normalized project with `driver.name: docker`. +- `up` creates or updates the project and managed agent definition. +- `ps` shows the `reviewer` agent using Docker and `ghcr.io/chaitin/agent-compose-guest:latest`. + +## Optional run test + +To start a runtime session and keep it alive: + +```bash +agent-compose run reviewer --keep-running --prompt "hello from docker minimal example" +``` + +A real agent run requires a working guest runtime and provider configuration in +the daemon. Long-lived credentials stay in the daemon; the guest Codex CLI uses +the sandbox-scoped LLM facade variables injected by agent-compose. + +If the runtime session is alive, you can run commands in it: ```bash agent-compose exec -- pwd agent-compose exec -- env -agent-compose logs reviewer +``` + +Clean up running project sessions: + +```bash agent-compose down ``` -Expected behavior: `config` reports `driver.name: docker`, `up` applies one -agent, the command prints `docker minimal ok`, and `down` stops its sandbox and -removes the applied project. +Replace `` with the sandbox id returned by `run` or shown by +`agent-compose ps --all`. `down` stops project sandboxes; use +`agent-compose rm ` when you also want to delete one. + +## What to verify + +The real-daemon Docker E2E runs this example. For a manual check, verify: + +- `config` reports `driver.name: docker` and the published guest image. +- `up` reports one applied project and one agent. +- `run reviewer --command "printf 'docker minimal ok\\n'"` succeeds without + provider credentials. +- a prompt run succeeds only when the daemon has a working LLM provider. +- a kept run has a non-empty sandbox id in `ps --all`. +- `exec -- pwd` returns the guest working directory. + +Project, revision, run, and sandbox IDs are generated per environment and are +intentionally not hard-coded. diff --git a/examples/agent-compose/docker-minimal/README.zh-CN.md b/examples/agent-compose/docker-minimal/README.zh-CN.md index 205db2179..9b49fa3e8 100644 --- a/examples/agent-compose/docker-minimal/README.zh-CN.md +++ b/examples/agent-compose/docker-minimal/README.zh-CN.md @@ -1,34 +1,130 @@ -# 最小 Docker project +# agent-compose Docker 最小示例 语言:[English](README.md) | 中文 -这是最小的 Docker project:一个 agent、显式 guest image,不配置 workspace -和 scheduler。 +本示例展示一个使用 Docker runtime driver 的最小可用 +`agent-compose.yml`。 + +它刻意保持最小化: + +- 一个 project +- 一个 agent +- Docker runtime driver +- 显式指定 guest image +- 不启用 scheduler +- `config`、`up` 和 `ps` 不要求配置模型或 API key ## 前置条件 -- agent-compose daemon 和 Docker daemon 正在运行。 -- 本地已有 `ghcr.io/chaitin/agent-compose-guest:latest`。 +- Docker daemon 正在运行。 +- `agent-compose` daemon 已经启动。 +- Docker 能拉取 `ghcr.io/chaitin/agent-compose-guest:latest`,或本地已有该镜像。 + +如果还没有 guest image,可拉取本示例实际引用的镜像: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +如果 `agent-compose` 二进制已经在 `PATH` 中,可以直接检查 daemon: + +```bash +agent-compose status +``` + +如果是在源码仓库中调试,也可以直接运行 CLI: + +```bash +go run ./cmd/agent-compose status +``` + +## Compose 文件 + +本目录包含一个最小 Docker project: + +```yaml +name: docker-minimal + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} +``` + +关键配置是: + +```yaml +driver: + docker: {} +``` + +如果 agent 省略 `driver`,compose normalizer 会默认使用 `docker`。 +本示例显式设置 `docker: {}`,是为了明确说明预期的 runtime。 + +## 运行示例 -## 运行 +在本目录执行: ```bash agent-compose config agent-compose up -agent-compose inspect agent reviewer -agent-compose run reviewer --command "printf 'docker minimal ok\\n'" --keep-running agent-compose ps ``` -`ps` 列出的是 sandbox,不是 project agent。需要继续执行命令时,从 `run` -或 `ps` 复制 sandbox ID: +如果没有安装二进制,也可以在仓库根目录执行: + +```bash +go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config +go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up +go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps +``` + +预期结果: + +- `config` 输出标准化后的 project,并显示 `driver.name: docker`。 +- `up` 创建或更新 project 和 managed agent definition。 +- `ps` 显示 `reviewer` agent 使用 Docker 和 `ghcr.io/chaitin/agent-compose-guest:latest`。 + +## 可选运行测试 + +启动一次 runtime session,并在运行结束后保留 session: + +```bash +agent-compose run reviewer --keep-running --prompt "hello from docker minimal example" +``` + +真正执行 agent 需要 guest runtime 可用,并在 daemon 中配置 provider。长期凭据 +保留在 daemon;guest Codex CLI 使用 agent-compose 注入的 sandbox 范围 LLM facade +变量。 + +如果 runtime session 仍在运行,可以在其中执行命令: ```bash agent-compose exec -- pwd agent-compose exec -- env -agent-compose logs reviewer +``` + +清理正在运行的 project sessions: + +```bash agent-compose down ``` -预期行为:`config` 显示 `driver.name: docker`,`up` 应用一个 agent,command -输出 `docker minimal ok`,`down` 停止 sandbox 并移除已应用的 project。 +将 `` 替换为 `run` 返回或 `agent-compose ps --all` 显示的 sandbox +ID。`down` 会停止项目 sandbox;如果还要删除它,请使用 +`agent-compose rm `。 + +## 验证要点 + +真实 daemon Docker E2E 会运行本示例。手工检查时应确认: + +- `config` 显示 `driver.name: docker` 和发布版 guest 镜像。 +- `up` 显示一个已应用项目和一个 agent。 +- `run reviewer --command "printf 'docker minimal ok\\n'"` 无需 provider 凭证即可成功。 +- prompt run 只有在 daemon 配置了可用 LLM provider 时才会成功。 +- 保留运行后,`ps --all` 显示非空 sandbox ID。 +- `exec -- pwd` 返回 guest 工作目录。 + +project、revision、run 和 sandbox ID 均由环境动态生成,因此本文不写死这些值。 diff --git a/examples/agent-compose/docker-multi-agent/README.md b/examples/agent-compose/docker-multi-agent/README.md index aa37202b8..140e0e9d1 100644 --- a/examples/agent-compose/docker-multi-agent/README.md +++ b/examples/agent-compose/docker-multi-agent/README.md @@ -5,12 +5,20 @@ Languages: English | [中文](README.zh-CN.md) Two agents share one workspace declaration while receiving independent sandbox copies and agent definitions. +## Prerequisites and configuration + +Docker and the daemon must be running. Both agents refer to the same local +workspace source, but each run creates an independent sandbox copy. Their +different `system_prompt` values apply to model prompts, not shell commands. + +## Run the tutorial + ```bash agent-compose up agent-compose inspect agent reviewer agent-compose inspect agent tester -agent-compose run reviewer --command "printf 'reviewer ok\\n'" -agent-compose run tester --command "printf 'tester ok\\n'" +agent-compose run reviewer --command "test -f project.txt && printf 'reviewer ok\\n'" +agent-compose run tester --command "test -f project.txt && printf 'tester ok\\n'" agent-compose logs reviewer agent-compose logs tester agent-compose down @@ -18,3 +26,10 @@ agent-compose down The command path does not invoke the configured provider. The distinct system prompts apply when the agents are run with `--prompt`. + +## What to verify + +`inspect agent` should show two definitions. Both command runs should read +`workspace/project.txt`, return their respective marker, and have different +run/sandbox IDs. Use `--prompt` only when the daemon has a configured provider. +`down` cleans both agents' project sandboxes. diff --git a/examples/agent-compose/docker-multi-agent/README.zh-CN.md b/examples/agent-compose/docker-multi-agent/README.zh-CN.md index 814fdaca1..b07d183b8 100644 --- a/examples/agent-compose/docker-multi-agent/README.zh-CN.md +++ b/examples/agent-compose/docker-multi-agent/README.zh-CN.md @@ -5,12 +5,20 @@ 两个 agent 引用同一个 workspace 声明,但会获得独立的 sandbox workspace 副本 和 agent definition。 +## 前置条件与配置 + +Docker 和 daemon 必须已启动。两个 agent 引用相同本地 workspace 源,但每次 run +创建独立 sandbox 副本。不同的 `system_prompt` 只作用于模型 prompt,不作用于 +shell command。 + +## 运行教程 + ```bash agent-compose up agent-compose inspect agent reviewer agent-compose inspect agent tester -agent-compose run reviewer --command "printf 'reviewer ok\\n'" -agent-compose run tester --command "printf 'tester ok\\n'" +agent-compose run reviewer --command "test -f project.txt && printf 'reviewer ok\\n'" +agent-compose run tester --command "test -f project.txt && printf 'tester ok\\n'" agent-compose logs reviewer agent-compose logs tester agent-compose down @@ -18,3 +26,9 @@ agent-compose down command 路径不会调用 provider;使用 `--prompt` 时才会应用两个 agent 各自的 system prompt。 + +## 验证要点 + +`inspect agent` 应显示两个 definition。两个 command run 都应能读取 workspace +fixture、输出各自 marker,并具有不同 run/sandbox ID。只有 daemon 配置了 provider +后才运行 `--prompt`。`down` 清理两个 agent 的项目 sandbox。 diff --git a/examples/agent-compose/docker-scheduler-cron/README.md b/examples/agent-compose/docker-scheduler-cron/README.md index ee3fbf262..d847f858b 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.md +++ b/examples/agent-compose/docker-scheduler-cron/README.md @@ -1,21 +1,135 @@ -# Declarative cron scheduler +# agent-compose Docker cron scheduler example Languages: English | [中文](README.zh-CN.md) -This project declares an hourly cron trigger. Scheduler state is inspected with -the scheduler commands; `ps` is reserved for sandboxes. +This example shows a Docker-backed agent-compose project with a managed cron +scheduler. + +It verifies the scheduler control-plane flow: + +- parse a cron trigger from `agent-compose.yml` +- apply the project to the daemon +- create a managed project scheduler and loader +- show the scheduler as enabled +- disable the scheduler with `agent-compose down` + +The example does not require a model call for `config`, `up`, `ps`, or `down`. +The scheduled run itself still requires a working guest runtime and provider +authentication. + +## Prerequisites + +- Docker daemon is running. +- The `agent-compose` daemon is already running. +- Docker can pull `ghcr.io/chaitin/agent-compose-guest:latest`, or it already exists locally. + +Pull the exact image referenced by this example if needed: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +## Compose file + +```yaml +name: docker-scheduler-cron + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + scheduler: + enabled: true + triggers: + - name: hourly-review + cron: "0 * * * *" + prompt: "Review the current project state and summarize any important changes." +``` + +The trigger uses standard cron syntax. The expression below runs at the top of +every hour: + +```yaml +cron: "0 * * * *" +``` + +## Run the example + +From this directory: ```bash agent-compose config agent-compose up +agent-compose ps agent-compose scheduler ls reviewer agent-compose scheduler inspect reviewer hourly-review agent-compose inspect project docker-scheduler-cron agent-compose down ``` -The normalized scheduler includes `sandbox_policy: new` and the trigger includes -`kind: cron`. `up`, inspection, and `down` do not need provider credentials. -Waiting for or manually running the trigger does need a working Codex provider. +From the repository root without installing the binary: + +```bash +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler ls reviewer +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler inspect reviewer hourly-review +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down +``` + +Expected result: + +- `config` prints the trigger as `kind: cron`. +- `up` creates `project_scheduler` and `loader` resources. +- `ps` shows the scheduler as `enabled`. +- `scheduler ls` reports `hourly-review` with kind `cron`. +- `scheduler inspect` reports the configured expression and prompt. +- `inspect project` shows `scheduler_count: 1` and `trigger_count: 1`. +- `down` disables the managed scheduler and loader. + +## Making the trigger easier to observe + +For a local demo where you want the scheduler to fire soon, use an interval +trigger instead of cron: + +```yaml +scheduler: + enabled: true + triggers: + - name: every-minute + interval: 1m + prompt: "Say hello from the interval trigger." +``` + +Use cron when you want calendar-based scheduling. Use interval when you want +short local feedback while testing. + +## Run the agent path immediately + +You do not need to change the committed cron expression to test the provider +path. A manual trigger uses the same managed agent prompt flow: + +```bash +agent-compose scheduler trigger reviewer hourly-review \ + --prompt "Reply with exactly: cron scheduler ok" +``` + +This requires a working Codex/LLM provider. A successful result has +`status: succeeded` and output containing `cron scheduler ok`. + +## What to verify + +The real-daemon E2E verifies the control plane, and the provider E2E executes +the trigger through a real Docker guest and Codex CLI. Check that: + +- `config` normalizes the trigger to `kind: cron`. +- `up` creates one enabled scheduler with one trigger. +- `scheduler ls reviewer` contains `hourly-review` and `cron`. +- the manual trigger returns a run id and `status: succeeded` with a provider. +- `down` disables the managed scheduler and loader. -For quicker experiments, replace the cron field with `interval: 1m`. +Generated project, scheduler, loader, and run IDs differ by environment. diff --git a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md index 4d05ac3e9..195882ac2 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md @@ -1,20 +1,131 @@ -# 声明式 cron scheduler +# agent-compose Docker cron scheduler 示例 语言:[English](README.md) | 中文 -该 project 声明一个每小时触发的 cron。scheduler 状态应通过 scheduler 命令 -查看;`ps` 只用于列出 sandbox。 +本示例展示一个使用 Docker runtime 的 agent-compose project,并为它配置 +managed cron scheduler。 + +它验证 scheduler 控制面流程: + +- 从 `agent-compose.yml` 解析 cron trigger +- 将 project 应用到 daemon +- 创建 managed project scheduler 和 loader +- 确认 scheduler 处于 enabled 状态 +- 使用 `agent-compose down` 禁用 scheduler + +本示例的 `config`、`up`、`ps` 和 `down` 不要求真实调用模型。真正的定时 +运行仍然需要 guest runtime 可用,并且 provider 已完成认证。 + +## 前置条件 + +- Docker daemon 正在运行。 +- `agent-compose` daemon 已经启动。 +- Docker 能拉取 `ghcr.io/chaitin/agent-compose-guest:latest`,或本地已有该镜像。 + +如果还没有 guest image,可拉取本示例实际引用的镜像: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +## Compose 文件 + +```yaml +name: docker-scheduler-cron + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + scheduler: + enabled: true + triggers: + - name: hourly-review + cron: "0 * * * *" + prompt: "Review the current project state and summarize any important changes." +``` + +trigger 使用标准 cron 语法。下面的表达式表示每小时整点运行: + +```yaml +cron: "0 * * * *" +``` + +## 运行示例 + +在本目录执行: ```bash agent-compose config agent-compose up +agent-compose ps agent-compose scheduler ls reviewer agent-compose scheduler inspect reviewer hourly-review agent-compose inspect project docker-scheduler-cron agent-compose down ``` -标准化配置包含 `sandbox_policy: new`,trigger 包含 `kind: cron`。`up`、查询和 -`down` 不需要 provider 凭证;等待或手动运行 trigger 需要可用的 Codex provider。 +如果没有安装二进制,也可以在仓库根目录执行: + +```bash +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler ls reviewer +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler inspect reviewer hourly-review +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down +``` + +预期结果: + +- `config` 显示 trigger 为 `kind: cron`。 +- `up` 创建 `project_scheduler` 和 `loader` 资源。 +- `ps` 显示 scheduler 为 `enabled`。 +- `scheduler ls` 显示 `hourly-review` 且类型为 `cron`。 +- `scheduler inspect` 显示配置的表达式和 prompt。 +- `inspect project` 显示 `scheduler_count: 1` 和 `trigger_count: 1`。 +- `down` 禁用 managed scheduler 和 loader。 + +## 更容易观察触发的方法 + +如果本地演示时希望 scheduler 很快触发,可以使用 interval trigger 替代 cron: + +```yaml +scheduler: + enabled: true + triggers: + - name: every-minute + interval: 1m + prompt: "Say hello from the interval trigger." +``` + +需要基于日历时间调度时使用 cron;需要本地快速反馈时使用 interval。 + +## 立即运行 agent 路径 + +测试 provider 路径不需要修改仓库中的 cron 表达式。手动 trigger 会使用相同的 +托管 agent prompt 流程: + +```bash +agent-compose scheduler trigger reviewer hourly-review \ + --prompt "Reply with exactly: cron scheduler ok" +``` + +该命令要求可用的 Codex/LLM provider。成功结果应包含 `status: succeeded`,输出中 +包含 `cron scheduler ok`。 + +## 验证要点 + +真实 daemon E2E 验证控制面,provider E2E 则通过真实 Docker guest 和 Codex CLI +执行 trigger。应确认: + +- `config` 将 trigger 归一化为 `kind: cron`。 +- `up` 创建一个启用的 scheduler 和一个 trigger。 +- `scheduler ls reviewer` 包含 `hourly-review` 和 `cron`。 +- 配置 provider 后,手动 trigger 返回 run ID 和 `status: succeeded`。 +- `down` 禁用托管 scheduler 和 loader。 -本地快速观察时,可以把 cron 字段替换为 `interval: 1m`。 +project、scheduler、loader 和 run ID 均由环境动态生成。 diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.md b/examples/agent-compose/docker-scheduler-script-runtime/README.md index fe754a3af..79bc9317b 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.md @@ -5,6 +5,14 @@ Languages: English | [中文](README.zh-CN.md) This inline QJS script combines a stable interval trigger, persisted state, loader logs, and a Docker-backed shell command. +## Prerequisites and configuration + +Docker and the daemon must be running. The scheduler uses `sandbox_policy: new` +and inline QJS. `scheduler.state` belongs to the loader and persists across +callbacks; `scheduler.shell` runs in a Docker sandbox. No provider call occurs. + +## Run the tutorial + ```bash agent-compose up agent-compose scheduler ls heartbeat @@ -17,3 +25,10 @@ agent-compose down The two automatic timeout runs produce `heartbeat 1` and `heartbeat 2`, demonstrating that loader state persists between runs. The interval remains as the long-running schedule. No model provider is called. + +## What to verify + +`scheduler ls heartbeat` should list `warmup`, `follow-up`, and `heartbeat`. +Inspect the two timeout triggers until their events contain `heartbeat 1` and +`heartbeat 2`. That ordered output proves state persisted between distinct +loader callbacks. `down` disables the interval and cleans project sandboxes. diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md index 979c7e28f..bc1232275 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md @@ -5,6 +5,14 @@ 该 inline QJS 脚本组合了稳定的 interval trigger、持久状态、Loader 日志和 Docker-backed shell command。 +## 前置条件与配置 + +Docker 和 daemon 必须已启动。scheduler 使用 `sandbox_policy: new` 和 inline QJS。 +`scheduler.state` 归 loader 所有并跨 callback 保留;`scheduler.shell` 在 Docker +sandbox 中执行,不调用模型 provider。 + +## 运行教程 + ```bash agent-compose up agent-compose scheduler ls heartbeat @@ -16,3 +24,9 @@ agent-compose down 两个自动 timeout run 分别产生 `heartbeat 1` 和 `heartbeat 2`,证明 Loader state 会在 run 之间持久化;interval 保留为长期调度。该流程不会调用模型 provider。 + +## 验证要点 + +`scheduler ls heartbeat` 应列出 `warmup`、`follow-up` 和 `heartbeat`。检查两个 +timeout trigger,直到 event 分别包含 `heartbeat 1` 和 `heartbeat 2`;有序输出证明 +state 在不同 loader callback 间持久化。`down` 禁用 interval 并清理 sandbox。 diff --git a/examples/agent-compose/docker-scheduler-script-url/README.md b/examples/agent-compose/docker-scheduler-script-url/README.md index 65501b0a5..1a5cce556 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.md @@ -1,23 +1,74 @@ -# Scheduler script URL +# Scheduler script URL example Languages: English | [中文](README.zh-CN.md) -The QJS scheduler is stored in `scheduler.js` and referenced with -`scheduler.script.url`. +This example keeps QJS in `scheduler.js` and references it from +`agent-compose.yml` with `scheduler.script.url`. + +It demonstrates two independent concerns: + +- `daily-review` keeps the tutorial's calendar-based + `scheduler.cron(...) + scheduler.agent(...)` flow. +- `source-loaded` is a two-second `scheduler.timeout(...)` shell callback used + to prove, with a real daemon, that the external script was loaded and run. + +## Prerequisites + +- Docker daemon and the `agent-compose` daemon are running. +- Docker can pull the published guest image, or it already exists locally. +- The cron agent callback requires a configured LLM provider when it fires. + The timeout shell callback does not use a model. + +## Compose and script + +The compose file enables the scheduler and points to a path relative to the +compose directory: + +```yaml +scheduler: + enabled: true + sandbox_policy: new + script: + url: ./scheduler.js +``` + +The script preserves the real cron agent example and adds a fast verification +trigger: + +```js +scheduler.cron("daily-review", "0 9 * * *", function dailyReview() { + return scheduler.agent("Review the current project state."); +}); + +scheduler.timeout("source-loaded", function sourceLoaded() { + return scheduler.shell("printf 'scheduler script URL ok\\n'"); +}, 2000); +``` + +## Run the example ```bash agent-compose config agent-compose up agent-compose scheduler ls reviewer +agent-compose scheduler inspect reviewer daily-review agent-compose scheduler inspect reviewer source-loaded -# Wait until scheduler inspect reports that source-loaded has fired. agent-compose down ``` -`config` and `up` resolve the relative path from the compose directory and send -an inline content snapshot to the daemon. Editing `scheduler.js` takes effect on -the next `up`; this is not a runtime import or background refresh. +`config` and `up` resolve the relative URL, read the script, and send an inline +content snapshot to the daemon. Editing `scheduler.js` takes effect on the next +`up`; it is not a runtime import or background refresh. + +## What to verify + +- `config` includes the resolved script content. +- `scheduler ls reviewer` lists both `daily-review` and `source-loaded`. +- after about two seconds, inspecting `source-loaded` shows that it fired and + its event contains `scheduler script URL ok`. +- `daily-review` remains scheduled for `0 9 * * *` and invokes the agent when + its calendar time arrives. +- `down` disables the scheduler and removes project sandboxes. -The two-second timeout trigger runs a local shell command, so provider -authentication is not required. It is driven by the scheduler runtime rather -than `scheduler trigger`, whose project-run path is intended for agent prompts. +Generated loader IDs and timestamps vary by environment. The real-daemon E2E +asserts the event message instead of hard-coding those values. diff --git a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md index 3980c8330..15ffafff1 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md @@ -1,20 +1,69 @@ -# Scheduler 脚本 URL +# Scheduler 脚本 URL 示例 语言:[English](README.md) | 中文 -QJS scheduler 保存在 `scheduler.js`,通过 `scheduler.script.url` 引用。 +本示例把 QJS 保存在 `scheduler.js`,并在 `agent-compose.yml` 中通过 +`scheduler.script.url` 引用。 + +它同时演示两个相互独立的能力: + +- `daily-review` 保留教程中的日历调度 + `scheduler.cron(...) + scheduler.agent(...)` 流程。 +- `source-loaded` 是两秒后执行的 `scheduler.timeout(...)` shell 回调,用真实 + daemon 证明外部脚本已被加载并运行。 + +## 前置条件 + +- Docker daemon 和 `agent-compose` daemon 已启动。 +- Docker 能拉取发布版 guest 镜像,或本地已有该镜像。 +- cron agent 回调真正触发时需要已配置的 LLM provider;timeout shell 回调不调用模型。 + +## Compose 与脚本 + +compose 文件启用 scheduler,并引用相对于 compose 目录的路径: + +```yaml +scheduler: + enabled: true + sandbox_policy: new + script: + url: ./scheduler.js +``` + +脚本保留真实 cron agent 示例,同时增加快速验证 trigger: + +```js +scheduler.cron("daily-review", "0 9 * * *", function dailyReview() { + return scheduler.agent("Review the current project state."); +}); + +scheduler.timeout("source-loaded", function sourceLoaded() { + return scheduler.shell("printf 'scheduler script URL ok\\n'"); +}, 2000); +``` + +## 运行示例 ```bash agent-compose config agent-compose up agent-compose scheduler ls reviewer +agent-compose scheduler inspect reviewer daily-review agent-compose scheduler inspect reviewer source-loaded -# 等待 scheduler inspect 显示 source-loaded 已触发。 agent-compose down ``` -`config` 和 `up` 以 compose 目录为基准解析相对路径,并把内容快照内联发送给 -daemon。修改 `scheduler.js` 后需要再次 `up`;它不是运行时 import,也不会后台刷新。 +`config` 和 `up` 会解析相对 URL、读取脚本,并把内容快照内联发送给 daemon。 +修改 `scheduler.js` 后需要再次执行 `up`;它不是运行时 import,也不会后台刷新。 + +## 验证要点 + +- `config` 包含解析后的脚本内容。 +- `scheduler ls reviewer` 同时列出 `daily-review` 和 `source-loaded`。 +- 约两秒后,检查 `source-loaded` 可看到它已触发,event 中包含 + `scheduler script URL ok`。 +- `daily-review` 仍按 `0 9 * * *` 调度,并在日历时间到达时调用 agent。 +- `down` 禁用 scheduler 并删除项目 sandbox。 -两秒 timeout trigger 执行本地 shell 命令,因此不需要 provider 认证。它由 -scheduler runtime 驱动;`scheduler trigger` 的 project-run 路径用于 agent prompt。 +loader ID 和时间戳由环境动态生成。真实 daemon E2E 会断言 event 内容,而不是 +写死这些值。 diff --git a/examples/agent-compose/docker-scheduler-script-url/scheduler.js b/examples/agent-compose/docker-scheduler-script-url/scheduler.js index 2b87eaff9..e50fd3aa5 100644 --- a/examples/agent-compose/docker-scheduler-script-url/scheduler.js +++ b/examples/agent-compose/docker-scheduler-script-url/scheduler.js @@ -1,3 +1,7 @@ +scheduler.cron("daily-review", "0 9 * * *", function dailyReview() { + return scheduler.agent("Review the current project state."); +}); + scheduler.timeout("source-loaded", function sourceLoaded() { return scheduler.shell("printf 'scheduler script URL ok\\n'"); }, 2000); diff --git a/examples/agent-compose/docker-scheduler-timeout/README.md b/examples/agent-compose/docker-scheduler-timeout/README.md index df8e59584..f5f7ffc0d 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.md @@ -1,27 +1,109 @@ -# Declarative timeout scheduler +# agent-compose Docker timeout scheduler example Languages: English | [中文](README.zh-CN.md) -This example fires once 15 seconds after the scheduler is applied and runs a -Codex prompt. +This example runs an end-to-end scheduled agent flow with the Docker runtime. + +It verifies that agent-compose can: + +- parse a timeout trigger from `agent-compose.yml` +- apply the project to the daemon +- create a managed scheduler and loader +- let the scheduler fire automatically +- start a Docker-backed agent runtime session +- run the configured agent prompt +- persist the successful project run and logs +- disable the scheduler with `agent-compose down` ## Prerequisites -Docker and the published guest image are required. The daemon must also have a -working Codex/OpenAI provider configuration. +- Docker daemon is running. +- The `agent-compose` daemon is already running. +- Docker can pull `ghcr.io/chaitin/agent-compose-guest:latest`, or it already exists locally. +- The daemon has a working Codex/LLM provider. Long-lived provider credentials + remain in the daemon; the guest calls its sandbox-scoped LLM facade. + +Pull the exact image referenced by this example if needed: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +## Compose file + +```yaml +name: docker-scheduler-timeout + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + scheduler: + enabled: true + sandbox_policy: new + triggers: + - name: run-once-after-15-seconds + timeout: 15s + prompt: "Reply with exactly: timeout scheduler ok" +``` + +The `timeout: 15s` trigger is intentionally short so the full flow can be +tested quickly. + +## Run the example + +From this directory: ```bash agent-compose config agent-compose up -agent-compose scheduler ls reviewer -agent-compose --json logs reviewer +sleep 35 +agent-compose ps agent-compose inspect run agent-compose logs --run agent-compose down ``` -Poll `logs reviewer` until the run appears; do not assume a fixed completion -time. The JSON log response contains the run ID. A successful detail reports -`source: scheduler`, `status: succeeded`, and Docker as the driver. +From the repository root without installing the binary: + +```bash +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up +sleep 35 +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down +``` + +Replace `` with the run id shown in the `ps` output. + +Expected result: + +- `config` prints the trigger as `kind: timeout`. +- `up` creates or updates the managed scheduler and loader. +- After the timeout fires once, `ps` shows a scheduler-created run. +- `inspect run ` shows `source: scheduler`, `status: succeeded`, `driver: docker`, and output from the agent. +- `logs --run ` prints the agent output. +- `down` disables the managed scheduler and loader. + +`sandbox_policy: new` creates a fresh sandbox for this scheduled run. The guest +receives a facade token and guest-reachable daemon URL, not the provider key. + +## What to verify + +The provider E2E runs this flow through a real daemon, Docker guest, and Codex +CLI. In a manual run, verify: + +- `config` reports `kind: timeout`, `timeout: 15s`, and `sandbox_policy: new`. +- `scheduler inspect reviewer run-once-after-15-seconds` eventually reports the + trigger has fired. +- `ps` shows a scheduler-sourced run with `status: succeeded`. +- `inspect run ` reports `source: scheduler`, `driver: docker`, a + non-empty sandbox id, and output containing `timeout scheduler ok`. +- `logs --run ` prints the same model output. +- `down` disables the scheduler and cleans up project sandboxes. -The normalized scheduler includes `sandbox_policy: new` and `kind: timeout`. +Generated IDs and exact durations vary and are intentionally not hard-coded. diff --git a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md index 5642f4818..3ccd1e3a6 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md @@ -1,26 +1,107 @@ -# 声明式 timeout scheduler +# agent-compose Docker timeout scheduler 示例 语言:[English](README.md) | 中文 -该示例在 scheduler 应用 15 秒后触发一次 Codex prompt。 +本示例使用 Docker runtime 跑通一个端到端的 scheduled agent 流程。 + +它验证 agent-compose 可以完成: + +- 从 `agent-compose.yml` 解析 timeout trigger +- 将 project 应用到 daemon +- 创建 managed scheduler 和 loader +- 由 scheduler 自动触发运行 +- 启动 Docker-backed agent runtime session +- 执行配置的 agent prompt +- 持久化成功的 project run 和日志 +- 使用 `agent-compose down` 禁用 scheduler ## 前置条件 -需要 Docker、发布的 guest image,以及 daemon 侧可用的 Codex/OpenAI provider -配置。 +- Docker daemon 正在运行。 +- `agent-compose` daemon 已经启动。 +- Docker 能拉取 `ghcr.io/chaitin/agent-compose-guest:latest`,或本地已有该镜像。 +- daemon 配置了可用的 Codex/LLM provider。长期 provider 凭证保留在 daemon 中; + guest 通过 sandbox 范围的 LLM facade 调用模型。 + +如果还没有 guest image,可拉取本示例实际引用的镜像: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +## Compose 文件 + +```yaml +name: docker-scheduler-timeout + +agents: + reviewer: + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + docker: {} + scheduler: + enabled: true + sandbox_policy: new + triggers: + - name: run-once-after-15-seconds + timeout: 15s + prompt: "Reply with exactly: timeout scheduler ok" +``` + +`timeout: 15s` 刻意设置得较短,方便快速验证完整流程。 + +## 运行示例 + +在本目录执行: ```bash agent-compose config agent-compose up -agent-compose scheduler ls reviewer -agent-compose --json logs reviewer +sleep 35 +agent-compose ps agent-compose inspect run agent-compose logs --run agent-compose down ``` -轮询 `logs reviewer` 直到 run 出现,不要依赖固定完成时间。JSON 日志响应中包含 -run ID。成功的 detail 应显示 `source: scheduler`、`status: succeeded` 和 Docker -driver。 +如果没有安装二进制,也可以在仓库根目录执行: + +```bash +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up +sleep 35 +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run +go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down +``` + +将 `` 替换为上一步 `ps` 输出中显示的 run id。 + +预期结果: + +- `config` 显示 trigger 为 `kind: timeout`。 +- `up` 创建或更新 managed scheduler 和 loader。 +- 等待 timeout 触发一次后,`ps` 显示 scheduler 创建的 run。 +- `inspect run ` 显示 `source: scheduler`、`status: succeeded`、`driver: docker`,并包含 agent 输出。 +- `logs --run ` 输出 agent 日志。 +- `down` 禁用 managed scheduler 和 loader。 + +`sandbox_policy: new` 会为 scheduled run 创建全新 sandbox。guest 获得 facade token +和可访问的 daemon URL,而不是长期 provider key。 + +## 验证要点 + +provider E2E 会通过真实 daemon、Docker guest 和 Codex CLI 运行该流程。手工运行时 +应确认: + +- `config` 显示 `kind: timeout`、`timeout: 15s` 和 `sandbox_policy: new`。 +- `scheduler inspect reviewer run-once-after-15-seconds` 最终显示 trigger 已触发。 +- `ps` 显示一个来自 scheduler 且 `status: succeeded` 的 run。 +- `inspect run ` 显示 `source: scheduler`、`driver: docker`、非空 sandbox + ID,且输出包含 `timeout scheduler ok`。 +- `logs --run ` 输出相同的模型结果。 +- `down` 禁用 scheduler 并清理项目 sandbox。 -标准化 scheduler 包含 `sandbox_policy: new` 和 `kind: timeout`。 +动态 ID 和实际耗时因环境而异,因此本文不写死这些值。 diff --git a/examples/agent-compose/docker-volume-persistence/README.md b/examples/agent-compose/docker-volume-persistence/README.md index 4c4b5162c..55e9419db 100644 --- a/examples/agent-compose/docker-volume-persistence/README.md +++ b/examples/agent-compose/docker-volume-persistence/README.md @@ -5,6 +5,14 @@ Languages: English | [中文](README.zh-CN.md) This project mounts a managed named volume at `/cache` and a read-only bind fixture at `/fixtures`. +## Prerequisites and configuration + +Docker and the daemon must be running. The top-level `cache` volume is managed +by the project and mounted read-write. `./fixtures:/fixtures:ro` resolves from +the compose directory and is mounted read-only. + +## Run the tutorial + ```bash agent-compose up agent-compose run worker --command "cat /fixtures/readonly.txt && printf 'persistent\\n' > /cache/value" --keep-running @@ -19,3 +27,10 @@ agent-compose down The cache value survives sandbox stop/resume. `down` removes project-managed volume ownership, so do not use this example as a backup mechanism. + +## What to verify + +Use the sandbox id returned by the kept run. The first command must read the +fixture and write `/cache/value`; after stop/resume, `cat` must return +`persistent`. The `touch` check must fail inside the read-only mount. Stop and +remove the sandbox before `down` for an explicit lifecycle cleanup. diff --git a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md index 3669e85d9..7e6911391 100644 --- a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md +++ b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md @@ -5,6 +5,13 @@ 该 project 把 managed named volume 挂载到 `/cache`,并把只读 bind fixture 挂载到 `/fixtures`。 +## 前置条件与配置 + +Docker 和 daemon 必须已启动。顶层 `cache` volume 由项目管理并以读写方式挂载; +`./fixtures:/fixtures:ro` 相对 compose 目录解析并只读挂载。 + +## 运行教程 + ```bash agent-compose up agent-compose run worker --command "cat /fixtures/readonly.txt && printf 'persistent\\n' > /cache/value" --keep-running @@ -19,3 +26,9 @@ agent-compose down cache 值在 sandbox stop/resume 后仍存在。`down` 会移除 project-managed volume 的归属,不应把该示例当作备份机制。 + +## 验证要点 + +使用保留 run 返回的 sandbox ID。第一次命令必须读取 fixture 并写入 +`/cache/value`;stop/resume 后 `cat` 必须返回 `persistent`。`touch` 检查必须因只读 +挂载而失败。最后显式 stop、rm,再执行 `down`。 diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.md b/examples/agent-compose/docker-workspace-lifecycle/README.md index 98b14df80..afcdccf4a 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.md @@ -5,6 +5,19 @@ Languages: English | [中文](README.zh-CN.md) This example copies `workspace/` into each new sandbox and demonstrates the current sandbox lifecycle without model credentials. +## Prerequisites + +- Docker and the `agent-compose` daemon are running. +- The published guest image is available to Docker. + +## Configuration + +The `source` workspace uses the local provider and `path: ./workspace`. The +`worker` agent refers to it by name. A new sandbox receives a copy; the host +source directory is not mounted as a writable working tree. + +## Run the tutorial + ```bash agent-compose up agent-compose run worker --command "printf 'sandbox-only\\n' > generated.txt" --keep-running @@ -20,3 +33,9 @@ agent-compose down `generated.txt` survives stop/resume but is not written into the committed `workspace/` source. A second new sandbox receives a fresh source copy. + +## Verification and cleanup + +Use the sandbox id returned by `run`. Confirm the file before and after +stop/resume, then check that `workspace/generated.txt` does not exist on the +host. `rm` deletes the stopped sandbox; `down` cleans remaining project state. diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md index 62703d580..a88f54f25 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md @@ -5,6 +5,14 @@ 该示例把 `workspace/` 复制到每个新 sandbox,并在不需要模型凭证的情况下演示 当前 sandbox 生命周期。 +## 前置条件与配置 + +Docker 和 `agent-compose` daemon 必须已启动,并且 Docker 可获得发布版 guest +镜像。`source` 使用本地 workspace provider,`worker` 按名称引用它。新 sandbox +得到源目录副本,不会把 host 源目录作为可写工作区挂载。 + +## 运行教程 + ```bash agent-compose up agent-compose run worker --command "printf 'sandbox-only\\n' > generated.txt" --keep-running @@ -20,3 +28,8 @@ agent-compose down `generated.txt` 在 stop/resume 后仍存在,但不会写回仓库中的 `workspace/` source。 新建第二个 sandbox 时会得到一份全新的 source 副本。 + +## 验证与清理 + +使用 `run` 返回的 sandbox ID,在 stop/resume 前后读取文件,并确认 host 上不存在 +`workspace/generated.txt`。`rm` 删除已停止 sandbox,`down` 清理剩余项目状态。 diff --git a/examples/agent-compose/microsandbox-minimal/README.md b/examples/agent-compose/microsandbox-minimal/README.md index 95751d909..2ada8bcc6 100644 --- a/examples/agent-compose/microsandbox-minimal/README.md +++ b/examples/agent-compose/microsandbox-minimal/README.md @@ -4,6 +4,18 @@ Languages: English | [中文](README.zh-CN.md) This is a configuration template for a Microsandbox-backed agent. +## Requirements + +Runtime use requires Linux, KVM access, Microsandbox artifacts, and a binary +whose `compiled_drivers` contains `microsandbox`. Check the host first: + +```bash +agent-compose --json version +test -r /dev/kvm && test -w /dev/kvm +``` + +## Run the tutorial + ```bash agent-compose config # On a prepared Linux/KVM host with a Microsandbox-enabled binary: @@ -13,3 +25,8 @@ agent-compose up The compose file is validated in normal tests. Runtime execution was not verified locally and requires Linux, `/dev/kvm`, Microsandbox runtime artifacts, and a binary whose `compiled_drivers` includes `microsandbox`. + +`config` is safe on any platform and should normalize +`driver.name: microsandbox`. On a prepared host, continue with +`run reviewer --command "uname -a"`, inspect the returned sandbox, and finish +with `down`. Runtime success is deliberately not claimed locally. diff --git a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md index 2a50955b8..b4553c0e9 100644 --- a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md +++ b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md @@ -4,6 +4,18 @@ 这是一个使用 Microsandbox driver 的配置模板。 +## 环境要求 + +实际运行要求 Linux、KVM 权限、Microsandbox artifacts,以及 `compiled_drivers` +包含 `microsandbox` 的二进制。先检查 host: + +```bash +agent-compose --json version +test -r /dev/kvm && test -w /dev/kvm +``` + +## 运行教程 + ```bash agent-compose config # 在准备好的 Linux/KVM 主机和包含 Microsandbox 的二进制上: @@ -13,3 +25,7 @@ agent-compose up compose 文件会进入常规测试,但本地未验证 runtime 执行。运行需要 Linux、 `/dev/kvm`、Microsandbox runtime artifacts,并且二进制的 `compiled_drivers` 包含 `microsandbox`。 + +`config` 可在任意平台安全执行,应归一化出 `driver.name: microsandbox`。在准备好的 +host 上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后 +执行 `down`。本地不会声称 Microsandbox runtime 已成功运行。 From 15db02ba81417204bba8dacdf0c187a3da3818f3 Mon Sep 17 00:00:00 2001 From: eetoc Date: Wed, 15 Jul 2026 11:48:55 +0800 Subject: [PATCH 3/5] docs: scope examples refresh to tutorials and e2e --- TESTING.md | 45 ----- Taskfile.yml | 57 ------ cmd/agent-compose/examples_contract_test.go | 164 ------------------ examples/agent-compose/README.md | 58 ++++--- examples/agent-compose/README.zh-CN.md | 54 +++--- .../agent-compose/boxlite-minimal/README.md | 19 ++ .../boxlite-minimal/README.zh-CN.md | 19 ++ examples/agent-compose/docker-build/README.md | 13 ++ .../docker-build/README.zh-CN.md | 13 ++ .../docker-env-secrets/README.md | 14 ++ .../docker-env-secrets/README.zh-CN.md | 13 ++ .../agent-compose/docker-minimal/README.md | 98 +++++++++-- .../docker-minimal/README.zh-CN.md | 95 ++++++++-- .../docker-multi-agent/README.md | 13 ++ .../docker-multi-agent/README.zh-CN.md | 13 ++ .../docker-scheduler-cron/README.md | 139 ++++++++++++--- .../docker-scheduler-cron/README.zh-CN.md | 137 ++++++++++++--- .../docker-scheduler-script-runtime/README.md | 13 ++ .../README.zh-CN.md | 13 ++ .../docker-scheduler-script-url/README.md | 87 ++++------ .../README.zh-CN.md | 77 ++++---- .../docker-scheduler-timeout/README.md | 133 ++++++++++++-- .../docker-scheduler-timeout/README.zh-CN.md | 130 ++++++++++++-- .../docker-volume-persistence/README.md | 11 ++ .../docker-volume-persistence/README.zh-CN.md | 11 ++ .../docker-workspace-lifecycle/README.md | 14 ++ .../README.zh-CN.md | 14 ++ .../microsandbox-minimal/README.md | 19 ++ .../microsandbox-minimal/README.zh-CN.md | 19 ++ examples/scheduler-script/README.md | 3 +- .../e2e/examples_docker_live_provider_test.go | 1 + test/e2e/examples_docker_provider_test.go | 2 + test/e2e/examples_docker_test.go | 9 + 33 files changed, 982 insertions(+), 538 deletions(-) delete mode 100644 cmd/agent-compose/examples_contract_test.go diff --git a/TESTING.md b/TESTING.md index 5cf6910fd..bf937837f 100644 --- a/TESTING.md +++ b/TESTING.md @@ -100,51 +100,6 @@ compiled only with the `docker_e2e` build tag, so the ordinary `task test` coverage gate does not include this scheduler Docker E2E or create its runtime containers. -Committed examples have two verification layers. The deterministic contract -test parses every compose file, resolves scheduler script URLs, validates all -QJS scripts, checks secret redaction, and is included in `task test`. Run it -directly with: - -```bash -task test:examples -``` - -The opt-in Docker examples lifecycle starts an isolated host daemon and runs -the committed Docker examples through the real CLI and guest image: - -```bash -task test:e2e:examples-docker -``` - -It covers command runs, workspace isolation, stop/resume, multiple agents, -environment and secret propagation, managed volumes, read-only bind mounts, -image builds, and scheduler shell scripts. A second task drives the declarative -cron and timeout examples through the guest Codex CLI and daemon LLM facade, -using a controlled local Chat Completions fixture instead of a public model: - -```bash -task test:e2e:examples-docker-provider -``` - -To verify the actual provider configured in the repository-local `.env`, run -the separate live-provider task. It requires non-empty `LLM_API_ENDPOINT`, -`LLM_API_KEY`, and `LLM_MODEL`, then sends a real prompt through the daemon, -Docker guest, runtime facade, and Codex CLI: - -```bash -task test:e2e:examples-docker-live-provider -``` - -This task is intentionally not part of deterministic test gates because it -uses external credentials, network access, and model quota. - -All three Docker example E2E tasks require the Docker daemon and -`ghcr.io/chaitin/agent-compose-guest:latest` locally. They use temporary daemon -state and unique build tags and remove owned containers, volumes, and images on -exit. BoxLite and Microsandbox example manifests receive deterministic config -validation only; runtime validation remains `task test:runtime-smoke` on a -prepared Linux/KVM host. - The full daemon image Docker lifecycle E2E is opt-in because it starts the daemon image and Docker sandbox containers through a local Docker socket. Run it after building both local images: diff --git a/Taskfile.yml b/Taskfile.yml index c7cfa7de9..1f06b4d2c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -128,12 +128,6 @@ tasks: - cd runtime/agent-compose-runtime-sdk && npm test - cd runtime/javascript && TEST_SHAPE=unit npm run test:unit - test:examples: - desc: Validate all committed compose and scheduler script examples - dir: '{{.TASKFILE_DIR}}' - cmds: - - GOCACHE={{.GO_BUILD_CACHE_DIR}} {{.GO_TOOLCHAIN_ENV}} go test ./cmd/agent-compose -run '^TestExampleFilesContract$' -count=1 - test:integration: desc: Run integration tests cmds: @@ -192,57 +186,6 @@ tasks: GOCACHE={{.GO_BUILD_CACHE_DIR}} \ {{.GO_TOOLCHAIN_ENV}} go test -tags docker_e2e ./cmd/agent-compose -run '^TestE2EDockerSchedulerScriptHelloWorldFlow$' -count=1 -v - test:e2e:examples-docker: - desc: Run committed Docker examples through an isolated local daemon - dir: '{{.TASKFILE_DIR}}' - cmds: - - | - image="${AGENT_COMPOSE_E2E_EXAMPLES_IMAGE:-ghcr.io/chaitin/agent-compose-guest:latest}" - if ! docker image inspect "$image" >/dev/null 2>&1; then - echo "Docker examples image $image is unavailable locally; pull it before running this task" >&2 - exit 1 - fi - AGENT_COMPOSE_E2E_EXAMPLES_IMAGE="$image" \ - GOCACHE={{.GO_BUILD_CACHE_DIR}} \ - {{.GO_TOOLCHAIN_ENV}} go test ./test/e2e -run '^TestE2EExamplesDocker$' -count=1 -v - - test:e2e:examples-docker-provider: - desc: Run cron and timeout through Codex with a controlled local provider - dir: '{{.TASKFILE_DIR}}' - cmds: - - | - image="${AGENT_COMPOSE_E2E_EXAMPLES_IMAGE:-ghcr.io/chaitin/agent-compose-guest:latest}" - if ! docker image inspect "$image" >/dev/null 2>&1; then - echo "Docker examples image $image is unavailable locally" >&2 - exit 1 - fi - AGENT_COMPOSE_E2E_EXAMPLES_IMAGE="$image" \ - AGENT_COMPOSE_E2E_EXAMPLES_PROVIDER=1 \ - GOCACHE={{.GO_BUILD_CACHE_DIR}} \ - {{.GO_TOOLCHAIN_ENV}} go test ./test/e2e -run '^TestE2EExamplesDockerProvider$' -count=1 -v - - test:e2e:examples-docker-live-provider: - desc: Run docker-minimal through Codex with the live provider configured in .env - dir: '{{.TASKFILE_DIR}}' - cmds: - - | - if [ ! -f .env ]; then - echo "A configured .env is required for the live-provider examples E2E" >&2 - exit 1 - fi - set -a - . ./.env - set +a - image="${AGENT_COMPOSE_E2E_EXAMPLES_IMAGE:-ghcr.io/chaitin/agent-compose-guest:latest}" - if ! docker image inspect "$image" >/dev/null 2>&1; then - echo "Docker examples image $image is unavailable locally" >&2 - exit 1 - fi - AGENT_COMPOSE_E2E_EXAMPLES_IMAGE="$image" \ - AGENT_COMPOSE_E2E_EXAMPLES_LIVE_PROVIDER=1 \ - GOCACHE={{.GO_BUILD_CACHE_DIR}} \ - {{.GO_TOOLCHAIN_ENV}} go test ./test/e2e -run '^TestE2EExamplesDockerLiveProvider$' -count=1 -v - test:e2e:image-docker: desc: Run the full daemon image without KVM and exercise a Docker sandbox lifecycle dir: '{{.TASKFILE_DIR}}' diff --git a/cmd/agent-compose/examples_contract_test.go b/cmd/agent-compose/examples_contract_test.go deleted file mode 100644 index dadc316f2..000000000 --- a/cmd/agent-compose/examples_contract_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "path/filepath" - "sort" - "strings" - "testing" - - "agent-compose/pkg/loaders" -) - -type exampleContract struct { - driver string - agentCount int - schedulers int -} - -func TestExampleFilesContract(t *testing.T) { - root := repoRootForComposeEnvTest(t) - examplesRoot := filepath.Join(root, "examples", "agent-compose") - want := map[string]exampleContract{ - "boxlite-minimal": {driver: "boxlite", agentCount: 1}, - "docker-build": {driver: "docker", agentCount: 1}, - "docker-env-secrets": {driver: "docker", agentCount: 1}, - "docker-minimal": {driver: "docker", agentCount: 1}, - "docker-multi-agent": {driver: "docker", agentCount: 2}, - "docker-scheduler-cron": {driver: "docker", agentCount: 1, schedulers: 1}, - "docker-scheduler-script-runtime": {driver: "docker", agentCount: 1, schedulers: 1}, - "docker-scheduler-script-url": {driver: "docker", agentCount: 1, schedulers: 1}, - "docker-scheduler-timeout": {driver: "docker", agentCount: 1, schedulers: 1}, - "docker-volume-persistence": {driver: "docker", agentCount: 1}, - "docker-workspace-lifecycle": {driver: "docker", agentCount: 1}, - "microsandbox-minimal": {driver: "microsandbox", agentCount: 1}, - } - - entries, err := os.ReadDir(examplesRoot) - if err != nil { - t.Fatalf("read examples directory: %v", err) - } - var gotNames []string - for _, entry := range entries { - if !entry.IsDir() { - continue - } - gotNames = append(gotNames, entry.Name()) - } - sort.Strings(gotNames) - wantNames := make([]string, 0, len(want)) - for name := range want { - wantNames = append(wantNames, name) - } - sort.Strings(wantNames) - if strings.Join(gotNames, "\n") != strings.Join(wantNames, "\n") { - t.Fatalf("example directories = %v, want %v", gotNames, wantNames) - } - - engine := &loaders.QJSLoaderEngine{} - for _, name := range wantNames { - t.Run(name, func(t *testing.T) { - dir := filepath.Join(examplesRoot, name) - for _, readme := range []string{"README.md", "README.zh-CN.md"} { - assertExampleFileExists(t, filepath.Join(dir, readme)) - } - _, normalized, err := loadResolvedNormalizedCompose(context.Background(), cliOptions{ - ComposeFile: filepath.Join(dir, "agent-compose.yml"), - }) - if err != nil { - t.Fatalf("normalize example: %v", err) - } - contract := want[name] - if normalized.Name != name || len(normalized.Agents) != contract.agentCount { - t.Fatalf("normalized project name/agents = %q/%d, want %q/%d", normalized.Name, len(normalized.Agents), name, contract.agentCount) - } - schedulers := 0 - for _, agent := range normalized.Agents { - if agent.Driver == nil || agent.Driver.Name != contract.driver { - t.Fatalf("agent %s driver = %#v, want %s", agent.Name, agent.Driver, contract.driver) - } - if agent.Scheduler == nil { - continue - } - schedulers++ - if agent.Scheduler.SandboxPolicy != "new" { - t.Fatalf("agent %s scheduler sandbox policy = %q, want new", agent.Name, agent.Scheduler.SandboxPolicy) - } - if strings.TrimSpace(agent.Scheduler.Script) != "" { - if _, err := engine.Validate(context.Background(), "scheduler", agent.Scheduler.Script); err != nil { - t.Fatalf("validate scheduler script: %v", err) - } - } - } - if schedulers != contract.schedulers { - t.Fatalf("scheduler count = %d, want %d", schedulers, contract.schedulers) - } - if name == "docker-env-secrets" { - redacted, err := normalized.MarshalCanonicalYAML(true) - if err != nil { - t.Fatalf("marshal redacted config: %v", err) - } - if strings.Contains(string(redacted), "safe-example-secret") { - t.Fatalf("redacted config leaked example secret: %s", redacted) - } - } - }) - } - - validateStandaloneSchedulerExamples(t, root, engine) - assertExampleDocsDoNotUseStaleCLI(t, root) -} - -func validateStandaloneSchedulerExamples(t *testing.T, root string, engine *loaders.QJSLoaderEngine) { - t.Helper() - files, err := filepath.Glob(filepath.Join(root, "examples", "scheduler-script", "*.js")) - if err != nil { - t.Fatalf("glob scheduler examples: %v", err) - } - if len(files) != 6 { - t.Fatalf("standalone scheduler example count = %d, want 6", len(files)) - } - for _, file := range files { - script, err := os.ReadFile(file) - if err != nil { - t.Fatalf("read %s: %v", file, err) - } - if _, err := engine.Validate(context.Background(), "scheduler", string(script)); err != nil { - t.Fatalf("validate %s: %v", file, err) - } - } -} - -func assertExampleDocsDoNotUseStaleCLI(t *testing.T, root string) { - t.Helper() - err := filepath.WalkDir(filepath.Join(root, "examples"), func(file string, entry os.DirEntry, walkErr error) error { - if walkErr != nil { - return walkErr - } - if entry.IsDir() || !strings.HasPrefix(entry.Name(), "README") || filepath.Ext(entry.Name()) != ".md" { - return nil - } - data, err := os.ReadFile(file) - if err != nil { - return err - } - for _, stale := range []string{"exec --agent", "SCHEDULER LATEST RUN", "RunLoaderNow"} { - if strings.Contains(string(data), stale) { - return fmt.Errorf("%s contains stale CLI/API text %q", file, stale) - } - } - return nil - }) - if err != nil { - t.Fatal(err) - } -} - -func assertExampleFileExists(t *testing.T, path string) { - t.Helper() - if info, err := os.Stat(path); err != nil || info.IsDir() { - t.Fatalf("required example file %s is unavailable: %v", path, err) - } -} diff --git a/examples/agent-compose/README.md b/examples/agent-compose/README.md index 17bfdbd0a..83bbfb2e2 100644 --- a/examples/agent-compose/README.md +++ b/examples/agent-compose/README.md @@ -2,29 +2,39 @@ Languages: English | [中文](README.zh-CN.md) -## Locally verified Docker examples +Runnable examples for the `agent-compose` Docker runtime driver, ordered from +simplest to most complete. -| Example | Capability | Provider auth | +| Example | What it shows | Needs provider auth | | --- | --- | --- | -| [docker-minimal](docker-minimal/) | Minimal project and current sandbox-oriented CLI | No | -| [docker-workspace-lifecycle](docker-workspace-lifecycle/) | Local workspace, exec, stop, resume, and isolation | No | -| [docker-multi-agent](docker-multi-agent/) | Multiple agents sharing a workspace declaration | No for command runs | -| [docker-env-secrets](docker-env-secrets/) | Dotenv, project variables, agent env, and redaction | No | -| [docker-volume-persistence](docker-volume-persistence/) | Managed volume and read-only bind mount | No | -| [docker-build](docker-build/) | Compose-driven guest image build | No | -| [docker-scheduler-cron](docker-scheduler-cron/) | Declarative cron control plane | Only to run the trigger | -| [docker-scheduler-timeout](docker-scheduler-timeout/) | Automatic scheduled provider run | Yes | -| [docker-scheduler-script-url](docker-scheduler-script-url/) | Relative scheduler script URL snapshot | No | -| [docker-scheduler-script-runtime](docker-scheduler-script-runtime/) | State, logs, interval, and scheduler shell | No | - -Docker examples require a running agent-compose daemon, Docker daemon, and the -published guest image locally. Their READMEs use stable behavior rather than -dynamic IDs or complete output snapshots. - -## Configuration-only KVM templates - -- [boxlite-minimal](boxlite-minimal/) -- [microsandbox-minimal](microsandbox-minimal/) - -These manifests are parsed by automated tests, but runtime execution requires a -prepared Linux/KVM host and was not verified locally. +| [docker-minimal](docker-minimal/) | Smallest Docker-backed project: one agent, no scheduler. | No, for `config`/`up`/`ps` | +| [docker-scheduler-cron](docker-scheduler-cron/) | Managed cron scheduler control plane. | No, for `config`/`up`/`ps`/`down` | +| [docker-scheduler-script-url](docker-scheduler-script-url/) | A scheduler script loaded from a relative file URL source. | No, for `config`/`up`/`ps`/`down` | +| [docker-scheduler-timeout](docker-scheduler-timeout/) | End-to-end scheduled run that fires, executes the agent, and persists logs. | Yes, for the scheduled run | +| [docker-workspace-lifecycle](docker-workspace-lifecycle/) | Local workspace copy plus sandbox stop, resume, exec, and removal. | No | +| [docker-multi-agent](docker-multi-agent/) | Two independent agents using the same workspace source. | No for command runs; yes for prompts | +| [docker-env-secrets](docker-env-secrets/) | Dotenv, project/agent variables, and secret redaction. | No | +| [docker-volume-persistence](docker-volume-persistence/) | Managed volumes and read-only bind mounts. | No | +| [docker-build](docker-build/) | Build and run a guest-derived Docker image. | No | +| [docker-scheduler-script-runtime](docker-scheduler-script-runtime/) | Inline QJS, persisted scheduler state, and shell callbacks. | No | +| [boxlite-minimal](boxlite-minimal/) | Minimal BoxLite configuration template. | Only for prompt runs | +| [microsandbox-minimal](microsandbox-minimal/) | Minimal Microsandbox configuration template. | Only for prompt runs | + +## Common prerequisites + +- Docker daemon is running. +- The `agent-compose` daemon is already running. +- Docker can access `ghcr.io/chaitin/agent-compose-guest:latest`. + +Pull the image used by the examples if needed: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +Each example has its own `README.md` with the exact commands and expected +output. + +BoxLite and Microsandbox additionally require Linux, KVM access, their runtime +artifacts, and a binary that includes the selected compiled driver. Their +examples are configuration templates unless run on a prepared host. diff --git a/examples/agent-compose/README.zh-CN.md b/examples/agent-compose/README.zh-CN.md index 0fee4ac0f..c0a5878ec 100644 --- a/examples/agent-compose/README.zh-CN.md +++ b/examples/agent-compose/README.zh-CN.md @@ -2,28 +2,36 @@ 语言:[English](README.md) | 中文 -## 已在本地验证的 Docker 示例 +`agent-compose` Docker runtime driver 的可运行示例,按从简单到完整的顺序排列。 -| 示例 | 能力 | Provider 认证 | +| 示例 | 演示内容 | 是否需要 provider 凭证 | | --- | --- | --- | -| [docker-minimal](docker-minimal/) | 最小 project 和当前 sandbox CLI | 不需要 | -| [docker-workspace-lifecycle](docker-workspace-lifecycle/) | Local workspace、exec、stop、resume 和隔离 | 不需要 | -| [docker-multi-agent](docker-multi-agent/) | 多个 agent 共享 workspace 声明 | command run 不需要 | -| [docker-env-secrets](docker-env-secrets/) | Dotenv、project variables、agent env 和隐藏 secret | 不需要 | -| [docker-volume-persistence](docker-volume-persistence/) | Managed volume 和只读 bind mount | 不需要 | -| [docker-build](docker-build/) | Compose 驱动的 guest image 构建 | 不需要 | -| [docker-scheduler-cron](docker-scheduler-cron/) | 声明式 cron 控制面 | 运行 trigger 时需要 | -| [docker-scheduler-timeout](docker-scheduler-timeout/) | 自动调度的 provider run | 需要 | -| [docker-scheduler-script-url](docker-scheduler-script-url/) | 相对 scheduler script URL 快照 | 不需要 | -| [docker-scheduler-script-runtime](docker-scheduler-script-runtime/) | State、日志、interval 和 scheduler shell | 不需要 | - -Docker 示例需要运行中的 agent-compose daemon、Docker daemon,以及本地已有 -发布版 guest image。README 描述稳定行为,不保存动态 ID 或完整输出快照。 - -## 仅做配置验证的 KVM 模板 - -- [boxlite-minimal](boxlite-minimal/) -- [microsandbox-minimal](microsandbox-minimal/) - -自动化测试会解析这些 manifest,但 runtime 执行需要准备好的 Linux/KVM 主机, -本地未做运行验证。 +| [docker-minimal](docker-minimal/) | 最小的 Docker project:一个 agent,不启用 scheduler。 | `config`/`up`/`ps` 不需要 | +| [docker-scheduler-cron](docker-scheduler-cron/) | managed cron scheduler 的控制面流程。 | `config`/`up`/`ps`/`down` 不需要 | +| [docker-scheduler-script-url](docker-scheduler-script-url/) | 从相对文件 URL 来源加载 scheduler 脚本。 | `config`/`up`/`ps`/`down` 不需要 | +| [docker-scheduler-timeout](docker-scheduler-timeout/) | 端到端的定时运行:触发、执行 agent 并持久化日志。 | 定时运行需要 | +| [docker-workspace-lifecycle](docker-workspace-lifecycle/) | 本地 workspace 副本及 sandbox stop、resume、exec、rm。 | 不需要 | +| [docker-multi-agent](docker-multi-agent/) | 两个独立 agent 使用同一 workspace source。 | command 不需要;prompt 需要 | +| [docker-env-secrets](docker-env-secrets/) | Dotenv、project/agent variables 和 secret 隐藏。 | 不需要 | +| [docker-volume-persistence](docker-volume-persistence/) | 托管 volume 和只读 bind mount。 | 不需要 | +| [docker-build](docker-build/) | 构建并运行基于 guest 的 Docker 镜像。 | 不需要 | +| [docker-scheduler-script-runtime](docker-scheduler-script-runtime/) | Inline QJS、持久 scheduler state 和 shell callback。 | 不需要 | +| [boxlite-minimal](boxlite-minimal/) | 最小 BoxLite 配置模板。 | prompt run 需要 | +| [microsandbox-minimal](microsandbox-minimal/) | 最小 Microsandbox 配置模板。 | prompt run 需要 | + +## 通用前置条件 + +- Docker daemon 正在运行。 +- `agent-compose` daemon 已经启动。 +- Docker 能访问 `ghcr.io/chaitin/agent-compose-guest:latest`。 + +如需获取示例使用的镜像,执行: + +```bash +docker pull ghcr.io/chaitin/agent-compose-guest:latest +``` + +每个示例都有自己的 `README.md`,包含完整命令和预期输出。 + +BoxLite 和 Microsandbox 还要求 Linux、KVM 权限、对应 runtime artifacts,以及包含 +所选 compiled driver 的二进制。未在准备好的 host 上运行时,它们只是配置模板。 diff --git a/examples/agent-compose/boxlite-minimal/README.md b/examples/agent-compose/boxlite-minimal/README.md index 256a9f2bf..2d0653948 100644 --- a/examples/agent-compose/boxlite-minimal/README.md +++ b/examples/agent-compose/boxlite-minimal/README.md @@ -30,3 +30,22 @@ a binary whose `compiled_drivers` includes `boxlite`. On a prepared host, continue with `run reviewer --command "uname -a"`, inspect the returned sandbox, and finish with `down`. Runtime success is deliberately not claimed by this repository-local validation. + +## Real local config output + +Captured with the current CLI on 2026-07-15: + +```yaml +name: boxlite-minimal +agents: + - name: reviewer + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + name: boxlite + boxlite: {} +network: + mode: default +``` + +This is config validation only; no BoxLite runtime output is claimed. diff --git a/examples/agent-compose/boxlite-minimal/README.zh-CN.md b/examples/agent-compose/boxlite-minimal/README.zh-CN.md index a15d99c58..54948a840 100644 --- a/examples/agent-compose/boxlite-minimal/README.zh-CN.md +++ b/examples/agent-compose/boxlite-minimal/README.zh-CN.md @@ -29,3 +29,22 @@ compose 文件会进入常规测试,但本地未验证 runtime 执行。运行 `config` 可在任意平台安全执行,应归一化出 `driver.name: boxlite`。在准备好的 host 上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后执行 `down`。仓库本地验证不会声称 BoxLite runtime 已成功运行。 + +## 本地真实 config 输出 + +以下输出由当前 CLI 于 2026-07-15 采集: + +```yaml +name: boxlite-minimal +agents: + - name: reviewer + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + name: boxlite + boxlite: {} +network: + mode: default +``` + +这里只验证 config,不声称 BoxLite runtime 已运行成功。 diff --git a/examples/agent-compose/docker-build/README.md b/examples/agent-compose/docker-build/README.md index 71d624045..ca9f5241b 100644 --- a/examples/agent-compose/docker-build/README.md +++ b/examples/agent-compose/docker-build/README.md @@ -32,3 +32,16 @@ clarity; automation should copy the example and substitute unique tags. file written during the image build and must print `built-by-agent-compose`. After `down`, both `rmi` commands remove the tutorial images. Avoid these fixed tags in concurrent automation; the E2E creates unique tags for that reason. + +## Real verification output + +Captured from a real Docker build and guest run on 2026-07-15: + +```console +status=succeeded +run=a023773553771e0be8d51fb1a983c37e66c2712697b37e9119be7ba4ccc04ef8 +sandbox=78459590803602e1945bdac9e3c74a1d9a656c29f57b6169bab757d1779b1d7e +built-by-agent-compose +``` + +The E2E substituted a unique image tag; generated run and sandbox IDs differ. diff --git a/examples/agent-compose/docker-build/README.zh-CN.md b/examples/agent-compose/docker-build/README.zh-CN.md index a94cf995a..2796df8b2 100644 --- a/examples/agent-compose/docker-build/README.zh-CN.md +++ b/examples/agent-compose/docker-build/README.zh-CN.md @@ -29,3 +29,16 @@ image。示例使用固定 tag 以便阅读;自动化测试应复制示例并 `build` 必须完成并创建两个本地 tag。worker 命令读取镜像构建阶段写入的文件,输出 必须为 `built-by-agent-compose`。`down` 后两个 `rmi` 命令删除教程镜像。并发自动化 不要复用这些固定 tag;E2E 会生成唯一 tag。 + +## 真实验证输出 + +以下结果采集自 2026-07-15 的真实 Docker build 和 guest run: + +```console +status=succeeded +run=a023773553771e0be8d51fb1a983c37e66c2712697b37e9119be7ba4ccc04ef8 +sandbox=78459590803602e1945bdac9e3c74a1d9a656c29f57b6169bab757d1779b1d7e +built-by-agent-compose +``` + +E2E 使用唯一 image tag;动态 run 和 sandbox ID 会不同。 diff --git a/examples/agent-compose/docker-env-secrets/README.md b/examples/agent-compose/docker-env-secrets/README.md index 2b69f13bb..ad235b514 100644 --- a/examples/agent-compose/docker-env-secrets/README.md +++ b/examples/agent-compose/docker-env-secrets/README.md @@ -30,3 +30,17 @@ Before `up`, confirm `agent-compose config` contains `********` and never prints `safe-example-secret`. The command run must print `environment ok`, proving the real guest received both scopes. The value is a non-sensitive fixture; do not commit production secrets or use this pattern as a secret manager. + +## Real verification output + +Captured from the real-daemon Docker E2E on 2026-07-15: + +```console +status=succeeded +run=f1d22000463b950c2251f72c77477d42fbe9a39b2663bbce39b0b20c04be05e8 +sandbox=9fffd2978773c87708dc46facb8bf5ba8b1edf275382afb56380b0294a497939 +environment ok +``` + +The E2E also asserted that rendered config contained `********` and did not +contain the fixture secret. Generated IDs will differ. diff --git a/examples/agent-compose/docker-env-secrets/README.zh-CN.md b/examples/agent-compose/docker-env-secrets/README.zh-CN.md index 4875d59d8..11a278a2c 100644 --- a/examples/agent-compose/docker-env-secrets/README.zh-CN.md +++ b/examples/agent-compose/docker-env-secrets/README.zh-CN.md @@ -28,3 +28,16 @@ env 只属于该 agent。启动 CLI 时的进程环境变量优先于 `example.e 执行 `up` 前,确认 `agent-compose config` 包含 `********`,且不输出 `safe-example-secret`。command run 必须输出 `environment ok`,证明真实 guest 收到 两个作用域的变量。该值只是非敏感 fixture;不要提交生产 secret。 + +## 真实验证输出 + +以下结果采集自 2026-07-15 的真实 daemon Docker E2E: + +```console +status=succeeded +run=f1d22000463b950c2251f72c77477d42fbe9a39b2663bbce39b0b20c04be05e8 +sandbox=9fffd2978773c87708dc46facb8bf5ba8b1edf275382afb56380b0294a497939 +environment ok +``` + +E2E 同时断言渲染配置包含 `********` 且不包含 fixture secret。动态 ID 会不同。 diff --git a/examples/agent-compose/docker-minimal/README.md b/examples/agent-compose/docker-minimal/README.md index a7126ea6a..080a0c812 100644 --- a/examples/agent-compose/docker-minimal/README.md +++ b/examples/agent-compose/docker-minimal/README.md @@ -18,9 +18,9 @@ It is intentionally minimal: - Docker daemon is running. - The `agent-compose` daemon is already running. -- Docker can pull `ghcr.io/chaitin/agent-compose-guest:latest`, or it already exists locally. +- Docker can access `ghcr.io/chaitin/agent-compose-guest:latest`. -Pull the exact image referenced by this example if needed: +Pull the image referenced by the compose file if needed: ```bash docker pull ghcr.io/chaitin/agent-compose-guest:latest @@ -85,7 +85,7 @@ Expected result: - `config` prints a normalized project with `driver.name: docker`. - `up` creates or updates the project and managed agent definition. -- `ps` shows the `reviewer` agent using Docker and `ghcr.io/chaitin/agent-compose-guest:latest`. +- `ps` shows the `reviewer` agent using Docker and the published guest image. ## Optional run test @@ -96,8 +96,8 @@ agent-compose run reviewer --keep-running --prompt "hello from docker minimal ex ``` A real agent run requires a working guest runtime and provider configuration in -the daemon. Long-lived credentials stay in the daemon; the guest Codex CLI uses -the sandbox-scoped LLM facade variables injected by agent-compose. +the daemon. The guest uses the sandbox-scoped LLM facade; long-lived provider +credentials are not copied into it. If the runtime session is alive, you can run commands in it: @@ -112,21 +112,81 @@ Clean up running project sessions: agent-compose down ``` -Replace `` with the sandbox id returned by `run` or shown by -`agent-compose ps --all`. `down` stops project sandboxes; use -`agent-compose rm ` when you also want to delete one. +## Verification output -## What to verify +Output from a local verification run. -The real-daemon Docker E2E runs this example. For a manual check, verify: +The recorded output below used the equivalent locally built +`agent-compose-guest:latest` image. The committed compose file now uses the +published image; generated IDs, hashes, and timestamps will also differ. -- `config` reports `driver.name: docker` and the published guest image. -- `up` reports one applied project and one agent. -- `run reviewer --command "printf 'docker minimal ok\\n'"` succeeds without - provider credentials. -- a prompt run succeeds only when the daemon has a working LLM provider. -- a kept run has a non-empty sandbox id in `ps --all`. -- `exec -- pwd` returns the guest working directory. +### 1. Config normalization -Project, revision, run, and sandbox IDs are generated per environment and are -intentionally not hard-coded. +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config +name: docker-minimal +agents: + - name: reviewer + provider: codex + image: agent-compose-guest:latest + driver: + name: docker + docker: {} +network: + mode: default +``` + +### 2. Apply project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up +Project: docker-minimal +ID: project-docker-minimal-ad604c8bf8d3 +Revision: 1 +Spec: sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd +Status: applied +Agents: 1 +Schedulers: 0 + +ACTION TYPE NAME ID +created project docker-minimal project-docker-minimal-ad604c8bf8d3 +created project_revision sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd project-docker-minimal-ad604c8bf8d3/1 +created project_agent reviewer agent-reviewer-a9f84de36227 +created agent_definition reviewer agent-reviewer-a9f84de36227 +``` + +### 3. Project status + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps +AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE +reviewer disabled - - - docker agent-compose-guest:latest +``` + +### 4. Docker runtime container + +```console +$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' +NAMES IMAGE STATUS +agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds +``` + +### 5. Current live-provider verification + +The following fields were captured on 2026-07-15 from the current source, +published guest image, real daemon facade, guest Codex CLI, and the provider +configured in the local `.env`: + +```json +{ + "id": "8363e8c144f6ab0124054c11a6ff06e67f74fe561c2af46e7b06dd2ffb420027", + "status": "succeeded", + "sandbox_id": "9f060d2ea52b1a4bedc740715ac8f745274820df03f5b551e01841315b006fb7", + "duration_ms": 15435, + "output": "agent-compose live provider ok", + "driver": "docker", + "image_ref": "ghcr.io/chaitin/agent-compose-guest:latest" +} +``` + +IDs and duration are generated values and will differ. diff --git a/examples/agent-compose/docker-minimal/README.zh-CN.md b/examples/agent-compose/docker-minimal/README.zh-CN.md index 9b49fa3e8..2885a62de 100644 --- a/examples/agent-compose/docker-minimal/README.zh-CN.md +++ b/examples/agent-compose/docker-minimal/README.zh-CN.md @@ -18,9 +18,9 @@ - Docker daemon 正在运行。 - `agent-compose` daemon 已经启动。 -- Docker 能拉取 `ghcr.io/chaitin/agent-compose-guest:latest`,或本地已有该镜像。 +- Docker 能访问 `ghcr.io/chaitin/agent-compose-guest:latest`。 -如果还没有 guest image,可拉取本示例实际引用的镜像: +如有需要,拉取 compose 文件引用的镜像: ```bash docker pull ghcr.io/chaitin/agent-compose-guest:latest @@ -85,7 +85,7 @@ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-co - `config` 输出标准化后的 project,并显示 `driver.name: docker`。 - `up` 创建或更新 project 和 managed agent definition。 -- `ps` 显示 `reviewer` agent 使用 Docker 和 `ghcr.io/chaitin/agent-compose-guest:latest`。 +- `ps` 显示 `reviewer` agent 使用 Docker 和发布版 guest 镜像。 ## 可选运行测试 @@ -95,9 +95,8 @@ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-co agent-compose run reviewer --keep-running --prompt "hello from docker minimal example" ``` -真正执行 agent 需要 guest runtime 可用,并在 daemon 中配置 provider。长期凭据 -保留在 daemon;guest Codex CLI 使用 agent-compose 注入的 sandbox 范围 LLM facade -变量。 +真正执行 agent 需要 guest runtime 可用,并在 daemon 中配置 provider。guest 使用 +sandbox 范围的 LLM facade,不会获得长期 provider 凭据。 如果 runtime session 仍在运行,可以在其中执行命令: @@ -112,19 +111,79 @@ agent-compose exec -- env agent-compose down ``` -将 `` 替换为 `run` 返回或 `agent-compose ps --all` 显示的 sandbox -ID。`down` 会停止项目 sandbox;如果还要删除它,请使用 -`agent-compose rm `。 +## 验证输出 -## 验证要点 +以下为一次本地验证运行的输出。 -真实 daemon Docker E2E 会运行本示例。手工检查时应确认: +下面记录的输出使用等价的本地构建镜像 `agent-compose-guest:latest`。当前 compose +文件使用发布版镜像;动态生成的 ID、hash 和时间戳也会不同。 -- `config` 显示 `driver.name: docker` 和发布版 guest 镜像。 -- `up` 显示一个已应用项目和一个 agent。 -- `run reviewer --command "printf 'docker minimal ok\\n'"` 无需 provider 凭证即可成功。 -- prompt run 只有在 daemon 配置了可用 LLM provider 时才会成功。 -- 保留运行后,`ps --all` 显示非空 sandbox ID。 -- `exec -- pwd` 返回 guest 工作目录。 +### 1. 配置标准化 -project、revision、run 和 sandbox ID 均由环境动态生成,因此本文不写死这些值。 +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml config +name: docker-minimal +agents: + - name: reviewer + provider: codex + image: agent-compose-guest:latest + driver: + name: docker + docker: {} +network: + mode: default +``` + +### 2. 应用 project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml up +Project: docker-minimal +ID: project-docker-minimal-ad604c8bf8d3 +Revision: 1 +Spec: sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd +Status: applied +Agents: 1 +Schedulers: 0 + +ACTION TYPE NAME ID +created project docker-minimal project-docker-minimal-ad604c8bf8d3 +created project_revision sha256:0e351a523ae793f780fc0933f3b88920501f20dfd4d855654fe711a8a3cb4edd project-docker-minimal-ad604c8bf8d3/1 +created project_agent reviewer agent-reviewer-a9f84de36227 +created agent_definition reviewer agent-reviewer-a9f84de36227 +``` + +### 3. Project 状态 + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps +AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE +reviewer disabled - - - docker agent-compose-guest:latest +``` + +### 4. Docker runtime 容器 + +```console +$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' +NAMES IMAGE STATUS +agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds +``` + +### 5. 当前真实 provider 验证 + +以下字段采集于 2026-07-15,链路为当前源码、发布版 guest 镜像、真实 daemon +facade、guest Codex CLI,以及本地 `.env` 配置的 provider: + +```json +{ + "id": "8363e8c144f6ab0124054c11a6ff06e67f74fe561c2af46e7b06dd2ffb420027", + "status": "succeeded", + "sandbox_id": "9f060d2ea52b1a4bedc740715ac8f745274820df03f5b551e01841315b006fb7", + "duration_ms": 15435, + "output": "agent-compose live provider ok", + "driver": "docker", + "image_ref": "ghcr.io/chaitin/agent-compose-guest:latest" +} +``` + +ID 和耗时是动态值,本地运行会不同。 diff --git a/examples/agent-compose/docker-multi-agent/README.md b/examples/agent-compose/docker-multi-agent/README.md index 140e0e9d1..b20df9186 100644 --- a/examples/agent-compose/docker-multi-agent/README.md +++ b/examples/agent-compose/docker-multi-agent/README.md @@ -33,3 +33,16 @@ prompts apply when the agents are run with `--prompt`. `workspace/project.txt`, return their respective marker, and have different run/sandbox IDs. Use `--prompt` only when the daemon has a configured provider. `down` cleans both agents' project sandboxes. + +## Real verification output + +Captured from the real-daemon Docker E2E on 2026-07-15: + +```console +reviewer status=succeeded sandbox=56dc449f3f6c47169bda2ca943a7681b847e0005c5b24aca3294aa5a5cb1a78e +reviewer ok +tester status=succeeded sandbox=4151fb772c909e76d9b07a6d2d86045037eece1548ea6e83380609c73ce01d4b +tester ok +``` + +The distinct generated sandbox IDs confirm the runs were isolated. diff --git a/examples/agent-compose/docker-multi-agent/README.zh-CN.md b/examples/agent-compose/docker-multi-agent/README.zh-CN.md index b07d183b8..a458ceb8b 100644 --- a/examples/agent-compose/docker-multi-agent/README.zh-CN.md +++ b/examples/agent-compose/docker-multi-agent/README.zh-CN.md @@ -32,3 +32,16 @@ system prompt。 `inspect agent` 应显示两个 definition。两个 command run 都应能读取 workspace fixture、输出各自 marker,并具有不同 run/sandbox ID。只有 daemon 配置了 provider 后才运行 `--prompt`。`down` 清理两个 agent 的项目 sandbox。 + +## 真实验证输出 + +以下结果采集自 2026-07-15 的真实 daemon Docker E2E: + +```console +reviewer status=succeeded sandbox=56dc449f3f6c47169bda2ca943a7681b847e0005c5b24aca3294aa5a5cb1a78e +reviewer ok +tester status=succeeded sandbox=4151fb772c909e76d9b07a6d2d86045037eece1548ea6e83380609c73ce01d4b +tester ok +``` + +两个动态 sandbox ID 不同,证明 run 相互隔离。 diff --git a/examples/agent-compose/docker-scheduler-cron/README.md b/examples/agent-compose/docker-scheduler-cron/README.md index d847f858b..8f05a6da0 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.md +++ b/examples/agent-compose/docker-scheduler-cron/README.md @@ -21,9 +21,9 @@ authentication. - Docker daemon is running. - The `agent-compose` daemon is already running. -- Docker can pull `ghcr.io/chaitin/agent-compose-guest:latest`, or it already exists locally. +- Docker can access `ghcr.io/chaitin/agent-compose-guest:latest`. -Pull the exact image referenced by this example if needed: +Pull the image referenced by the compose file if needed: ```bash docker pull ghcr.io/chaitin/agent-compose-guest:latest @@ -63,8 +63,6 @@ From this directory: agent-compose config agent-compose up agent-compose ps -agent-compose scheduler ls reviewer -agent-compose scheduler inspect reviewer hourly-review agent-compose inspect project docker-scheduler-cron agent-compose down ``` @@ -75,8 +73,6 @@ From the repository root without installing the binary: go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler ls reviewer -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler inspect reviewer hourly-review go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down ``` @@ -86,8 +82,6 @@ Expected result: - `config` prints the trigger as `kind: cron`. - `up` creates `project_scheduler` and `loader` resources. - `ps` shows the scheduler as `enabled`. -- `scheduler ls` reports `hourly-review` with kind `cron`. -- `scheduler inspect` reports the configured expression and prompt. - `inspect project` shows `scheduler_count: 1` and `trigger_count: 1`. - `down` disables the managed scheduler and loader. @@ -108,28 +102,123 @@ scheduler: Use cron when you want calendar-based scheduling. Use interval when you want short local feedback while testing. -## Run the agent path immediately +## Verification output -You do not need to change the committed cron expression to test the provider -path. A manual trigger uses the same managed agent prompt flow: +Output from a local verification run. -```bash -agent-compose scheduler trigger reviewer hourly-review \ - --prompt "Reply with exactly: cron scheduler ok" +The recorded output below used the equivalent locally built +`agent-compose-guest:latest` image. The committed compose file now uses the +published image; generated IDs, hashes, and timestamps will also differ. + +### 1. Config normalization + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config +name: docker-scheduler-cron +agents: + - name: reviewer + provider: codex + image: agent-compose-guest:latest + driver: + name: docker + docker: {} + scheduler: + enabled: true + triggers: + - name: hourly-review + kind: cron + cron: 0 * * * * + prompt: Review the current project state and summarize any important changes. +network: + mode: default ``` -This requires a working Codex/LLM provider. A successful result has -`status: succeeded` and output containing `cron scheduler ok`. +### 2. Apply project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up +Project: docker-scheduler-cron +ID: project-docker-scheduler-cron-034aaf526f91 +Revision: 1 +Spec: sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 +Status: applied +Agents: 1 +Schedulers: 1 + +ACTION TYPE NAME ID +created project docker-scheduler-cron project-docker-scheduler-cron-034aaf526f91 +created project_revision sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 project-docker-scheduler-cron-034aaf526f91/1 +created project_agent reviewer agent-reviewer-4bff2fb6372a +created agent_definition reviewer agent-reviewer-4bff2fb6372a +created project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa +created loader docker-scheduler-cron/reviewer scheduler loader-reviewer-default-ed0b5bed0daa +``` -## What to verify +### 3. Scheduler status -The real-daemon E2E verifies the control plane, and the provider E2E executes -the trigger through a real Docker guest and Codex CLI. Check that: +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps +AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE +reviewer enabled - - - docker agent-compose-guest:latest +``` -- `config` normalizes the trigger to `kind: cron`. -- `up` creates one enabled scheduler with one trigger. -- `scheduler ls reviewer` contains `hourly-review` and `cron`. -- the manual trigger returns a run id and `status: succeeded` with a provider. -- `down` disables the managed scheduler and loader. +### 4. Inspect project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron +{ + "project": { + "id": "project-docker-scheduler-cron-034aaf526f91", + "name": "docker-scheduler-cron", + "current_revision": 1, + "agent_count": 1, + "scheduler_count": 1 + }, + "agents": [ + { + "agent_name": "reviewer", + "provider": "codex", + "image": "agent-compose-guest:latest", + "driver": "docker", + "scheduler_enabled": true + } + ], + "schedulers": [ + { "agent_name": "reviewer", "enabled": true, "trigger_count": 1 } + ] +} +``` + +### 5. Disable scheduler + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down +Project: docker-scheduler-cron +ID: project-docker-scheduler-cron-034aaf526f91 +Status: down +Failed session stops: 0 + +ACTION TYPE NAME ID MESSAGE +updated project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa disabled by project down +updated loader reviewer loader-reviewer-default-ed0b5bed0daa disabled by project down +``` + +### 6. Current provider-path verification + +On 2026-07-15 the E2E manually triggered the cron entry through a real daemon, +Docker guest, and guest Codex CLI. Its provider endpoint was a controlled local +fixture so the result remains deterministic: + +```json +{ + "id": "b602c4c8479449081121ed0c9c8bfb9ded5c74d77027e6169098bc5f179e954c", + "status": "succeeded", + "sandbox_id": "2d60f25ed6752646d7468500d112dbf01c2b2f0f9c6c8aba8a965deab52c92e2", + "duration_ms": 10822, + "output": "cron scheduler ok", + "driver": "docker", + "image_ref": "ghcr.io/chaitin/agent-compose-guest:latest" +} +``` -Generated project, scheduler, loader, and run IDs differ by environment. +IDs and duration are generated values and will differ. diff --git a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md index 195882ac2..5ff76f925 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md @@ -20,9 +20,9 @@ managed cron scheduler。 - Docker daemon 正在运行。 - `agent-compose` daemon 已经启动。 -- Docker 能拉取 `ghcr.io/chaitin/agent-compose-guest:latest`,或本地已有该镜像。 +- Docker 能访问 `ghcr.io/chaitin/agent-compose-guest:latest`。 -如果还没有 guest image,可拉取本示例实际引用的镜像: +如有需要,拉取 compose 文件引用的镜像: ```bash docker pull ghcr.io/chaitin/agent-compose-guest:latest @@ -61,8 +61,6 @@ cron: "0 * * * *" agent-compose config agent-compose up agent-compose ps -agent-compose scheduler ls reviewer -agent-compose scheduler inspect reviewer hourly-review agent-compose inspect project docker-scheduler-cron agent-compose down ``` @@ -73,8 +71,6 @@ agent-compose down go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler ls reviewer -go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml scheduler inspect reviewer hourly-review go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down ``` @@ -84,8 +80,6 @@ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/a - `config` 显示 trigger 为 `kind: cron`。 - `up` 创建 `project_scheduler` 和 `loader` 资源。 - `ps` 显示 scheduler 为 `enabled`。 -- `scheduler ls` 显示 `hourly-review` 且类型为 `cron`。 -- `scheduler inspect` 显示配置的表达式和 prompt。 - `inspect project` 显示 `scheduler_count: 1` 和 `trigger_count: 1`。 - `down` 禁用 managed scheduler 和 loader。 @@ -104,28 +98,121 @@ scheduler: 需要基于日历时间调度时使用 cron;需要本地快速反馈时使用 interval。 -## 立即运行 agent 路径 +## 验证输出 -测试 provider 路径不需要修改仓库中的 cron 表达式。手动 trigger 会使用相同的 -托管 agent prompt 流程: +以下为一次本地验证运行的输出。 -```bash -agent-compose scheduler trigger reviewer hourly-review \ - --prompt "Reply with exactly: cron scheduler ok" +下面记录的输出使用等价的本地构建镜像 `agent-compose-guest:latest`。当前 compose +文件使用发布版镜像;动态生成的 ID、hash 和时间戳也会不同。 + +### 1. 配置标准化 + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml config +name: docker-scheduler-cron +agents: + - name: reviewer + provider: codex + image: agent-compose-guest:latest + driver: + name: docker + docker: {} + scheduler: + enabled: true + triggers: + - name: hourly-review + kind: cron + cron: 0 * * * * + prompt: Review the current project state and summarize any important changes. +network: + mode: default +``` + +### 2. 应用 project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml up +Project: docker-scheduler-cron +ID: project-docker-scheduler-cron-034aaf526f91 +Revision: 1 +Spec: sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 +Status: applied +Agents: 1 +Schedulers: 1 + +ACTION TYPE NAME ID +created project docker-scheduler-cron project-docker-scheduler-cron-034aaf526f91 +created project_revision sha256:93950d90a6dbd56a141cbd0b059c06eb37b4db6bb27860b24cb78bea781536d5 project-docker-scheduler-cron-034aaf526f91/1 +created project_agent reviewer agent-reviewer-4bff2fb6372a +created agent_definition reviewer agent-reviewer-4bff2fb6372a +created project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa +created loader docker-scheduler-cron/reviewer scheduler loader-reviewer-default-ed0b5bed0daa ``` -该命令要求可用的 Codex/LLM provider。成功结果应包含 `status: succeeded`,输出中 -包含 `cron scheduler ok`。 +### 3. Scheduler 状态 -## 验证要点 +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps +AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE +reviewer enabled - - - docker agent-compose-guest:latest +``` + +### 4. 查看 project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml inspect project docker-scheduler-cron +{ + "project": { + "id": "project-docker-scheduler-cron-034aaf526f91", + "name": "docker-scheduler-cron", + "current_revision": 1, + "agent_count": 1, + "scheduler_count": 1 + }, + "agents": [ + { + "agent_name": "reviewer", + "provider": "codex", + "image": "agent-compose-guest:latest", + "driver": "docker", + "scheduler_enabled": true + } + ], + "schedulers": [ + { "agent_name": "reviewer", "enabled": true, "trigger_count": 1 } + ] +} +``` + +### 5. 禁用 scheduler -真实 daemon E2E 验证控制面,provider E2E 则通过真实 Docker guest 和 Codex CLI -执行 trigger。应确认: +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml down +Project: docker-scheduler-cron +ID: project-docker-scheduler-cron-034aaf526f91 +Status: down +Failed session stops: 0 -- `config` 将 trigger 归一化为 `kind: cron`。 -- `up` 创建一个启用的 scheduler 和一个 trigger。 -- `scheduler ls reviewer` 包含 `hourly-review` 和 `cron`。 -- 配置 provider 后,手动 trigger 返回 run ID 和 `status: succeeded`。 -- `down` 禁用托管 scheduler 和 loader。 +ACTION TYPE NAME ID MESSAGE +updated project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa disabled by project down +updated loader reviewer loader-reviewer-default-ed0b5bed0daa disabled by project down +``` + +### 6. 当前 provider 路径验证 + +2026-07-15 的 E2E 通过真实 daemon、Docker guest 和 guest Codex CLI 手动触发 cron +条目。provider endpoint 使用受控本地 fixture,以保持结果可重复: + +```json +{ + "id": "b602c4c8479449081121ed0c9c8bfb9ded5c74d77027e6169098bc5f179e954c", + "status": "succeeded", + "sandbox_id": "2d60f25ed6752646d7468500d112dbf01c2b2f0f9c6c8aba8a965deab52c92e2", + "duration_ms": 10822, + "output": "cron scheduler ok", + "driver": "docker", + "image_ref": "ghcr.io/chaitin/agent-compose-guest:latest" +} +``` -project、scheduler、loader 和 run ID 均由环境动态生成。 +ID 和耗时是动态值,本地运行会不同。 diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.md b/examples/agent-compose/docker-scheduler-script-runtime/README.md index 79bc9317b..3ce9a9175 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.md @@ -32,3 +32,16 @@ the long-running schedule. No model provider is called. Inspect the two timeout triggers until their events contain `heartbeat 1` and `heartbeat 2`. That ordered output proves state persisted between distinct loader callbacks. `down` disables the interval and cleans project sandboxes. + +## Real verification output + +Captured from the real scheduler runtime on 2026-07-15: + +```console +type=loader.log +message="heartbeat completed" +payload={"count":2,"output":"heartbeat 2\n"} +``` + +The E2E first observed `heartbeat 1`, then the event above. This proves the +loader state survived between callbacks. diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md index bc1232275..9bec6476f 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md @@ -30,3 +30,16 @@ state 会在 run 之间持久化;interval 保留为长期调度。该流程不 `scheduler ls heartbeat` 应列出 `warmup`、`follow-up` 和 `heartbeat`。检查两个 timeout trigger,直到 event 分别包含 `heartbeat 1` 和 `heartbeat 2`;有序输出证明 state 在不同 loader callback 间持久化。`down` 禁用 interval 并清理 sandbox。 + +## 真实验证输出 + +以下结果采集自 2026-07-15 的真实 scheduler runtime: + +```console +type=loader.log +message="heartbeat completed" +payload={"count":2,"output":"heartbeat 2\n"} +``` + +E2E 先观察到 `heartbeat 1`,再观察到上面的 event,证明 loader state 跨 callback +保留。 diff --git a/examples/agent-compose/docker-scheduler-script-url/README.md b/examples/agent-compose/docker-scheduler-script-url/README.md index 1a5cce556..5ad855ff3 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.md @@ -5,70 +5,51 @@ Languages: English | [中文](README.zh-CN.md) This example keeps QJS in `scheduler.js` and references it from `agent-compose.yml` with `scheduler.script.url`. -It demonstrates two independent concerns: +The script keeps the original daily `scheduler.cron(...)` agent callback and +also defines a two-second `scheduler.timeout(...)` shell callback. The timeout +provides a quick way to verify that the external file was loaded; it does not +replace the cron tutorial. -- `daily-review` keeps the tutorial's calendar-based - `scheduler.cron(...) + scheduler.agent(...)` flow. -- `source-loaded` is a two-second `scheduler.timeout(...)` shell callback used - to prove, with a real daemon, that the external script was loaded and run. - -## Prerequisites - -- Docker daemon and the `agent-compose` daemon are running. -- Docker can pull the published guest image, or it already exists locally. -- The cron agent callback requires a configured LLM provider when it fires. - The timeout shell callback does not use a model. - -## Compose and script - -The compose file enables the scheduler and points to a path relative to the -compose directory: - -```yaml -scheduler: - enabled: true - sandbox_policy: new - script: - url: ./scheduler.js -``` - -The script preserves the real cron agent example and adds a fast verification -trigger: - -```js -scheduler.cron("daily-review", "0 9 * * *", function dailyReview() { - return scheduler.agent("Review the current project state."); -}); - -scheduler.timeout("source-loaded", function sourceLoaded() { - return scheduler.shell("printf 'scheduler script URL ok\\n'"); -}, 2000); -``` - -## Run the example +## Run step by step ```bash agent-compose config agent-compose up +agent-compose ps agent-compose scheduler ls reviewer agent-compose scheduler inspect reviewer daily-review agent-compose scheduler inspect reviewer source-loaded agent-compose down ``` -`config` and `up` resolve the relative URL, read the script, and send an inline -content snapshot to the daemon. Editing `scheduler.js` takes effect on the next -`up`; it is not a runtime import or background refresh. +`config` prints the fetched script inline. `up` fetches it once more, hashes the +content snapshot, and sends only script text to the daemon. Editing +`scheduler.js` takes effect on the next `up`. The relative path is resolved from +the directory containing `agent-compose.yml`. + +The control-plane commands do not require provider authentication. A scheduled +agent callback requires a working guest runtime and daemon provider +configuration. The `source-loaded` shell callback does not call a model. -## What to verify +Expected checks: -- `config` includes the resolved script content. -- `scheduler ls reviewer` lists both `daily-review` and `source-loaded`. -- after about two seconds, inspecting `source-loaded` shows that it fired and - its event contains `scheduler script URL ok`. -- `daily-review` remains scheduled for `0 9 * * *` and invokes the agent when - its calendar time arrives. -- `down` disables the scheduler and removes project sandboxes. +1. `config` contains the resolved QJS content. +2. `scheduler ls reviewer` lists `daily-review` and `source-loaded`. +3. After about two seconds, `source-loaded` has fired and its event contains + `scheduler script URL ok`. +4. `daily-review` remains scheduled for `0 9 * * *` and calls the agent when its + calendar time arrives. +5. `down` disables the scheduler and cleans project sandboxes. + +## Real verification output + +Captured from the real scheduler runtime on 2026-07-15: + +```console +type=loader.command.completed +message="scheduler script URL ok" +payload={"exitCode":0,"mode":"shell","stderrTruncated":false,"stdoutTruncated":false,"success":true} +``` -Generated loader IDs and timestamps vary by environment. The real-daemon E2E -asserts the event message instead of hard-coding those values. +The original event also contained generated cell and sandbox IDs, omitted here +for readability. The E2E asserts the message and successful shell result. diff --git a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md index 15ffafff1..24ff3deeb 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md @@ -5,65 +5,46 @@ 本示例把 QJS 保存在 `scheduler.js`,并在 `agent-compose.yml` 中通过 `scheduler.script.url` 引用。 -它同时演示两个相互独立的能力: +脚本保留原来的每日 `scheduler.cron(...)` agent callback,同时增加两秒后执行的 +`scheduler.timeout(...)` shell callback。timeout 用于快速验证外部文件已加载,不会 +替代 cron 教程。 -- `daily-review` 保留教程中的日历调度 - `scheduler.cron(...) + scheduler.agent(...)` 流程。 -- `source-loaded` 是两秒后执行的 `scheduler.timeout(...)` shell 回调,用真实 - daemon 证明外部脚本已被加载并运行。 - -## 前置条件 - -- Docker daemon 和 `agent-compose` daemon 已启动。 -- Docker 能拉取发布版 guest 镜像,或本地已有该镜像。 -- cron agent 回调真正触发时需要已配置的 LLM provider;timeout shell 回调不调用模型。 - -## Compose 与脚本 - -compose 文件启用 scheduler,并引用相对于 compose 目录的路径: - -```yaml -scheduler: - enabled: true - sandbox_policy: new - script: - url: ./scheduler.js -``` - -脚本保留真实 cron agent 示例,同时增加快速验证 trigger: - -```js -scheduler.cron("daily-review", "0 9 * * *", function dailyReview() { - return scheduler.agent("Review the current project state."); -}); - -scheduler.timeout("source-loaded", function sourceLoaded() { - return scheduler.shell("printf 'scheduler script URL ok\\n'"); -}, 2000); -``` - -## 运行示例 +## 分步运行 ```bash agent-compose config agent-compose up +agent-compose ps agent-compose scheduler ls reviewer agent-compose scheduler inspect reviewer daily-review agent-compose scheduler inspect reviewer source-loaded agent-compose down ``` -`config` 和 `up` 会解析相对 URL、读取脚本,并把内容快照内联发送给 daemon。 -修改 `scheduler.js` 后需要再次执行 `up`;它不是运行时 import,也不会后台刷新。 +`config` 会把获取到的脚本以内联形式输出。`up` 再获取一次,基于内容快照计算 +hash,并且只把脚本文本发送给 daemon。修改 `scheduler.js` 后需再次执行 `up` +才会生效。相对路径以 `agent-compose.yml` 所在目录为基准。 + +控制面命令不要求 provider 凭证;scheduled agent callback 要求可用的 guest +runtime 和 daemon provider 配置。`source-loaded` shell callback 不调用模型。 -## 验证要点 +预期检查: -- `config` 包含解析后的脚本内容。 -- `scheduler ls reviewer` 同时列出 `daily-review` 和 `source-loaded`。 -- 约两秒后,检查 `source-loaded` 可看到它已触发,event 中包含 - `scheduler script URL ok`。 -- `daily-review` 仍按 `0 9 * * *` 调度,并在日历时间到达时调用 agent。 -- `down` 禁用 scheduler 并删除项目 sandbox。 +1. `config` 包含解析后的 QJS 内容。 +2. `scheduler ls reviewer` 同时列出 `daily-review` 和 `source-loaded`。 +3. 约两秒后,`source-loaded` 已触发,event 中包含 `scheduler script URL ok`。 +4. `daily-review` 仍按 `0 9 * * *` 调度,并在日历时间到达时调用 agent。 +5. `down` 禁用 scheduler 并清理项目 sandbox。 + +## 真实验证输出 + +以下结果采集自 2026-07-15 的真实 scheduler runtime: + +```console +type=loader.command.completed +message="scheduler script URL ok" +payload={"exitCode":0,"mode":"shell","stderrTruncated":false,"stdoutTruncated":false,"success":true} +``` -loader ID 和时间戳由环境动态生成。真实 daemon E2E 会断言 event 内容,而不是 -写死这些值。 +原 event 还包含动态 cell 和 sandbox ID,此处为可读性省略。E2E 会断言 message 和 +shell 成功结果。 diff --git a/examples/agent-compose/docker-scheduler-timeout/README.md b/examples/agent-compose/docker-scheduler-timeout/README.md index f5f7ffc0d..766c21a17 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.md @@ -19,11 +19,11 @@ It verifies that agent-compose can: - Docker daemon is running. - The `agent-compose` daemon is already running. -- Docker can pull `ghcr.io/chaitin/agent-compose-guest:latest`, or it already exists locally. -- The daemon has a working Codex/LLM provider. Long-lived provider credentials - remain in the daemon; the guest calls its sandbox-scoped LLM facade. +- Docker can access `ghcr.io/chaitin/agent-compose-guest:latest`. +- The daemon has a working Codex/LLM provider configuration. The guest uses the + sandbox-scoped LLM facade rather than receiving the long-lived provider key. -Pull the exact image referenced by this example if needed: +Pull the image referenced by the compose file if needed: ```bash docker pull ghcr.io/chaitin/agent-compose-guest:latest @@ -89,21 +89,118 @@ Expected result: - `logs --run ` prints the agent output. - `down` disables the managed scheduler and loader. -`sandbox_policy: new` creates a fresh sandbox for this scheduled run. The guest -receives a facade token and guest-reachable daemon URL, not the provider key. +## Verification output -## What to verify +Output from a local verification run. The run id below is from that run; yours +will differ. -The provider E2E runs this flow through a real daemon, Docker guest, and Codex -CLI. In a manual run, verify: +The recorded output used the equivalent locally built +`agent-compose-guest:latest` image. The committed compose file now uses the +published image; generated IDs, hashes, timestamps, and durations will differ. -- `config` reports `kind: timeout`, `timeout: 15s`, and `sandbox_policy: new`. -- `scheduler inspect reviewer run-once-after-15-seconds` eventually reports the - trigger has fired. -- `ps` shows a scheduler-sourced run with `status: succeeded`. -- `inspect run ` reports `source: scheduler`, `driver: docker`, a - non-empty sandbox id, and output containing `timeout scheduler ok`. -- `logs --run ` prints the same model output. -- `down` disables the scheduler and cleans up project sandboxes. +### 1. Config normalization -Generated IDs and exact durations vary and are intentionally not hard-coded. +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config +name: docker-scheduler-timeout +agents: + - name: reviewer + provider: codex + image: agent-compose-guest:latest + driver: + name: docker + docker: {} + scheduler: + enabled: true + triggers: + - name: run-once-after-15-seconds + kind: timeout + timeout: 15s + prompt: 'Reply with exactly: timeout scheduler ok' +network: + mode: default +``` + +### 2. Apply project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up +Project: docker-scheduler-timeout +ID: project-docker-scheduler-timeout-3a00cafbae27 +Revision: 1 +Spec: sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 +Status: applied +Agents: 1 +Schedulers: 1 + +ACTION TYPE NAME ID +created project docker-scheduler-timeout project-docker-scheduler-timeout-3a00cafbae27 +created project_revision sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 project-docker-scheduler-timeout-3a00cafbae27/1 +created project_agent reviewer agent-reviewer-a0befcb745b8 +created agent_definition reviewer agent-reviewer-a0befcb745b8 +created project_scheduler reviewer scheduler-reviewer-default-181247660dc1 +created loader docker-scheduler-timeout/reviewer scheduler loader-reviewer-default-181247660dc1 +``` + +### 3. Successful scheduled run + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps +AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE +reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker agent-compose-guest:latest +``` + +### 4. Inspect successful run + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run run-reviewer-28c0ef985c8d +{ + "run_id": "run-reviewer-28c0ef985c8d", + "project_name": "docker-scheduler-timeout", + "agent_name": "reviewer", + "source": "scheduler", + "status": "succeeded", + "session_id": "23a1ede4-3325-470d-99db-377e3296e7a2", + "exit_code": 0, + "duration_ms": 10917, + "prompt": "Reply with exactly: timeout scheduler ok", + "output": "timeout scheduler ok", + "result_json": "{\"agent\":\"codex\",\"exitCode\":0,\"stopReason\":\"completed\",\"success\":true}", + "driver": "docker", + "image_ref": "agent-compose-guest:latest" +} +``` + +### 5. Run logs + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run run-reviewer-28c0ef985c8d +timeout scheduler ok +``` + +### 6. Disable scheduler + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down +Project: docker-scheduler-timeout +ID: project-docker-scheduler-timeout-3a00cafbae27 +Status: down +Failed session stops: 0 + +ACTION TYPE NAME ID MESSAGE +updated project_scheduler reviewer scheduler-reviewer-default-181247660dc1 disabled by project down +updated loader reviewer loader-reviewer-default-181247660dc1 disabled by project down +``` + +### 7. Current automatic-timeout verification + +The 2026-07-15 E2E observed the automatic trigger through a real daemon, +Docker guest, and guest Codex CLI, with a controlled provider fixture: + +```console +status=RUN_STATUS_SUCCEEDED +run=236f8ab07988370527571cd40eaf38e9ced83afb22651e2c8b0f4b6e15d4b960 +sandbox=825642e2863644eac04f63e4bec17af025379ce8a050561abe58a178a3443c0e +``` + +Run and sandbox IDs are generated and will differ. diff --git a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md index 3ccd1e3a6..e3cdb9094 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md @@ -19,11 +19,11 @@ - Docker daemon 正在运行。 - `agent-compose` daemon 已经启动。 -- Docker 能拉取 `ghcr.io/chaitin/agent-compose-guest:latest`,或本地已有该镜像。 -- daemon 配置了可用的 Codex/LLM provider。长期 provider 凭证保留在 daemon 中; - guest 通过 sandbox 范围的 LLM facade 调用模型。 +- Docker 能访问 `ghcr.io/chaitin/agent-compose-guest:latest`。 +- daemon 已配置可用的 Codex/LLM provider。guest 使用 sandbox 范围的 LLM facade, + 不会获得长期 provider key。 -如果还没有 guest image,可拉取本示例实际引用的镜像: +如有需要,拉取 compose 文件引用的镜像: ```bash docker pull ghcr.io/chaitin/agent-compose-guest:latest @@ -88,20 +88,116 @@ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeou - `logs --run ` 输出 agent 日志。 - `down` 禁用 managed scheduler 和 loader。 -`sandbox_policy: new` 会为 scheduled run 创建全新 sandbox。guest 获得 facade token -和可访问的 daemon URL,而不是长期 provider key。 +## 验证输出 -## 验证要点 +以下为一次本地验证运行的输出。其中的 run id 来自该次运行,你本地的会不同。 -provider E2E 会通过真实 daemon、Docker guest 和 Codex CLI 运行该流程。手工运行时 -应确认: +下面记录的输出使用等价的本地构建镜像 `agent-compose-guest:latest`。当前 compose +文件使用发布版镜像;动态生成的 ID、hash、时间戳和耗时也会不同。 -- `config` 显示 `kind: timeout`、`timeout: 15s` 和 `sandbox_policy: new`。 -- `scheduler inspect reviewer run-once-after-15-seconds` 最终显示 trigger 已触发。 -- `ps` 显示一个来自 scheduler 且 `status: succeeded` 的 run。 -- `inspect run ` 显示 `source: scheduler`、`driver: docker`、非空 sandbox - ID,且输出包含 `timeout scheduler ok`。 -- `logs --run ` 输出相同的模型结果。 -- `down` 禁用 scheduler 并清理项目 sandbox。 +### 1. 配置标准化 -动态 ID 和实际耗时因环境而异,因此本文不写死这些值。 +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml config +name: docker-scheduler-timeout +agents: + - name: reviewer + provider: codex + image: agent-compose-guest:latest + driver: + name: docker + docker: {} + scheduler: + enabled: true + triggers: + - name: run-once-after-15-seconds + kind: timeout + timeout: 15s + prompt: 'Reply with exactly: timeout scheduler ok' +network: + mode: default +``` + +### 2. 应用 project + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml up +Project: docker-scheduler-timeout +ID: project-docker-scheduler-timeout-3a00cafbae27 +Revision: 1 +Spec: sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 +Status: applied +Agents: 1 +Schedulers: 1 + +ACTION TYPE NAME ID +created project docker-scheduler-timeout project-docker-scheduler-timeout-3a00cafbae27 +created project_revision sha256:283623fe82f0f04270f27a0ec9da4809fc45b4a45c3f15df3f688aba074990b2 project-docker-scheduler-timeout-3a00cafbae27/1 +created project_agent reviewer agent-reviewer-a0befcb745b8 +created agent_definition reviewer agent-reviewer-a0befcb745b8 +created project_scheduler reviewer scheduler-reviewer-default-181247660dc1 +created loader docker-scheduler-timeout/reviewer scheduler loader-reviewer-default-181247660dc1 +``` + +### 3. 成功的 scheduled run + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps +AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE +reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker agent-compose-guest:latest +``` + +### 4. 查看成功 run + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml inspect run run-reviewer-28c0ef985c8d +{ + "run_id": "run-reviewer-28c0ef985c8d", + "project_name": "docker-scheduler-timeout", + "agent_name": "reviewer", + "source": "scheduler", + "status": "succeeded", + "session_id": "23a1ede4-3325-470d-99db-377e3296e7a2", + "exit_code": 0, + "duration_ms": 10917, + "prompt": "Reply with exactly: timeout scheduler ok", + "output": "timeout scheduler ok", + "result_json": "{\"agent\":\"codex\",\"exitCode\":0,\"stopReason\":\"completed\",\"success\":true}", + "driver": "docker", + "image_ref": "agent-compose-guest:latest" +} +``` + +### 5. Run 日志 + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml logs --run run-reviewer-28c0ef985c8d +timeout scheduler ok +``` + +### 6. 禁用 scheduler + +```console +$ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml down +Project: docker-scheduler-timeout +ID: project-docker-scheduler-timeout-3a00cafbae27 +Status: down +Failed session stops: 0 + +ACTION TYPE NAME ID MESSAGE +updated project_scheduler reviewer scheduler-reviewer-default-181247660dc1 disabled by project down +updated loader reviewer loader-reviewer-default-181247660dc1 disabled by project down +``` + +### 7. 当前自动 timeout 验证 + +2026-07-15 的 E2E 通过真实 daemon、Docker guest 和 guest Codex CLI 观察自动 +trigger,provider 使用受控 fixture: + +```console +status=RUN_STATUS_SUCCEEDED +run=236f8ab07988370527571cd40eaf38e9ced83afb22651e2c8b0f4b6e15d4b960 +sandbox=825642e2863644eac04f63e4bec17af025379ce8a050561abe58a178a3443c0e +``` + +run 和 sandbox ID 每次动态生成,本地结果会不同。 diff --git a/examples/agent-compose/docker-volume-persistence/README.md b/examples/agent-compose/docker-volume-persistence/README.md index 55e9419db..dbf79ef8a 100644 --- a/examples/agent-compose/docker-volume-persistence/README.md +++ b/examples/agent-compose/docker-volume-persistence/README.md @@ -34,3 +34,14 @@ Use the sandbox id returned by the kept run. The first command must read the fixture and write `/cache/value`; after stop/resume, `cat` must return `persistent`. The `touch` check must fail inside the read-only mount. Stop and remove the sandbox before `down` for an explicit lifecycle cleanup. + +## Real verification output + +Captured after a real Docker sandbox stop/resume on 2026-07-15: + +```console +$ agent-compose exec -- cat /cache/value +persistent +$ agent-compose exec -- sh -c 'if touch /fixtures/unexpected 2>/dev/null; then exit 1; fi' +# exit status 0: writing to the read-only mount was rejected +``` diff --git a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md index 7e6911391..8121b3e5c 100644 --- a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md +++ b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md @@ -32,3 +32,14 @@ cache 值在 sandbox stop/resume 后仍存在。`down` 会移除 project-managed 使用保留 run 返回的 sandbox ID。第一次命令必须读取 fixture 并写入 `/cache/value`;stop/resume 后 `cat` 必须返回 `persistent`。`touch` 检查必须因只读 挂载而失败。最后显式 stop、rm,再执行 `down`。 + +## 真实验证输出 + +以下结果采集自 2026-07-15 真实 Docker sandbox 的 stop/resume: + +```console +$ agent-compose exec -- cat /cache/value +persistent +$ agent-compose exec -- sh -c 'if touch /fixtures/unexpected 2>/dev/null; then exit 1; fi' +# exit status 0:只读挂载拒绝写入 +``` diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.md b/examples/agent-compose/docker-workspace-lifecycle/README.md index afcdccf4a..80a053e93 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.md @@ -39,3 +39,17 @@ agent-compose down Use the sandbox id returned by `run`. Confirm the file before and after stop/resume, then check that `workspace/generated.txt` does not exist on the host. `rm` deletes the stopped sandbox; `down` cleans remaining project state. + +## Real verification output + +Captured from the real-daemon Docker E2E on 2026-07-15: + +```console +status=succeeded +run=7f1f9aaf0cd1d1c125c51bac9f915cdcf57aa9d531353beb9e77faa4ed4109d7 +sandbox=8eac6735af343c91804590c5329d57e274a1e395c6a3ecb41f4c0c62c1ff4629 +$ agent-compose exec -- cat generated.txt +sandbox-only +``` + +Run and sandbox IDs are generated and will differ. diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md index a88f54f25..b88b4c6d4 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md @@ -33,3 +33,17 @@ agent-compose down 使用 `run` 返回的 sandbox ID,在 stop/resume 前后读取文件,并确认 host 上不存在 `workspace/generated.txt`。`rm` 删除已停止 sandbox,`down` 清理剩余项目状态。 + +## 真实验证输出 + +以下结果采集自 2026-07-15 的真实 daemon Docker E2E: + +```console +status=succeeded +run=7f1f9aaf0cd1d1c125c51bac9f915cdcf57aa9d531353beb9e77faa4ed4109d7 +sandbox=8eac6735af343c91804590c5329d57e274a1e395c6a3ecb41f4c0c62c1ff4629 +$ agent-compose exec -- cat generated.txt +sandbox-only +``` + +run 和 sandbox ID 每次动态生成,本地结果会不同。 diff --git a/examples/agent-compose/microsandbox-minimal/README.md b/examples/agent-compose/microsandbox-minimal/README.md index 2ada8bcc6..cd48330e1 100644 --- a/examples/agent-compose/microsandbox-minimal/README.md +++ b/examples/agent-compose/microsandbox-minimal/README.md @@ -30,3 +30,22 @@ and a binary whose `compiled_drivers` includes `microsandbox`. `driver.name: microsandbox`. On a prepared host, continue with `run reviewer --command "uname -a"`, inspect the returned sandbox, and finish with `down`. Runtime success is deliberately not claimed locally. + +## Real local config output + +Captured with the current CLI on 2026-07-15: + +```yaml +name: microsandbox-minimal +agents: + - name: reviewer + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + name: microsandbox + microsandbox: {} +network: + mode: default +``` + +This is config validation only; no Microsandbox runtime output is claimed. diff --git a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md index b4553c0e9..8233c2175 100644 --- a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md +++ b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md @@ -29,3 +29,22 @@ compose 文件会进入常规测试,但本地未验证 runtime 执行。运行 `config` 可在任意平台安全执行,应归一化出 `driver.name: microsandbox`。在准备好的 host 上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后 执行 `down`。本地不会声称 Microsandbox runtime 已成功运行。 + +## 本地真实 config 输出 + +以下输出由当前 CLI 于 2026-07-15 采集: + +```yaml +name: microsandbox-minimal +agents: + - name: reviewer + provider: codex + image: ghcr.io/chaitin/agent-compose-guest:latest + driver: + name: microsandbox + microsandbox: {} +network: + mode: default +``` + +这里只验证 config,不声称 Microsandbox runtime 已运行成功。 diff --git a/examples/scheduler-script/README.md b/examples/scheduler-script/README.md index 308b1d116..e583a6cfb 100644 --- a/examples/scheduler-script/README.md +++ b/examples/scheduler-script/README.md @@ -111,8 +111,7 @@ scheduler.schedule(triggerId, expression, callback, options); 手动运行时,agent-compose 会优先调用全局 `main(payload)`。如果没有 `main()` 且脚本只注册了一个触发器,则会调用这个触发器的 callback;如果有多个触发器,必须显式选择触发器或定义 `main()`。 -- 手动运行的 `payload` 来自统一 run 请求的 `payload_json`;CLI 对应 - `agent-compose scheduler trigger --payload '{...}'`。 +- 手动运行的 `payload` 来自 `RunLoaderNow.payloadJson`。 - `scheduler.on(...)` handler 收到事件 envelope:`{ topic, createdAt, payload }`。 - interval、timeout、cron handler 默认收到 `undefined`,除非你在脚本里自己转发自定义上下文。 diff --git a/test/e2e/examples_docker_live_provider_test.go b/test/e2e/examples_docker_live_provider_test.go index 7cfa71f6f..645a1218b 100644 --- a/test/e2e/examples_docker_live_provider_test.go +++ b/test/e2e/examples_docker_live_provider_test.go @@ -56,6 +56,7 @@ func TestE2EExamplesDockerLiveProvider(t *testing.T) { defer downExampleProject(t, ctx, binary, root, baseURL, file) const marker = "agent-compose live provider ok" out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "--json", "run", "reviewer", "--prompt", "Reply with exactly: "+marker) + t.Logf("verified live-provider output: %s", strings.TrimSpace(out)) var run exampleRunOutput if err := json.Unmarshal([]byte(out), &run); err != nil { t.Fatalf("decode live-provider run output %q: %v", out, err) diff --git a/test/e2e/examples_docker_provider_test.go b/test/e2e/examples_docker_provider_test.go index c505a6db7..8ffcd144a 100644 --- a/test/e2e/examples_docker_provider_test.go +++ b/test/e2e/examples_docker_provider_test.go @@ -64,6 +64,7 @@ func TestE2EExamplesDockerProvider(t *testing.T) { applyExampleProject(t, ctx, binary, root, baseURL, file) defer downExampleProject(t, ctx, binary, root, baseURL, file) out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "--json", "scheduler", "trigger", "reviewer", "hourly-review", "--prompt", "Reply with exactly: cron scheduler ok") + t.Logf("verified manual cron output: %s", strings.TrimSpace(out)) if !strings.Contains(out, `"status": "succeeded"`) || !strings.Contains(out, "cron scheduler ok") { t.Fatalf("cron trigger output = %q", out) } @@ -93,6 +94,7 @@ func TestE2EExamplesDockerProvider(t *testing.T) { if err == nil { for _, run := range resp.Msg.GetRuns() { if run.GetStatus() == agentcomposev2.RunStatus_RUN_STATUS_SUCCEEDED { + t.Logf("verified timeout run: id=%s sandbox=%s status=%s", run.GetRunId(), run.GetSandboxId(), run.GetStatus()) return } if run.GetStatus() == agentcomposev2.RunStatus_RUN_STATUS_FAILED { diff --git a/test/e2e/examples_docker_test.go b/test/e2e/examples_docker_test.go index 6d1d54819..95b60db37 100644 --- a/test/e2e/examples_docker_test.go +++ b/test/e2e/examples_docker_test.go @@ -67,6 +67,7 @@ func TestE2EExamplesDocker(t *testing.T) { applyExampleProject(t, ctx, binary, root, baseURL, file) defer downExampleProject(t, ctx, binary, root, baseURL, file) run := runExampleCommand(t, ctx, binary, root, baseURL, file, "reviewer", "printf 'docker minimal ok\\n'", true) + t.Logf("verified run: id=%s sandbox=%s status=%s output=%q", run.ID, run.SandboxID, run.Status, run.Output) if run.Status != "succeeded" || !strings.Contains(run.Output, "docker minimal ok") || run.SandboxID == "" { t.Fatalf("minimal run = %#v", run) } @@ -82,12 +83,14 @@ func TestE2EExamplesDocker(t *testing.T) { applyExampleProject(t, ctx, binary, root, baseURL, file) defer downExampleProject(t, ctx, binary, root, baseURL, file) run := runExampleCommand(t, ctx, binary, root, baseURL, file, "worker", "test -f README.md && printf 'sandbox-only\\n' > generated.txt", true) + t.Logf("verified run: id=%s sandbox=%s status=%s", run.ID, run.SandboxID, run.Status) exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "stop", run.SandboxID) exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "resume", run.SandboxID) out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "exec", run.SandboxID, "--", "cat", "generated.txt") if strings.TrimSpace(out) != "sandbox-only" { t.Fatalf("resumed workspace output = %q", out) } + t.Logf("verified resumed output: %q", strings.TrimSpace(out)) if _, err := os.Stat(filepath.Join(dir, "workspace", "generated.txt")); !os.IsNotExist(err) { t.Fatalf("sandbox change leaked to source: %v", err) } @@ -101,6 +104,7 @@ func TestE2EExamplesDocker(t *testing.T) { defer downExampleProject(t, ctx, binary, root, baseURL, file) for _, agent := range []string{"reviewer", "tester"} { run := runExampleCommand(t, ctx, binary, root, baseURL, file, agent, "test -f project.txt && printf '"+agent+" ok\\n'", false) + t.Logf("verified %s run: id=%s sandbox=%s status=%s output=%q", agent, run.ID, run.SandboxID, run.Status, run.Output) if run.AgentName != agent || !strings.Contains(run.Output, agent+" ok") { t.Fatalf("%s run = %#v", agent, run) } @@ -117,6 +121,7 @@ func TestE2EExamplesDocker(t *testing.T) { defer downExampleProject(t, ctx, binary, root, baseURL, file) command := `test "$PROJECT_VALUE" = project-level && test "$AGENT_VALUE" = agent-level && test "$PROJECT_SECRET" = safe-example-secret && test "$AGENT_SECRET" = safe-example-secret && printf 'environment ok\n'` run := runExampleCommand(t, ctx, binary, root, baseURL, file, "inspector", command, false) + t.Logf("verified run: id=%s sandbox=%s status=%s output=%q", run.ID, run.SandboxID, run.Status, run.Output) if !strings.Contains(run.Output, "environment ok") { t.Fatalf("environment run = %#v", run) } @@ -134,6 +139,7 @@ func TestE2EExamplesDocker(t *testing.T) { if strings.TrimSpace(out) != "persistent" { t.Fatalf("persistent volume output = %q", out) } + t.Logf("verified resumed volume output: %q", strings.TrimSpace(out)) exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "exec", run.SandboxID, "--", "sh", "-c", "if touch /fixtures/unexpected 2>/dev/null; then exit 1; fi") exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "stop", run.SandboxID) exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "rm", run.SandboxID) @@ -163,6 +169,7 @@ func TestE2EExamplesDocker(t *testing.T) { applyExampleProject(t, ctx, binary, root, baseURL, file) defer downExampleProject(t, ctx, binary, root, baseURL, file) out := exampleCLI(t, ctx, binary, root, baseURL, "--file", file, "scheduler", "ls", "reviewer") + t.Logf("verified scheduler list:\n%s", strings.TrimSpace(out)) if !strings.Contains(out, "hourly-review") || !strings.Contains(out, "cron") { t.Fatalf("cron scheduler list = %q", out) } @@ -177,6 +184,7 @@ func TestE2EExamplesDocker(t *testing.T) { applyExampleProject(t, ctx, binary, root, baseURL, file) defer downExampleProject(t, ctx, binary, root, baseURL, file) run := runExampleCommand(t, ctx, binary, root, baseURL, file, "worker", "cat /opt/agent-compose-example.txt", false) + t.Logf("verified run: id=%s sandbox=%s status=%s output=%q", run.ID, run.SandboxID, run.Status, run.Output) if !strings.Contains(run.Output, "built-by-agent-compose") { t.Fatalf("build run = %#v", run) } @@ -283,6 +291,7 @@ func waitForExampleSchedulerEvent(t *testing.T, ctx context.Context, baseURL, pr t.Fatalf("scheduler run failed: %s", event.GetMessage()) } if strings.Contains(event.GetMessage(), want) || strings.Contains(event.GetPayloadJson(), want) { + t.Logf("verified scheduler event: type=%s message=%q payload=%s", event.GetType(), event.GetMessage(), event.GetPayloadJson()) return } } From 8fd659ab7a6827322917737c1560a536af13afeb Mon Sep 17 00:00:00 2001 From: eetoc Date: Wed, 15 Jul 2026 12:30:45 +0800 Subject: [PATCH 4/5] docs: present example outputs as reusable results --- examples/agent-compose/boxlite-minimal/README.md | 4 ++-- examples/agent-compose/boxlite-minimal/README.zh-CN.md | 4 ++-- examples/agent-compose/docker-build/README.md | 9 +++++---- examples/agent-compose/docker-build/README.zh-CN.md | 8 ++++---- examples/agent-compose/docker-env-secrets/README.md | 8 ++++---- .../agent-compose/docker-env-secrets/README.zh-CN.md | 6 +++--- examples/agent-compose/docker-minimal/README.md | 6 ++---- examples/agent-compose/docker-minimal/README.zh-CN.md | 5 ++--- examples/agent-compose/docker-multi-agent/README.md | 4 ++-- .../agent-compose/docker-multi-agent/README.zh-CN.md | 4 ++-- examples/agent-compose/docker-scheduler-cron/README.md | 7 +++---- .../agent-compose/docker-scheduler-cron/README.zh-CN.md | 5 ++--- .../docker-scheduler-script-runtime/README.md | 8 ++++---- .../docker-scheduler-script-runtime/README.zh-CN.md | 8 ++++---- .../agent-compose/docker-scheduler-script-url/README.md | 8 ++++---- .../docker-scheduler-script-url/README.zh-CN.md | 8 ++++---- .../agent-compose/docker-scheduler-timeout/README.md | 5 ++--- .../docker-scheduler-timeout/README.zh-CN.md | 5 ++--- .../agent-compose/docker-volume-persistence/README.md | 5 +++-- .../docker-volume-persistence/README.zh-CN.md | 4 ++-- .../agent-compose/docker-workspace-lifecycle/README.md | 4 ++-- .../docker-workspace-lifecycle/README.zh-CN.md | 4 ++-- examples/agent-compose/microsandbox-minimal/README.md | 4 ++-- .../agent-compose/microsandbox-minimal/README.zh-CN.md | 4 ++-- 24 files changed, 66 insertions(+), 71 deletions(-) diff --git a/examples/agent-compose/boxlite-minimal/README.md b/examples/agent-compose/boxlite-minimal/README.md index 2d0653948..6d14f4d7a 100644 --- a/examples/agent-compose/boxlite-minimal/README.md +++ b/examples/agent-compose/boxlite-minimal/README.md @@ -31,9 +31,9 @@ On a prepared host, continue with `run reviewer --command "uname -a"`, inspect the returned sandbox, and finish with `down`. Runtime success is deliberately not claimed by this repository-local validation. -## Real local config output +## Normalized config output -Captured with the current CLI on 2026-07-15: +`agent-compose config` produces: ```yaml name: boxlite-minimal diff --git a/examples/agent-compose/boxlite-minimal/README.zh-CN.md b/examples/agent-compose/boxlite-minimal/README.zh-CN.md index 54948a840..a2fe377b7 100644 --- a/examples/agent-compose/boxlite-minimal/README.zh-CN.md +++ b/examples/agent-compose/boxlite-minimal/README.zh-CN.md @@ -30,9 +30,9 @@ compose 文件会进入常规测试,但本地未验证 runtime 执行。运行 上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后执行 `down`。仓库本地验证不会声称 BoxLite runtime 已成功运行。 -## 本地真实 config 输出 +## Config 归一化输出 -以下输出由当前 CLI 于 2026-07-15 采集: +`agent-compose config` 输出如下: ```yaml name: boxlite-minimal diff --git a/examples/agent-compose/docker-build/README.md b/examples/agent-compose/docker-build/README.md index ca9f5241b..ae278d6ee 100644 --- a/examples/agent-compose/docker-build/README.md +++ b/examples/agent-compose/docker-build/README.md @@ -31,11 +31,11 @@ clarity; automation should copy the example and substitute unique tags. `build` must complete and create both local tags. The worker command reads the file written during the image build and must print `built-by-agent-compose`. After `down`, both `rmi` commands remove the tutorial images. Avoid these fixed -tags in concurrent automation; the E2E creates unique tags for that reason. +tags in concurrent automation; use a unique tag for each build. -## Real verification output +## Example successful output -Captured from a real Docker build and guest run on 2026-07-15: +A successful image build and guest run produces output like: ```console status=succeeded @@ -44,4 +44,5 @@ sandbox=78459590803602e1945bdac9e3c74a1d9a656c29f57b6169bab757d1779b1d7e built-by-agent-compose ``` -The E2E substituted a unique image tag; generated run and sandbox IDs differ. +Generated run and sandbox IDs differ. Concurrent automation should substitute +a unique image tag. diff --git a/examples/agent-compose/docker-build/README.zh-CN.md b/examples/agent-compose/docker-build/README.zh-CN.md index 2796df8b2..651022514 100644 --- a/examples/agent-compose/docker-build/README.zh-CN.md +++ b/examples/agent-compose/docker-build/README.zh-CN.md @@ -28,11 +28,11 @@ image。示例使用固定 tag 以便阅读;自动化测试应复制示例并 `build` 必须完成并创建两个本地 tag。worker 命令读取镜像构建阶段写入的文件,输出 必须为 `built-by-agent-compose`。`down` 后两个 `rmi` 命令删除教程镜像。并发自动化 -不要复用这些固定 tag;E2E 会生成唯一 tag。 +不要复用这些固定 tag;每次 build 应使用唯一 tag。 -## 真实验证输出 +## 成功输出示例 -以下结果采集自 2026-07-15 的真实 Docker build 和 guest run: +镜像构建和 guest run 成功后,输出示例如下: ```console status=succeeded @@ -41,4 +41,4 @@ sandbox=78459590803602e1945bdac9e3c74a1d9a656c29f57b6169bab757d1779b1d7e built-by-agent-compose ``` -E2E 使用唯一 image tag;动态 run 和 sandbox ID 会不同。 +动态 run 和 sandbox ID 会不同;并发自动化应替换为唯一 image tag。 diff --git a/examples/agent-compose/docker-env-secrets/README.md b/examples/agent-compose/docker-env-secrets/README.md index ad235b514..bdcd09774 100644 --- a/examples/agent-compose/docker-env-secrets/README.md +++ b/examples/agent-compose/docker-env-secrets/README.md @@ -31,9 +31,9 @@ Before `up`, confirm `agent-compose config` contains `********` and never prints real guest received both scopes. The value is a non-sensitive fixture; do not commit production secrets or use this pattern as a secret manager. -## Real verification output +## Example successful output -Captured from the real-daemon Docker E2E on 2026-07-15: +A successful environment check produces output like: ```console status=succeeded @@ -42,5 +42,5 @@ sandbox=9fffd2978773c87708dc46facb8bf5ba8b1edf275382afb56380b0294a497939 environment ok ``` -The E2E also asserted that rendered config contained `********` and did not -contain the fixture secret. Generated IDs will differ. +Rendered config must contain `********` and must not contain the fixture +secret. Generated IDs will differ. diff --git a/examples/agent-compose/docker-env-secrets/README.zh-CN.md b/examples/agent-compose/docker-env-secrets/README.zh-CN.md index 11a278a2c..912c72a2d 100644 --- a/examples/agent-compose/docker-env-secrets/README.zh-CN.md +++ b/examples/agent-compose/docker-env-secrets/README.zh-CN.md @@ -29,9 +29,9 @@ env 只属于该 agent。启动 CLI 时的进程环境变量优先于 `example.e `safe-example-secret`。command run 必须输出 `environment ok`,证明真实 guest 收到 两个作用域的变量。该值只是非敏感 fixture;不要提交生产 secret。 -## 真实验证输出 +## 成功输出示例 -以下结果采集自 2026-07-15 的真实 daemon Docker E2E: +环境检查成功后,输出示例如下: ```console status=succeeded @@ -40,4 +40,4 @@ sandbox=9fffd2978773c87708dc46facb8bf5ba8b1edf275382afb56380b0294a497939 environment ok ``` -E2E 同时断言渲染配置包含 `********` 且不包含 fixture secret。动态 ID 会不同。 +渲染配置必须包含 `********` 且不能包含 fixture secret。动态 ID 会不同。 diff --git a/examples/agent-compose/docker-minimal/README.md b/examples/agent-compose/docker-minimal/README.md index 080a0c812..e680c96a2 100644 --- a/examples/agent-compose/docker-minimal/README.md +++ b/examples/agent-compose/docker-minimal/README.md @@ -171,11 +171,9 @@ NAMES IMAGE agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds ``` -### 5. Current live-provider verification +### 5. Successful provider run -The following fields were captured on 2026-07-15 from the current source, -published guest image, real daemon facade, guest Codex CLI, and the provider -configured in the local `.env`: +With a working daemon provider, a successful prompt run returns fields like: ```json { diff --git a/examples/agent-compose/docker-minimal/README.zh-CN.md b/examples/agent-compose/docker-minimal/README.zh-CN.md index 2885a62de..bfc1bddeb 100644 --- a/examples/agent-compose/docker-minimal/README.zh-CN.md +++ b/examples/agent-compose/docker-minimal/README.zh-CN.md @@ -169,10 +169,9 @@ NAMES IMAGE agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds ``` -### 5. 当前真实 provider 验证 +### 5. Provider 成功输出 -以下字段采集于 2026-07-15,链路为当前源码、发布版 guest 镜像、真实 daemon -facade、guest Codex CLI,以及本地 `.env` 配置的 provider: +daemon 配置可用 provider 后,prompt run 成功输出如下: ```json { diff --git a/examples/agent-compose/docker-multi-agent/README.md b/examples/agent-compose/docker-multi-agent/README.md index b20df9186..409784e1f 100644 --- a/examples/agent-compose/docker-multi-agent/README.md +++ b/examples/agent-compose/docker-multi-agent/README.md @@ -34,9 +34,9 @@ prompts apply when the agents are run with `--prompt`. run/sandbox IDs. Use `--prompt` only when the daemon has a configured provider. `down` cleans both agents' project sandboxes. -## Real verification output +## Example successful output -Captured from the real-daemon Docker E2E on 2026-07-15: +Successful runs from both agents produce output like: ```console reviewer status=succeeded sandbox=56dc449f3f6c47169bda2ca943a7681b847e0005c5b24aca3294aa5a5cb1a78e diff --git a/examples/agent-compose/docker-multi-agent/README.zh-CN.md b/examples/agent-compose/docker-multi-agent/README.zh-CN.md index a458ceb8b..8950e5b73 100644 --- a/examples/agent-compose/docker-multi-agent/README.zh-CN.md +++ b/examples/agent-compose/docker-multi-agent/README.zh-CN.md @@ -33,9 +33,9 @@ system prompt。 fixture、输出各自 marker,并具有不同 run/sandbox ID。只有 daemon 配置了 provider 后才运行 `--prompt`。`down` 清理两个 agent 的项目 sandbox。 -## 真实验证输出 +## 成功输出示例 -以下结果采集自 2026-07-15 的真实 daemon Docker E2E: +两个 agent 都成功运行后,输出示例如下: ```console reviewer status=succeeded sandbox=56dc449f3f6c47169bda2ca943a7681b847e0005c5b24aca3294aa5a5cb1a78e diff --git a/examples/agent-compose/docker-scheduler-cron/README.md b/examples/agent-compose/docker-scheduler-cron/README.md index 8f05a6da0..ebe31b21b 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.md +++ b/examples/agent-compose/docker-scheduler-cron/README.md @@ -203,11 +203,10 @@ updated project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa d updated loader reviewer loader-reviewer-default-ed0b5bed0daa disabled by project down ``` -### 6. Current provider-path verification +### 6. Successful manual trigger -On 2026-07-15 the E2E manually triggered the cron entry through a real daemon, -Docker guest, and guest Codex CLI. Its provider endpoint was a controlled local -fixture so the result remains deterministic: +With a working provider, manually triggering the cron entry returns fields +like: ```json { diff --git a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md index 5ff76f925..4694ce519 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md @@ -198,10 +198,9 @@ updated project_scheduler reviewer scheduler-reviewer-default-ed0b5bed0daa d updated loader reviewer loader-reviewer-default-ed0b5bed0daa disabled by project down ``` -### 6. 当前 provider 路径验证 +### 6. 手动 trigger 成功输出 -2026-07-15 的 E2E 通过真实 daemon、Docker guest 和 guest Codex CLI 手动触发 cron -条目。provider endpoint 使用受控本地 fixture,以保持结果可重复: +配置可用 provider 后,手动触发 cron 条目的输出如下: ```json { diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.md b/examples/agent-compose/docker-scheduler-script-runtime/README.md index 3ce9a9175..c487c634a 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.md @@ -33,9 +33,9 @@ Inspect the two timeout triggers until their events contain `heartbeat 1` and `heartbeat 2`. That ordered output proves state persisted between distinct loader callbacks. `down` disables the interval and cleans project sandboxes. -## Real verification output +## Example successful output -Captured from the real scheduler runtime on 2026-07-15: +After the second callback succeeds, the scheduler event looks like: ```console type=loader.log @@ -43,5 +43,5 @@ message="heartbeat completed" payload={"count":2,"output":"heartbeat 2\n"} ``` -The E2E first observed `heartbeat 1`, then the event above. This proves the -loader state survived between callbacks. +The first callback outputs `heartbeat 1`, followed by the event above. This +proves the loader state survived between callbacks. diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md index 9bec6476f..fe163a498 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md @@ -31,9 +31,9 @@ state 会在 run 之间持久化;interval 保留为长期调度。该流程不 timeout trigger,直到 event 分别包含 `heartbeat 1` 和 `heartbeat 2`;有序输出证明 state 在不同 loader callback 间持久化。`down` 禁用 interval 并清理 sandbox。 -## 真实验证输出 +## 成功输出示例 -以下结果采集自 2026-07-15 的真实 scheduler runtime: +第二个 callback 成功后,scheduler event 如下: ```console type=loader.log @@ -41,5 +41,5 @@ message="heartbeat completed" payload={"count":2,"output":"heartbeat 2\n"} ``` -E2E 先观察到 `heartbeat 1`,再观察到上面的 event,证明 loader state 跨 callback -保留。 +第一个 callback 输出 `heartbeat 1`,随后出现上面的 event,证明 loader state 跨 +callback 保留。 diff --git a/examples/agent-compose/docker-scheduler-script-url/README.md b/examples/agent-compose/docker-scheduler-script-url/README.md index 5ad855ff3..80ac60972 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.md @@ -41,9 +41,9 @@ Expected checks: calendar time arrives. 5. `down` disables the scheduler and cleans project sandboxes. -## Real verification output +## Example successful output -Captured from the real scheduler runtime on 2026-07-15: +When the `source-loaded` callback succeeds, its event looks like: ```console type=loader.command.completed @@ -51,5 +51,5 @@ message="scheduler script URL ok" payload={"exitCode":0,"mode":"shell","stderrTruncated":false,"stdoutTruncated":false,"success":true} ``` -The original event also contained generated cell and sandbox IDs, omitted here -for readability. The E2E asserts the message and successful shell result. +The full event also contains generated cell and sandbox IDs, omitted here for +readability. The message and success fields confirm the script ran. diff --git a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md index 24ff3deeb..364f4170e 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md @@ -36,9 +36,9 @@ runtime 和 daemon provider 配置。`source-loaded` shell callback 不调用模 4. `daily-review` 仍按 `0 9 * * *` 调度,并在日历时间到达时调用 agent。 5. `down` 禁用 scheduler 并清理项目 sandbox。 -## 真实验证输出 +## 成功输出示例 -以下结果采集自 2026-07-15 的真实 scheduler runtime: +`source-loaded` callback 成功后,event 如下: ```console type=loader.command.completed @@ -46,5 +46,5 @@ message="scheduler script URL ok" payload={"exitCode":0,"mode":"shell","stderrTruncated":false,"stdoutTruncated":false,"success":true} ``` -原 event 还包含动态 cell 和 sandbox ID,此处为可读性省略。E2E 会断言 message 和 -shell 成功结果。 +完整 event 还包含动态 cell 和 sandbox ID,此处为可读性省略。message 和 success +字段可以确认脚本已运行。 diff --git a/examples/agent-compose/docker-scheduler-timeout/README.md b/examples/agent-compose/docker-scheduler-timeout/README.md index 766c21a17..9331142d9 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.md @@ -192,10 +192,9 @@ updated project_scheduler reviewer scheduler-reviewer-default-181247660dc1 d updated loader reviewer loader-reviewer-default-181247660dc1 disabled by project down ``` -### 7. Current automatic-timeout verification +### 7. Successful automatic timeout run -The 2026-07-15 E2E observed the automatic trigger through a real daemon, -Docker guest, and guest Codex CLI, with a controlled provider fixture: +After the automatic timeout trigger succeeds, the run reports: ```console status=RUN_STATUS_SUCCEEDED diff --git a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md index e3cdb9094..59d6c3591 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md @@ -189,10 +189,9 @@ updated project_scheduler reviewer scheduler-reviewer-default-181247660dc1 d updated loader reviewer loader-reviewer-default-181247660dc1 disabled by project down ``` -### 7. 当前自动 timeout 验证 +### 7. 自动 timeout 成功输出 -2026-07-15 的 E2E 通过真实 daemon、Docker guest 和 guest Codex CLI 观察自动 -trigger,provider 使用受控 fixture: +自动 timeout trigger 成功后,run 输出如下: ```console status=RUN_STATUS_SUCCEEDED diff --git a/examples/agent-compose/docker-volume-persistence/README.md b/examples/agent-compose/docker-volume-persistence/README.md index dbf79ef8a..96d122be7 100644 --- a/examples/agent-compose/docker-volume-persistence/README.md +++ b/examples/agent-compose/docker-volume-persistence/README.md @@ -35,9 +35,10 @@ fixture and write `/cache/value`; after stop/resume, `cat` must return `persistent`. The `touch` check must fail inside the read-only mount. Stop and remove the sandbox before `down` for an explicit lifecycle cleanup. -## Real verification output +## Example successful output -Captured after a real Docker sandbox stop/resume on 2026-07-15: +After a successful sandbox stop/resume, the persisted value and read-only check +look like: ```console $ agent-compose exec -- cat /cache/value diff --git a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md index 8121b3e5c..62989b4e5 100644 --- a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md +++ b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md @@ -33,9 +33,9 @@ cache 值在 sandbox stop/resume 后仍存在。`down` 会移除 project-managed `/cache/value`;stop/resume 后 `cat` 必须返回 `persistent`。`touch` 检查必须因只读 挂载而失败。最后显式 stop、rm,再执行 `down`。 -## 真实验证输出 +## 成功输出示例 -以下结果采集自 2026-07-15 真实 Docker sandbox 的 stop/resume: +sandbox 成功 stop/resume 后,持久化值和只读检查如下: ```console $ agent-compose exec -- cat /cache/value diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.md b/examples/agent-compose/docker-workspace-lifecycle/README.md index 80a053e93..251693c84 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.md @@ -40,9 +40,9 @@ Use the sandbox id returned by `run`. Confirm the file before and after stop/resume, then check that `workspace/generated.txt` does not exist on the host. `rm` deletes the stopped sandbox; `down` cleans remaining project state. -## Real verification output +## Example successful output -Captured from the real-daemon Docker E2E on 2026-07-15: +A successful run and stop/resume cycle produces output like: ```console status=succeeded diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md index b88b4c6d4..315ae0e8b 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md @@ -34,9 +34,9 @@ agent-compose down 使用 `run` 返回的 sandbox ID,在 stop/resume 前后读取文件,并确认 host 上不存在 `workspace/generated.txt`。`rm` 删除已停止 sandbox,`down` 清理剩余项目状态。 -## 真实验证输出 +## 成功输出示例 -以下结果采集自 2026-07-15 的真实 daemon Docker E2E: +成功完成 run 和 stop/resume 后,输出示例如下: ```console status=succeeded diff --git a/examples/agent-compose/microsandbox-minimal/README.md b/examples/agent-compose/microsandbox-minimal/README.md index cd48330e1..85ce30dce 100644 --- a/examples/agent-compose/microsandbox-minimal/README.md +++ b/examples/agent-compose/microsandbox-minimal/README.md @@ -31,9 +31,9 @@ and a binary whose `compiled_drivers` includes `microsandbox`. `run reviewer --command "uname -a"`, inspect the returned sandbox, and finish with `down`. Runtime success is deliberately not claimed locally. -## Real local config output +## Normalized config output -Captured with the current CLI on 2026-07-15: +`agent-compose config` produces: ```yaml name: microsandbox-minimal diff --git a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md index 8233c2175..c83673ddd 100644 --- a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md +++ b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md @@ -30,9 +30,9 @@ compose 文件会进入常规测试,但本地未验证 runtime 执行。运行 host 上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后 执行 `down`。本地不会声称 Microsandbox runtime 已成功运行。 -## 本地真实 config 输出 +## Config 归一化输出 -以下输出由当前 CLI 于 2026-07-15 采集: +`agent-compose config` 输出如下: ```yaml name: microsandbox-minimal From 689488e13a62eac30820c62de5c62c0839a24e92 Mon Sep 17 00:00:00 2001 From: eetoc Date: Wed, 15 Jul 2026 13:13:57 +0800 Subject: [PATCH 5/5] docs: make example guides publication-ready --- examples/agent-compose/README.md | 6 ++-- examples/agent-compose/README.zh-CN.md | 4 +-- .../agent-compose/boxlite-minimal/README.md | 32 ++++++++++++------- .../boxlite-minimal/README.zh-CN.md | 30 ++++++++++------- examples/agent-compose/docker-build/README.md | 7 ++-- .../docker-build/README.zh-CN.md | 7 ++-- .../docker-env-secrets/README.md | 15 ++++++--- .../docker-env-secrets/README.zh-CN.md | 15 ++++++--- .../agent-compose/docker-minimal/README.md | 17 ++++------ .../docker-minimal/README.zh-CN.md | 15 ++++----- .../docker-multi-agent/README.md | 7 ++-- .../docker-multi-agent/README.zh-CN.md | 7 ++-- .../docker-scheduler-cron/README.md | 15 ++++----- .../docker-scheduler-cron/README.zh-CN.md | 13 +++----- .../docker-scheduler-script-runtime/README.md | 7 ++-- .../README.zh-CN.md | 7 ++-- .../docker-scheduler-script-url/README.md | 5 +-- .../README.zh-CN.md | 5 +-- .../docker-scheduler-timeout/README.md | 16 ++++------ .../docker-scheduler-timeout/README.zh-CN.md | 13 +++----- .../docker-volume-persistence/README.md | 7 ++-- .../docker-volume-persistence/README.zh-CN.md | 7 ++-- .../docker-workspace-lifecycle/README.md | 7 ++-- .../README.zh-CN.md | 7 ++-- .../microsandbox-minimal/README.md | 32 ++++++++++++------- .../microsandbox-minimal/README.zh-CN.md | 30 ++++++++++------- 26 files changed, 186 insertions(+), 147 deletions(-) diff --git a/examples/agent-compose/README.md b/examples/agent-compose/README.md index 83bbfb2e2..aeffb3d1c 100644 --- a/examples/agent-compose/README.md +++ b/examples/agent-compose/README.md @@ -35,6 +35,6 @@ docker pull ghcr.io/chaitin/agent-compose-guest:latest Each example has its own `README.md` with the exact commands and expected output. -BoxLite and Microsandbox additionally require Linux, KVM access, their runtime -artifacts, and a binary that includes the selected compiled driver. Their -examples are configuration templates unless run on a prepared host. +Running the BoxLite and Microsandbox examples additionally requires Linux, KVM +access, their runtime artifacts, and a binary that includes the selected +compiled driver. diff --git a/examples/agent-compose/README.zh-CN.md b/examples/agent-compose/README.zh-CN.md index c0a5878ec..555a99851 100644 --- a/examples/agent-compose/README.zh-CN.md +++ b/examples/agent-compose/README.zh-CN.md @@ -33,5 +33,5 @@ docker pull ghcr.io/chaitin/agent-compose-guest:latest 每个示例都有自己的 `README.md`,包含完整命令和预期输出。 -BoxLite 和 Microsandbox 还要求 Linux、KVM 权限、对应 runtime artifacts,以及包含 -所选 compiled driver 的二进制。未在准备好的 host 上运行时,它们只是配置模板。 +运行 BoxLite 和 Microsandbox 示例还要求 Linux、KVM 权限、对应 runtime +artifacts,以及包含所选 compiled driver 的二进制。 diff --git a/examples/agent-compose/boxlite-minimal/README.md b/examples/agent-compose/boxlite-minimal/README.md index 6d14f4d7a..54a6d23c7 100644 --- a/examples/agent-compose/boxlite-minimal/README.md +++ b/examples/agent-compose/boxlite-minimal/README.md @@ -2,7 +2,7 @@ Languages: English | [中文](README.zh-CN.md) -This is a configuration template for a BoxLite-backed agent. +This example defines a single Codex agent backed by the BoxLite driver. ## Requirements @@ -14,22 +14,29 @@ agent-compose --json version test -r /dev/kvm && test -w /dev/kvm ``` -## Run the tutorial +## Inspect the configuration ```bash agent-compose config -# On a prepared Linux/KVM host with a BoxLite-enabled binary: -agent-compose up ``` -The compose file is validated in normal tests. Runtime execution was not -verified locally and requires Linux, `/dev/kvm`, BoxLite runtime artifacts, and -a binary whose `compiled_drivers` includes `boxlite`. +The normalized output should contain `driver.name: boxlite`. + +## Run on a BoxLite host + +After the requirements above are satisfied: + +```bash +agent-compose up +agent-compose run reviewer --command "uname -a" +agent-compose ps --all +agent-compose down +``` -`config` is safe on any platform and should normalize `driver.name: boxlite`. -On a prepared host, continue with `run reviewer --command "uname -a"`, inspect -the returned sandbox, and finish with `down`. Runtime success is deliberately -not claimed by this repository-local validation. +`run` should return `status: succeeded`, a non-empty sandbox ID, and the guest +kernel information. If the binary does not include BoxLite, the command reports +the driver as unsupported. Missing KVM access or runtime artifacts cause +BoxLite initialization to fail. ## Normalized config output @@ -48,4 +55,5 @@ network: mode: default ``` -This is config validation only; no BoxLite runtime output is claimed. +This output confirms that the project selects BoxLite. Runtime output depends +on the BoxLite artifacts and KVM environment on the host. diff --git a/examples/agent-compose/boxlite-minimal/README.zh-CN.md b/examples/agent-compose/boxlite-minimal/README.zh-CN.md index a2fe377b7..9e2faa0dd 100644 --- a/examples/agent-compose/boxlite-minimal/README.zh-CN.md +++ b/examples/agent-compose/boxlite-minimal/README.zh-CN.md @@ -2,7 +2,7 @@ 语言:[English](README.md) | 中文 -这是一个使用 BoxLite driver 的配置模板。 +本示例定义一个使用 BoxLite driver 的 Codex agent。 ## 环境要求 @@ -14,21 +14,28 @@ agent-compose --json version test -r /dev/kvm && test -w /dev/kvm ``` -## 运行教程 +## 查看配置 ```bash agent-compose config -# 在准备好的 Linux/KVM 主机和包含 BoxLite 的二进制上: -agent-compose up ``` -compose 文件会进入常规测试,但本地未验证 runtime 执行。运行需要 Linux、 -`/dev/kvm`、BoxLite runtime artifacts,并且二进制的 `compiled_drivers` 包含 -`boxlite`。 +归一化输出应包含 `driver.name: boxlite`。 + +## 在 BoxLite host 上运行 + +满足上述环境要求后执行: + +```bash +agent-compose up +agent-compose run reviewer --command "uname -a" +agent-compose ps --all +agent-compose down +``` -`config` 可在任意平台安全执行,应归一化出 `driver.name: boxlite`。在准备好的 host -上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后执行 -`down`。仓库本地验证不会声称 BoxLite runtime 已成功运行。 +`run` 应返回 `status: succeeded`、非空 sandbox ID 和 guest kernel 信息。如果二进制 +未包含 BoxLite,命令会报告 driver 不受支持;缺少 KVM 权限或 runtime artifacts +会导致 BoxLite 初始化失败。 ## Config 归一化输出 @@ -47,4 +54,5 @@ network: mode: default ``` -这里只验证 config,不声称 BoxLite runtime 已运行成功。 +该输出表明 project 已选择 BoxLite;runtime 输出取决于 host 上的 BoxLite artifacts +和 KVM 环境。 diff --git a/examples/agent-compose/docker-build/README.md b/examples/agent-compose/docker-build/README.md index ae278d6ee..42497bd55 100644 --- a/examples/agent-compose/docker-build/README.md +++ b/examples/agent-compose/docker-build/README.md @@ -11,7 +11,8 @@ published guest base image. `build.context` is this directory, `dockerfile` selects `Dockerfile`, `args` supplies the marker, and `tags` adds a second local tag to the primary `image` reference. -## Run the tutorial +## Run the example +From this example directory: ```bash agent-compose build @@ -26,14 +27,14 @@ The expected marker is `built-by-agent-compose`. The build requires Docker and access to the published guest base image. The example uses fixed local tags for clarity; automation should copy the example and substitute unique tags. -## What to verify +## Expected result `build` must complete and create both local tags. The worker command reads the file written during the image build and must print `built-by-agent-compose`. After `down`, both `rmi` commands remove the tutorial images. Avoid these fixed tags in concurrent automation; use a unique tag for each build. -## Example successful output +## Example output A successful image build and guest run produces output like: diff --git a/examples/agent-compose/docker-build/README.zh-CN.md b/examples/agent-compose/docker-build/README.zh-CN.md index 651022514..70bdeec09 100644 --- a/examples/agent-compose/docker-build/README.zh-CN.md +++ b/examples/agent-compose/docker-build/README.zh-CN.md @@ -10,7 +10,8 @@ Docker 和 daemon 必须已启动,Docker 还需能获得发布版 guest 基础 `build.context` 指向本目录,`dockerfile` 选择 Dockerfile,`args` 提供 marker, `tags` 为主 `image` 引用增加第二个本地 tag。 -## 运行教程 +## 运行示例 +在示例目录中执行: ```bash agent-compose build @@ -24,13 +25,13 @@ agent-compose rmi agent-compose-example-build:local --force 预期 marker 为 `built-by-agent-compose`。构建需要 Docker 能访问发布版 guest base image。示例使用固定 tag 以便阅读;自动化测试应复制示例并替换成唯一 tag。 -## 验证要点 +## 预期结果 `build` 必须完成并创建两个本地 tag。worker 命令读取镜像构建阶段写入的文件,输出 必须为 `built-by-agent-compose`。`down` 后两个 `rmi` 命令删除教程镜像。并发自动化 不要复用这些固定 tag;每次 build 应使用唯一 tag。 -## 成功输出示例 +## 输出示例 镜像构建和 guest run 成功后,输出示例如下: diff --git a/examples/agent-compose/docker-env-secrets/README.md b/examples/agent-compose/docker-env-secrets/README.md index bdcd09774..748e8cfe0 100644 --- a/examples/agent-compose/docker-env-secrets/README.md +++ b/examples/agent-compose/docker-env-secrets/README.md @@ -11,12 +11,19 @@ Docker and the daemon must be running. `env_file: example.env` supplies `EXAMPLE_SECRET`; project `variables` apply to every agent, while `agents.*.env` is agent-scoped. `secret: true` marks values for redaction in rendered config. -## Run the tutorial +## Run the example +From this example directory: ```bash agent-compose config agent-compose up -agent-compose run inspector --command 'test "$PROJECT_VALUE" = project-level && test "$AGENT_VALUE" = agent-level && test "$PROJECT_SECRET" = safe-example-secret && test "$AGENT_SECRET" = safe-example-secret && echo "environment ok"' +agent-compose run inspector --command ' + test "$PROJECT_VALUE" = project-level && + test "$AGENT_VALUE" = agent-level && + test "$PROJECT_SECRET" = safe-example-secret && + test "$AGENT_SECRET" = safe-example-secret && + echo "environment ok" +' agent-compose down ``` @@ -24,14 +31,14 @@ agent-compose down runs, while agent env is scoped to that agent. Process environment values passed to the CLI take precedence over `example.env`. -## What to verify +## Expected result Before `up`, confirm `agent-compose config` contains `********` and never prints `safe-example-secret`. The command run must print `environment ok`, proving the real guest received both scopes. The value is a non-sensitive fixture; do not commit production secrets or use this pattern as a secret manager. -## Example successful output +## Example output A successful environment check produces output like: diff --git a/examples/agent-compose/docker-env-secrets/README.zh-CN.md b/examples/agent-compose/docker-env-secrets/README.zh-CN.md index 912c72a2d..7752a96dc 100644 --- a/examples/agent-compose/docker-env-secrets/README.zh-CN.md +++ b/examples/agent-compose/docker-env-secrets/README.zh-CN.md @@ -11,25 +11,32 @@ Docker 和 daemon 必须已启动。`env_file: example.env` 提供 `EXAMPLE_SECR project `variables` 作用于所有 agent,`agents.*.env` 只作用于对应 agent。 `secret: true` 要求渲染配置时隐藏该值。 -## 运行教程 +## 运行示例 +在示例目录中执行: ```bash agent-compose config agent-compose up -agent-compose run inspector --command 'test "$PROJECT_VALUE" = project-level && test "$AGENT_VALUE" = agent-level && test "$PROJECT_SECRET" = safe-example-secret && test "$AGENT_SECRET" = safe-example-secret && echo "environment ok"' +agent-compose run inspector --command ' + test "$PROJECT_VALUE" = project-level && + test "$AGENT_VALUE" = agent-level && + test "$PROJECT_SECRET" = safe-example-secret && + test "$AGENT_SECRET" = safe-example-secret && + echo "environment ok" +' agent-compose down ``` `config` 会隐藏标记为 `secret: true` 的值。Project variables 会传给 run,agent env 只属于该 agent。启动 CLI 时的进程环境变量优先于 `example.env`。 -## 验证要点 +## 预期结果 执行 `up` 前,确认 `agent-compose config` 包含 `********`,且不输出 `safe-example-secret`。command run 必须输出 `environment ok`,证明真实 guest 收到 两个作用域的变量。该值只是非敏感 fixture;不要提交生产 secret。 -## 成功输出示例 +## 输出示例 环境检查成功后,输出示例如下: diff --git a/examples/agent-compose/docker-minimal/README.md b/examples/agent-compose/docker-minimal/README.md index e680c96a2..aa7f25528 100644 --- a/examples/agent-compose/docker-minimal/README.md +++ b/examples/agent-compose/docker-minimal/README.md @@ -87,7 +87,7 @@ Expected result: - `up` creates or updates the project and managed agent definition. - `ps` shows the `reviewer` agent using Docker and the published guest image. -## Optional run test +## Run the agent To start a runtime session and keep it alive: @@ -112,13 +112,10 @@ Clean up running project sessions: agent-compose down ``` -## Verification output +## Example output -Output from a local verification run. - -The recorded output below used the equivalent locally built -`agent-compose-guest:latest` image. The committed compose file now uses the -published image; generated IDs, hashes, and timestamps will also differ. +A successful run produces output like the following. Generated IDs, hashes, +and timestamps will differ. ### 1. Config normalization @@ -128,7 +125,7 @@ name: docker-minimal agents: - name: reviewer provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: name: docker docker: {} @@ -160,7 +157,7 @@ created agent_definition reviewer ```console $ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer disabled - - - docker agent-compose-guest:latest +reviewer disabled - - - docker ghcr.io/chaitin/agent-compose-guest:latest ``` ### 4. Docker runtime container @@ -168,7 +165,7 @@ reviewer disabled - - - docker agent-compose-gues ```console $ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' NAMES IMAGE STATUS -agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds +agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f ghcr.io/chaitin/agent-compose-guest:latest Up 14 seconds ``` ### 5. Successful provider run diff --git a/examples/agent-compose/docker-minimal/README.zh-CN.md b/examples/agent-compose/docker-minimal/README.zh-CN.md index bfc1bddeb..f88ce3f27 100644 --- a/examples/agent-compose/docker-minimal/README.zh-CN.md +++ b/examples/agent-compose/docker-minimal/README.zh-CN.md @@ -87,7 +87,7 @@ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-co - `up` 创建或更新 project 和 managed agent definition。 - `ps` 显示 `reviewer` agent 使用 Docker 和发布版 guest 镜像。 -## 可选运行测试 +## 运行 agent 启动一次 runtime session,并在运行结束后保留 session: @@ -111,12 +111,9 @@ agent-compose exec -- env agent-compose down ``` -## 验证输出 +## 输出示例 -以下为一次本地验证运行的输出。 - -下面记录的输出使用等价的本地构建镜像 `agent-compose-guest:latest`。当前 compose -文件使用发布版镜像;动态生成的 ID、hash 和时间戳也会不同。 +成功运行后的输出示例如下。动态生成的 ID、hash 和时间戳会不同。 ### 1. 配置标准化 @@ -126,7 +123,7 @@ name: docker-minimal agents: - name: reviewer provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: name: docker docker: {} @@ -158,7 +155,7 @@ created agent_definition reviewer ```console $ go run ./cmd/agent-compose --file examples/agent-compose/docker-minimal/agent-compose.yml ps AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer disabled - - - docker agent-compose-guest:latest +reviewer disabled - - - docker ghcr.io/chaitin/agent-compose-guest:latest ``` ### 4. Docker runtime 容器 @@ -166,7 +163,7 @@ reviewer disabled - - - docker agent-compose-gues ```console $ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' NAMES IMAGE STATUS -agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f agent-compose-guest:latest Up 14 seconds +agent-compose-8aa2625d-db67-4428-82ae-8bef1a137a2f ghcr.io/chaitin/agent-compose-guest:latest Up 14 seconds ``` ### 5. Provider 成功输出 diff --git a/examples/agent-compose/docker-multi-agent/README.md b/examples/agent-compose/docker-multi-agent/README.md index 409784e1f..e3f27f60e 100644 --- a/examples/agent-compose/docker-multi-agent/README.md +++ b/examples/agent-compose/docker-multi-agent/README.md @@ -11,7 +11,8 @@ Docker and the daemon must be running. Both agents refer to the same local workspace source, but each run creates an independent sandbox copy. Their different `system_prompt` values apply to model prompts, not shell commands. -## Run the tutorial +## Run the example +From this example directory: ```bash agent-compose up @@ -27,14 +28,14 @@ agent-compose down The command path does not invoke the configured provider. The distinct system prompts apply when the agents are run with `--prompt`. -## What to verify +## Expected result `inspect agent` should show two definitions. Both command runs should read `workspace/project.txt`, return their respective marker, and have different run/sandbox IDs. Use `--prompt` only when the daemon has a configured provider. `down` cleans both agents' project sandboxes. -## Example successful output +## Example output Successful runs from both agents produce output like: diff --git a/examples/agent-compose/docker-multi-agent/README.zh-CN.md b/examples/agent-compose/docker-multi-agent/README.zh-CN.md index 8950e5b73..6b83718c7 100644 --- a/examples/agent-compose/docker-multi-agent/README.zh-CN.md +++ b/examples/agent-compose/docker-multi-agent/README.zh-CN.md @@ -11,7 +11,8 @@ Docker 和 daemon 必须已启动。两个 agent 引用相同本地 workspace 创建独立 sandbox 副本。不同的 `system_prompt` 只作用于模型 prompt,不作用于 shell command。 -## 运行教程 +## 运行示例 +在示例目录中执行: ```bash agent-compose up @@ -27,13 +28,13 @@ agent-compose down command 路径不会调用 provider;使用 `--prompt` 时才会应用两个 agent 各自的 system prompt。 -## 验证要点 +## 预期结果 `inspect agent` 应显示两个 definition。两个 command run 都应能读取 workspace fixture、输出各自 marker,并具有不同 run/sandbox ID。只有 daemon 配置了 provider 后才运行 `--prompt`。`down` 清理两个 agent 的项目 sandbox。 -## 成功输出示例 +## 输出示例 两个 agent 都成功运行后,输出示例如下: diff --git a/examples/agent-compose/docker-scheduler-cron/README.md b/examples/agent-compose/docker-scheduler-cron/README.md index ebe31b21b..440843fbc 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.md +++ b/examples/agent-compose/docker-scheduler-cron/README.md @@ -102,13 +102,10 @@ scheduler: Use cron when you want calendar-based scheduling. Use interval when you want short local feedback while testing. -## Verification output +## Example output -Output from a local verification run. - -The recorded output below used the equivalent locally built -`agent-compose-guest:latest` image. The committed compose file now uses the -published image; generated IDs, hashes, and timestamps will also differ. +A successful run produces output like the following. Generated IDs, hashes, +and timestamps will differ. ### 1. Config normalization @@ -118,7 +115,7 @@ name: docker-scheduler-cron agents: - name: reviewer provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: name: docker docker: {} @@ -159,7 +156,7 @@ created loader docker-scheduler-cron/reviewer scheduler ```console $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled - - - docker agent-compose-guest:latest +reviewer enabled - - - docker ghcr.io/chaitin/agent-compose-guest:latest ``` ### 4. Inspect project @@ -178,7 +175,7 @@ $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron { "agent_name": "reviewer", "provider": "codex", - "image": "agent-compose-guest:latest", + "image": "ghcr.io/chaitin/agent-compose-guest:latest", "driver": "docker", "scheduler_enabled": true } diff --git a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md index 4694ce519..37f54936f 100644 --- a/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-cron/README.zh-CN.md @@ -98,12 +98,9 @@ scheduler: 需要基于日历时间调度时使用 cron;需要本地快速反馈时使用 interval。 -## 验证输出 +## 输出示例 -以下为一次本地验证运行的输出。 - -下面记录的输出使用等价的本地构建镜像 `agent-compose-guest:latest`。当前 compose -文件使用发布版镜像;动态生成的 ID、hash 和时间戳也会不同。 +成功运行后的输出示例如下。动态生成的 ID、hash 和时间戳会不同。 ### 1. 配置标准化 @@ -113,7 +110,7 @@ name: docker-scheduler-cron agents: - name: reviewer provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: name: docker docker: {} @@ -154,7 +151,7 @@ created loader docker-scheduler-cron/reviewer scheduler ```console $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron/agent-compose.yml ps AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled - - - docker agent-compose-guest:latest +reviewer enabled - - - docker ghcr.io/chaitin/agent-compose-guest:latest ``` ### 4. 查看 project @@ -173,7 +170,7 @@ $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-cron { "agent_name": "reviewer", "provider": "codex", - "image": "agent-compose-guest:latest", + "image": "ghcr.io/chaitin/agent-compose-guest:latest", "driver": "docker", "scheduler_enabled": true } diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.md b/examples/agent-compose/docker-scheduler-script-runtime/README.md index c487c634a..2e9794846 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.md @@ -11,7 +11,8 @@ Docker and the daemon must be running. The scheduler uses `sandbox_policy: new` and inline QJS. `scheduler.state` belongs to the loader and persists across callbacks; `scheduler.shell` runs in a Docker sandbox. No provider call occurs. -## Run the tutorial +## Run the example +From this example directory: ```bash agent-compose up @@ -26,14 +27,14 @@ The two automatic timeout runs produce `heartbeat 1` and `heartbeat 2`, demonstrating that loader state persists between runs. The interval remains as the long-running schedule. No model provider is called. -## What to verify +## Expected result `scheduler ls heartbeat` should list `warmup`, `follow-up`, and `heartbeat`. Inspect the two timeout triggers until their events contain `heartbeat 1` and `heartbeat 2`. That ordered output proves state persisted between distinct loader callbacks. `down` disables the interval and cleans project sandboxes. -## Example successful output +## Example output After the second callback succeeds, the scheduler event looks like: diff --git a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md index fe163a498..441a149e8 100644 --- a/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-runtime/README.zh-CN.md @@ -11,7 +11,8 @@ Docker 和 daemon 必须已启动。scheduler 使用 `sandbox_policy: new` 和 i `scheduler.state` 归 loader 所有并跨 callback 保留;`scheduler.shell` 在 Docker sandbox 中执行,不调用模型 provider。 -## 运行教程 +## 运行示例 +在示例目录中执行: ```bash agent-compose up @@ -25,13 +26,13 @@ agent-compose down 两个自动 timeout run 分别产生 `heartbeat 1` 和 `heartbeat 2`,证明 Loader state 会在 run 之间持久化;interval 保留为长期调度。该流程不会调用模型 provider。 -## 验证要点 +## 预期结果 `scheduler ls heartbeat` 应列出 `warmup`、`follow-up` 和 `heartbeat`。检查两个 timeout trigger,直到 event 分别包含 `heartbeat 1` 和 `heartbeat 2`;有序输出证明 state 在不同 loader callback 间持久化。`down` 禁用 interval 并清理 sandbox。 -## 成功输出示例 +## 输出示例 第二个 callback 成功后,scheduler event 如下: diff --git a/examples/agent-compose/docker-scheduler-script-url/README.md b/examples/agent-compose/docker-scheduler-script-url/README.md index 80ac60972..d79e35071 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.md @@ -10,7 +10,8 @@ also defines a two-second `scheduler.timeout(...)` shell callback. The timeout provides a quick way to verify that the external file was loaded; it does not replace the cron tutorial. -## Run step by step +## Run the example +From this example directory: ```bash agent-compose config @@ -41,7 +42,7 @@ Expected checks: calendar time arrives. 5. `down` disables the scheduler and cleans project sandboxes. -## Example successful output +## Example output When the `source-loaded` callback succeeds, its event looks like: diff --git a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md index 364f4170e..44b4d3b92 100644 --- a/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-script-url/README.zh-CN.md @@ -9,7 +9,8 @@ `scheduler.timeout(...)` shell callback。timeout 用于快速验证外部文件已加载,不会 替代 cron 教程。 -## 分步运行 +## 运行示例 +在示例目录中执行: ```bash agent-compose config @@ -36,7 +37,7 @@ runtime 和 daemon provider 配置。`source-loaded` shell callback 不调用模 4. `daily-review` 仍按 `0 9 * * *` 调度,并在日历时间到达时调用 agent。 5. `down` 禁用 scheduler 并清理项目 sandbox。 -## 成功输出示例 +## 输出示例 `source-loaded` callback 成功后,event 如下: diff --git a/examples/agent-compose/docker-scheduler-timeout/README.md b/examples/agent-compose/docker-scheduler-timeout/README.md index 9331142d9..f793097de 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.md @@ -89,14 +89,10 @@ Expected result: - `logs --run ` prints the agent output. - `down` disables the managed scheduler and loader. -## Verification output +## Example output -Output from a local verification run. The run id below is from that run; yours -will differ. - -The recorded output used the equivalent locally built -`agent-compose-guest:latest` image. The committed compose file now uses the -published image; generated IDs, hashes, timestamps, and durations will differ. +A successful scheduled run produces output like the following. Generated IDs, +hashes, timestamps, and durations will differ. ### 1. Config normalization @@ -106,7 +102,7 @@ name: docker-scheduler-timeout agents: - name: reviewer provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: name: docker docker: {} @@ -147,7 +143,7 @@ created loader docker-scheduler-timeout/reviewer scheduler ```console $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker agent-compose-guest:latest +reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker ghcr.io/chaitin/agent-compose-guest:latest ``` ### 4. Inspect successful run @@ -167,7 +163,7 @@ $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-time "output": "timeout scheduler ok", "result_json": "{\"agent\":\"codex\",\"exitCode\":0,\"stopReason\":\"completed\",\"success\":true}", "driver": "docker", - "image_ref": "agent-compose-guest:latest" + "image_ref": "ghcr.io/chaitin/agent-compose-guest:latest" } ``` diff --git a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md index 59d6c3591..45de28501 100644 --- a/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md +++ b/examples/agent-compose/docker-scheduler-timeout/README.zh-CN.md @@ -88,12 +88,9 @@ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeou - `logs --run ` 输出 agent 日志。 - `down` 禁用 managed scheduler 和 loader。 -## 验证输出 +## 输出示例 -以下为一次本地验证运行的输出。其中的 run id 来自该次运行,你本地的会不同。 - -下面记录的输出使用等价的本地构建镜像 `agent-compose-guest:latest`。当前 compose -文件使用发布版镜像;动态生成的 ID、hash、时间戳和耗时也会不同。 +scheduled run 成功后的输出示例如下。动态生成的 ID、hash、时间戳和耗时会不同。 ### 1. 配置标准化 @@ -103,7 +100,7 @@ name: docker-scheduler-timeout agents: - name: reviewer provider: codex - image: agent-compose-guest:latest + image: ghcr.io/chaitin/agent-compose-guest:latest driver: name: docker docker: {} @@ -144,7 +141,7 @@ created loader docker-scheduler-timeout/reviewer scheduler ```console $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-timeout/agent-compose.yml ps AGENT SCHEDULER LATEST RUN RUN STATUS SESSION DRIVER IMAGE -reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker agent-compose-guest:latest +reviewer enabled run-reviewer-28c0ef985c8d succeeded - docker ghcr.io/chaitin/agent-compose-guest:latest ``` ### 4. 查看成功 run @@ -164,7 +161,7 @@ $ go run ./cmd/agent-compose --file examples/agent-compose/docker-scheduler-time "output": "timeout scheduler ok", "result_json": "{\"agent\":\"codex\",\"exitCode\":0,\"stopReason\":\"completed\",\"success\":true}", "driver": "docker", - "image_ref": "agent-compose-guest:latest" + "image_ref": "ghcr.io/chaitin/agent-compose-guest:latest" } ``` diff --git a/examples/agent-compose/docker-volume-persistence/README.md b/examples/agent-compose/docker-volume-persistence/README.md index 96d122be7..70834d506 100644 --- a/examples/agent-compose/docker-volume-persistence/README.md +++ b/examples/agent-compose/docker-volume-persistence/README.md @@ -11,7 +11,8 @@ Docker and the daemon must be running. The top-level `cache` volume is managed by the project and mounted read-write. `./fixtures:/fixtures:ro` resolves from the compose directory and is mounted read-only. -## Run the tutorial +## Run the example +From this example directory: ```bash agent-compose up @@ -28,14 +29,14 @@ agent-compose down The cache value survives sandbox stop/resume. `down` removes project-managed volume ownership, so do not use this example as a backup mechanism. -## What to verify +## Expected result Use the sandbox id returned by the kept run. The first command must read the fixture and write `/cache/value`; after stop/resume, `cat` must return `persistent`. The `touch` check must fail inside the read-only mount. Stop and remove the sandbox before `down` for an explicit lifecycle cleanup. -## Example successful output +## Example output After a successful sandbox stop/resume, the persisted value and read-only check look like: diff --git a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md index 62989b4e5..4bb3f85e6 100644 --- a/examples/agent-compose/docker-volume-persistence/README.zh-CN.md +++ b/examples/agent-compose/docker-volume-persistence/README.zh-CN.md @@ -10,7 +10,8 @@ Docker 和 daemon 必须已启动。顶层 `cache` volume 由项目管理并以读写方式挂载; `./fixtures:/fixtures:ro` 相对 compose 目录解析并只读挂载。 -## 运行教程 +## 运行示例 +在示例目录中执行: ```bash agent-compose up @@ -27,13 +28,13 @@ agent-compose down cache 值在 sandbox stop/resume 后仍存在。`down` 会移除 project-managed volume 的归属,不应把该示例当作备份机制。 -## 验证要点 +## 预期结果 使用保留 run 返回的 sandbox ID。第一次命令必须读取 fixture 并写入 `/cache/value`;stop/resume 后 `cat` 必须返回 `persistent`。`touch` 检查必须因只读 挂载而失败。最后显式 stop、rm,再执行 `down`。 -## 成功输出示例 +## 输出示例 sandbox 成功 stop/resume 后,持久化值和只读检查如下: diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.md b/examples/agent-compose/docker-workspace-lifecycle/README.md index 251693c84..85df433c6 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.md @@ -16,7 +16,8 @@ The `source` workspace uses the local provider and `path: ./workspace`. The `worker` agent refers to it by name. A new sandbox receives a copy; the host source directory is not mounted as a writable working tree. -## Run the tutorial +## Run the example +From this example directory: ```bash agent-compose up @@ -34,13 +35,13 @@ agent-compose down `generated.txt` survives stop/resume but is not written into the committed `workspace/` source. A second new sandbox receives a fresh source copy. -## Verification and cleanup +## Check and clean up Use the sandbox id returned by `run`. Confirm the file before and after stop/resume, then check that `workspace/generated.txt` does not exist on the host. `rm` deletes the stopped sandbox; `down` cleans remaining project state. -## Example successful output +## Example output A successful run and stop/resume cycle produces output like: diff --git a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md index 315ae0e8b..0133afaf6 100644 --- a/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md +++ b/examples/agent-compose/docker-workspace-lifecycle/README.zh-CN.md @@ -11,7 +11,8 @@ Docker 和 `agent-compose` daemon 必须已启动,并且 Docker 可获得发 镜像。`source` 使用本地 workspace provider,`worker` 按名称引用它。新 sandbox 得到源目录副本,不会把 host 源目录作为可写工作区挂载。 -## 运行教程 +## 运行示例 +在示例目录中执行: ```bash agent-compose up @@ -29,12 +30,12 @@ agent-compose down `generated.txt` 在 stop/resume 后仍存在,但不会写回仓库中的 `workspace/` source。 新建第二个 sandbox 时会得到一份全新的 source 副本。 -## 验证与清理 +## 检查与清理 使用 `run` 返回的 sandbox ID,在 stop/resume 前后读取文件,并确认 host 上不存在 `workspace/generated.txt`。`rm` 删除已停止 sandbox,`down` 清理剩余项目状态。 -## 成功输出示例 +## 输出示例 成功完成 run 和 stop/resume 后,输出示例如下: diff --git a/examples/agent-compose/microsandbox-minimal/README.md b/examples/agent-compose/microsandbox-minimal/README.md index 85ce30dce..46e001145 100644 --- a/examples/agent-compose/microsandbox-minimal/README.md +++ b/examples/agent-compose/microsandbox-minimal/README.md @@ -2,7 +2,7 @@ Languages: English | [中文](README.zh-CN.md) -This is a configuration template for a Microsandbox-backed agent. +This example defines a single Codex agent backed by the Microsandbox driver. ## Requirements @@ -14,22 +14,29 @@ agent-compose --json version test -r /dev/kvm && test -w /dev/kvm ``` -## Run the tutorial +## Inspect the configuration ```bash agent-compose config -# On a prepared Linux/KVM host with a Microsandbox-enabled binary: -agent-compose up ``` -The compose file is validated in normal tests. Runtime execution was not -verified locally and requires Linux, `/dev/kvm`, Microsandbox runtime artifacts, -and a binary whose `compiled_drivers` includes `microsandbox`. +The normalized output should contain `driver.name: microsandbox`. + +## Run on a Microsandbox host + +After the requirements above are satisfied: + +```bash +agent-compose up +agent-compose run reviewer --command "uname -a" +agent-compose ps --all +agent-compose down +``` -`config` is safe on any platform and should normalize -`driver.name: microsandbox`. On a prepared host, continue with -`run reviewer --command "uname -a"`, inspect the returned sandbox, and finish -with `down`. Runtime success is deliberately not claimed locally. +`run` should return `status: succeeded`, a non-empty sandbox ID, and the guest +kernel information. If the binary does not include Microsandbox, the command +reports the driver as unsupported. Missing KVM access or runtime artifacts +cause Microsandbox initialization to fail. ## Normalized config output @@ -48,4 +55,5 @@ network: mode: default ``` -This is config validation only; no Microsandbox runtime output is claimed. +This output confirms that the project selects Microsandbox. Runtime output +depends on the Microsandbox artifacts and KVM environment on the host. diff --git a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md index c83673ddd..7c2d6ce6c 100644 --- a/examples/agent-compose/microsandbox-minimal/README.zh-CN.md +++ b/examples/agent-compose/microsandbox-minimal/README.zh-CN.md @@ -2,7 +2,7 @@ 语言:[English](README.md) | 中文 -这是一个使用 Microsandbox driver 的配置模板。 +本示例定义一个使用 Microsandbox driver 的 Codex agent。 ## 环境要求 @@ -14,21 +14,28 @@ agent-compose --json version test -r /dev/kvm && test -w /dev/kvm ``` -## 运行教程 +## 查看配置 ```bash agent-compose config -# 在准备好的 Linux/KVM 主机和包含 Microsandbox 的二进制上: -agent-compose up ``` -compose 文件会进入常规测试,但本地未验证 runtime 执行。运行需要 Linux、 -`/dev/kvm`、Microsandbox runtime artifacts,并且二进制的 `compiled_drivers` -包含 `microsandbox`。 +归一化输出应包含 `driver.name: microsandbox`。 + +## 在 Microsandbox host 上运行 + +满足上述环境要求后执行: + +```bash +agent-compose up +agent-compose run reviewer --command "uname -a" +agent-compose ps --all +agent-compose down +``` -`config` 可在任意平台安全执行,应归一化出 `driver.name: microsandbox`。在准备好的 -host 上继续执行 `run reviewer --command "uname -a"`、检查返回的 sandbox,最后 -执行 `down`。本地不会声称 Microsandbox runtime 已成功运行。 +`run` 应返回 `status: succeeded`、非空 sandbox ID 和 guest kernel 信息。如果二进制 +未包含 Microsandbox,命令会报告 driver 不受支持;缺少 KVM 权限或 runtime +artifacts 会导致 Microsandbox 初始化失败。 ## Config 归一化输出 @@ -47,4 +54,5 @@ network: mode: default ``` -这里只验证 config,不声称 Microsandbox runtime 已运行成功。 +该输出表明 project 已选择 Microsandbox;runtime 输出取决于 host 上的 +Microsandbox artifacts 和 KVM 环境。