@openhands/extensions 0.5.0 → 0.6.0

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 (132) hide show
  1. package/.github/workflows/pypi-publish.yml +104 -0
  2. package/.github/workflows/tests.yml +6 -0
  3. package/.release-please-manifest.json +1 -1
  4. package/AGENTS.md +1 -1
  5. package/README.md +31 -3
  6. package/integrations/README.md +17 -7
  7. package/integrations/catalog/airtable.json +40 -10
  8. package/integrations/catalog/apify.json +0 -2
  9. package/integrations/catalog/asana.json +38 -0
  10. package/integrations/catalog/atlassian.json +5 -8
  11. package/integrations/catalog/bitbucket.json +39 -0
  12. package/integrations/catalog/box.json +39 -0
  13. package/integrations/catalog/brave-search.json +1 -2
  14. package/integrations/catalog/browser-mcp.json +0 -3
  15. package/integrations/catalog/canva.json +35 -0
  16. package/integrations/catalog/clickhouse.json +1 -2
  17. package/integrations/catalog/clickup.json +36 -0
  18. package/integrations/catalog/cloudflare-bindings.json +5 -8
  19. package/integrations/catalog/cloudflare-browser-rendering.json +5 -8
  20. package/integrations/catalog/cloudflare-builds.json +6 -9
  21. package/integrations/catalog/cloudflare-docs.json +4 -7
  22. package/integrations/catalog/cloudflare-observability.json +5 -8
  23. package/integrations/catalog/confluence.json +38 -0
  24. package/integrations/catalog/datadog.json +34 -0
  25. package/integrations/catalog/deepwiki.json +3 -7
  26. package/integrations/catalog/discord.json +39 -0
  27. package/integrations/catalog/dropbox.json +38 -0
  28. package/integrations/catalog/elevenlabs.json +27 -28
  29. package/integrations/catalog/everything.json +0 -2
  30. package/integrations/catalog/exa.json +0 -2
  31. package/integrations/catalog/fetch.json +0 -2
  32. package/integrations/catalog/figma.json +41 -9
  33. package/integrations/catalog/filesystem.json +0 -3
  34. package/integrations/catalog/firecrawl.json +0 -2
  35. package/integrations/catalog/freshdesk.json +38 -0
  36. package/integrations/catalog/git.json +0 -3
  37. package/integrations/catalog/github.json +43 -10
  38. package/integrations/catalog/gitlab.json +38 -0
  39. package/integrations/catalog/gmail.json +38 -0
  40. package/integrations/catalog/google-calendar.json +38 -0
  41. package/integrations/catalog/google-docs.json +38 -0
  42. package/integrations/catalog/google-drive.json +38 -0
  43. package/integrations/catalog/google-sheets.json +38 -0
  44. package/integrations/catalog/hubspot.json +34 -0
  45. package/integrations/catalog/huggingface.json +1 -2
  46. package/integrations/catalog/intercom.json +39 -0
  47. package/integrations/catalog/jira.json +38 -0
  48. package/integrations/catalog/kagi.json +2 -3
  49. package/integrations/catalog/linear.json +47 -16
  50. package/integrations/catalog/mailchimp.json +38 -0
  51. package/integrations/catalog/memory.json +0 -2
  52. package/integrations/catalog/microsoft-outlook.json +38 -0
  53. package/integrations/catalog/microsoft-teams.json +38 -0
  54. package/integrations/catalog/miro.json +38 -0
  55. package/integrations/catalog/monday.json +38 -0
  56. package/integrations/catalog/mongodb.json +1 -2
  57. package/integrations/catalog/neon.json +0 -2
  58. package/integrations/catalog/netlify.json +38 -0
  59. package/integrations/catalog/notion.json +37 -10
  60. package/integrations/catalog/obsidian.json +1 -3
  61. package/integrations/catalog/okta.json +38 -0
  62. package/integrations/catalog/onedrive.json +38 -0
  63. package/integrations/catalog/ordinal.json +33 -0
  64. package/integrations/catalog/paypal.json +5 -8
  65. package/integrations/catalog/pipedrive.json +39 -0
  66. package/integrations/catalog/plaid.json +31 -0
  67. package/integrations/catalog/playwright.json +0 -2
  68. package/integrations/catalog/posthog.json +38 -0
  69. package/integrations/catalog/quickbooks.json +38 -0
  70. package/integrations/catalog/redis.json +1 -3
  71. package/integrations/catalog/resend.json +1 -2
  72. package/integrations/catalog/salesforce.json +38 -0
  73. package/integrations/catalog/sentry.json +52 -14
  74. package/integrations/catalog/sequential-thinking.json +0 -2
  75. package/integrations/catalog/servicenow.json +39 -0
  76. package/integrations/catalog/sharepoint.json +38 -0
  77. package/integrations/catalog/shopify.json +38 -0
  78. package/integrations/catalog/slack.json +49 -10
  79. package/integrations/catalog/stripe.json +44 -14
  80. package/integrations/catalog/supabase.json +36 -9
  81. package/integrations/catalog/tavily.json +0 -2
  82. package/integrations/catalog/time.json +0 -2
  83. package/integrations/catalog/trello.json +31 -0
  84. package/integrations/catalog/vercel.json +38 -0
  85. package/integrations/catalog/webflow.json +34 -0
  86. package/integrations/catalog/xero.json +38 -0
  87. package/integrations/catalog/zendesk.json +38 -0
  88. package/integrations/catalog/zoom.json +38 -0
  89. package/integrations/catalog-index.js +171 -0
  90. package/integrations/index.d.ts +24 -60
  91. package/integrations/index.js +29 -207
  92. package/marketplaces/openhands-extensions.json +14 -1
  93. package/package.json +4 -7
  94. package/plugins/cobol-modernization/README.md +3 -3
  95. package/plugins/cobol-modernization/skills/{cobol-modernization-overview → cobol-modernization}/SKILL.md +1 -1
  96. package/plugins/magic-test/skills/magic-word/SKILL.md +1 -0
  97. package/plugins/migration-scoring/README.md +1 -1
  98. package/pyproject.toml +17 -3
  99. package/python/openhands_extensions/__init__.py +24 -0
  100. package/python/openhands_extensions/_version.py +24 -0
  101. package/python/openhands_extensions/integrations.py +92 -0
  102. package/release-please-config.json +2 -1
  103. package/scripts/build-integration-catalog.mjs +45 -0
  104. package/skills/agent-canvas-environment/.plugin/plugin.json +19 -0
  105. package/skills/agent-canvas-environment/README.md +5 -0
  106. package/skills/agent-canvas-environment/SKILL.md +141 -0
  107. package/skills/github-pr-reviewer/references/state-schema.md +18 -4
  108. package/skills/github-pr-reviewer/scripts/main.py +66 -12
  109. package/skills/github-repo-monitor/references/state-schema.md +11 -4
  110. package/skills/github-repo-monitor/scripts/main.py +67 -19
  111. package/skills/github-repo-monitor/tests/test_main.py +67 -8
  112. package/skills/index.js +19 -5
  113. package/skills/openhands-api/README.md +5 -1
  114. package/skills/openhands-api/SKILL.md +74 -5
  115. package/skills/openhands-automation/SKILL.md +3 -1
  116. package/skills/openhands-automation/references/custom-automation.md +166 -0
  117. package/skills/openhands-sdk/SKILL.md +8 -2
  118. package/skills/slack-channel-monitor/README.md +3 -3
  119. package/skills/slack-channel-monitor/SKILL.md +3 -2
  120. package/skills/slack-channel-monitor/references/state-schema.md +11 -4
  121. package/skills/slack-channel-monitor/scripts/main.py +111 -41
  122. package/tests/test_catalogs.py +21 -3
  123. package/tests/test_integration_catalog_in_sync.py +204 -0
  124. package/tests/test_slack_channel_monitor.py +142 -0
  125. package/tests/test_version_alignment.py +64 -0
  126. package/integrations/logos.d.ts +0 -5
  127. package/integrations/logos.js +0 -94
  128. package/integrations/oauth-provider-catalog.js +0 -553
  129. package/integrations/oauth-provider-registration-defaults.js +0 -610
  130. /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/SKILL.md +0 -0
  131. /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/references/cics-transformation-examples.md +0 -0
  132. /package/plugins/migration-scoring/skills/{migration-scoring-overview → migration-scoring}/SKILL.md +0 -0
