tangle-cli 0.1.9__tar.gz → 0.1.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/PKG-INFO +103 -3
  2. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/README.md +102 -2
  3. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
  4. tangle_cli-0.1.11/packages/tangle-cli/src/tangle_cli/args_container.py +518 -0
  5. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +37 -15
  6. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +179 -3
  7. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +12 -0
  8. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +20 -2
  9. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +18 -2
  10. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +4 -5
  11. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/pyproject.toml +1 -1
  12. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/pyproject.toml.orig +1 -1
  13. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/OSS-CONVENTIONS.md +1 -1
  14. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/tangle-tools.md +1 -1
  15. tangle_cli-0.1.9/packages/tangle-cli/src/tangle_cli/args_container.py +0 -244
  16. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/api_cli.py +0 -0
  17. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/api_schema.py +0 -0
  18. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/api_transport.py +0 -0
  19. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
  20. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +0 -0
  21. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/cli.py +0 -0
  22. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +0 -0
  23. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/cli_options.py +0 -0
  24. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/client.py +0 -0
  25. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/component_from_func.py +0 -0
  26. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/component_generator.py +0 -0
  27. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
  28. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
  29. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
  30. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +0 -0
  31. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
  32. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
  33. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
  34. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
  35. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
  36. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
  37. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
  38. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
  39. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +0 -0
  40. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
  41. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +0 -0
  42. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
  43. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
  44. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +0 -0
  45. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
  46. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
  47. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +0 -0
  48. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
  49. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +0 -0
  50. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipelines.py +0 -0
  51. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +0 -0
  52. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +0 -0
  53. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
  54. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
  55. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
  56. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +0 -0
  57. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
  58. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
  59. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
  60. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
  61. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
  62. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
  63. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
  64. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +0 -0
  65. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
  66. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
  67. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
  68. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
  69. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/schema_validation.py +0 -0
  70. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
  71. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -0
  72. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
  73. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +0 -0
  74. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
  75. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
  76. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/PORT-README.md +0 -0
  77. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/SKILL.md +0 -0
  78. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/agents/auth-wizard.md +0 -0
  79. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/agents/builder.md +0 -0
  80. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/agents/debugger.md +0 -0
  81. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/agents/reporter.md +0 -0
  82. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/agents/researcher.md +0 -0
  83. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/agents/reviewer.md +0 -0
  84. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/agents/scenario-builder.md +0 -0
  85. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/data-sources.md +0 -0
  86. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/event-log.md +0 -0
  87. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
  88. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
  89. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
  90. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/iterating-on-runs.md +0 -0
  91. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/knowledge-corpus.md +0 -0
  92. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/secrets.md +0 -0
  93. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/setup.md +0 -0
  94. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-0-initialize.md +0 -0
  95. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-1-analyze.md +0 -0
  96. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-2-hypothesize.md +0 -0
  97. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-3-submit.md +0 -0
  98. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-4-monitor.md +0 -0
  99. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-5-evaluate.md +0 -0
  100. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-6-synthesize.md +0 -0
  101. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/step-7-decide.md +0 -0
  102. {tangle_cli-0.1.9 → tangle_cli-0.1.11}/skills/tangent/references/uploading-artifacts.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tangle-cli
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: CLI for Tangle, the open-source ML pipeline orchestration platform
5
5
  Author: Alexey Volkov, Tangle authors, Volv Grebennikov
6
6
  Author-email: Alexey Volkov <alexey.volkov@ark-kun.com>, Volv Grebennikov <volv.grebennikov@shopify.com>
@@ -83,7 +83,7 @@ API-backed commands commonly accept these options. Explicit CLI options win over
83
83
  | `--token`, `TANGLE_API_TOKEN` | Bearer token shorthand. |
84
84
  | `--auth-header`, `TANGLE_API_AUTH_HEADER`, `TANGLE_AUTH_HEADER` | Full `Authorization` value such as `Bearer ...` or `Basic ...`. |
85
85
  | `-H`, `--header`, `TANGLE_API_HEADERS` | Extra headers. Repeatable as CLI flags; env accepts a JSON object or newline-separated `Name: value` entries. |
86
- | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`. |
86
+ | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`, optionally wrapped in a top-level `_select` environment selector. |
87
87
  | `--log-type` | SDK progress logs: `console`, `none`, or `file`. Logs go to stderr or a temp log file so structured stdout stays parseable. |
88
88
  | `TANGLE_VERBOSE=1` | Redacted HTTP request/response diagnostics only. This is separate from normal progress logging. |
