Skip to content

Commit 0ff75fa

Browse files
nficanoclaude
andcommitted
examples: rewrite as 14 self-contained ARCP example codebases
Replaces the shared-infra examples layout with 14 standalone examples covering RFC-0001 v2 primitives: cancellation, capability_negotiation, delegation, extensions, handoff, heartbeats, human_input, leases, lease_revocation, mcp, permission_challenge, reasoning_streams, resumability, subscriptions. Drops the _shared/ tree, conftest, docker-compose, pytest.ini, requirements.txt, docs/. Includes minor adjustments in src/arcp/{__init__,client/client,envelope}. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 57871cb commit 0ff75fa

85 files changed

Lines changed: 3988 additions & 553 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/.env.example

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,49 @@
1-
# Environment variables recognized by examples/. None are required for the
2-
# default (scripted) run. Copy this file to `.env` and set what you need.
3-
4-
# ---- LLM providers ------------------------------------------------------
5-
ARCP_EXAMPLES_ANTHROPIC_API_KEY=
6-
ARCP_EXAMPLES_OPENAI_API_KEY=
7-
ARCP_EXAMPLES_GEMINI_API_KEY=
8-
ARCP_EXAMPLES_ZHIPU_API_KEY=
9-
ARCP_EXAMPLES_DEEPSEEK_API_KEY=
10-
LITELLM_PROXY_URL=
11-
12-
# ---- JWT signing (examples 3, 5, 6, 8) ----------------------------------
13-
# Either inline the PEM (escape newlines) or point to a file path.
14-
ARCP_EXAMPLES_JWT_PRIVATE_KEY=
15-
ARCP_EXAMPLES_JWT_PUBLIC_KEY=
16-
# Deterministic seed for golden tests; leave unset for ephemeral keys.
17-
ARCP_EXAMPLES_JWT_SEED=
18-
19-
# ---- Destinations (example 3) -------------------------------------------
20-
NTFY_URL=https://ntfy.sh
21-
NTFY_TOPIC=
22-
TELEGRAM_BOT_TOKEN=
23-
TELEGRAM_CHAT_ID=
24-
SMTP_HOST=
25-
SMTP_PORT=587
26-
SMTP_USER=
27-
SMTP_PASS=
28-
EMAIL_TO=
29-
30-
# ---- LDAP (example 6) ---------------------------------------------------
31-
OPENLDAP_URL=
32-
OPENLDAP_BIND_DN=
33-
OPENLDAP_BIND_PW=
34-
35-
# ---- Observability (example 7) ------------------------------------------
36-
LANGFUSE_HOST=https://cloud.langfuse.com
37-
LANGFUSE_PUBLIC_KEY=
38-
LANGFUSE_SECRET_KEY=
39-
DD_API_KEY=
40-
DD_SITE=datadoghq.com
41-
OTEL_EXPORTER_OTLP_ENDPOINT=
42-
43-
# ---- OpenClaw (example 8) -----------------------------------------------
44-
OPENCLAW_URL=
45-
AUTHENTIK_TOKEN=
1+
# Shared environment variables referenced by the eleven examples.
2+
# Each example only needs the subset its config.py reads; copy this
3+
# file to `.env` and fill in what you actually use.
4+
5+
# Common
6+
ARCP_RUNTIME_URL=wss://arcp.internal/v1
7+
8+
# Per-example bearer tokens (issued by the runtime's auth provider).
9+
ARCP_OBSERVER_TOKEN=
10+
ARCP_TARGET_SESSION_ID=
11+
ARCP_BACKFILL_AFTER=
12+
13+
ARCP_SYSOPS_TOKEN=
14+
SYSOPS_HOST=edge-pod-04
15+
16+
ARCP_DBADMIN_TOKEN=
17+
WAREHOUSE_DSN=postgres://reader@warehouse.internal:5432/orders
18+
19+
ARCP_RESEARCH_TOKEN=
20+
RESEARCH_INDEX_ROOT=/var/lib/arcp/research/index
21+
RESUME_JOB_ID=
22+
RESUME_AFTER_MSG_ID=
23+
RESUME_CHECKPOINT_ID=
24+
25+
ARCP_TIER_TOKEN=
26+
ARCP_REVIEW_TOKEN=
27+
ARCP_OPENCLAW_TOKEN=
28+
ARCP_SUPERVISOR_URL=wss://supervisor.openclaw.internal
29+
ARCP_MARKET_TOKEN=
30+
ARCP_PRIMARY_TOKEN=
31+
ARCP_MIRROR_TOKEN=
32+
PRIMARY_SESSION_ID=
33+
34+
ARCP_SDR_TOKEN=
35+
SDR_SOAPY_ARGS=driver=rtlsdr
36+
37+
# LLM provider keys
38+
ANTHROPIC_API_KEY=
39+
OPENAI_API_KEY=
40+
ANTHROPIC_MODEL=claude-opus-4-7
41+
OPENAI_MODEL=gpt-4o-mini
42+
PRIMARY_MODEL=claude-opus-4-7
43+
CRITIC_MODEL=claude-haiku-4-5-20251001
44+
CHEAP_MODEL=claude-haiku-4-5-20251001
45+
GENERATOR_MODEL=gpt-4o
46+
REVIEWER_MODEL=gpt-4o
47+
48+
# Code review repo
49+
REPO_ROOT=/srv/repo