@@ -1,9 +1,11 @@
1
1
  # openhands-api
2
2
 
3
- Reference skill + minimal clients for the **OpenHands Cloud API** (V1).
3
+ Reference skill + minimal clients for the **OpenHands Cloud API** (V1) and common **agent-server APIs**.
4
4
 
5
5
  This skill now also covers the **multi-conversation delegation pattern**: start additional Cloud conversations when you want fresh context windows, background work, or parallel tasks.
6
6
 
7
+ It also covers direct local Agent Canvas backend conversations when you need to call a local agent server with `X-Session-API-Key` instead of creating a Cloud app conversation.
8
+
7
9
  - Skill instructions and endpoint overview: [`SKILL.md`](./SKILL.md)
8
10
  - Minimal Python client: [`scripts/openhands_api.py`](./scripts/openhands_api.py)
9
11
  - Minimal TypeScript client: [`scripts/openhands_api.ts`](./scripts/openhands_api.ts)
@@ -42,6 +44,8 @@ If `app_conversation_id` is missing from the initial response, fetch it via:
42
44
  This skill is aligned against:
43
45
 
44
46
  - `OpenHands/docs/openhands/usage/cloud/cloud-api.mdx`
47
+ - `OpenHands/docs/openhands/usage/agent-canvas/backend-setup/local.mdx`
48
+ - `OpenHands/docs/sdk/arch/agent-server.mdx`
45
49
  - `OpenHands/docs/openhands/usage/api/v1.mdx`
