Skip to content

[AAASM-1846] ✨ (core): Add gateway URL / api_key resolver + local auto-start#58

Merged
Chisanan232 merged 19 commits into
masterfrom
v0.0.1/AAASM-1846/feat/sdk_auto_detect_local_gateway
May 23, 2026
Merged

[AAASM-1846] ✨ (core): Add gateway URL / api_key resolver + local auto-start#58
Chisanan232 merged 19 commits into
masterfrom
v0.0.1/AAASM-1846/feat/sdk_auto_detect_local_gateway

Conversation

@Chisanan232
Copy link
Copy Markdown
Contributor

@Chisanan232 Chisanan232 commented May 22, 2026

Description

Add zero-config gateway resolution to the Python SDK so init_assembly() with no arguments connects to a local gateway at http://localhost:7391, auto-starting aasm start --mode local --foreground if nothing is listening.

Implements ST-1 (AAASM-1846) of Story AAASM-1581 (E17 S-G — SDK auto-detect across Python / Node / Go) under Epic AAASM-1568.

Resolution order

  1. Explicit kwarg (init_assembly(gateway_url="...", api_key="..."))
  2. Environment variable (AAASM_GATEWAY_URL, AAASM_API_KEY)
  3. Config file (~/.aasm/config.yaml, soft pyyaml dependency)
  4. Local default — probe http://localhost:7391/healthz; if unreachable, spawn aasm start --mode local --foreground (detached via start_new_session=True) and wait up to 5s

Files

  • New agent_assembly/core/gateway_resolver.py_probe_healthz, _wait_for_healthz, _load_config_file, _auto_start_gateway, public resolve_gateway_url / resolve_api_key
  • Edit agent_assembly/core/assembly.pyinit_assembly() makes gateway_url and api_key keyword-only optional, wires resolvers; _validate_inputs no longer rejects empty api_key (local mode is unauth-accepting per Epic 17)
  • New test/unit/core/test_gateway_resolver.py — 25 unit tests
  • Edit test/unit/test_assembly.py — adds zero-arg + explicit-args regression tests; drops empty-string-raises assertions
  • Edit test/integration/test_assembly_integration.py — same alignment

Type of Change

  • ✨ New feature

Breaking Changes

  • No
  • Yes (please describe below)

init_assembly(gateway_url, api_key, ...) — both arguments are now optional, defaulting to None. Callers that explicitly passed empty strings (gateway_url="" / api_key="") now get resolver fallback behavior instead of an immediate ConfigurationError. Callers passing real values are unaffected (covered by a dedicated regression test).

Related Issues

Testing

  • Unit tests added (25 new in test/unit/core/test_gateway_resolver.py)
  • Unit tests added (2 new in test/unit/test_assembly.py — zero-arg + explicit-args regression)
  • Integration test updated to match new contract
  • Manual testing performed — full suite: 404 passed, 11 skipped (up from 377 on master)
.venv/bin/python -m pytest test/ --no-cov -q
…
404 passed, 11 skipped, 1 rerun in 15.30s

All subprocess and HTTP calls are mocked in unit tests — no real aasm spawn, no real network. Healthz probe / auto-start timing patched (time.sleep patched too) so the resolver test suite runs in <50ms total.

Checklist

  • Code follows project style guidelines (granular per-helper / per-test commits, GitEmoji subjects)
  • Self-review completed
  • Resolver chain documented in module docstring + per-function docstrings
  • No new runtime dependencies (PyYAML is a soft dep via try: import yaml)
  • All tests passing

