Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ee69932
feat(alk): platform-generated scenarios + canonical voice prompts + L…
azain-commits Jul 28, 2026
67033fa
feat(alk): wire scenario generation into CLI + Studio downloader upgr…
azain-commits Jul 28, 2026
c063404
feat(sim): stage-0/5 protocol layer, evidence sources, matrix runner,…
azain-commits Jul 29, 2026
e16933a
fix(sim): repair LiveKit SIP transport, wire Vapi originator, tighten…
azain-commits Jul 29, 2026
3cf8c8f
feat(simulate): add direct voice SDK workflow
azain-commits Jul 30, 2026
963a399
feat(simulate): separate voice target from FutureAGI LiveKit runtime …
azain-commits Jul 30, 2026
35d6525
fix(studio): omit scenario-only agent flag
azain-commits Jul 30, 2026
d2b1420
feat(simulate): complete provider acceptance flows
azain-commits Jul 31, 2026
b53e4a7
feat(simulate): pool-aware LiveKit engine + provider acceptance harde…
azain-commits Aug 4, 2026
1999a1e
fix issues on livekit engine, add support for posting simulate data t…
azain-commits Aug 5, 2026
cf3f3eb
feat(simulate): real platform submission in FutureAGIResultSink
azain-commits Aug 5, 2026
6484b2b
feat(simulate): submit target-agent token usage + cost to platform
azain-commits Aug 5, 2026
c239ace
fix(evals): exclude non-applicable metrics from agent-report aggregate
azain-commits Aug 6, 2026
e0d9e2e
feat(evals): live litellm/Vertex provider for eval + optimize-eval su…
azain-commits Aug 6, 2026
ed86c24
fix(opt): configurable task_model, drop hardcoded gpt-4o-mini/gpt-5-mini
azain-commits Aug 6, 2026
01f6904
feat(evals,opt): wire platform evals + all optimizers into agent-learn
azain-commits Aug 6, 2026
bcd1a76
fix(simulate): tool-call fidelity + per-turn latency in chat runs
azain-commits Aug 10, 2026
c695347
refactor(simulate): gym-model runtime — adapter registries + world_ki…
azain-commits Aug 10, 2026
d1208ce
feat(simulate): SDK hosted-runner package (child entrypoint + job + t…
azain-commits Aug 10, 2026
e573c93
feat(simulate): per-persona TTS voice selection
azain-commits Aug 11, 2026
3db32eb
fix(simulate): send internal Bearer secret alongside FI keys on submit
azain-commits Aug 11, 2026
ee26069
fix(simulate): enable preemptive generation to cut voice turn latency
azain-commits Aug 11, 2026
60a924c
feat(voice): run a scenario's cases concurrently, bounded by agent ca…
azain-commits Aug 12, 2026
3f1413d
feat(simulate): stream hosted voice cases to the platform as each fin…
azain-commits Aug 12, 2026
c95629f
fix(voice): run sims to natural end + stop transcript role reversal
azain-commits Aug 12, 2026
d13f8c2
fix(livekit): send empty dispatch metadata to external target agents …
azain-commits Aug 12, 2026
b93f2c9
fix(livekit): capture target-agent turns so webrtc sims run a real co…
azain-commits Aug 12, 2026
1fc2a40
fix(simulate): drive agent_first conversations from the target greeting
azain-commits Aug 13, 2026
0f4b5de
Revert "fix(simulate): drive agent_first conversations from the targe…
azain-commits Aug 13, 2026
fa87079
fix(simulate): capture the target's opening greeting in agent_first
azain-commits Aug 13, 2026
7f080e6
feat(simulate): stereo voice recording (customer left / assistant right)
azain-commits Aug 13, 2026
5de580c
feat(simulate): streaming Gemini TTS for the simulator voice (multili…
azain-commits Aug 13, 2026
bfc6caf
fix(simulate): capture final target turn, truncate interrupted turns,…
azain-commits Aug 13, 2026
34711b7
fix(simulate): commit target turns directly so the trailing closing i…
azain-commits Aug 13, 2026
0d75d91
fix(simulate): capture target closing via independent stream, not the…
azain-commits Aug 13, 2026
82f23a8
fix(simulate): also wait for the simulator's own final turn before sn…
azain-commits Aug 13, 2026
f429e1b
fix(simulate): delete the room on conversation end so the call actual…
azain-commits Aug 13, 2026
ecd0153
feat(simulate): per-case ONGOING status for hosted sims
azain-commits Aug 15, 2026
260e366
fix(simulate): align SDK voice results and recordings
azain-commits Aug 17, 2026
32101a1
fix(results): allow internal service authentication
azain-commits Aug 18, 2026
74f1c28
docs(simulate): add Spec + Runner quickstart, drop "gym" branding
azain-commits Aug 18, 2026
df2d379
fix(simulate): repair the livekit engine test suite + gate it in CI
azain-commits Aug 18, 2026
c719ffd
fix(simulate): make fi.simulate importable without the livekit extra
azain-commits Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/sdk-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: SDK smoke

on:
push:
pull_request:

jobs:
clean-install:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install package
run: python -m pip install .
- name: Run doctor
run: agent-learn doctor --quiet
- name: Run local text simulation
env:
AGENT_LEARNING_RUN_EXAMPLE_KEY: smoke
run: >-
agent-learn simulation run examples/run_manifest.json
--output smoke-report.json --quiet
- name: Verify simulation report
run: >-
python -c "import json, pathlib;
report=json.loads(pathlib.Path('smoke-report.json').read_text());
assert report['status'] == 'ran' and report['report']['results']"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow doesn't run the tests this PR adds.

Credit first: this file is new in this PR, so it takes the repo from no CI to some CI. That's the right direction.

But as written it installs the package, runs agent-learn doctor, and executes one local text simulation. No pytest, no lint. So roughly 3,000 lines of new tests — test_livekit_engine.py (1,915), test_manifest_engine_dispatch.py (521), test_acceptance_regressions.py (490) — never execute in CI.

That's the mechanism behind the five failures above surviving six commits: nothing was watching.

A step like:

      - name: Run tests
        run: |
          python -m pip install pytest pytest-asyncio
          python -m pytest tests/ -q

would close it. Two caveats from running the suite locally: tests/test_config_and_facades.py hangs (>3 min, also on the base branch — pre-existing, worth an --ignore or a timeout), and tests/test_phase7_persona_studio.py has 2 network-dependent failures that also reproduce on base. Neither is introduced here, but both need handling before a pytest gate goes green.

- name: Install test dependencies
run: python -m pip install '.[livekit]' pytest pytest-asyncio
- name: Run engine + dispatch + acceptance tests
# Scoped to the suites this PR adds/relies on. The broader tests/ tree has
# pre-existing failures unrelated to the voice engine (CLI golden-path,
# init presets, plus test_config_and_facades hangs) — track those
# separately so this gate stays green and meaningful.
run: >-
python -m pytest -q
tests/runtime/test_livekit_engine.py
tests/runtime/test_manifest_engine_dispatch.py
tests/test_acceptance_regressions.py
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ coverage/
.DS_Store
.env
.env.*
!**/.env.example
artifacts/
!src/fi/simulate/artifacts/
!src/fi/simulate/artifacts/*.py
examples/artifacts/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ Then run the golden path against the bundled example manifests. The
`required_env` list — that list is CI wiring metadata, not a provider
credential, so any placeholder value works.

> Prefer the SDK spine over the CLI?
> [Spec + Runner](docs/simulate/spec-and-runner.md) runs the same simulation as
> one `SimulationSpec` fed to one `SimulationRunner` — the plug-and-play surface
> behind every simulation.

Evaluate a suite:

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/cookbooks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ opt_in_lane: false

| | Behavior | Capability | Reliability | Safety |
| --- | --- | --- | --- | --- |
| **simulate** | [golden-path-run](../quickstart/golden-path-run.md) · [first-run](../simulate/first-run.md) · [worlds-and-hooks](../simulate/worlds-and-hooks.md) | [simulate-any-framework](../simulate/simulate-any-framework.md) · [memory](../simulate/memory.md) · [multimodal-image](../simulate/multimodal-image.md) · [voice-realtime](../simulate/voice-realtime.md) · [langchain](../frameworks/langchain.md) · [langgraph](../frameworks/langgraph.md) · [llamaindex](../frameworks/llamaindex.md) · [autogen](../frameworks/autogen.md) · [crewai](../frameworks/crewai.md) · [openai-agents](../frameworks/openai-agents.md) · [pydantic-ai](../frameworks/pydantic-ai.md) · [livekit](../frameworks/livekit.md) · [pipecat](../frameworks/pipecat.md) · [browser-use](../frameworks/browser-use.md) · [mcp](../frameworks/mcp.md) · [a2a](../frameworks/a2a.md) · [custom](../frameworks/custom.md) · [openenv](../frameworks/openenv.md) · [persona-scenario-studio](../simulate/persona-scenario-studio.md) | [multi-agent](../simulate/multi-agent.md) · [orchestration](../simulate/orchestration.md) | [golden-path-redteam](../quickstart/golden-path-redteam.md) · [red-team-anything](../redteam/red-team-anything.md) · [corpus](../redteam/corpus.md) · [first-campaign](../redteam/first-campaign.md) · [stored-prompt-injection](../redteam/stored-prompt-injection.md) · [long-horizon](../redteam/long-horizon.md) · [autonomous-task-world](../redteam/autonomous-task-world.md) · [persona-conditioned-attacks](../redteam/persona-conditioned-attacks.md) |
| **simulate** | [golden-path-run](../quickstart/golden-path-run.md) · [spec-and-runner](../simulate/spec-and-runner.md) · [first-run](../simulate/first-run.md) · [worlds-and-hooks](../simulate/worlds-and-hooks.md) | [simulate-any-framework](../simulate/simulate-any-framework.md) · [memory](../simulate/memory.md) · [multimodal-image](../simulate/multimodal-image.md) · [voice-realtime](../simulate/voice-realtime.md) · [langchain](../frameworks/langchain.md) · [langgraph](../frameworks/langgraph.md) · [llamaindex](../frameworks/llamaindex.md) · [autogen](../frameworks/autogen.md) · [crewai](../frameworks/crewai.md) · [openai-agents](../frameworks/openai-agents.md) · [pydantic-ai](../frameworks/pydantic-ai.md) · [livekit](../frameworks/livekit.md) · [pipecat](../frameworks/pipecat.md) · [browser-use](../frameworks/browser-use.md) · [mcp](../frameworks/mcp.md) · [a2a](../frameworks/a2a.md) · [custom](../frameworks/custom.md) · [openenv](../frameworks/openenv.md) · [persona-scenario-studio](../simulate/persona-scenario-studio.md) | [multi-agent](../simulate/multi-agent.md) · [orchestration](../simulate/orchestration.md) | [golden-path-redteam](../quickstart/golden-path-redteam.md) · [red-team-anything](../redteam/red-team-anything.md) · [corpus](../redteam/corpus.md) · [first-campaign](../redteam/first-campaign.md) · [stored-prompt-injection](../redteam/stored-prompt-injection.md) · [long-horizon](../redteam/long-horizon.md) · [autonomous-task-world](../redteam/autonomous-task-world.md) · [persona-conditioned-attacks](../redteam/persona-conditioned-attacks.md) |
| **evaluate** | [evaluate-any-task](../eval/evaluate-any-task.md) · [eval-suites](../eval/eval-suites.md) · [artifact-evals](../eval/artifact-evals.md) | [task-evidence](../eval/task-evidence.md) · [eval-hooks](../eval/eval-hooks.md) · [benchmark-overview](../eval/benchmark-overview.md) · [benchmark-coding](../eval/benchmark-coding.md) · [benchmark-voice](../eval/benchmark-voice.md) · [benchmark-pull-rl](../eval/benchmark-pull-rl.md) · [benchmark-write-a-suite](../eval/benchmark-write-a-suite.md) | [judge-reliability](../eval/judge-reliability.md) · [persona-fidelity](../simulate/persona-fidelity.md) · [benchmark-command-graded](../eval/benchmark-command-graded.md) | [causal-attribution](../redteam/causal-attribution.md) · [benchmark-sandboxes](../eval/benchmark-sandboxes.md) |
| **optimize** | [golden-path-optimize](../quickstart/golden-path-optimize.md) · [optimize-any-agent](../optimize/optimize-any-agent.md) · [optimization-lifecycle](../optimize/optimization-lifecycle.md) · [behavior-and-collaboration](../optimize/behavior-and-collaboration.md) | [world-model](../optimize/world-model.md) · [memory-targets](../optimize/memory-targets.md) · [workflow-profile-matrix](../optimize/workflow-profile-matrix.md) · [optimizer-profile-matrix](../optimize/optimizer-profile-matrix.md) · [agent-control-plane](../optimize/agent-control-plane.md) | [governance](../optimize/governance.md) · [optimizer-portfolio](../optimize/optimizer-portfolio.md) · [backend-routing](../optimize/backend-routing.md) · [multi-agent-targets](../optimize/multi-agent-targets.md) · [eval-suite-optimization](../optimize/eval-suite-optimization.md) | [society-of-agents](../optimize/society-of-agents.md) · [campaign-optimization](../redteam/campaign-optimization.md) · [adaptive-loop](../redteam/adaptive-loop.md) |
| **promote** | [regression-lifecycle](../simulate/regression-lifecycle.md) | [promote-to-regression](../redteam/promote-to-regression.md) | [golden-path-ci](../quickstart/golden-path-ci.md) · [attack-evolution-shrink](../redteam/attack-evolution-shrink.md) · [capability-profile-freezing](../optimize/capability-profile-freezing.md) | [promote-to-regression](../redteam/promote-to-regression.md) |
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ same set `docs/llms.txt` lists) is [cookbooks/index.md](cookbooks/index.md).

| | Behavior | Capability | Reliability | Safety |
| --- | --- | --- | --- | --- |
| **simulate** | [first-run](simulate/first-run.md) · [worlds-and-hooks](simulate/worlds-and-hooks.md) | [simulate-any-framework](simulate/simulate-any-framework.md) · 14 [framework pages](cookbooks/index.md#by-framework) | [multi-agent](simulate/multi-agent.md) · [orchestration](simulate/orchestration.md) | [first-campaign](redteam/first-campaign.md) · [stored-prompt-injection](redteam/stored-prompt-injection.md) |
| **simulate** | [spec-and-runner](simulate/spec-and-runner.md) · [first-run](simulate/first-run.md) · [worlds-and-hooks](simulate/worlds-and-hooks.md) | [simulate-any-framework](simulate/simulate-any-framework.md) · 14 [framework pages](cookbooks/index.md#by-framework) | [multi-agent](simulate/multi-agent.md) · [orchestration](simulate/orchestration.md) | [first-campaign](redteam/first-campaign.md) · [stored-prompt-injection](redteam/stored-prompt-injection.md) |
| **evaluate** | [eval-suites](eval/eval-suites.md) · [artifact-evals](eval/artifact-evals.md) | [task-evidence](eval/task-evidence.md) | [judge-reliability](eval/judge-reliability.md) | [causal-attribution](redteam/causal-attribution.md) |
| **optimize** | [optimization-lifecycle](optimize/optimization-lifecycle.md) | [workflow-profile-matrix](optimize/workflow-profile-matrix.md) | [governance](optimize/governance.md) · [optimizer-portfolio](optimize/optimizer-portfolio.md) | [society-of-agents](optimize/society-of-agents.md) |
| **promote** | [regression-lifecycle](simulate/regression-lifecycle.md) | [promote-to-regression](redteam/promote-to-regression.md) | [attack-evolution-shrink](redteam/attack-evolution-shrink.md) | [promote-to-regression](redteam/promote-to-regression.md) |
Expand Down
1 change: 1 addition & 0 deletions docs/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- [Persona & Scenario Studio: typed test cases you can measure](docs/simulate/persona-scenario-studio.md): twin=examples/sdk_persona_scenario_studio.py kind=agent-learning.persona-library.v1
- [The Regression Lifecycle: baseline, compare, promote, replay, shrink](docs/simulate/regression-lifecycle.md): twin=examples/sdk_regression_artifact_suite.py kind=agent-learning.baseline.v1
- [Simulate Any Framework](docs/simulate/simulate-any-framework.md): twin=examples/sdk_multi_framework_simulation.py kind=agent-learning.suite.v1
- [The Simulation Spine — One Spec, One Runner](docs/simulate/spec-and-runner.md): twin=examples/sdk_spec_runner_quickstart.py kind=none
- [Voice loopback: the deterministic audio channel, credential-free](docs/simulate/voice-loopback.md): twin=examples/sdk_voice_loopback.py kind=agent-learning.run.v1
- [Voice and Realtime: simulate the session before you dial it](docs/simulate/voice-realtime.md): twin=examples/sdk_framework_adapter_realtime_trace.py kind=agent-learning.run.v1
- [Worlds and Hooks: simulate against executable state](docs/simulate/worlds-and-hooks.md): twin=examples/sdk_world_hooks_optimization.py kind=agent-learning.optimization.v1
Expand Down
4 changes: 4 additions & 0 deletions docs/simulate/first-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ opt_in_lane: false
> · emits `agent-learning.run.v1` · offline, no credentials.
> A coding agent can complete this page from the frontmatter alone.

> **New here?** This page is the manifest/CLI front door. To see the same run
> as the underlying `SimulationSpec` + `SimulationRunner` — the spine every
> simulation flows through — start with [Spec + Runner](spec-and-runner.md).

## 1. What you are testing

A simulation run is the kit's atomic unit: one manifest in, one
Expand Down
134 changes: 134 additions & 0 deletions docs/simulate/spec-and-runner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
kind: agent-learning.docs-page.v1
track: simulate
objective: behavior
stage: simulate
backing:
- examples/sdk_spec_runner_quickstart.py
artifact_kinds: []
commands:
- python examples/sdk_spec_runner_quickstart.py artifacts/spec-and-runner.json
postcondition: python -c "import json; p=json.load(open('artifacts/spec-and-runner.json')); assert 'COMPLETED' in p['status'], p['status']; print('ok')"
claims: []
doctor_checks:
- missing_engine_modules
- public_boundary_passed
opt_in_lane: false
---

# The Simulation Spine — One Spec, One Runner

> **Twin:** [`examples/sdk_spec_runner_quickstart.py`](../../examples/sdk_spec_runner_quickstart.py)
> · offline, no credentials.
> For the full end-to-end tour (real LLM, voice, platform submit, custom
> worlds) see [`examples/agent_learning_sdk_demo_v2.ipynb`](../../examples/agent_learning_sdk_demo_v2.ipynb).

Under the manifest and CLI surfaces, every simulation — chat, voice, or a
custom world — runs through **one** spine: a frozen `SimulationSpec` fed to
**one** `SimulationRunner`. Providers, environments, and agent kinds are
registry entries, not hardcoded branches. This page runs the smallest version of
that spine directly, so the moving parts are visible.

| Piece | In the SDK | You supply |
| --- | --- | --- |
| **Environment** | a registered plugin (`chat`, `voice`, or your own) that owns the world + action space | pick one, or `@register_environment` your own |
| **Target** | an ActorSource (`system_prompt` \| callable \| `factory` \| `http` \| `framework`), or any object with `.call()` | drop in your agent |
| **Scenario** | personas + situations + desired outcomes | describe the test |
| **Contract** | a frozen `SimulationSpec` tying the three together | declarative, secret-free |
| **Runner** | one `SimulationRunner` — same spine for chat and voice | `.run(spec, target=...)` |

## 1. Install and check

```bash
git clone https://github.com/future-agi/agent-learning-kit
cd agent-learning-kit
uv sync # or: pip install -e .
uv run agent-learn doctor # status: passed
```

## 2. Run the smallest simulation

A synthetic user drives a conversation against a plain `.call()` target through
the runner. No API key, no network.

```python
import asyncio
import fi.alk.simulate as S
from fi.simulate.agent.wrapper import AgentInput, AgentResponse


class EchoAgent:
async def call(self, agent_input: AgentInput) -> AgentResponse:
last = agent_input.messages[-1]["content"] if agent_input.messages else "hi"
return AgentResponse(content=f"You said: {last}. How can I help further?")


async def main():
print(sorted(S.environment_registry.names())) # ['chat', 'voice']
spec = S.SimulationSpec(
run_id="spec_runner_quickstart",
environment=S.EnvironmentSpec(
adapter=S.EnvironmentAdapters.CHAT,
world_kind=S.WorldKinds.CONVERSATION,
config={"max_turns": 3, "min_turns": 1},
),
target=S.AgentEndpointSpec(adapter=S.TargetAdapters.CALLABLE),
simulator=S.SimulatorPolicySpec(adapter=S.SimulatorAdapters.SYNTHETIC_USER),
scenario=S.Scenario(name="late-delivery", dataset=[
S.Persona(persona={"name": "Morgan", "role": "customer"},
situation="A delivery is 3 days late; ask for status and ETA.",
outcome="Get a clear status and a concrete next step.")]),
)
report = await S.SimulationRunner().run(spec, target=EchoAgent())
print(report.status)
print(report.test_cases[0].result.transcript)


asyncio.run(main())
```

The backing twin is the same run, writing the finished report to a path:

```bash
python examples/sdk_spec_runner_quickstart.py artifacts/spec-and-runner.json
python -c "import json; p=json.load(open('artifacts/spec-and-runner.json')); assert 'COMPLETED' in p['status']; print('ok')"
```

Every adapter slot accepts the enum (`S.EnvironmentAdapters.CHAT`) **or** the
plain string it equals (`"chat"`) — same `spec_hash`. Enums give you
autocomplete and typo-safety for the built-ins; custom registered names stay
plain strings.

## 3. Drop in your own agent

`EchoAgent` was passed straight to the runner. Every other way you'd hand the
kit an agent is an ActorSource resolved through one registry — you declare what
you have, you never edit the engine:

```python
S.get_profile("system_prompt").resolve_target({"system_prompt": "...", "model": "gpt-4o"})
S.get_profile("factory").resolve_target({"target": "mypkg.agents:Support", "factory": True})
S.get_profile("http").resolve_target({"url": "https://my-agent/turn"})
S.get_profile("framework").resolve_target({"target": "mypkg:graph"}) # LangGraph / CrewAI / ...
```

Code-loading kinds (`factory`, `import_object`, `framework`, callable) carry
`runs_caller_code == True` and are deny-by-default in hosted runs — locally they
resolve in-process, hosted they route through the sandbox. `http` and
`system_prompt` are the hosted-safe kinds. Check with
`S.get_profile("http").runs_caller_code`.

## 4. Where to go next

| Task | Spec + Runner API |
| --- | --- |
| Real-LLM chat | `SimulationRunner().run(spec, target=your_llm_object)` |
| Voice (Vapi / Retell / WebRTC) | `SimulationRunner().run(voice_spec)` — see the v2 notebook §7 |
| Score a run | `evaluate_agent_report(report.to_legacy())` |
| Register your own world | `@register_environment("name")` |
| Submit to the platform | `result_sink=FutureAGIResultSink(...)` |

- Full runnable tour: [`examples/agent_learning_sdk_demo_v2.ipynb`](../../examples/agent_learning_sdk_demo_v2.ipynb).
- The older manifest/CLI front door: [`first-run.md`](first-run.md) — same
concepts, assembled into a `SimulationSpec` underneath.
- Frameworks as targets: [`simulate-any-framework.md`](simulate-any-framework.md).
Loading
Loading