46
50
  - `OpenHands/OpenHands/openhands/app_server/v1_router.py`
47
51
  - `OpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_router.py`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: openhands-api
3
- description: Reference skill for the OpenHands Cloud REST API (V1), including how to start additional cloud conversations for fresh-context or delegated work. Use when you need to automate common OpenHands Cloud actions; don't use for general sandbox/dev tasks unrelated to the OpenHands API.
3
+ description: Reference skill for the OpenHands Cloud REST API (V1) and agent-server APIs, including how to start additional cloud or local backend conversations for fresh-context or delegated work.
4
4
  triggers:
5
5
  - openhands-api
6
6
  - openhands-api-v1
@@ -10,14 +10,15 @@ triggers:
10
10
  - oh-cloud-api-v1
11
11
  ---
12
12
 
13
- This skill documents the **OpenHands Cloud API** (V1) and provides small, easy-to-copy clients.
13
+ This skill documents the **OpenHands Cloud API** (V1), commonly used **agent-server APIs**, and small, easy-to-copy clients.
14
14
 
15
- It is intentionally focused on common OpenHands Cloud workflows:
15
+ It is intentionally focused on common OpenHands API workflows:
16
16
 
17
17
  - Defaults to OpenHands Cloud (`https://app.all-hands.dev`).
18
18
  - Targets the **V1 app server REST API** under `/api/v1/...`.
19
19
  - Includes a few **agent server** endpoints (inside a sandbox) that use `X-Session-API-Key`.
20
20
  - Covers the **multi-conversation delegation pattern**: start separate Cloud conversations when you want fresh context windows or background work.
21
+ - Covers **local Agent Canvas backend conversations**: start or inspect conversations by calling a local agent server directly.
21
22
 
22
23
  ## When to use this skill
23
24
 
@@ -28,6 +29,7 @@ Use this skill when you need to:
28
29
  - monitor execution status for long-running jobs
29
30
  - create separate Cloud conversations for parallel or background work
30
31
  - access sandbox agent-server endpoints once a conversation is running
32
+ - start or inspect conversations on a local Agent Canvas backend or local agent server
31
33
 
32
34
  ## Auth
33
35
 
@@ -73,6 +75,72 @@ agent_server_url = conversation_url.rsplit("/api/conversations", 1)[0]
73
75
 
74
76
  If those fields are not present on the conversation record, list/search sandboxes (`GET /api/v1/sandboxes/search`) and use the sandbox referenced by the conversation to locate the agent server URL + session key.
75
77
 