89
89
  | `--ca-bundle` | Global CLI flag: path to a PEM CA bundle used as the TLS trust store for every transport. Overrides `TANGLE_API_CA_BUNDLE`. Place before the subcommand. |
@@ -256,6 +256,56 @@ uv run tangle sdk pipeline-runs submit --config submit.yaml
256
256
 
257
257
  For generated `tangle api` commands, config keys use generated CLI parameter names such as `base_url`, `schema_source`, `body`, and endpoint parameters like `limit`, `filter`, or `id`.
258
258
 
259
+ ### Environment-selected configs (`_select`)
260
+
261
+ Any command that accepts `--config` can pick one of several config documents from an environment variable by making `_select` the top-level node:
262
+
263
+ ```yaml
264
+ _shared: &shared
265
+ log_type: none
266
+
267
+ _select:
268
+ env: TANGLE_ENV
269
+ cases:
270
+ dev:
271
+ <<: *shared
272
+ base_url: https://api.dev
273
+ prod:
274
+ _defaults:
275
+ <<: *shared
276
+ base_url: https://api.prod
277
+ configs:
278
+ - filter: active
279
+ - filter: finished
280
+ ```
281
+
282
+ The selected branch is a complete config document — a single object, a list of objects, or `_defaults` + `configs` — and is then loaded exactly as if it had been written on its own. A branch may itself be another `_select` node, which composes multiple dimensions (for example environment and region).
283
+
284
+ Selection fails closed by default. To opt into a fallback, author an explicit `default` branch:
285
+
286
+ ```yaml
287
+ _select:
288
+ env: TANGLE_ENV
289
+ cases:
290
+ prod:
291
+ base_url: https://api.prod
292
+ default:
293
+ base_url: https://api.dev
294
+ ```
295
+
296
+ With `default`, an unset variable or a value matching no case resolves to that branch; an exact case match always wins over it. Without `default`, both remain errors. `default` is a sibling of `cases`, not an entry inside it: a case *named* `default` stays an ordinary exact-match case. Like any branch, `default` must be a complete valid config shape and may itself be another `_select`; an inner selector does not inherit the outer `default`.
297
+
298
+ Rules:
299
+
300
+ - `_select` is the only newly reserved key, and it is an exact key name rather than a prefix. Configs without `_select` are unchanged byte-for-byte and semantically.
301
+ - At a `_select` node, only `_select` and other underscore-prefixed helper keys (YAML anchor holders such as `_shared`) may appear; ordinary sibling keys are rejected.
302
+ - `_select` accepts only `env`, `cases`, and the optional `default`. There are no aliases: `else`, `fallback`, and `defaults` are rejected.
303
+ - The selector shape, the `env` name, every `cases` key, and every case and `default` branch are validated before the environment is read, so a malformed selector fails identically in every environment. Branches are checked as complete config documents with the same rules the loader applies to a whole file, and nested selectors are validated recursively.
304
+ - Only environment *lookups* are lazy. A dormant branch is fully shape-checked, but its `env` variable is never required unless that branch is actually selected. Command-specific field names and types are still validated later, against the selected branch only.
305
+ - Selector nesting is capped at 32 levels, which also stops a self-referential YAML alias. A node shared by several anchors is validated once, so anchor-heavy files stay fast.
306
+ - Matching uses `os.environ[NAME]` exactly: case sensitive, with no trimming, case folding, or interpolation.
307
+ - Fallback exists only where it is authored. Without `default`, an unset variable or an unmatched value is an error; there is never an implicit default or implicit production branch. The raw environment value is never echoed — diagnostics list only the configured case names.
308
+
259
309
  ## API schema cache and dynamic commands
260
310
 
261
311
  Refresh the local schema cache for a live backend with:
@@ -331,7 +381,7 @@ uv run tangle sdk published-components deprecate sha256:old --superseded-by sha2
331
381
 
332
382
  `publish` accepts `--image`, `--name`, `--description`, `--annotations` (JSON), `--dry-run`, `--published-by`, generic git metadata fields, generic API auth fields, `--log-type`, and `--config`. By default it scopes version checks and automatic old-version deprecation to the current authenticated user via `users_me()`; use `--published-by` to supply an explicit owner/publisher filter. Publishing fails closed if no owner can be determined.
333
383
 
