Skip to content

feat(adr-006): Phase 1 — Python SDK + scaffolder + self-serve webhook install#197

Closed
lilyshen0722 wants to merge 1 commit intomainfrom
adr-006-phase-1
Closed

feat(adr-006): Phase 1 — Python SDK + scaffolder + self-serve webhook install#197
lilyshen0722 wants to merge 1 commit intomainfrom
adr-006-phase-1

Conversation

@lilyshen0722
Copy link
Copy Markdown
Contributor

Summary

Three artifacts in one PR per ADR-006 §Migration path Phase 1. The whole point: `commonly agent init --language python --name research-bot --pod ` produces a running custom Commonly agent in 60 seconds. No publish step, no admin involvement, no framework adoption — just the four CAP verbs.

What's in

1. Backend self-serve webhook install (touches kernel — needs deploy)

  • `backend/models/AgentRegistry.ts` — added `ephemeral?: boolean`. `search()` filters `ephemeral: { $ne: true }` so the marketplace catalog never surfaces self-serve rows. Direct `getByName()` still resolves them so install/uninstall flows work.
  • `backend/routes/registry/install.ts` — when the lookup returns null AND `config.runtime.runtimeType === 'webhook'`, synthesize an ephemeral registry row owned by the installing user. Pod-membership check fronts everything; non-webhook installs without manifest still 404; `podId` required (ADR-006 invariant 7); `agentName` regex-validated (returns 400 instead of Mongoose 500); structured `[cap self-serve-install] user=… pod=… agent=… runtime=webhook` log on every synthesis.
  • `backend/routes/registry/pod-agents.ts` — comment in the uninstall route flagging the ephemeral GC gap (ADR-006 OQ Add basic unit tests for backend functions #1) for the day someone wires a janitor.
  • `backend/tests/integration/self-serve-install.test.js` — 5 cases: happy path (token works), non-webhook 404, malformed name 400, non-member 403, catalog excludes ephemeral.

2. Python SDK (in-repo reference, no published package per ADR-006 §invariant 5)

  • `examples/sdk/python/commonly.py` — ~150 LOC, stdlib only (`urllib.request`). Class `Commonly` with the four CAP verbs (`poll_events` / `ack` / `post_message` / `get_memory` / `sync_memory`) + a `run()` convenience loop. `CommonlyError` carries `.status` + `.body` so callers branch on 401/404. ADR-003 invariant Fix GitHub workflow to test frontend with coverage #9 documented in `sync_memory`'s docstring (server-stamps `byteSize`/`updatedAt`/`schemaVersion`). `run()` docstring now matches the actual no-ack-on-handler-error semantics (kernel re-delivers, per ADR-005 §Spawning semantics).
  • `examples/hello-world-python/bot.py` — ~50 LOC echo template; reads token from `COMMONLY_TOKEN` env or KEY=VALUE-formatted `.commonly-env` file.

3. CLI scaffolder

  • `cli/src/commands/agent.js` — `init --language python --name --pod ` subcommand and an exported `performInit` core. Refuses to clobber any of the 3 output files (`commonly.py`, `.py`, `.commonly-env`) AND aborts before issuing the install POST if any clobber is detected. Writes `.commonly-env` with mode 0600 in `COMMONLY_TOKEN=cm_agent_…` format (sourceable + dotenv-friendly). Reads SDK + template from the repo via `import.meta.url`-relative paths; works from any cwd.
  • `cli/tests/agent-init.test.mjs` — 4 cases: full happy path with byte-for-byte SDK/template equality assertions vs the canonical examples, `/runtime-tokens` fallback, clobber refusal (asserts `client.post` was never called), unknown-language reject.

Reviewer pass (code-reviewer agent, grounded in REVIEW.md + ADR-006)

1 Critical + 4 Important + several nits, all addressed before commit:

  • `run()` docstring contradicted code (claimed always-ack; code skips ack on handler error). Doc fixed.
  • `agentName` regex pre-validation (avoid Mongoose 500 → return 400).
  • Explicit `podId` guard on the self-serve branch (defense in depth for ADR-006 invariant 7).
  • Test for malformed agentName added.
  • Added `COMMONLY_TOKEN=`-prefixed `.commonly-env` for sourceable / dotenv compatibility.
  • `repoFile()` documented with ADR-006 §Phase 4 removal condition (publish on npm).
  • Catalog test fixture cleanup widened so test order can't leak.
  • Comment in uninstall route pointing to ephemeral GC gap (OQ Add basic unit tests for backend functions #1).

What's NOT in (deferred)

  • Node SDK + `init --language node` — Phase 2 of ADR-006. Mirror PR.
  • `docs/webhook-sdk.md` quickstart — Phase 3.
  • Published `pip install commonly` — Phase 4 (gated on CAP freeze + 2+ external authors using the in-repo reference).
  • Per-user install cap / janitor for ephemeral rows — open questions in ADR; tracked.

Test plan

  • `cd cli && npm test` — 57/57 passing (6 suites)
  • `cd backend && npx jest tests/integration/self-serve-install` — 5/5 passing
  • Backend type-check clean
  • Both Python files compile
  • Live smoke against api-dev once deployed — will run `commonly agent init … && python .py` end-to-end and verify pod post

🤖 Generated with Claude Code

… install

Three artifacts in one PR per ADR-006 §Migration path Phase 1:

1. Backend self-serve webhook install
   - models/AgentRegistry.ts: `ephemeral?: boolean`; search() filters ephemeral
     rows so they never appear in the marketplace catalog. Direct getByName()
     still resolves them for install/uninstall.
   - routes/registry/install.ts: pod-member who installs a webhook agent with
     no published manifest gets an ephemeral registry row synthesized in
     their name. Membership check fronts everything; non-webhook installs
     without manifest still 404; podId required (ADR-006 invariant 7);
     agentName regex-validated (returns 400 vs Mongoose 500); structured
     [cap self-serve-install] log fires on every synthesis.
   - routes/registry/pod-agents.ts: comment noting the ephemeral GC gap
     (ADR-006 OQ #1) for the day someone wires the janitor.
   - __tests__/integration/self-serve-install.test.js: 5 cases — happy path
     (token works), non-webhook 404, malformed name 400, non-member 403,
     catalog excludes ephemeral.

2. Python SDK
   - examples/sdk/python/commonly.py: ~150 LOC, stdlib only. Class Commonly
     with the four CAP verbs (poll_events / ack / post_message / get_memory /
     sync_memory) + a run() loop. CommonlyError carries .status + .body.
     ADR-003 invariant #9 documented in sync_memory; run() docstring matches
     the actual no-ack-on-handler-error semantics (kernel re-delivers).
   - examples/hello-world-python/bot.py: ~50 LOC echo template; reads token
     from COMMONLY_TOKEN env or KEY=VALUE-formatted .commonly-env file.

3. CLI scaffolder
   - cli/src/commands/agent.js: `init --language python --name <n> --pod <id>`
     subcommand and an exported performInit core. Refuses to clobber any of
     the 3 output files (`commonly.py`, `<name>.py`, `.commonly-env`); writes
     .commonly-env with mode 0600 in KEY=VALUE format. Reads SDK + template
     via import.meta.url (works from any cwd; will need bundling at Phase 4
     publish — comment links to ADR section).
   - cli/__tests__/agent-init.test.mjs: 4 cases — full happy path with
     byte-for-byte SDK/template equality, /runtime-tokens fallback, clobber
     refusal (asserts NO install POST issued), unknown-language reject.

57/57 CLI tests, 5/5 backend integration tests, type-check clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lilyshen0722 added a commit that referenced this pull request Apr 16, 2026
… install (#197)

Self-serve webhook install: pod members can POST /install with
runtimeType:'webhook' and no pre-published manifest — backend
synthesizes an ephemeral AgentRegistry row, mints a runtime token,
and the agent polls /events immediately. Ephemeral rows are excluded
from marketplace catalog browse.

Python SDK (examples/sdk/python/commonly.py): ~150 LOC reference
client — poll_events, ack, post_message, get/sync_memory, run().
Skips ack on handler exception so kernel re-delivers (at-least-once).

CLI scaffolder (`commonly agent init --language python`): copies SDK +
bot template, writes .commonly-env (0600), issues install + token mint.
Clobber-protection refuses to overwrite existing files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lilyshen0722
Copy link
Copy Markdown
Contributor Author

Merged via local squash+push → db7a223

lilyshen0722 added a commit that referenced this pull request Apr 16, 2026
…1 as shipped

Sweep of documentation catching up with today's merges:

- ADR-003: status line now includes Phase 3 §Deliverable 3 shipped
  (2026-04-15). Added revision note pointing at the cross-check test
  file and its seven assertions. Deliverable 3 itself marked shipped
  inline.

- ADR-005: status bumped from Draft to Accepted. Revision history
  records Phase 1a (PR #194), Phase 1b (PR #195), and the three
  follow-up fixes that landed during/after live smoke (cwd guard +
  --instance shadowing #196, self-mention loop #201, URL/key
  asymmetry #202). Phase 1a + 1b headers marked shipped inline.

- ADR-006: status bumped from Draft to Accepted. Revision history
  names Phase 1 (PR #197), the Cloudflare UA follow-up
  (5db9376), and the live-smoke result on api-dev. Open Question
  #1 (ephemeral GC) updated with current TODO location.

- CLAUDE.md Agent Runtime Quick Rules: five new prescriptive rules
  — self-mention guard, `commonly agent init` self-serve path,
  Python SDK User-Agent requirement, CLI --instance symmetry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lilyshen0722 added a commit that referenced this pull request Apr 16, 2026
…ipped (#203)

* fix(cli): resolve --instance symmetrically (saved key OR URL)

Historically `resolveInstanceUrl` treated --instance as a URL and
`getToken` treated it as a config key. So:

  commonly whoami --instance https://api-dev.commonly.me
  → URL resolved correctly, but token lookup returned null (no auth)

  commonly whoami --instance dev
  → token found, but "dev" was returned literally as the URL (ENOTFOUND)

Neither form worked end-to-end. Filed as a follow-up during ADR-005
Phase 1b smoke.

Fix: funnel both functions through a new `resolveInstance(identifier)`
helper that probes URL-shaped inputs (http[s]://) against saved URLs
first (case-insensitive, trailing-slash tolerant), then falls back to
exact-key lookup, then to an unknown-URL bootstrap, then to null. Both
forms of --instance now work for both URL and token resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(adr): mark ADR-003 Phase 3, ADR-005 Phases 1a+1b, ADR-006 Phase 1 as shipped

Sweep of documentation catching up with today's merges:

- ADR-003: status line now includes Phase 3 §Deliverable 3 shipped
  (2026-04-15). Added revision note pointing at the cross-check test
  file and its seven assertions. Deliverable 3 itself marked shipped
  inline.

- ADR-005: status bumped from Draft to Accepted. Revision history
  records Phase 1a (PR #194), Phase 1b (PR #195), and the three
  follow-up fixes that landed during/after live smoke (cwd guard +
  --instance shadowing #196, self-mention loop #201, URL/key
  asymmetry #202). Phase 1a + 1b headers marked shipped inline.

- ADR-006: status bumped from Draft to Accepted. Revision history
  names Phase 1 (PR #197), the Cloudflare UA follow-up
  (5db9376), and the live-smoke result on api-dev. Open Question
  #1 (ephemeral GC) updated with current TODO location.

- CLAUDE.md Agent Runtime Quick Rules: five new prescriptive rules
  — self-mention guard, `commonly agent init` self-serve path,
  Python SDK User-Agent requirement, CLI --instance symmetry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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