78
+ ### Local Agent Canvas backend
79
+
80
+ Use the local backend flow only for local Agent Canvas / agent-server development, such as `agent-canvas`, `agent-canvas --backend-only`, or `npm run dev` with ingress at `http://localhost:8000`. This calls the agent server directly with `X-Session-API-Key`. It is not an automation, and it is different from OpenHands Cloud delegation through `POST /api/v1/app-conversations`, which uses Bearer auth against the Cloud app API and may return asynchronous start-task records.
81
+
82
+ When Agent Canvas runs locally, the launcher uses `LOCAL_BACKEND_API_KEY` when it is set. Otherwise it generates and persists the session API key at `~/.openhands/agent-canvas/api-key.txt`. Set `OH_SESSION_API_KEY_PATH` to override the persisted key path. Never print, log, or paste the actual key; use command substitution or an environment variable in examples and scripts.
83
+
84
+ ```bash
85
+ LOCAL_AGENT_SERVER_URL="${LOCAL_AGENT_SERVER_URL:-http://localhost:8000}"
86
+ SESSION_API_KEY="${LOCAL_BACKEND_API_KEY:-$(cat "${OH_SESSION_API_KEY_PATH:-$HOME/.openhands/agent-canvas/api-key.txt}")}"
87
+ ```
88
+
89
+ Check the local server before creating a backend conversation:
90
+
91
+ ```bash
92
+ curl -sS "${LOCAL_AGENT_SERVER_URL}/server_info" \
93
+ -H "X-Session-API-Key: ${SESSION_API_KEY}"
94
+ ```
95
+
96
+ Start a backend conversation with `POST /api/conversations`. Include the agent settings and workspace expected by that backend. Local agent-server calls use an explicit `workspace` such as `{"kind": "LocalWorkspace", "working_dir": "/workspace"}`; Cloud app-conversation delegation instead uses app-server fields such as `selected_repository` and `selected_branch`. If you are starting the conversation from an existing Agent Canvas session, pass through the current configured settings or encrypted settings rather than hard-coding secrets into scripts.
97
+
98
+ ```bash
99
+ CONVERSATION_JSON=$(curl -sS -X POST "${LOCAL_AGENT_SERVER_URL}/api/conversations" \
100
+ -H "X-Session-API-Key: ${SESSION_API_KEY}" \
101
+ -H "Content-Type: application/json" \
102
+ -d @- <<'JSON'
103
+ {
104
+ "agent": {
105
+ "kind": "Agent",
106
+ "llm": {
107
+ "model": "your-model-provider/your-model-name",
108
+ "api_key": "**********"
109
+ },
110
+ "tools": [
111
+ {"name": "terminal"},
112
+ {"name": "file_editor"},
113
+ {"name": "task_tracker"}
114
+ ]
115
+ },
116
+ "workspace": {"kind": "LocalWorkspace", "working_dir": "/workspace"},
117
+ "initial_message": {
118
+ "content": [{"text": "Summarize the current workspace."}],
119
+ "run": true
120
+ }
121
+ }
122
+ JSON
123
+ )
124
+ CONVERSATION_ID=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"${CONVERSATION_JSON}")
125
+ printf 'Conversation: %s/api/conversations/%s\n' "${LOCAL_AGENT_SERVER_URL}" "${CONVERSATION_ID}"
126
+ ```
127
+
128
+ Poll status and inspect recent events:
129
+
130
+ ```bash
131
+ curl -sS "${LOCAL_AGENT_SERVER_URL}/api/conversations/${CONVERSATION_ID}" \
132
+ -H "X-Session-API-Key: ${SESSION_API_KEY}"
133
+
134
+ curl -sS "${LOCAL_AGENT_SERVER_URL}/api/conversations/${CONVERSATION_ID}/events/search?limit=20&sort_order=TIMESTAMP_DESC" \
135
+ -H "X-Session-API-Key: ${SESSION_API_KEY}"
136
+ ```
137
+
138
+ If the same base URL serves the Agent Canvas UI, the browser route is:
139
+
140
+ ```bash
141
+ printf '%s/conversations/%s\n' "${LOCAL_AGENT_SERVER_URL}" "${CONVERSATION_ID}"
142
+ ```
143
+
76
144
  ## Common V1 app server endpoints
77
145
 
78
146
  The following are the main endpoints implemented in the minimal client:
@@ -389,11 +457,12 @@ See also:
389
457
 
390
458
  ## Source of truth
391
459
 
392
- This skill is aligned against the current V1 docs and implementation:
460
+ This skill is aligned against the current OpenHands API docs and implementation:
393
461
 
394
462
  - `OpenHands/docs/openhands/usage/cloud/cloud-api.mdx`
463
+ - `OpenHands/docs/openhands/usage/agent-canvas/backend-setup/local.mdx`
464
+ - `OpenHands/docs/sdk/arch/agent-server.mdx`
395
465
  - `OpenHands/docs/openhands/usage/api/v1.mdx`
396
466
  - `OpenHands/OpenHands/openhands/app_server/v1_router.py`
397
467
  - `OpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_router.py`
398
468
  - `OpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_models.py`
399
-
@@ -106,6 +106,8 @@ def fire_callback(status="COMPLETED", error=None):
106
106
 
107
107
  Entrypoint must be `python3 main.py` (no `setup.sh` needed). Wrap your main logic in `try/except` and call `fire_callback("FAILED", str(e))` in the except block.
108
108
 
109
+ **State persistence between runs** — polling automations that track a "last processed" timestamp or active conversation IDs must use the built-in KV store rather than local files. Local files are lost when a run ends on a cloud pod. The KV store is available when `AUTOMATION_KV_TOKEN` is injected into the run environment. See `references/custom-automation.md#state-persistence-kv-store` for ready-to-copy `kv_get` / `kv_set` / `load_state` / `save_state` helpers.
110
+
109
111
  ---
110
112
 
111
113
  ## Authentication
@@ -871,5 +873,5 @@ The **prompt preset** is the right default for genuinely agent-shaped work — a
871
873
 
872
874
  ## Reference Files
873
875
 
874
- - **`references/custom-automation.md`** — Detailed guide for custom automations: tarball uploads, code structure (SDK and no-LLM), environment variables, validation rules, and complete examples. Consult this whenever you need to evaluate or recommend the custom path (including for deterministic / cost-sensitive tasks per rule 0). Only *implement* a custom automation after the user agrees to that path.
876
+ - **`references/custom-automation.md`** — Detailed guide for custom automations: tarball uploads, code structure (SDK and no-LLM), state persistence via the KV store, environment variables, validation rules, and complete examples. Consult this whenever you need to evaluate or recommend the custom path (including for deterministic / cost-sensitive tasks per rule 0). Only *implement* a custom automation after the user agrees to that path.
875
877
  - **`references/ab-testing.md`** — A/B testing for plugin automations: defining variants with weights, experiment configuration, variant selection logic, observability via conversation tags, and complete examples. Consult this when a user wants to compare plugin versions or configurations.
@@ -439,6 +439,170 @@ def fire_callback(status: str = "COMPLETED", error: str | None = None) -> None:
439
439
 
440
440
  ---
441
441
 
442
+ ## State Persistence (KV Store)
443
+
444
+ Polling automations that run on a schedule need to remember state between runs — for example, the timestamp of the last processed event, or which conversation IDs are currently active. Storing this in a local file does not work on cloud deployments where each run may land on a fresh pod.
445
+
446
+ The automation service provides a built-in key-value store scoped per-automation. It is available in every run when the service is configured with `AUTOMATION_KV_SECRET`. Detect availability by checking for `AUTOMATION_KV_TOKEN` in the environment.
447
+
448
+ ### KV Store API
449
+
450
+ | Endpoint | Method | Description |
451
+ |----------|--------|-------------|
452
+ | `/v1/kv/{key}` | GET | Get value (404 if not found) |
453
+ | `/v1/kv/{key}` | PUT | Set value (201 on create, 200 on update) |
454
+ | `/v1/kv/{key}` | DELETE | Delete key |
455
+ | `/v1/kv/{key}/incr` | POST | Atomic integer increment |
456
+ | `/v1/kv/{key}/decr` | POST | Atomic integer decrement |
457
+ | `/v1/kv/{key}/rpush` | POST | Append item to a list |
458
+ | `/v1/kv/{key}/lpop` | POST | Pop item from front of a list |
459
+
460
+ **Authentication:** `Authorization: Bearer $AUTOMATION_KV_TOKEN`
461
+
462
+ **Base URL:** `$AUTOMATION_API_URL` (e.g., `https://app.all-hands.dev/api/automation`)
463
+
464
+ Values are arbitrary JSON (dict, list, number, string). All keys are isolated per-automation — different automations cannot access each other's data.
465
+
466
+ ### KV Store Helpers
467
+
468
+ Copy these helpers into any deterministic script that needs state persistence:
469
+
470
+ ```python
471
+ import json, os, urllib.error, urllib.request
472
+
473
+ _KV_TOKEN = os.environ.get("AUTOMATION_KV_TOKEN", "")
474
+ _KV_BASE = os.environ.get("AUTOMATION_API_URL", "").rstrip("/")
475
+
476
+
477
+ def kv_available() -> bool:
478
+ """Return True when the KV store is reachable in this run."""
479
+ return bool(_KV_TOKEN and _KV_BASE)
480
+
481
+
482
+ def kv_get(key: str):
483
+ """Fetch a value from the KV store. Returns None if the key does not exist."""
484
+ req = urllib.request.Request(
485
+ f"{_KV_BASE}/v1/kv/{key}",
486
+ headers={"Authorization": f"Bearer {_KV_TOKEN}"},
487
+ )
488
+ try:
489
+ with urllib.request.urlopen(req) as r:
490
+ return json.loads(r.read())["value"]
491
+ except urllib.error.HTTPError as exc:
492
+ if exc.code == 404:
493
+ return None
494
+ raise
495
+
496
+
497
+ def kv_set(key: str, value) -> None:
498
+ """Write a value to the KV store."""
499
+ req = urllib.request.Request(
500
+ f"{_KV_BASE}/v1/kv/{key}",
501
+ data=json.dumps(value).encode(),
502
+ headers={
503
+ "Authorization": f"Bearer {_KV_TOKEN}",
504
+ "Content-Type": "application/json",
505
+ },
506
+ method="PUT",
507
+ )
508
+ with urllib.request.urlopen(req) as r:
509
+ r.read()
510
+ ```
511
+
512
+ ### Load / Save Pattern
513
+
514
+ For polling scripts that maintain a single state document, use a KV-first pattern with a local-file fallback so the script also works in local/dev environments where the KV store is not configured:
515
+
516
+ ```python
517
+ import json, os, urllib.error, urllib.request
518
+ from pathlib import Path
519
+
520
+ _KV_TOKEN = os.environ.get("AUTOMATION_KV_TOKEN", "")
521
+ _KV_BASE = os.environ.get("AUTOMATION_API_URL", "").rstrip("/")
522
+ _STATE_KEY = "state"
523
+
524
+
525
+ def kv_available() -> bool:
526
+ return bool(_KV_TOKEN and _KV_BASE)
527
+
528
+
529
+ def kv_get(key: str):
530
+ req = urllib.request.Request(
531
+ f"{_KV_BASE}/v1/kv/{key}",
532
+ headers={"Authorization": f"Bearer {_KV_TOKEN}"},
533
+ )
534
+ try:
535
+ with urllib.request.urlopen(req) as r:
536
+ return json.loads(r.read())["value"]
537
+ except urllib.error.HTTPError as exc:
538
+ if exc.code == 404:
539
+ return None
540
+ raise
541
+
542
+
543
+ def kv_set(key: str, value) -> None:
544
+ req = urllib.request.Request(
545
+ f"{_KV_BASE}/v1/kv/{key}",
546
+ data=json.dumps(value).encode(),
547
+ headers={
548
+ "Authorization": f"Bearer {_KV_TOKEN}",
549
+ "Content-Type": "application/json",
550
+ },
551
+ method="PUT",
552
+ )
553
+ with urllib.request.urlopen(req) as r:
554
+ r.read()
555
+
556
+
557
+ def _state_file_path() -> Path:
558
+ workspace = os.environ.get("WORKSPACE_BASE", "")
559
+ if workspace:
560
+ root = Path(workspace).resolve().parent.parent
561
+ else:
562
+ root = Path.home() / ".openhands" / "workspaces"
563
+ state_dir = root / "automation-state"
564
+ state_dir.mkdir(parents=True, exist_ok=True)
565
+ payload = json.loads(os.environ.get("AUTOMATION_EVENT_PAYLOAD", "{}"))
566
+ automation_id = payload.get("automation_id", "default")
567
+ return state_dir / f"my_poller_{automation_id}.json"
568
+
569
+
570
+ def _default_state() -> dict:
571
+ return {"version": 1, "last_poll": None}
572
+
573
+
574
+ def load_state() -> dict:
575
+ if kv_available():
576
+ data = kv_get(_STATE_KEY)
577
+ if data is not None:
578
+ print("State loaded from KV store")
579
+ return data
580
+ return _default_state()
581
+ path = _state_file_path()
582
+ if path.exists():
583
+ try:
584
+ return json.loads(path.read_text())
585
+ except Exception as exc:
586
+ print(f"Warning: state file unreadable ({exc}); starting fresh")
587
+ return _default_state()
588
+
589
+
590
+ def save_state(state: dict) -> None:
591
+ if kv_available():
592
+ kv_set(_STATE_KEY, state)
593
+ print("State saved to KV store")
594
+ return
595
+ path = _state_file_path()
596
+ tmp = path.with_suffix(".tmp")
597
+ tmp.write_text(json.dumps(state, indent=2))
598
+ tmp.replace(path)
599
+ print(f"State saved to {path}")
600
+ ```
601
+
602
+ > **Why a single document?** The KV store uses a single-document model under the hood (all keys for an automation share one encrypted row). Storing your entire state under a single key like `"state"` is the most efficient pattern — it avoids multiple round-trips and ensures atomic reads and writes.
603
+
604
+ ---
605
+
442
606
  ## Environment Variables
443
607
 
444
608
  The automation service injects these environment variables into every run:
@@ -451,6 +615,8 @@ The automation service injects these environment variables into every run:
451
615
  | `AUTOMATION_CALLBACK_API_KEY` | — | Bearer token for the completion callback POST |
452
616
  | `AUTOMATION_RUN_ID` | — | Run ID to include in the completion callback payload |
453
617
  | `AUTOMATION_EVENT_PAYLOAD` | — | JSON with trigger context: `automation_id`, `automation_name`, `trigger` type, and (for webhook runs) the raw event payload |
618
+ | `AUTOMATION_API_URL` | — | Base URL of the automation service (e.g., `https://app.all-hands.dev/api/automation`). Used to reach the KV store API |
619
+ | `AUTOMATION_KV_TOKEN` | — | Bearer token for the KV store API. Present whenever the service has `AUTOMATION_KV_SECRET` configured. Check for this variable to detect KV availability |
454
620
 
455
621
  > **Note:** The session API key has two names: `SESSION_API_KEY` (cloud) and `OH_SESSION_API_KEYS_0` (local/dev). Always read both — see the code examples above.
456
622
 
@@ -86,18 +86,21 @@ print("All done!")
86
86
  - [Browser Session Recording](https://docs.openhands.dev/sdk/guides/browser-session-recording.md): Record and replay your agent's browser sessions using rrweb.
87
87
  - [Browser Use](https://docs.openhands.dev/sdk/guides/agent-browser-use.md): Enable web browsing and interaction capabilities for your agent.
88
88
  - [Context Condenser](https://docs.openhands.dev/sdk/guides/context-condenser.md): Manage agent memory by condensing conversation history to save tokens.
89
+ - [Conversation Goals](https://docs.openhands.dev/sdk/guides/agent-server/conversation-goals.md): Add a resumable goal strategy to a normal agent-server conversation.
89
90
  - [Conversation with Async](https://docs.openhands.dev/sdk/guides/convo-async.md): Use async/await for concurrent agent operations and non-blocking execution.
90
91
  - [Creating Custom Agent](https://docs.openhands.dev/sdk/guides/agent-custom.md): Learn how to design specialized agents with custom tool sets
91
92
  - [Critic (Experimental)](https://docs.openhands.dev/sdk/guides/critic.md): Real-time evaluation of agent actions using an LLM-based critic model, with built-in iterative refinement.
92
93
  - [Custom Tools](https://docs.openhands.dev/sdk/guides/custom-tools.md): Tools define what agents can do. The SDK includes built-in tools for common operations and supports creating custom tools for specialized needs.
93
94
  - [Custom Tools with Remote Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/custom-tools.md): Learn how to use custom tools with a remote agent server by building a custom base image that includes your tool implementations.
94
95
  - [Custom Visualizer](https://docs.openhands.dev/sdk/guides/convo-custom-visualizer.md): Customize conversation visualization by creating custom visualizers or configuring the default visualizer.
96
+ - [Deferred Init (Warm-Pool)](https://docs.openhands.dev/sdk/guides/agent-server/deferred-init.md): Pre-warm agent-server pods before a user is matched, then activate them at runtime with POST /api/init.
95
97
  - [Docker Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/docker-sandbox.md): Run agent server in isolated Docker containers for security and reproducibility.
96
98
  - [Exception Handling](https://docs.openhands.dev/sdk/guides/llm-error-handling.md): Provider‑agnostic exceptions raised by the SDK and recommended patterns for handling them.
97
99
  - [FAQ](https://docs.openhands.dev/sdk/faq.md): Frequently asked questions about the OpenHands SDK
98
100
  - [File-Based Agents](https://docs.openhands.dev/sdk/guides/agent-file-based.md): Define specialized sub-agents as simple Markdown files with YAML frontmatter — no Python code required.
99
101
  - [Fork a Conversation](https://docs.openhands.dev/sdk/guides/convo-fork.md): Branch off an existing conversation for follow-up exploration without contaminating the original.
100
102
  - [Getting Started](https://docs.openhands.dev/sdk/getting-started.md): Install the OpenHands SDK and build AI agents that write software.
103
+ - [Goal Completion Loop](https://docs.openhands.dev/sdk/guides/convo-goal.md): Drive a conversation toward a verifiable objective with a judge-driven, self-continuing completion loop.
101
104
  - [GPT-5 Preset (ApplyPatchTool)](https://docs.openhands.dev/sdk/guides/llm-gpt5-preset.md): Use the GPT-5 preset to build an agent that swaps the standard FileEditorTool for ApplyPatchTool.
102
105
  - [Hello World](https://docs.openhands.dev/sdk/guides/hello-world.md): The simplest possible OpenHands agent - configure an LLM, create an agent, and complete a task.
103
106
  - [Hooks](https://docs.openhands.dev/sdk/guides/hooks.md): Use lifecycle hooks to observe, log, and customize agent execution.
@@ -109,11 +112,12 @@ print("All done!")
109
112
  - [LLM Registry](https://docs.openhands.dev/sdk/guides/llm-registry.md): Dynamically select and configure language models using the LLM registry.
110
113
  - [LLM Streaming](https://docs.openhands.dev/sdk/guides/llm-streaming.md): Stream LLM responses token-by-token for real-time display and interactive user experiences.
111
114
  - [LLM Subscriptions](https://docs.openhands.dev/sdk/guides/llm-subscriptions.md): Use your ChatGPT Plus/Pro subscription to access Codex models without consuming API credits.
112
- - [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md): Run agents through a local HTTP server with RemoteConversation for client-server architecture.
115
+ - [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md): Install and run an OpenHands Agent Server on your machine, then connect to it from the SDK.
113
116
  - [Metrics Tracking](https://docs.openhands.dev/sdk/guides/metrics.md): Track token usage, costs, and latency metrics for your agents.
114
117
  - [Model Context Protocol](https://docs.openhands.dev/sdk/guides/mcp.md): Model Context Protocol (MCP) enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically.
115
118
  - [Model Routing](https://docs.openhands.dev/sdk/guides/llm-routing.md): Route agent's LLM requests to different models.
116
119
  - [Observability & Tracing](https://docs.openhands.dev/sdk/guides/observability.md): Enable OpenTelemetry tracing to monitor and debug your agent's execution with tools like Laminar, MLflow, Honeycomb, or any OTLP-compatible backend.
120
+ - [OpenAI-Compatible Endpoint](https://docs.openhands.dev/sdk/guides/agent-server/openai-gateway.md): Call an OpenHands agent-server through the OpenAI Chat Completions protocol.
117
121
  - [OpenHands Cloud Workspace](https://docs.openhands.dev/sdk/guides/agent-server/cloud-workspace.md): Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance.
118
122
  - [Overview](https://docs.openhands.dev/sdk/guides/agent-server/overview.md): Run agents on remote servers with isolated workspaces for production deployments.
119
123
  - [Parallel Tool Execution](https://docs.openhands.dev/sdk/guides/parallel-tool-execution.md): Execute multiple tools concurrently within a single LLM response to improve throughput for independent operations.
@@ -180,7 +184,6 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
180
184
  - [`40_acp_agent_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/40_acp_agent_example.py)
181
185
  - [`41_task_tool_set.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/41_task_tool_set.py)
182
186
  - [`42_file_based_subagents.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/42_file_based_subagents.py)
183
- - [`43_mixed_marketplace_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/43_mixed_marketplace_skills)
184
187
  - [`44_model_switching_in_convo.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/44_model_switching_in_convo.py)
185
188
  - [`45_parallel_tool_execution.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/45_parallel_tool_execution.py)
186
189
  - [`46_agent_settings.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/46_agent_settings.py)
@@ -191,6 +194,7 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
191
194
  - [`51_agent_hooks`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/51_agent_hooks)
192
195
  - [`52_dynamic_workflow.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/52_dynamic_workflow.py)
193
196
  - [`53_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/53_client_defined_tools.py)
197
+ - [`54_goal_completion_loop.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/54_goal_completion_loop.py)
194
198
 
195
199
  ### [`02_remote_agent_server/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server)
196
200
 
@@ -209,6 +213,7 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
209
213
  - [`13_workspace_get_llm.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/13_workspace_get_llm.py)
210
214
  - [`14_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/14_client_defined_tools.py)
211
215
  - [`15_openai_compatible_gateway.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/15_openai_compatible_gateway.py)
216
+ - [`16_deferred_init.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/16_deferred_init.py)
212
217
  - [`hook_scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/hook_scripts)
213
218
  - [`scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/scripts)
214
219
 
@@ -230,4 +235,5 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
230
235
  - [`01_loading_agentskills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/01_loading_agentskills)
231
236
  - [`02_loading_plugins`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/02_loading_plugins)
232
237
  - [`03_managing_installed_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/03_managing_installed_skills)
238
+ - [`04_mixed_marketplace_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/04_mixed_marketplace_skills)
233
239
 
@@ -83,9 +83,9 @@ Each cron run (every minute):
83
83
  Slack thread
84
84
  4. Forwards triggered replies in tracked threads to the existing conversation,
85
85
  while ignoring replies that do not contain the trigger phrase
86
- 5. Checks active conversations - posts the agent's final response back to
87
- Slack when the conversation completes, then watches briefly for triggered
88
- follow-up replies
86
+ 5. Checks active conversations - posts the agent's final response with
87
+ Slack's `markdown_text` field so Markdown renders correctly, then watches
88
+ briefly for triggered follow-up replies
89
89
 
90
90
  ## See Also
91
91
 
@@ -256,8 +256,9 @@ Each cron run executes `main.py`, which runs **10 polling iterations** (every
256
256
  `time.time() - last_activity > 15 s`:
257
257
  - If status is `idle`, `finished`, `error`, or `stuck` → fetch the agent's
258
258
  final response via `/api/conversations/{id}/agent_final_response` and post
259
- it to the Slack thread. Mark the record `watching` for five minutes so
260
- triggered follow-up replies can continue the same conversation.
259
+ it to the Slack thread using Slack's `markdown_text` field so Markdown
260
+ formatting renders correctly. Mark the record `watching` for five minutes
261
+ so triggered follow-up replies can continue the same conversation.
261
262
  7. **Advances `last_poll`** to `now - 10 s` (overlap window prevents boundary
262
263
  races). If a conversation creation failed, pins `last_poll` further back to
263
264
  retry on the next iteration.
@@ -1,12 +1,19 @@
1
- # State File Schema
1
+ # State Schema
2
2
 
3
- The automation maintains a JSON state file that persists across polling runs.
4
- This file is the source of truth for which conversations are active, which
3
+ The automation maintains a JSON state document that persists across polling runs.
4
+ This document is the source of truth for which conversations are active, which
5
5
  timestamps have been processed, and which messages were posted by the bot.
6
6
 
7
7
  ---
8
8
 
9
- ## File Location
9
+ ## Storage
10
+
11
+ **Primary (cloud):** The state is stored in the automation service's built-in KV
12
+ store under the key `"state"`. The KV store is available when `AUTOMATION_KV_TOKEN`
13
+ is injected into the run environment. Each automation has its own isolated namespace.
14
+
15
+ **Fallback (local/dev):** When the KV store is not available, the state is written
16
+ to a local JSON file at:
10
17
 
11
18
  ```
12
19
  {WORKSPACE_BASE_ROOT}/automation-state/slack_poller_{automation_id}.json