334
- There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors.
384
+ There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors. A top-level `_select` node can choose between such documents per environment (see [Environment-selected configs](#environment-selected-configs-_select)).
335
385
 
336
386
  ```yaml
337
387
  _defaults:
@@ -439,6 +489,56 @@ def collision(runtime_condition: In[str]) -> Out[str]:
439
489
 
440
490
  The bound value remains under `arguments.is_enabled`; the call-site value emits as `isEnabled`. Tangle does not evaluate conditions on graph-component tasks, so `subpipeline(...)(is_enabled=...)` is rejected with guidance to condition tasks inside the child pipeline. A child graph input with that name remains available through `subpipeline(...).bind(is_enabled=...)(...)`. There is no `condition` alias.
441
491
 
492
+ ##### Task execution options and caching
493
+
494
+ Tangle caches task results, so a task that reads state which changes between runs (a run's `createdBy`, wall-clock time, an external table that the graph does not depend on) must opt out of caching explicitly. Use the reserved task-call metadata keyword `max_cache_staleness=`:
495
+
496
+ ```python
497
+ @pipeline("Scheduled-run gate")
498
+ def scheduled_gate() -> Out[str]:
499
+ is_scheduled = read_runtime_state(
500
+ name="CLOUD_PIPELINES_PIPELINE_RUN_CREATED_BY",
501
+ max_cache_staleness="P0D",
502
+ )
503
+ return is_scheduled.Output
504
+ ```
505
+
506
+ This emits the canonical task field rather than a component argument:
507
+
508
+ ```yaml
509
+ executionOptions:
510
+ cachingStrategy:
511
+ maxCacheStaleness: P0D
512
+ ```
513
+
514
+ `P0D` means "never reuse a cached result"; any other ISO-8601 duration (`P7D`) caps how stale a reusable result may be. For the rest of `ExecutionOptionsSpec`, use the general `execution_options=` passthrough:
515
+
516
+ ```python
517
+ uploaded = flaky_upload(
518
+ payload=data.Output,
519
+ execution_options={"retryStrategy": {"maxRetries": 3}},
520
+ )
521
+ ```
522
+
523
+ Both keywords may be combined; `max_cache_staleness=` wins over a `cachingStrategy.maxCacheStaleness` supplied through `execution_options=`, and every other passthrough field is preserved. A mapping passed as `execution_options=` is never mutated, so one shared constant can be reused across tasks.
524
+
525
+ Tangle models exactly two execution-option groups today — `cachingStrategy.maxCacheStaleness` and `retryStrategy.maxRetries` (required whenever `retryStrategy` is present). Any other key is rejected at compile time: the backend ignores unmodeled keys silently, so accepting one would advertise a setting that never takes effect.
526
+
527
+ Execution options are STATIC compile-time settings, so `max_cache_staleness` takes an RFC3339 duration string and `retryStrategy.maxRetries` a non-negative integer. Graph inputs, task outputs, `dynamic_secret(...)`, and `raw(...)` values are rejected because the backend does not resolve them for `executionOptions`. Passing an empty `execution_options={}` is an error — omit the keyword instead.
528
+
529
+ If a component itself declares an input named `max_cache_staleness` or `execution_options`, bind that component argument separately while using the call-site keyword for task metadata:
530
+
531
+ ```python
532
+ result = work.bind(max_cache_staleness="component-input-value")(
533
+ message="hello",
534
+ max_cache_staleness="P0D",
535
+ )
536
+ ```
537
+
538
+ The bound value remains under `arguments.max_cache_staleness`; the call-site value emits as `executionOptions`. Tangle applies caching and retries to container-component tasks, so `subpipeline(...)(max_cache_staleness=...)` and `subpipeline(...)(execution_options=...)` are rejected with guidance to set them on tasks inside the child pipeline. A child graph input with either name remains available through `subpipeline(...).bind(...)`.
539
+
540
+ See `examples/python_pipeline/execution_options_pipeline.py` for a runnable example.
541
+
442
542
  ##### Task images, dependencies, and image IDs
443
543
 
444
544
  Use `@task(image="...")` to write the component image directly. Use `dependencies_from="pyproject.toml"` when generated components need to install Python dependencies. Several tasks can share one authoring-only `TaskEnv`:
@@ -57,7 +57,7 @@ API-backed commands commonly accept these options. Explicit CLI options win over
57
57
  | `--token`, `TANGLE_API_TOKEN` | Bearer token shorthand. |
58
58
  | `--auth-header`, `TANGLE_API_AUTH_HEADER`, `TANGLE_AUTH_HEADER` | Full `Authorization` value such as `Bearer ...` or `Basic ...`. |
59
59
  | `-H`, `--header`, `TANGLE_API_HEADERS` | Extra headers. Repeatable as CLI flags; env accepts a JSON object or newline-separated `Name: value` entries. |
60
- | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`. |
60
+ | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`, optionally wrapped in a top-level `_select` environment selector. |
61
61
  | `--log-type` | SDK progress logs: `console`, `none`, or `file`. Logs go to stderr or a temp log file so structured stdout stays parseable. |
62
62
  | `TANGLE_VERBOSE=1` | Redacted HTTP request/response diagnostics only. This is separate from normal progress logging. |
63
63
  | `--ca-bundle` | Global CLI flag: path to a PEM CA bundle used as the TLS trust store for every transport. Overrides `TANGLE_API_CA_BUNDLE`. Place before the subcommand. |
@@ -230,6 +230,56 @@ uv run tangle sdk pipeline-runs submit --config submit.yaml
230
230
 
231
231
  For generated `tangle api` commands, config keys use generated CLI parameter names such as `base_url`, `schema_source`, `body`, and endpoint parameters like `limit`, `filter`, or `id`.
232
232
 
233
+ ### Environment-selected configs (`_select`)
234
+
235
+ Any command that accepts `--config` can pick one of several config documents from an environment variable by making `_select` the top-level node:
236
+
237
+ ```yaml
238
+ _shared: &shared
239
+ log_type: none
240
+
241
+ _select:
242
+ env: TANGLE_ENV
243
+ cases:
244
+ dev:
245
+ <<: *shared
246
+ base_url: https://api.dev
247
+ prod:
248
+ _defaults:
249
+ <<: *shared
250
+ base_url: https://api.prod
251
+ configs:
252
+ - filter: active
253
+ - filter: finished
254
+ ```
255
+
256
+ The selected branch is a complete config document — a single object, a list of objects, or `_defaults` + `configs` — and is then loaded exactly as if it had been written on its own. A branch may itself be another `_select` node, which composes multiple dimensions (for example environment and region).
257
+
258
+ Selection fails closed by default. To opt into a fallback, author an explicit `default` branch:
259
+
260
+ ```yaml
261
+ _select:
262
+ env: TANGLE_ENV
263
+ cases:
264
+ prod:
265
+ base_url: https://api.prod
266
+ default:
267
+ base_url: https://api.dev
268
+ ```
269
+
270
+ With `default`, an unset variable or a value matching no case resolves to that branch; an exact case match always wins over it. Without `default`, both remain errors. `default` is a sibling of `cases`, not an entry inside it: a case *named* `default` stays an ordinary exact-match case. Like any branch, `default` must be a complete valid config shape and may itself be another `_select`; an inner selector does not inherit the outer `default`.
271
+
272
+ Rules:
273
+
274
+ - `_select` is the only newly reserved key, and it is an exact key name rather than a prefix. Configs without `_select` are unchanged byte-for-byte and semantically.
275
+ - At a `_select` node, only `_select` and other underscore-prefixed helper keys (YAML anchor holders such as `_shared`) may appear; ordinary sibling keys are rejected.
276
+ - `_select` accepts only `env`, `cases`, and the optional `default`. There are no aliases: `else`, `fallback`, and `defaults` are rejected.
277
+ - The selector shape, the `env` name, every `cases` key, and every case and `default` branch are validated before the environment is read, so a malformed selector fails identically in every environment. Branches are checked as complete config documents with the same rules the loader applies to a whole file, and nested selectors are validated recursively.
278
+ - Only environment *lookups* are lazy. A dormant branch is fully shape-checked, but its `env` variable is never required unless that branch is actually selected. Command-specific field names and types are still validated later, against the selected branch only.
279
+ - Selector nesting is capped at 32 levels, which also stops a self-referential YAML alias. A node shared by several anchors is validated once, so anchor-heavy files stay fast.
280
+ - Matching uses `os.environ[NAME]` exactly: case sensitive, with no trimming, case folding, or interpolation.
281
+ - Fallback exists only where it is authored. Without `default`, an unset variable or an unmatched value is an error; there is never an implicit default or implicit production branch. The raw environment value is never echoed — diagnostics list only the configured case names.
282
+
233
283
  ## API schema cache and dynamic commands
234
284
 
235
285
  Refresh the local schema cache for a live backend with:
@@ -305,7 +355,7 @@ uv run tangle sdk published-components deprecate sha256:old --superseded-by sha2
305
355
 
306
356
  `publish` accepts `--image`, `--name`, `--description`, `--annotations` (JSON), `--dry-run`, `--published-by`, generic git metadata fields, generic API auth fields, `--log-type`, and `--config`. By default it scopes version checks and automatic old-version deprecation to the current authenticated user via `users_me()`; use `--published-by` to supply an explicit owner/publisher filter. Publishing fails closed if no owner can be determined.
307
357
 
308
- There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors.
358
+ There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors. A top-level `_select` node can choose between such documents per environment (see [Environment-selected configs](#environment-selected-configs-_select)).
309
359
 
310
360
  ```yaml
311
361
  _defaults:
@@ -413,6 +463,56 @@ def collision(runtime_condition: In[str]) -> Out[str]:
413
463
 
414
464
  The bound value remains under `arguments.is_enabled`; the call-site value emits as `isEnabled`. Tangle does not evaluate conditions on graph-component tasks, so `subpipeline(...)(is_enabled=...)` is rejected with guidance to condition tasks inside the child pipeline. A child graph input with that name remains available through `subpipeline(...).bind(is_enabled=...)(...)`. There is no `condition` alias.
415
465
 
466
+ ##### Task execution options and caching
467
+
468
+ Tangle caches task results, so a task that reads state which changes between runs (a run's `createdBy`, wall-clock time, an external table that the graph does not depend on) must opt out of caching explicitly. Use the reserved task-call metadata keyword `max_cache_staleness=`:
469
+
470
+ ```python
471
+ @pipeline("Scheduled-run gate")
472
+ def scheduled_gate() -> Out[str]:
473
+ is_scheduled = read_runtime_state(
474
+ name="CLOUD_PIPELINES_PIPELINE_RUN_CREATED_BY",
475
+ max_cache_staleness="P0D",
476
+ )
477
+ return is_scheduled.Output
478
+ ```
479
+
480
+ This emits the canonical task field rather than a component argument:
481
+
482
+ ```yaml
483
+ executionOptions:
484
+ cachingStrategy:
485
+ maxCacheStaleness: P0D
486
+ ```
487
+
488
+ `P0D` means "never reuse a cached result"; any other ISO-8601 duration (`P7D`) caps how stale a reusable result may be. For the rest of `ExecutionOptionsSpec`, use the general `execution_options=` passthrough:
489
+
490
+ ```python
491
+ uploaded = flaky_upload(
492
+ payload=data.Output,
493
+ execution_options={"retryStrategy": {"maxRetries": 3}},
494
+ )
495
+ ```
496
+
497
+ Both keywords may be combined; `max_cache_staleness=` wins over a `cachingStrategy.maxCacheStaleness` supplied through `execution_options=`, and every other passthrough field is preserved. A mapping passed as `execution_options=` is never mutated, so one shared constant can be reused across tasks.
498
+
499
+ Tangle models exactly two execution-option groups today — `cachingStrategy.maxCacheStaleness` and `retryStrategy.maxRetries` (required whenever `retryStrategy` is present). Any other key is rejected at compile time: the backend ignores unmodeled keys silently, so accepting one would advertise a setting that never takes effect.
500
+
501
+ Execution options are STATIC compile-time settings, so `max_cache_staleness` takes an RFC3339 duration string and `retryStrategy.maxRetries` a non-negative integer. Graph inputs, task outputs, `dynamic_secret(...)`, and `raw(...)` values are rejected because the backend does not resolve them for `executionOptions`. Passing an empty `execution_options={}` is an error — omit the keyword instead.
502
+
503
+ If a component itself declares an input named `max_cache_staleness` or `execution_options`, bind that component argument separately while using the call-site keyword for task metadata:
504
+
505
+ ```python
506
+ result = work.bind(max_cache_staleness="component-input-value")(
507
+ message="hello",
508
+ max_cache_staleness="P0D",
509
+ )
510
+ ```
511
+
512
+ The bound value remains under `arguments.max_cache_staleness`; the call-site value emits as `executionOptions`. Tangle applies caching and retries to container-component tasks, so `subpipeline(...)(max_cache_staleness=...)` and `subpipeline(...)(execution_options=...)` are rejected with guidance to set them on tasks inside the child pipeline. A child graph input with either name remains available through `subpipeline(...).bind(...)`.
513
+
514
+ See `examples/python_pipeline/execution_options_pipeline.py` for a runnable example.
515
+
416
516
  ##### Task images, dependencies, and image IDs
417
517
 
418
518
  Use `@task(image="...")` to write the component image directly. Use `dependencies_from="pyproject.toml"` when generated components need to install Python dependencies. Several tasks can share one authoring-only `TaskEnv`:
@@ -14,6 +14,6 @@ from tangle_cli.dynamic_discovery_client import TangleDynamicDiscoveryClient
14
14
  try:
15
15
  __version__ = metadata_version("tangle-cli")
16
16
  except PackageNotFoundError:
17
- __version__ = "0.1.9"
17
+ __version__ = "0.1.11"
18
18
 
19
19
  __all__ = ["TangleDynamicDiscoveryClient", "__version__"]