examples/.ruff.toml

Lines changed: 0 additions & 58 deletions
This file was deleted.

examples/EXAMPLES_PLAN.md

Lines changed: 0 additions & 202 deletions
This file was deleted.

examples/LEARNED.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# LEARNED
2+
3+
Notes from writing the fourteen examples against RFC-0001 v2. Items
4+
roughly ordered by how often they bit. Some are real spec gaps;
5+
some are friction in expressing the spec in Python.
6+
7+
## Spec gaps and ambiguities
8+
9+
### 1. `extensions.optional` is convention, not schema
10+
11+
§21.3 says receivers MUST nack unknowns *unless* the sender marked
12+
the message `extensions.optional: true`. The envelope schema
13+
documents `extensions` as a free-form `dict[str, Any]` but never
14+
reserves the `optional` key. Two examples (`extensions`,
15+
`reasoning_streams`) lean on this convention. Spec should reserve
16+
the key or define a typed envelope flag.
17+
18+
### 2. Backfill terminator envelope is unspecified
19+
20+
§13.3 prescribes "a synthetic `subscription.backfill_complete`
21+
marker" but doesn't pin the envelope type. We adopt
22+
`event.emit` with `payload.name = "subscription.backfill_complete"`.
23+
`resumability` relies on this to know when replay ends.
24+
25+
### 3. Lease `resource` grammar is free-form
26+
27+
§15.5 defines `resource` as a string; the examples drift to
28+
different conventions:
29+
30+
- `leases`: `host:<host>/<binary>/<argv1>`
31+
- `lease_revocation`: `table:<schema>.<name>`
32+
- `permission_challenge`: `ticket:<id>/<patch_sha>`
33+
34+
All reasonable. None interoperate. A loose grammar
35+
(`<scheme>:<path>`) would help observability sinks slice by
36+
resource type.
37+
38+
### 4. Handoff context transfer is gestural
39+
40+
§14 mentions `shared_memory_ref` in an example payload but
41+
`AgentHandoffPayload` only models `target_runtime`, `job_id`,
42+
`session_id`. `handoff` packs the conversation into an
43+
`artifact.put` and references it from a non-modeled
44+
`shared_memory_ref` field. Either formalize the field or specify
45+
that handoff context flows exclusively as artifacts.
46+
47+
### 5. Capability extensions need a value type
48+
49+
§7 lets capabilities carry arbitrary additional fields (the SDK's
50+
`Capabilities` model has `extra="allow"`), but §21 only addresses
51+
extension *messages*, not extension *capability values*.
52+
`capability_negotiation` advertises `arcpx.market.cost_per_mtok.v1` as
53+
a numeric capability value. The spec should explicitly cover this.
54+
55+
### 6. Cooperative cancel contract is loose
56+
57+
§10.4 says "the runtime SHOULD drive the target to a clean
58+
checkpoint within `deadline_ms` before terminating" but leaves
59+
escalation to "MAY hard-kill". Implementations will diverge on
60+
whether `deadline_ms` resets on each progress event or is absolute.
61+
`cancellation` documents the contract but can't enforce it.
62+
63+
### 7. `permission.request` reply envelope is implicit
64+
65+
The §15.4 example shows `permission.grant` / `permission.deny` but
66+
the spec also implies `lease.granted` follows on grant. Whether the
67+
*reply* to a `permission.request` is `permission.grant` (with
68+
`lease.granted` arriving separately) or `lease.granted` directly is
69+
implementation-defined. The SDK collapses these; `leases`,
70+
`lease_revocation`, and `permission_challenge` all treat
71+
`lease.granted` as the success reply. Worth pinning.
72+
73+
### 8. Mirror role is between Observer and Peer
74+
75+
§5 defines Observers as "subscriptions only; never command" and
76+
Peer Runtimes as the federation/`agent.delegate` party. The mirror
77+
in `reasoning_streams` does both: it subscribes to the primary's
78+
thoughts AND emits `agent.delegate` carrying critiques back into
79+
the primary's session. We classify it as a peer runtime
80+
(`trust_level: trusted`); §5 is silent on this hybrid.
81+
82+
## Python expression friction
83+
84+
### 9. `client.events()` is single-consumer
85+
86+
`ARCPClient.events()` exposes a single shared queue. Multiple
87+
coroutines iterating it in parallel starve each other. `delegation`
88+
ships a `JobMux` that owns the iterator and demuxes by `job_id`.
89+
The SDK should ship something like this, or at least document the
90+
constraint.
91+
92+
### 10. Boilerplate before `client.envelope()` was painful
93+
94+
Pre-helper, every example had its own `_msg_id()` and threaded
95+
`session_id` by hand. We added `ARCPClient.envelope(type, ...)` to
96+
the SDK partway through; it cut ~6 LOC per call site. If the SDK
97+
hadn't grown this method these examples would be 30% noise.
98+
99+
### 11. Reasoning streams want stronger typing
100+
101+
`stream.chunk.payload` is `extra="allow"` because chunk shape
102+
varies by `kind`. For `kind: thought` we end up with a hand-rolled
103+
contract (`role: assistant_thought`, `content: str`,
104+
`redacted: bool`). A nested model per kind would be friendlier to
105+
write tests against.
106+
107+
### 12. `idempotency_key` retention horizon is unstated
108+
109+
§6.4 says runtimes "SHOULD persist `(session_principal,
110+
idempotency_key)` for at least the lease horizon of the operation",
111+
but `resumability` and `heartbeats` use
112+
`idempotency_key` for things that aren't lease-scoped (workflow
113+
steps, worker re-dispatch). A "MUST persist for at least the
114+
declared retention window" clause would settle this.
115+
116+
### 13. Unbounded `asyncio.create_task` for runtime-of-process tasks
117+
118+
Several examples spawn supervisor / drain / route tasks that live
119+
for `main()`'s whole lifetime. Ruff's RUF006 wants the task stored
120+
to a name; we suppress because storing in a never-read variable is
121+
weirder than just letting the task run. This isn't an ARCP issue —
122+
it's a Python-async one — but writing this many event loops back to
123+
back made it loud.
124+
125+
## What I'd change in the spec
126+
127+
- Reserve `extensions.optional: bool` in §21.3.
128+
- Pin the backfill terminator envelope in §13.3.
129+
- Sketch a loose `<scheme>:<path>` lease `resource` grammar.
130+
- Either model `shared_memory_ref` on `AgentHandoffPayload` or
131+
state the artifact-only convention in §14.
132+
- Cover capability extension *values* in §21.
133+
- Clarify the `permission.request``lease.granted` collapse in
134+
§15.4 / §15.5.
135+
- Document the hybrid Observer/Peer role exemplified by
136+
`reasoning_streams`.
137+
- Add a non-normative note in Appendix B that the canonical event
138+
surface is single-consumer (or recommend a JobMux pattern).

0 commit comments

Comments
 (0)