Lays down the new module that will host the zero-config resolution logic
for init_assembly (AAASM-1846 / E17 S-G). Defines the default gateway URL,
healthz path, probe / auto-start timeouts, env-var names, and the
auto-start argv tuple. No behavior yet.
Synchronous httpx GET against ``{base_url}/healthz`` with a tight
default 500ms timeout. Any HTTPError is swallowed (timeout, connection
refused, DNS failure) and surfaces as False — the resolver treats
unreachable as "absent" rather than fatal.
Covers the three expected outcomes: 2xx → True (and verifies the
``/healthz`` suffix), httpx exception → False, non-2xx → False
(parametrized across 400/404/500/503). All httpx calls mocked — no
network.
Polls the gateway healthz endpoint until success or timeout. Used after
``_auto_start_gateway`` to know when the freshly-spawned local CP is
ready to accept connections. Default 5s budget per Story AC; final
re-probe after the deadline ensures borderline races resolve cleanly.
Three behaviors: success on first probe (no sleep), success after two
prior failures (verifies the poll-then-sleep loop body), and false when
the timeout elapses with no success. All time.sleep calls patched so the
test stays under 50ms.
Reads ~/.aasm/config.yaml when present. PyYAML is treated as a soft
dependency — missing import returns an empty dict so the resolver
falls through to the local-default step. File-missing, OS errors,
parse errors, and non-mapping payloads all collapse to the same empty
result — config-file lookup is purely advisory.
Covers four behaviors: missing file → {}, well-formed YAML →
parsed mapping, non-mapping root (e.g. top-level list) → {},
and yaml-module-absent → {} (simulated by stubbing sys.modules).
Uses tmp_path; no real ~/.aasm/ touched.
Spawns ``aasm start --mode local --foreground`` as a detached subprocess
and waits for /healthz to become ready. Raises ConfigurationError when
the aasm binary is missing from PATH and GatewayError when the spawned
gateway doesn't come up within the timeout. The detached
start_new_session=True is the docker-daemon-style hand-off that lets
the gateway survive after the calling Python process exits.
Three behaviors: aasm-missing → ConfigurationError with install hint,
spawn succeeds + healthz ready → returns None (also pins the argv and
start_new_session=True for the detach contract), spawn succeeds but
timeout elapses → GatewayError. All subprocess and HTTP calls patched.
Implements the 4-step precedence chain from Epic 17 S-G: explicit kwarg
→ AAASM_GATEWAY_URL env var → ~/.aasm/config.yaml agent.gateway_url →
local default (probe + auto-start). Steps 1-3 short-circuit; step 4 may
spawn ``aasm`` and bubble ConfigurationError/GatewayError when the local
gateway is unavailable and cannot be brought up.
Five tests exercising the 4-step precedence chain:
explicit > env > config > local-default; the local-default branch is
split into probe-hit (no auto-start) and probe-miss (auto-start
invoked with the canonical localhost URL). monkeypatch handles env-var
isolation so tests stay parallel-safe.
Mirrors resolve_gateway_url's 4-step precedence chain for api_key:
explicit kwarg → AAASM_API_KEY env → config file → empty default.
No auto-start path — local mode is unauth-accepting per the Epic, so
the empty fallback is the documented default rather than an error.
Four tests mirroring resolve_gateway_url's chain: explicit > env >
config > empty-default. No raise on missing — empty string is the
documented local-mode default.
Relaxes init_assembly to accept None for gateway_url and api_key and
calls resolve_gateway_url / resolve_api_key to fill them in via the
4-step precedence chain (explicit → env → config → local default +
auto-start). _validate_inputs no longer rejects empty api_key — local
mode is unauth-accepting per Epic 17. Existing callers that pass a
real URL + key are unaffected.
Three small fixes so pre-commit mypy passes on the new module: explicit
int annotation on httpx response.status_code to avoid Any return,
multi-code type: ignore on the soft yaml import (covers both the
import-untyped at runtime and the unused-ignore when stubs are present
in pre-commit's isolated mypy env), and string-form patch() in the
tests to avoid attr-defined complaints on module-level imports.
Empty gateway_url and empty api_key are no longer hard errors —
they now route through the resolver chain (env → config file →
local default) per AAASM-1846. Only the truly invalid mode case
still raises ConfigurationError immediately. The zero-config and
explicit-args paths are covered by separate new tests.
Exercises the Story AAASM-1846 primary AC: init_assembly() with no
arguments and no env vars connects to http://localhost:7391 and uses
empty api_key. Probes are patched so the test doesn't require a real
local gateway or aasm binary on PATH.
Story AC: existing callers passing both gateway_url and api_key must be
unaffected by the resolver path. Patches _probe_healthz and
_auto_start_gateway with sentinels that raise if invoked — proves the
resolver short-circuits on explicit args and the client binds verbatim.
Empty gateway_url and empty api_key are now resolver-handled, so they
no longer raise ConfigurationError directly — same behavior shift as
the unit test. Keeping only the unknown-mode case, which remains an
immediate ConfigurationError. Fixes integration suite regression from
this branch and prevents the leaked _ACTIVE_CONTEXT chain that
cascaded into the topology-registration tests.
@sonarqubecloud
Copy link
Copy Markdown

@codecov
Copy link
Copy Markdown

codecov Bot commented May 22, 2026

Codecov Report

❌ Patch coverage is 97.67442% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
agent_assembly/core/gateway_resolver.py 97.53% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Chisanan232
Copy link
Copy Markdown
Contributor Author

Chisanan232 commented May 23, 2026

🤖 Claude Code review record — AAASM-1846 ST-1

Reviewer: Claude Code (Opus 4.7, 1M context)
Review date: 2026-05-23
Branch head: v0.0.1/AAASM-1846/feat/sdk_auto_detect_local_gateway

CI status

Check group Result
Unit tests (3.13 on ubuntu-latest / ubuntu-22.04 / macos-latest / macos-14)
Integration tests (3.13 across same matrix)
Native core build check
Benchmarks
codecov/patch
SonarCloud Code Analysis ✅ Quality Gate Passed — 97.7% coverage on new code, 0 issues, 0 hotspots
mergeStateStatus CLEAN

All 22 checks green. No failing gates.

Scope coverage vs ticket

Cross-checked PR diff against AAASM-1846 description:

  • agent_assembly/core/gateway_resolver.py ships all six required helpers (_probe_healthz, _wait_for_healthz, _load_config_file, _auto_start_gateway, resolve_gateway_url, resolve_api_key)
  • agent_assembly/core/assembly.py relaxed — init_assembly accepts None for gateway_url / api_key, calls resolvers, validation no longer rejects empty api_key
  • 25 new resolver tests in test/unit/core/test_gateway_resolver.py
  • Zero-arg + explicit-args regression tests added (co-located in existing test/unit/test_assembly.py rather than a separate test_init_zero_config.py — minor path deviation, functionally equivalent)
  • Story description named agent_assembly/core/init.py; targeted the real module agent_assembly/core/assembly.py. Documented in PR body.

Acceptance criteria coverage

AC Status Evidence
init_assembly() no-args connects to http://localhost:7391 test_init_assembly_zero_arg_resolves_local_default
AAASM_GATEWAY_URL env var overrides default TestResolveGatewayUrl::test_env_var_takes_precedence_over_config_and_default
aasm on PATH + probe miss → auto-start + connect test_spawns_subprocess_and_returns_when_ready (also pins argv + start_new_session=True)
aasm absent + probe miss → ConfigurationError with install hint test_raises_configuration_error_when_aasm_not_on_path
5s auto-start timeout → GatewayError test_raises_gateway_error_on_timeout
Existing positional callers unaffected test_init_assembly_explicit_args_bypass_resolver (sentinel stubs on resolver internals)
Unit tests mock subprocess + HTTP (no real spawn / network) Verified — patch("agent_assembly.core.gateway_resolver.subprocess.Popen") + patch("agent_assembly.core.gateway_resolver.shutil.which") + httpx mocks throughout

End-to-end smoke (run 2026-05-23 against aa-gateway --mode local)

  • ✅ Probe-hit: init_assembly(mode='sdk-only')gateway_url='http://localhost:7391', api_key=''
  • ✅ Aasm-missing: raises ConfigurationError: No gateway found at http://localhost:7391 and 'aasm' is not on PATH. Install it with: pip install agent-assembly[cli]

Smoke output captured in the cross-SDK verification report (agent-assembly #727).

Verdict

Approved for merge. CI fully green, scope complete, all 7 ACs covered with proof tests, end-to-end smoke passes.

@Chisanan232 Chisanan232 merged commit 14c1a0e into master May 23, 2026
24 checks passed
@Chisanan232 Chisanan232 deleted the v0.0.1/AAASM-1846/feat/sdk_auto_detect_local_gateway branch May 23, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant