centcom 1.2.0__tar.gz → 1.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: centcom
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Official Contro1 Python SDK for human approvals, human input, signed webhooks, and audit evidence
5
5
  Project-URL: Homepage, https://contro1.com
6
6
  Project-URL: Documentation, https://contro1.com/docs
@@ -14,6 +14,8 @@ Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Topic :: Internet :: WWW/HTTP
15
15
  Requires-Python: >=3.9
16
16
  Requires-Dist: httpx>=0.27.0
17
+ Provides-Extra: runtime
18
+ Requires-Dist: cryptography>=42.0; extra == 'runtime'
17
19
  Description-Content-Type: text/markdown
18
20
 
19
21
  # centcom
@@ -1,266 +1,266 @@
1
- # centcom
2
-
3
- Official Contro1 Python SDK for human approvals, human input, signed webhooks, and audit evidence.
4
-
5
- `centcom` is the framework-agnostic Python package. Use it from any Python agent,
6
- service, worker, tool runner, or webhook bridge. Framework and coding-agent
7
- connectors are separate adapters that use the same Contro1 API and decision model.
8
-
9
- ## Agent Integration Kit
10
-
11
- To save time, give your coding agent this skill. It inspects your system, reports governance gaps, and suggests Contro1 integration (optional):
12
-
13
- ```
14
- https://contro1.com/agent-kit
15
- ```
16
-
17
- For LangGraph integration, use the companion package:
18
- [`centcom-langgraph`](https://github.com/contro1-hq/centcom-langgraph).
19
-
20
- ## Skill
21
-
22
- This repo includes an integration skill:
23
- - `skills/centcom-python-sdk.md`
24
- - `skills/contro1-eu-oversight.md`
25
- - `skills/contro1-us-ai-governance.md`
26
-
27
- ## Connector Repositories
28
-
29
- - [centcom-openai-agents](https://github.com/contro1-hq/centcom-openai-agents)
30
- - [centcom-crewai](https://github.com/contro1-hq/centcom-crewai)
31
- - [centcom-n8n](https://github.com/contro1-hq/centcom-n8n)
32
- - [@contro1/claude-code](https://github.com/contro1-hq/centcom-claude-code) — Node-based Claude Code connector, published on npm rather than PyPI
33
-
34
- ## Install
35
-
36
- ```bash
37
- pip install centcom
38
- ```
39
-
40
- ## Quick Start
41
-
42
- ```python
43
- import os
44
- from centcom import CentcomClient
45
-
46
- def issue_refund() -> None:
47
- ... # Your business logic runs only after approval.
48
-
49
- client = CentcomClient(api_key=os.environ["CENTCOM_API_KEY"])
50
- agent = client.register_agent("Refund Agent", framework="custom-agent")["agent"]
51
-
52
- req = client.create_protocol_request({
53
- "title": "Approve refund for order #123?",
54
- "request_type": "approval",
55
- "source": {"integration": "python-sdk"},
56
- "actor": {"agent_id": agent["agent_id"]},
57
- "context": {
58
- "action": {"tool": "issue_refund", "input": {"order_id": 123, "amount_usd": 1200}},
59
- "machine_observed": {"trigger": "Customer refund request"},
60
- "agent_reported": {"justification": "Shipping-failure exception"},
61
- },
62
- "continuation": {"mode": "decision"},
63
- "risk_level": "high",
64
- "policy_trigger": "Refunds above $1,000 require manager review",
65
- })
66
-
67
- decision = client.wait_for_protocol_response(req["request_id"])
68
- if decision["decision_type"] == "approve":
69
- issue_refund() # Resume only after the canonical human decision.
70
- ```
71
-
72
- For callback-based agents, add `callback_url` inside the same `continuation` object and verify the signed webhook before resuming. Partial approvals are audit events and do not resume the agent.
73
-
74
- ## Send context the reviewer can trust
75
-
76
- Build the request's `context` at the gate (the code that intercepts the tool call), from three sources: the exact tool input copied verbatim by your code, the user message or event that triggered the run, and the agent's own justification (make `reason` a required parameter of the risky tool so the model produces it at decision time, not after the fact).
77
-
78
- Keep provenance separate inside `context`: a `machine_observed` block for facts your code observed, and an `agent_reported` block for text the model wrote. `agent_reported` text must never change routing, `risk_level`, or approval policy - it only informs the human, since a prompt-injected agent can write a very persuasive justification. If a high-risk request arrives without its required `machine_observed` context, fail closed instead of asking a human to guess.
79
-
80
- ```python
81
- req = client.create_protocol_request({
82
- "title": "Approve $12,400 transfer to acct_889?",
83
- "request_type": "approval",
84
- "context": {
85
- "action": {"tool": "transfer_money", "input": {"to": "acct_889", "amount_usd": 12400}},
86
- "machine_observed": {
87
- "triggered_by": "Support ticket #5521: customer requests refund for order #1842",
88
- "recent_tool_calls": ["lookup_order", "check_refund_policy"],
89
- },
90
- "agent_reported": {
91
- "justification": "Refund qualifies under the shipping-failure exception policy.",
92
- },
93
- },
94
- "risk_level": "high",
95
- })
96
- ```
97
-
98
- See https://contro1.com/docs/requests-api for the full pattern.
99
-
100
- ## Enforce approvals at execution (anti-bypass guardrail)
101
-
102
- The signed webhook is cryptographic proof of a human decision. Put the check inside the code that performs the action - not inside the agent. When the executing code refuses to act without a verified approval, no agent can trigger that action by skipping Contro1, including shadow agents nobody registered. Any tool that must never run without human sign-off (payments, deploys, data deletion) should demand a verified signed approval at its execution point.
103
-
104
- Four rules turn the webhook into a real gate:
105
-
106
- 1. **Signature + freshness**: `verify_webhook` rejects invalid signatures and timestamps older than 5 minutes (replay protection). The timestamp marks callback delivery, not request creation - a decision that takes hours or days still arrives freshly signed, and every retry is re-signed, so long SLAs are unaffected.
107
- 2. **Bind the approval to the exact action**: match `metadata` / `correlation_id` and the action parameters (amount, target, record ids) before executing. "An approval arrived" is never permission for a different action.
108
- 3. **One-time use**: execute each `request_id` exactly once (keep an idempotency record).
109
- 4. **Pull-verify when in doubt**: confirm state directly with `client.get_request(request_id)` using a read-only API key instead of trusting state an agent hands you.
110
-
111
- ```python
112
- from centcom import verify_webhook
113
-
114
- # The execution gate lives in the service that performs the action - not in the agent.
115
- @app.post("/webhooks/contro1")
116
- async def contro1_webhook(request: Request):
117
- raw = await request.body()
118
- # 1. Signature + freshness: rejects forgeries and replayed approvals.
119
- # The timestamp is the callback's SEND time, not the request's creation
120
- # time - a decision that took days still verifies (retries are re-signed).
121
- if not verify_webhook(raw, request.headers["X-CentCom-Signature"],
122
- request.headers["X-CentCom-Timestamp"], WEBHOOK_SECRET):
123
- raise HTTPException(401, "invalid signature")
124
-
125
- payload = json.loads(raw)
126
- if payload.get("status") != "approved":
127
- return {"ok": True}
128
-
129
- # 2. Bind the approval to the exact pending action and its parameters
130
- action = pending_actions.get(payload["metadata"]["case_id"])
131
- if not action or action.amount != payload["metadata"]["amount"]:
132
- alert_security("approval does not match a pending action", payload["request_id"])
133
- return {"ok": True}
134
-
135
- # 3. One-time use: a request_id executes exactly once
136
- if not executed.add_if_absent(payload["request_id"]):
137
- return {"ok": True}
138
-
139
- # 4. Only now perform the action
140
- perform_action(action)
141
- return {"ok": True}
142
- ```
143
-
144
- ## Correlation and Routing
145
-
146
- - `external_request_id` = one external action idempotency key.
147
- - `case_id` (send as `correlation_id`) = broader business case that can contain multiple requests and audit records.
148
- - `in_reply_to` = direct continuation of a prior request or audit record.
149
- - `POST /api/centcom/v1/requests/control-map` optionally previews role mapping, fallback reviewers, shift coverage, and policy satisfiability for complex routing.
150
-
151
- ## Policy evidence fields
152
-
153
- Use these fields from any policy or risk source, not only a specific framework:
154
-
155
- - `risk_level`: `low`, `medium`, `high`, or `critical`.
156
- - `policy_trigger`: short human-readable reason review is required.
157
- - `policy_context`: evidence envelope with `source`, `policy_name`, `rule_id`, `rule_reason`, `policy_version`, and `enforcement`.
158
- - `approval_comment_required`: force reviewer justification even when risk is low or medium.
159
- - `decision_comment_policy`: effective per-key snapshot returned as `optional`, `risk_based`, or `always`; a request can tighten it but cannot loosen it.
160
-
161
- Keep these concepts separate: `policy_trigger` explains why automation paused; `context.agent_reported.justification` is the agent's unverified claim; an approval decision comment is written by the reviewer when policy requires it; a `free_text` response is the requested human input itself and is always non-empty.
162
-
163
- Contro1 does not need to own your policy engine. Your app, rules service, Microsoft AGT, OPA, Cedar, or custom code can decide that review is required; Contro1 handles routing, human decision, signed callback, and audit evidence.
164
-
165
- ## Customer Agent Plugin Pattern
166
-
167
- Build one small adapter in the customer orchestrator so agent prompts stay minimal and token-efficient:
168
-
169
- ```python
170
- class Contro1Plugin:
171
- def __init__(self, client):
172
- self.client = client
173
- self._control_map_cache = None
174
- self._control_map_ts = 0
175
-
176
- def preview_policy(self, payload, ttl_sec=300):
177
- now = time.time()
178
- if self._control_map_cache and now - self._control_map_ts < ttl_sec:
179
- return self._control_map_cache
180
- self._control_map_cache = self.client.preview_control_map(payload)
181
- self._control_map_ts = now
182
- return self._control_map_cache
183
-
184
- def request_human_review(self, *, title, context, case_id, action_id, **kwargs):
185
- return self.client.create_protocol_request({
186
- "title": title,
187
- "context": context,
188
- "external_request_id": action_id,
189
- "correlation_id": case_id,
190
- **kwargs,
191
- })
192
-
193
- def log_audit_action(self, *, action, summary, case_id, in_reply_to=None, **kwargs):
194
- return self.client.log_action(
195
- action=action,
196
- summary=summary,
197
- correlation_id=case_id,
198
- in_reply_to=in_reply_to,
199
- **kwargs,
200
- )
201
- ```
202
-
203
- ## Quick Verify
204
-
205
- ```bash
206
- python -c "import centcom; print('centcom installed')"
207
- ```
208
-
209
- ## Related Packages
210
-
211
- - [`centcom-langgraph`](https://github.com/contro1-hq/centcom-langgraph) for LangGraph pause/resume workflows
212
- - [`contro1-microsoft-agent-governance-toolkit-integration`](https://github.com/contro1-hq/contro1-microsoft-agent-governance-toolkit-integration) for Microsoft AGT `require_approval` policy decisions
213
- - [`@contro1/sdk`](https://github.com/contro1-hq/centcom-sdk) for Node/TypeScript integrations
214
- - [`@contro1/claude-code`](https://github.com/contro1-hq/centcom-claude-code) for gating any selected Claude Code tool action through a `PreToolUse` hook; published on npm rather than PyPI
215
-
216
- ## Ask a human
217
-
218
- ```python
219
- client = CentcomClient(api_key=os.environ["CENTCOM_API_KEY"])
220
- thread_id = client.new_thread_id()
221
-
222
- request = client.create_protocol_request({
223
- "title": "Approve vendor transfer?",
224
- "description": "Payment run 1024 wants to transfer funds to a vendor.",
225
- "request_type": "approval",
226
- "source": {"integration": "finance-agent"},
227
- "risk_level": "high",
228
- "policy_trigger": "Payments above $10,000 require finance approval.",
229
- "policy_context": {
230
- "source": "custom_rules",
231
- "policy_name": "finance-transfer-controls",
232
- "rule_id": "payment-over-10000",
233
- "rule_reason": "Payments above $10,000 require finance approval.",
234
- "policy_version": "git:8f42c1a",
235
- "enforcement": "require_approval",
236
- },
237
- "approval_comment_required": True,
238
- "continuation": {"mode": "decision", "callback_url": "https://agent.example.com/webhook"},
239
- "external_request_id": "payment:run_1024:approve",
240
- "correlation_id": "case_payment_run_1024",
241
- })
242
- ```
243
-
244
- ## Log an autonomous action
245
-
246
- ```python
247
- client.log_action(
248
- action="transfer.executed",
249
- summary="Transferred $500 to approved vendor account",
250
- source={"integration": "finance-agent"},
251
- outcome="success",
252
- correlation_id="case_payment_run_1024",
253
- in_reply_to={"type": "request", "id": request["id"]},
254
- )
255
- ```
256
-
257
- Use the same API key and base URL for both calls.
258
-
259
- ## API
260
-
261
- - `request(method, path, **kwargs)`, `get(path, params=None)`, `post(path, json=None)`, `delete(path, json=None)`
262
- - `create_request(...)`, `create_protocol_request(request)`, `log_action(...)`
263
- - `preview_control_map(params)`, `list_requests(...)`, `get_request(request_id)`
264
- - `get_protocol_response(request_id)`, `wait_for_response(...)`, `wait_for_protocol_response(...)`
265
- - `get_request_evidence(request_id)`, `get_thread(thread_id)`, `get_trace(trace_id)`
266
- - `register_agent(...)`, `list_agents(...)`, `get_agent(...)`, `get_agent_trail(...)`, `get_agent_evidence(...)`
1
+ # centcom
2
+
3
+ Official Contro1 Python SDK for human approvals, human input, signed webhooks, and audit evidence.
4
+
5
+ `centcom` is the framework-agnostic Python package. Use it from any Python agent,
6
+ service, worker, tool runner, or webhook bridge. Framework and coding-agent
7
+ connectors are separate adapters that use the same Contro1 API and decision model.
8
+
9
+ ## Agent Integration Kit
10
+
11
+ To save time, give your coding agent this skill. It inspects your system, reports governance gaps, and suggests Contro1 integration (optional):
12
+
13
+ ```
14
+ https://contro1.com/agent-kit
15
+ ```
16
+
17
+ For LangGraph integration, use the companion package:
18
+ [`centcom-langgraph`](https://github.com/contro1-hq/centcom-langgraph).
19
+
20
+ ## Skill
21
+
22
+ This repo includes an integration skill:
23
+ - `skills/centcom-python-sdk.md`
24
+ - `skills/contro1-eu-oversight.md`
25
+ - `skills/contro1-us-ai-governance.md`
26
+
27
+ ## Connector Repositories
28
+
29
+ - [centcom-openai-agents](https://github.com/contro1-hq/centcom-openai-agents)
30
+ - [centcom-crewai](https://github.com/contro1-hq/centcom-crewai)
31
+ - [centcom-n8n](https://github.com/contro1-hq/centcom-n8n)
32
+ - [@contro1/claude-code](https://github.com/contro1-hq/centcom-claude-code) — Node-based Claude Code connector, published on npm rather than PyPI
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install centcom
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ import os
44
+ from centcom import CentcomClient
45
+
46
+ def issue_refund() -> None:
47
+ ... # Your business logic runs only after approval.
48
+
49
+ client = CentcomClient(api_key=os.environ["CENTCOM_API_KEY"])
50
+ agent = client.register_agent("Refund Agent", framework="custom-agent")["agent"]
51
+
52
+ req = client.create_protocol_request({
53
+ "title": "Approve refund for order #123?",
54
+ "request_type": "approval",
55
+ "source": {"integration": "python-sdk"},
56
+ "actor": {"agent_id": agent["agent_id"]},
57
+ "context": {
58
+ "action": {"tool": "issue_refund", "input": {"order_id": 123, "amount_usd": 1200}},
59
+ "machine_observed": {"trigger": "Customer refund request"},
60
+ "agent_reported": {"justification": "Shipping-failure exception"},
61
+ },
62
+ "continuation": {"mode": "decision"},
63
+ "risk_level": "high",
64
+ "policy_trigger": "Refunds above $1,000 require manager review",
65
+ })
66
+
67
+ decision = client.wait_for_protocol_response(req["request_id"])
68
+ if decision["decision_type"] == "approve":
69
+ issue_refund() # Resume only after the canonical human decision.
70
+ ```
71
+
72
+ For callback-based agents, add `callback_url` inside the same `continuation` object and verify the signed webhook before resuming. Partial approvals are audit events and do not resume the agent.
73
+
74
+ ## Send context the reviewer can trust
75
+
76
+ Build the request's `context` at the gate (the code that intercepts the tool call), from three sources: the exact tool input copied verbatim by your code, the user message or event that triggered the run, and the agent's own justification (make `reason` a required parameter of the risky tool so the model produces it at decision time, not after the fact).
77
+
78
+ Keep provenance separate inside `context`: a `machine_observed` block for facts your code observed, and an `agent_reported` block for text the model wrote. `agent_reported` text must never change routing, `risk_level`, or approval policy - it only informs the human, since a prompt-injected agent can write a very persuasive justification. If a high-risk request arrives without its required `machine_observed` context, fail closed instead of asking a human to guess.
79
+
80
+ ```python
81
+ req = client.create_protocol_request({
82
+ "title": "Approve $12,400 transfer to acct_889?",
83
+ "request_type": "approval",
84
+ "context": {
85
+ "action": {"tool": "transfer_money", "input": {"to": "acct_889", "amount_usd": 12400}},
86
+ "machine_observed": {
87
+ "triggered_by": "Support ticket #5521: customer requests refund for order #1842",
88
+ "recent_tool_calls": ["lookup_order", "check_refund_policy"],
89
+ },
90
+ "agent_reported": {
91
+ "justification": "Refund qualifies under the shipping-failure exception policy.",
92
+ },
93
+ },
94
+ "risk_level": "high",
95
+ })
96
+ ```
97
+
98
+ See https://contro1.com/docs/requests-api for the full pattern.
99
+
100
+ ## Enforce approvals at execution (anti-bypass guardrail)
101
+
102
+ The signed webhook is cryptographic proof of a human decision. Put the check inside the code that performs the action - not inside the agent. When the executing code refuses to act without a verified approval, no agent can trigger that action by skipping Contro1, including shadow agents nobody registered. Any tool that must never run without human sign-off (payments, deploys, data deletion) should demand a verified signed approval at its execution point.
103
+
104
+ Four rules turn the webhook into a real gate:
105
+
106
+ 1. **Signature + freshness**: `verify_webhook` rejects invalid signatures and timestamps older than 5 minutes (replay protection). The timestamp marks callback delivery, not request creation - a decision that takes hours or days still arrives freshly signed, and every retry is re-signed, so long SLAs are unaffected.
107
+ 2. **Bind the approval to the exact action**: match `metadata` / `correlation_id` and the action parameters (amount, target, record ids) before executing. "An approval arrived" is never permission for a different action.
108
+ 3. **One-time use**: execute each `request_id` exactly once (keep an idempotency record).
109
+ 4. **Pull-verify when in doubt**: confirm state directly with `client.get_request(request_id)` using a read-only API key instead of trusting state an agent hands you.
110
+
111
+ ```python
112
+ from centcom import verify_webhook
113
+
114
+ # The execution gate lives in the service that performs the action - not in the agent.
115
+ @app.post("/webhooks/contro1")
116
+ async def contro1_webhook(request: Request):
117
+ raw = await request.body()
118
+ # 1. Signature + freshness: rejects forgeries and replayed approvals.
119
+ # The timestamp is the callback's SEND time, not the request's creation
120
+ # time - a decision that took days still verifies (retries are re-signed).
121
+ if not verify_webhook(raw, request.headers["X-CentCom-Signature"],
122
+ request.headers["X-CentCom-Timestamp"], WEBHOOK_SECRET):
123
+ raise HTTPException(401, "invalid signature")
124
+
125
+ payload = json.loads(raw)
126
+ if payload.get("status") != "approved":
127
+ return {"ok": True}
128
+
129
+ # 2. Bind the approval to the exact pending action and its parameters
130
+ action = pending_actions.get(payload["metadata"]["case_id"])
131
+ if not action or action.amount != payload["metadata"]["amount"]:
132
+ alert_security("approval does not match a pending action", payload["request_id"])
133
+ return {"ok": True}
134
+
135
+ # 3. One-time use: a request_id executes exactly once
136
+ if not executed.add_if_absent(payload["request_id"]):
137
+ return {"ok": True}
138
+
139
+ # 4. Only now perform the action
140
+ perform_action(action)
141
+ return {"ok": True}
142
+ ```
143
+
144
+ ## Correlation and Routing
145
+
146
+ - `external_request_id` = one external action idempotency key.
147
+ - `case_id` (send as `correlation_id`) = broader business case that can contain multiple requests and audit records.
148
+ - `in_reply_to` = direct continuation of a prior request or audit record.
149
+ - `POST /api/centcom/v1/requests/control-map` optionally previews role mapping, fallback reviewers, shift coverage, and policy satisfiability for complex routing.
150
+
151
+ ## Policy evidence fields
152
+
153
+ Use these fields from any policy or risk source, not only a specific framework:
154
+
155
+ - `risk_level`: `low`, `medium`, `high`, or `critical`.
156
+ - `policy_trigger`: short human-readable reason review is required.
157
+ - `policy_context`: evidence envelope with `source`, `policy_name`, `rule_id`, `rule_reason`, `policy_version`, and `enforcement`.
158
+ - `approval_comment_required`: force reviewer justification even when risk is low or medium.
159
+ - `decision_comment_policy`: effective per-key snapshot returned as `optional`, `risk_based`, or `always`; a request can tighten it but cannot loosen it.
160
+
161
+ Keep these concepts separate: `policy_trigger` explains why automation paused; `context.agent_reported.justification` is the agent's unverified claim; an approval decision comment is written by the reviewer when policy requires it; a `free_text` response is the requested human input itself and is always non-empty.
162
+
163
+ Contro1 does not need to own your policy engine. Your app, rules service, Microsoft AGT, OPA, Cedar, or custom code can decide that review is required; Contro1 handles routing, human decision, signed callback, and audit evidence.
164
+
165
+ ## Customer Agent Plugin Pattern
166
+
167
+ Build one small adapter in the customer orchestrator so agent prompts stay minimal and token-efficient:
168
+
169
+ ```python
170
+ class Contro1Plugin:
171
+ def __init__(self, client):
172
+ self.client = client
173
+ self._control_map_cache = None
174
+ self._control_map_ts = 0
175
+
176
+ def preview_policy(self, payload, ttl_sec=300):
177
+ now = time.time()
178
+ if self._control_map_cache and now - self._control_map_ts < ttl_sec:
179
+ return self._control_map_cache
180
+ self._control_map_cache = self.client.preview_control_map(payload)
181
+ self._control_map_ts = now
182
+ return self._control_map_cache
183
+
184
+ def request_human_review(self, *, title, context, case_id, action_id, **kwargs):
185
+ return self.client.create_protocol_request({
186
+ "title": title,
187
+ "context": context,
188
+ "external_request_id": action_id,
189
+ "correlation_id": case_id,
190
+ **kwargs,
191
+ })
192
+
193
+ def log_audit_action(self, *, action, summary, case_id, in_reply_to=None, **kwargs):
194
+ return self.client.log_action(
195
+ action=action,
196
+ summary=summary,
197
+ correlation_id=case_id,
198
+ in_reply_to=in_reply_to,
199
+ **kwargs,
200
+ )
201
+ ```
202
+
203
+ ## Quick Verify
204
+
205
+ ```bash
206
+ python -c "import centcom; print('centcom installed')"
207
+ ```
208
+
209
+ ## Related Packages
210
+
211
+ - [`centcom-langgraph`](https://github.com/contro1-hq/centcom-langgraph) for LangGraph pause/resume workflows
212
+ - [`contro1-microsoft-agent-governance-toolkit-integration`](https://github.com/contro1-hq/contro1-microsoft-agent-governance-toolkit-integration) for Microsoft AGT `require_approval` policy decisions
213
+ - [`@contro1/sdk`](https://github.com/contro1-hq/centcom-sdk) for Node/TypeScript integrations
214
+ - [`@contro1/claude-code`](https://github.com/contro1-hq/centcom-claude-code) for gating any selected Claude Code tool action through a `PreToolUse` hook; published on npm rather than PyPI
215
+
216
+ ## Ask a human
217
+
218
+ ```python
219
+ client = CentcomClient(api_key=os.environ["CENTCOM_API_KEY"])
220
+ thread_id = client.new_thread_id()
221
+
222
+ request = client.create_protocol_request({
223
+ "title": "Approve vendor transfer?",
224
+ "description": "Payment run 1024 wants to transfer funds to a vendor.",
225
+ "request_type": "approval",
226
+ "source": {"integration": "finance-agent"},
227
+ "risk_level": "high",
228
+ "policy_trigger": "Payments above $10,000 require finance approval.",
229
+ "policy_context": {
230
+ "source": "custom_rules",
231
+ "policy_name": "finance-transfer-controls",
232
+ "rule_id": "payment-over-10000",
233
+ "rule_reason": "Payments above $10,000 require finance approval.",
234
+ "policy_version": "git:8f42c1a",
235
+ "enforcement": "require_approval",
236
+ },
237
+ "approval_comment_required": True,
238
+ "continuation": {"mode": "decision", "callback_url": "https://agent.example.com/webhook"},
239
+ "external_request_id": "payment:run_1024:approve",
240
+ "correlation_id": "case_payment_run_1024",
241
+ })
242
+ ```
243
+
244
+ ## Log an autonomous action
245
+
246
+ ```python
247
+ client.log_action(
248
+ action="transfer.executed",
249
+ summary="Transferred $500 to approved vendor account",
250
+ source={"integration": "finance-agent"},
251
+ outcome="success",
252
+ correlation_id="case_payment_run_1024",
253
+ in_reply_to={"type": "request", "id": request["id"]},
254
+ )
255
+ ```
256
+
257
+ Use the same API key and base URL for both calls.
258
+
259
+ ## API
260
+
261
+ - `request(method, path, **kwargs)`, `get(path, params=None)`, `post(path, json=None)`, `delete(path, json=None)`
262
+ - `create_request(...)`, `create_protocol_request(request)`, `log_action(...)`
263
+ - `preview_control_map(params)`, `list_requests(...)`, `get_request(request_id)`
264
+ - `get_protocol_response(request_id)`, `wait_for_response(...)`, `wait_for_protocol_response(...)`
265
+ - `get_request_evidence(request_id)`, `get_thread(thread_id)`, `get_trace(trace_id)`
266
+ - `register_agent(...)`, `list_agents(...)`, `get_agent(...)`, `get_agent_trail(...)`, `get_agent_evidence(...)`
@@ -0,0 +1,58 @@
1
+ from .client import CentcomClient
2
+ from .protocol import (
3
+ CONTRO1_CONTINUATION_MODES,
4
+ CONTRO1_PRIORITIES,
5
+ CONTRO1_REQUEST_TYPES,
6
+ CONTRO1_RISK_LEVELS,
7
+ CONTRO1_STATUSES,
8
+ from_legacy_request,
9
+ to_legacy_create_request_params,
10
+ validate_contro1_request,
11
+ validate_contro1_response,
12
+ )
13
+ from .webhook import verify_webhook
14
+
15
+ # Runtime connections need the optional "cryptography" extra:
16
+ # pip install "centcom[runtime]"
17
+ try: # pragma: no cover - optional extra
18
+ from .runtime.auth import RuntimeAuth, broker_transport
19
+ from .runtime.dpop import DpopKey
20
+ from .runtime.enrollment import exchange_workload_token, register_key_with_ticket, wait_for_approval
21
+ from .runtime.token_provider import (
22
+ FileCredentialStore,
23
+ InMemoryCredentialStore,
24
+ RuntimeCredentialError,
25
+ RuntimeTokenProvider,
26
+ StoredCredential,
27
+ )
28
+
29
+ _RUNTIME_EXPORTS = [
30
+ "RuntimeAuth",
31
+ "RuntimeTokenProvider",
32
+ "RuntimeCredentialError",
33
+ "StoredCredential",
34
+ "InMemoryCredentialStore",
35
+ "FileCredentialStore",
36
+ "DpopKey",
37
+ "broker_transport",
38
+ "register_key_with_ticket",
39
+ "wait_for_approval",
40
+ "exchange_workload_token",
41
+ ]
42
+ except ImportError: # pragma: no cover - optional extra
43
+ _RUNTIME_EXPORTS = []
44
+
45
+ __all__ = _RUNTIME_EXPORTS + [
46
+ "CentcomClient",
47
+ "verify_webhook",
48
+ "CONTRO1_REQUEST_TYPES",
49
+ "CONTRO1_CONTINUATION_MODES",
50
+ "CONTRO1_PRIORITIES",
51
+ "CONTRO1_RISK_LEVELS",
52
+ "CONTRO1_STATUSES",
53
+ "validate_contro1_request",
54
+ "validate_contro1_response",
55
+ "to_legacy_create_request_params",
56
+ "from_legacy_request",
57
+ ]
58
+ __version__ = "1.2.0"