From 653fe4c3312ac429e959e6ccdc6cc99a77ba3f6a Mon Sep 17 00:00:00 2001 From: Nick Huo Date: Tue, 22 Sep 2026 14:46:20 -0700 Subject: [PATCH 1/5] docs: require explicit workflow output resolution in porting guide --- .../porting-to-canyonos/references/adapter.md | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.claude/skills/porting-to-canyonos/references/adapter.md b/.claude/skills/porting-to-canyonos/references/adapter.md index 4fbb841c..2207f4bb 100644 --- a/.claude/skills/porting-to-canyonos/references/adapter.md +++ b/.claude/skills/porting-to-canyonos/references/adapter.md @@ -11,7 +11,8 @@ Use this order: 1. Choose a safe entrypoint module for each service. 2. Write a no-argument synchronous adapter around source-owned behavior. 3. Bridge async or session state only when the source requires it. -4. Write the workflow and preserve parallel dispatch. +4. Write the workflow, preserve parallel dispatch, and resolve final outputs + with `.value()` before returning. Complete `manifest.md`, then validate only the authored contracts that CanyonOS does not already guarantee. @@ -22,6 +23,7 @@ only when a container loads. ## Contents - Adapter and workflow shape +- Resolving workflow outputs - Choosing the entrypoint - Bridging async - Multi-turn and session state @@ -46,6 +48,26 @@ writes there holds the LLM proxy alone, under an `__init__` that exports nothing. `from canyonos_core import deploy` builds green and raises ImportError at container start. V021. +## Resolving workflow outputs + +Every remote service call returns a Future, not its computed value. In +`main(query: str)`, explicitly call `.value()` on each Future contributing to +the final output before returning it, including values nested in dictionaries, +lists, or tuples. Do not rely on `deploy`'s automatic resolution: the authored +workflow must return concrete values even when called directly. + +```python +def main(query: str) -> dict[str, str]: + answer = agent.work(query=query) + return {"answer": answer.value()} +``` + +For a single output, use `return answer.value()`. Never return the Future +itself or convert it with `str(...)` or `json.dumps(...)` as a substitute for +resolution. `.value()` returns text; use `json.loads(...)` only when the service +returns JSON and the workflow needs the decoded structure. Leave already +concrete values unchanged. + For parallel remote calls, dispatch all work before resolving any result: ```python @@ -55,6 +77,12 @@ results = [json.loads(future.value()) for future in futures] Combining dispatch and `.value()` in one comprehension serializes the work. +Before completing the workflow, trace every `return` in `main`, including +early returns and conditional branches. Verify that every remote result in +the returned payload passes through `.value()` before parsing, formatting, or +serialization, and that no nested Future escapes. Static validation passing +does not replace this return-path review. + ## Choosing the entrypoint The entrypoint is the one module in the copy the build destroys: each agent's From aba0be80533ac99b6017e3943f10a40d1c8b6c64 Mon Sep 17 00:00:00 2001 From: Nick Huo Date: Tue, 22 Sep 2026 14:49:24 -0700 Subject: [PATCH 2/5] update --- .claude/skills/porting-to-canyonos/references/adapter.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.claude/skills/porting-to-canyonos/references/adapter.md b/.claude/skills/porting-to-canyonos/references/adapter.md index 2207f4bb..b20a60f8 100644 --- a/.claude/skills/porting-to-canyonos/references/adapter.md +++ b/.claude/skills/porting-to-canyonos/references/adapter.md @@ -53,8 +53,7 @@ at container start. V021. Every remote service call returns a Future, not its computed value. In `main(query: str)`, explicitly call `.value()` on each Future contributing to the final output before returning it, including values nested in dictionaries, -lists, or tuples. Do not rely on `deploy`'s automatic resolution: the authored -workflow must return concrete values even when called directly. +lists, or tuples. ```python def main(query: str) -> dict[str, str]: @@ -80,8 +79,7 @@ Combining dispatch and `.value()` in one comprehension serializes the work. Before completing the workflow, trace every `return` in `main`, including early returns and conditional branches. Verify that every remote result in the returned payload passes through `.value()` before parsing, formatting, or -serialization, and that no nested Future escapes. Static validation passing -does not replace this return-path review. +serialization, and that no nested Future escapes. ## Choosing the entrypoint From 8bc27efdbd8fae5d13d699c8034f975dc3916e02 Mon Sep 17 00:00:00 2001 From: Nick Huo Date: Tue, 22 Sep 2026 15:17:44 -0700 Subject: [PATCH 3/5] correct --- .claude/skills/porting-to-canyonos/references/adapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/porting-to-canyonos/references/adapter.md b/.claude/skills/porting-to-canyonos/references/adapter.md index b20a60f8..db7d9df0 100644 --- a/.claude/skills/porting-to-canyonos/references/adapter.md +++ b/.claude/skills/porting-to-canyonos/references/adapter.md @@ -63,7 +63,7 @@ def main(query: str) -> dict[str, str]: For a single output, use `return answer.value()`. Never return the Future itself or convert it with `str(...)` or `json.dumps(...)` as a substitute for -resolution. `.value()` returns text; use `json.loads(...)` only when the service +resolution. `.value()` returns the computed result; use `json.loads(...)` only when the service returns JSON and the workflow needs the decoded structure. Leave already concrete values unchanged. From feac4644d55d449c0fbe0edce29d4f8aa6840b9b Mon Sep 17 00:00:00 2001 From: Nick Huo Date: Tue, 22 Sep 2026 15:21:27 -0700 Subject: [PATCH 4/5] docs: scope future resolution guidance to workflow outputs --- .../skills/porting-to-canyonos/references/adapter.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.claude/skills/porting-to-canyonos/references/adapter.md b/.claude/skills/porting-to-canyonos/references/adapter.md index db7d9df0..649c06f4 100644 --- a/.claude/skills/porting-to-canyonos/references/adapter.md +++ b/.claude/skills/porting-to-canyonos/references/adapter.md @@ -61,11 +61,12 @@ def main(query: str) -> dict[str, str]: return {"answer": answer.value()} ``` -For a single output, use `return answer.value()`. Never return the Future -itself or convert it with `str(...)` or `json.dumps(...)` as a substitute for -resolution. `.value()` returns the computed result; use `json.loads(...)` only when the service -returns JSON and the workflow needs the decoded structure. Leave already -concrete values unchanged. +Before returning from `main`, resolve any Future included in the final output +with `.value()`. For example, if `answer` is a Future, use +`return answer.value()`. Calling `str(...)` or `json.dumps(...)` does not resolve +a Future. Decode the resolved value with `json.loads(...)` only when it is JSON +text and the workflow needs the decoded structure. Return already concrete +values unchanged. For parallel remote calls, dispatch all work before resolving any result: From 083815cc627d9c5e51ede5d88727a924cf9d8f79 Mon Sep 17 00:00:00 2001 From: Nick Huo Date: Tue, 22 Sep 2026 15:32:45 -0700 Subject: [PATCH 5/5] docs: add readiness gate and concise porting handoff --- .claude/skills/porting-to-canyonos/SKILL.md | 9 ++- .../references/llm-proxy.md | 4 +- .../references/source-survey.md | 32 +++++++- .../references/validation-and-deploy.md | 75 ++++++++++++------- 4 files changed, 85 insertions(+), 35 deletions(-) diff --git a/.claude/skills/porting-to-canyonos/SKILL.md b/.claude/skills/porting-to-canyonos/SKILL.md index 7d4a092b..35621574 100644 --- a/.claude/skills/porting-to-canyonos/SKILL.md +++ b/.claude/skills/porting-to-canyonos/SKILL.md @@ -15,7 +15,7 @@ without a question. Never deploy or ask whether to deploy from that flow. ## Progress -Copy this checklist into the response and update it while working: +Use this checklist for progress updates, not the final handoff: ```text Port progress: @@ -35,7 +35,8 @@ import root from actual imports and use `prepare.py`; do not assemble or refresh Read [references/source-survey.md](references/source-survey.md) in full. Produce its survey record and choose the smallest useful service map before writing -runtime code or configuration. +runtime code or configuration. Apply its application-readiness gate; pause the +build and cite the guide when the source has a blocking prerequisite. ## 3. Implement the port @@ -63,7 +64,9 @@ Read these only when triggered: Read [references/validation-and-deploy.md](references/validation-and-deploy.md). Validate only authored contracts that CanyonOS tooling does not strongly -guarantee. Fix every reported error, hand off warnings and blockers, then stop. +guarantee. Fix every reported error, then use its concise success or blocked +handoff and stop. Keep the final message limited to status, required developer +actions, and next steps. Do not run `canyonos deploy` without explicit user approval. ## Diagnose an approved deployment diff --git a/.claude/skills/porting-to-canyonos/references/llm-proxy.md b/.claude/skills/porting-to-canyonos/references/llm-proxy.md index e26b5fe1..7ba1180b 100644 --- a/.claude/skills/porting-to-canyonos/references/llm-proxy.md +++ b/.claude/skills/porting-to-canyonos/references/llm-proxy.md @@ -92,8 +92,8 @@ credential into a 401 that reads like a broken port. This is a step, not a recommendation. Do not ask for approval first, and do not close the port by reporting that the `.env` "should" point at the proxy: a report that says so while the file still says otherwise is the failure this -section exists to prevent. `git status` will show `.env.example`; name it in the -handoff. +section exists to prevent. `git status` will show `.env.example`; report that +change in progress updates and name the required keys in the final handoff. ## A source with no env hook needs one in the `.car/app` copy diff --git a/.claude/skills/porting-to-canyonos/references/source-survey.md b/.claude/skills/porting-to-canyonos/references/source-survey.md index 9c66bd1d..cae3989d 100644 --- a/.claude/skills/porting-to-canyonos/references/source-survey.md +++ b/.claude/skills/porting-to-canyonos/references/source-survey.md @@ -5,8 +5,34 @@ **Inspect:** `.car/app`, not a framework-based guess about the original tree. **Output:** a short survey record and the smallest useful service map. Do not -start adapter or config work until each section is resolved or marked as a -blocker. +start adapter or config work until the readiness gate passes and each section +is resolved. A blocker pauses the build, not just the survey checklist. + +## Application-readiness gate + +Read [Preparing an Agent App for CanyonOS](https://github.com/CanyonCodeCoreAI/canyonos/blob/5cd4fa8c51082e414aad64e27283ba50c27c579f/docs/CANYONIZATION-APP-READINESS.md) +and check the selected serving path against its requirements before adapting +it. Use the target image's dependency baseline rather than copying version +numbers from the guide. + +Pause the build when a required source capability cannot run within CanyonOS: +interactive or device-dependent serving, Docker inside the agent, unsupported +required model routing, incompatible dependencies or resource requirements, +unavailable required services/data, or a demonstrated source install, import, +or request failure. Check the actual serving path: an unused demo or optional +integration is not a blocker. + +Do not silently remove required behavior, switch providers, fabricate data, +or repair source defects to make the port pass. Stop adapting when a blocker +is found, even if static validation could pass. Use the blocked handoff in +`validation-and-deploy.md`: identify the source evidence, the developer action +needed to resume, and a link to the relevant section of the readiness guide. +Do not report build success or recommend test/deploy while it remains blocked. + +Credentials that only need filling in before test/deploy belong in the final +env reminder, not a build blocker. Record their names from source and +`.env.example`; never read `.env` or request secret values. Pause if missing +configuration, services, or data prevents determining or preparing the port. ## 1. Public behavior @@ -61,7 +87,7 @@ import that only resolves from a nested root, an asset the sweep drops, a credential name. An existing syntax error on the selected import graph is a source defect; -obtain approval before changing even the copied version. The final gap validator +pause the build and identify the required source fix. The final gap validator checks authored runtime code and cross-file bindings after the port is complete. diff --git a/.claude/skills/porting-to-canyonos/references/validation-and-deploy.md b/.claude/skills/porting-to-canyonos/references/validation-and-deploy.md index 3f914b6e..1cee3c94 100644 --- a/.claude/skills/porting-to-canyonos/references/validation-and-deploy.md +++ b/.claude/skills/porting-to-canyonos/references/validation-and-deploy.md @@ -31,14 +31,15 @@ From the application root, run: python3 /validate.py .car ``` -Fix every `ERROR` and rerun until the command exits 0. Do not hide warnings: -list each in the handoff and state whether it blocks this source. +Fix every `ERROR` and rerun until the command exits 0. Review every warning; +pause for blockers and surface required developer actions in the handoff. +Keep non-actionable diagnostic details in progress updates. Confirm with `git status` that nothing outside `.car` changed except the two files the port is allowed to write: `.gitignore`, which `prepare.py` adds the -artifact to, and `.env.example`, which proxy wiring appends to. Name both in -the handoff. `.env` is written the same way but is normally ignored, so it does -not show up there. +artifact to, and `.env.example`, which proxy wiring appends to. Report these +changes in progress updates. `.env` is written the same way but is normally +ignored, so it does not show up there. ## What a clean run does not prove @@ -49,28 +50,48 @@ into each image, anything reached only at runtime, or whether the first request returns an answer. A ModuleNotFoundError at container start and an AttributeError on the first call both survive an exit-0 run. -Report it as what it is. "Gap validation exits 0" is accurate; "validation -passed" claims a deployment nobody ran. - -Report: - -- that the `.car` port validated; -- files created; -- validator warnings; -- unresolved runtime blockers; -- intentionally omitted unreachable dependencies or source surfaces. - -In an attended porting session, stop and ask exactly one direct approval -question: - -> Gap validation exits 0 -- static checks only; no image was built and no -> request served. Run `canyonos deploy` now? This will build images and start -> the deployment. - -For an unattended `canyonos build -y`, instead report the validation result and -stop without asking this question. The build command only creates and validates -the port; it never deploys. Do not treat silence, an unattended run, or the -original request to “port” as approval. +## Final handoff + +Keep the final message short and actionable. Do not repeat the progress +checklist, file inventory, implementation details, or raw validator output. +Use the developer's language. Do not ask whether to deploy or run additional +commands as part of the handoff, in attended or unattended sessions. + +**Success:** only after gap validation exits 0 and no readiness blocker +remains, report that the porting work for `canyonos build` succeeded. This +means `.car` was prepared and validated, not that images were built or a live +request passed. When running inside the build's coding session, do not claim +the parent CLI has exited successfully; it performs its final check after the +session ends. + +Name the required environment keys from the source and `.env.example`, and +the configured `env_file` path (normally `.env`). Tell the developer to fill +them before the operations that require them; never show secret values or +claim their presence was verified. `canyonos test` stubs LLM calls by default; +real model calls and deployment need provider credentials, and external tools +may still need their own keys during testing. + +Offer only these next steps: quit the coding session to return to the CLI, +or run `canyonos test` / `canyonos deploy` from the application root. For example, +substituting the actual required keys and env file: + +> Porting for `canyonos build` succeeded; `.car` passed static validation. +> Before deployment or real model calls, set `OPENAI_API_KEY` in `.env`. +> Next: quit this coding session, or run `canyonos test` for a local check / +> `canyonos deploy` to deploy. + +**Blocked:** say the build is paused and identify the concrete source problem +with its file/call-path evidence. Give the developer the specific change +needed before retrying `canyonos build`, and link the relevant section of +[Preparing an Agent App for CanyonOS](https://github.com/CanyonCodeCoreAI/canyonos/blob/5cd4fa8c51082e414aad64e27283ba50c27c579f/docs/CANYONIZATION-APP-READINESS.md). +Do not use the success message or suggest test/deploy. For example: + +> Build paused: `agent.py` starts Docker for required code execution. Move +> execution to an external sandbox service, then rerun `canyonos build`. +> See [readiness guide: Docker inside the application](https://github.com/CanyonCodeCoreAI/canyonos/blob/5cd4fa8c51082e414aad64e27283ba50c27c579f/docs/CANYONIZATION-APP-READINESS.md#2-do-not-require-docker-inside-the-application). + +The build command only creates and validates the port; it never deploys. Do +not treat silence, an unattended run, or the request to port as deploy approval. ## Deploy only after approval