@aiaiai-pt/martha-cli 0.2.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.
@@ -0,0 +1,907 @@
1
+ ---
2
+ name: martha-cli
3
+ description: Complete reference for the Martha CLI. Use this whenever you need to manage Martha definitions (functions, workflows, agents), execute workflows, manage agent tasks, configure tracker integrations (Linear/GitHub/GitLab), upload documents for RAG, handle approvals, manage credentials in Vault, or interact with Martha's AI platform from the command line.
4
+ ---
5
+
6
+ # Martha CLI Reference
7
+
8
+ Martha is an AI workflow orchestration platform. Agents, workflows, and tasks run on a Temporal-backed engine. External agent harnesses (ork, Claude Code, CrewAI, Pydantic AI) interact through this CLI. This skill is the operational reference — assume you have shell access and never need `--help`.
9
+
10
+ ## Conceptual model
11
+
12
+ Martha has a few distinct primitives that get confused in everyday talk. This is the precise meaning each one has in the platform:
13
+
14
+ | Concept | What it is | When to use |
15
+ |---|---|---|
16
+ | **Tenant** | Opaque data isolation boundary (`tenant_id` string). All queries filter by this. Set from the JWT, never from request body. | Every entity is tenant-scoped. You don't pass it explicitly to the CLI — it comes from your token. |
17
+ | **Client** | A chat-API consumer (web app, SMS sender, voice line). Has `keycloak_client_id`, `system_prompts`, allowlists. **Not a tenant.** | Use when you're configuring how a frontend or messaging channel talks to Martha. |
18
+ | **Agent** | An `AgentDefinition` row: prompt + LLM config + loop config + tool grants. Cloud or external. | Cloud agents are run by Martha (Temporal). External agents are remote harnesses that authenticate and execute Martha tasks. |
19
+ | **Team** | A named group of agents with a routing strategy (`round_robin`, `manual`, `external`). | Use to spread work across many similar agents (e.g. 5 ork instances doing code review). |
20
+ | **Task** | A unit of work with goal, priority, lifecycle (`open`/`claimed`/`running`/`completed`/`failed`/`cancelled`/`stale`/`poisoned`). Optionally linked to a tracker issue. | Use to queue async work for agents. Humans or agents can create them. |
21
+ | **Function** | An HTTP endpoint or platform Python callable that an agent can invoke as a tool. Stored as `FunctionDefinition`. | Define once, grant to many agents. |
22
+ | **Workflow** | A graph of nodes (LLM, function, choice, parallel, agent_loop, etc.) executed by Temporal. | Multi-step pipelines that an agent or human triggers. |
23
+ | **Connection** | A stored credential for an integration (tracker, OpenAPI service). Auth values live in Vault, not in DB. | One per (tenant, integration_name, name). Used by tracker adapters and HTTP function execution. |
24
+ | **Tracker** | Built-in adapter for an external issue tracker (`linear`, `github`, `gitlab`). Bidirectional sync with Martha tasks. | Configure once per tenant, then tasks can carry `external_ref` + `tracker_type`. |
25
+ | **Trigger** | Event-driven workflow/function dispatch. Listens for `event_type`, optionally filters, then fires a target. | Wire `task.completed` → notify, or `webhook.received` → sync inbound. |
26
+ | **Webhook** | Inbound endpoint with per-webhook HMAC secret. Emits `webhook.received` events that triggers consume. | Receive callbacks from external services (trackers, payment processors, etc.). |
27
+ | **Approval** | Human-in-the-loop pause point inside a workflow. | Use the `approval_gate` workflow node when you need a human OK before continuing. |
28
+
29
+ Everything below operates on these primitives. When in doubt, run `martha status` to confirm tenant + auth before issuing commands.
30
+
31
+ ---
32
+
33
+ ## Global Options (every command)
34
+
35
+ | Flag | Purpose |
36
+ |---|---|
37
+ | `--json` | Machine-readable JSON output. **Always set this when piping to `jq` or parsing.** Without it, output is human-formatted and may include color codes. |
38
+ | `--profile <name>` | Use a named profile from `~/.martha/profiles/`. Default profile is `default`. |
39
+ | `--api-url <url>` | Override `MARTHA_API_URL`. Useful for hitting staging/prod from the same shell. |
40
+ | `--verbose` | DEBUG-level logging on stderr. Shows HTTP requests, retry attempts, token expiry decisions. |
41
+ | `--quiet` | Suppress informational output. Errors still print. |
42
+ | `--yes` | Skip confirmation prompts. Required in non-TTY contexts (CI, agents). **The CLI does NOT silently cancel on no-TTY** — it errors with `use --yes`. |
43
+
44
+ **Exit codes:** `0` success · `1` generic error · `2` auth failure · `3` not found · `4` validation · `5` conflict (409)
45
+
46
+ ---
47
+
48
+ ## Authentication
49
+
50
+ The CLI resolves credentials in this priority order. The first non-empty wins:
51
+
52
+ 1. `MARTHA_TOKEN` — raw JWT, bypasses all login flows. Highest priority.
53
+ 2. `MARTHA_CLIENT_ID` + `MARTHA_CLIENT_SECRET` — OAuth2 client credentials, auto-refreshes.
54
+ 3. Profile-stored token from prior `martha auth login` (cached at `~/.martha/profiles/<name>.json`).
55
+ 4. Browser-based OIDC (interactive only, won't fire in non-TTY).
56
+
57
+ ```bash
58
+ # Headless login (humans, scripts)
59
+ martha auth login --username admin --password admin123
60
+
61
+ # Service account (agents, CI)
62
+ export MARTHA_CLIENT_ID="martha-agent-<uuid>"
63
+ export MARTHA_CLIENT_SECRET="..."
64
+ martha auth login --service-account
65
+
66
+ # Browser flow (local dev)
67
+ martha auth login
68
+
69
+ # Inspect current state
70
+ martha auth status # profile, user, tenant, expiry, roles
71
+ martha auth token # raw JWT to stdout (for piping into curl)
72
+ martha auth logout
73
+ ```
74
+
75
+ **Token lifecycle:** Service-account tokens auto-refresh ~30s before expiry. Human tokens expire after ~5min and require re-login (or use `MARTHA_TOKEN` for long-running scripts).
76
+
77
+ **Troubleshooting:**
78
+ - `2 auth failure` on first call → token missing or expired. Run `martha auth status` to confirm, then re-login.
79
+ - `403 forbidden` → authenticated but lacking the required role. Most admin endpoints need `admin` realm role; executor endpoints accept any authenticated user.
80
+ - `401 unauthorized` despite valid token → check `MARTHA_API_URL` matches the Keycloak realm the token was issued for.
81
+
82
+ ---
83
+
84
+ ## Definitions: functions, workflows, agents
85
+
86
+ Definitions are the durable building blocks. They're versioned (every update creates a `DefinitionVersion` row), tenant-scoped, and rollback-able.
87
+
88
+ ### Declarative Apply (preferred for IaC-style workflows)
89
+
90
+ ```bash
91
+ martha definitions apply -f definitions.yaml [--dry-run] [--yes]
92
+ ```
93
+
94
+ Reads YAML or JSON. Multi-document files (separated by `---`) are processed in order. The CLI **never deletes** — to remove a definition, use the per-kind `delete` command.
95
+
96
+ ```yaml
97
+ kind: Function
98
+ name: get-weather
99
+ description: Fetches current weather by city name
100
+ definition:
101
+ endpoint: https://api.weather.com/current
102
+ http_method: GET
103
+ parameters:
104
+ city:
105
+ type: string
106
+ required: true
107
+ location: query
108
+ auth:
109
+ scheme: bearer
110
+ credential_source: weather_api
111
+ ---
112
+ kind: Workflow
113
+ name: morning-briefing
114
+ description: Get weather + headlines, summarize
115
+ definition:
116
+ nodes:
117
+ - id: weather
118
+ type: function
119
+ config: { function_name: get-weather, inputs: { city: "{{user.city}}" } }
120
+ - id: summarize
121
+ type: llm
122
+ config: { prompt: "Summarize for a morning briefing: {{steps.weather.output}}" }
123
+ edges:
124
+ - { source: weather, target: summarize }
125
+ ---
126
+ kind: Agent
127
+ name: briefing-agent
128
+ description: Sends a daily morning briefing
129
+ agent_type: cloud
130
+ auth_method: service_account # Slice 3B: provisions Keycloak SA on create
131
+ system_prompt: "You write friendly morning briefings."
132
+ llm_config: { provider: anthropic, model: claude-sonnet-4-5-20250929 }
133
+ loop_config: { enabled: true, max_iterations: 5 }
134
+ ```
135
+
136
+ `--dry-run` prints what would change without writing. `--yes` skips the confirmation when applying to a non-empty tenant.
137
+
138
+ ### Export (back up or migrate)
139
+
140
+ ```bash
141
+ martha definitions export [--format yaml|json] [--output FILE] [--inactive]
142
+ martha definitions export --functions-only
143
+ martha definitions export --workflows-only
144
+ ```
145
+
146
+ ### Stats
147
+
148
+ ```bash
149
+ martha definitions stats # Counts by kind and source (manual/openapi/plugin)
150
+ ```
151
+
152
+ ### Functions CRUD
153
+
154
+ ```bash
155
+ martha functions list [--source manual|openapi|platform|plugin] [--tag TAG] [--inactive] [--limit 50]
156
+ martha functions get <name> # Full definition + auth + extra_headers
157
+ martha functions create -f definition.yaml
158
+ martha functions update <name> -f definition.yaml # Bumps version
159
+ martha functions delete <name> [--hard] [--yes] # Soft delete by default; --hard purges
160
+ martha functions versions <name> # Version history with timestamps
161
+ martha functions rollback <name> <version> # Restore prior version (creates new version)
162
+ martha functions export [--format yaml|json] [--output FILE]
163
+ ```
164
+
165
+ Function `auth` field is the most error-prone part — see the [Function authentication patterns](#function-authentication-patterns) section below.
166
+
167
+ ### Workflows CRUD + Execution
168
+
169
+ ```bash
170
+ martha workflows list [--inactive] [--limit 50]
171
+ martha workflows get <name> # Full graph + variables + format_version
172
+ martha workflows create -f definition.yaml
173
+ martha workflows update <name> -f definition.yaml
174
+ martha workflows delete <name> [--hard] [--yes]
175
+ martha workflows versions <name>
176
+ martha workflows rollback <name> <version>
177
+ martha workflows export [--format yaml|json] [--output FILE]
178
+ ```
179
+
180
+ **Execution:**
181
+
182
+ ```bash
183
+ # Fire and forget
184
+ martha workflows execute <name> --inputs '{"key": "value"}'
185
+
186
+ # Wait for completion (blocks up to --timeout seconds, default 300)
187
+ martha workflows execute <name> --inputs '{"key": "value"}' --wait
188
+
189
+ # Wait + stream node-by-node progress to stdout
190
+ martha workflows execute <name> --inputs '{"key": "value"}' --wait --follow
191
+
192
+ # Inspect a running execution
193
+ martha workflows execution <execution-id> # Snapshot
194
+ martha workflows execution <execution-id> --follow # Live stream
195
+
196
+ # List recent executions
197
+ martha workflows executions [--status running|completed|failed] [--workflow <name>] [--limit 20]
198
+
199
+ # Cancel a running execution (signals Temporal workflow)
200
+ martha workflows cancel <execution-id>
201
+ ```
202
+
203
+ **Inspection (don't memorize, query the system):**
204
+
205
+ ```bash
206
+ martha workflows inputs <name> # Inputs the workflow expects, with types
207
+ martha workflows nodes <name> # All nodes + edges, useful for debugging
208
+ martha workflows node-types # Available node types you can use
209
+ martha workflows node-type <type> # Schema for a specific node type (e.g. agent_loop)
210
+ ```
211
+
212
+ **Project a workflow as portable self-guidance (Phase 2 / Slice 2B):**
213
+
214
+ When an external harness (ork, Claude Code, CrewAI, Pydantic AI) wants to *use* a Martha workflow as a recipe — without Martha being in the loop at execution time — project it to a portable format:
215
+
216
+ ```bash
217
+ # JSON plan: topologically ordered nodes + execute_on hints + graph structure
218
+ martha workflows project <name> # default --format=plan
219
+ martha workflows project <name> --format=plan -o plan.json
220
+
221
+ # Markdown run-book: numbered steps with prompts inlined
222
+ martha workflows project <name> --format=skill # writes to stdout
223
+ martha workflows project <name> --format=skill -o skill.md
224
+ ```
225
+
226
+ **Plan format** is for programmatic agents. Each node carries `id`, `type`, `execute_on` (hint only: `martha` = a server-side tool like a function; `local` = the agent runs it), `depends_on`, and — when known — `input_schema` / `output_schema`. Structural `start`/`end` nodes are filtered out; `edges` is preserved. No CLI commands are emitted — the projection is guidance, not a script.
227
+
228
+ **Skill format** is Markdown, suitable for embedding in an LLM prompt or dropping into a Claude Code / ork skill directory. Numbered steps with LLM prompts inlined for `local` steps and Martha function names surfaced for `martha` steps.
229
+
230
+ **How to execute a projected workflow:** the agent chooses. Common options:
231
+ - **Run it yourself end-to-end**: use your own LLM for `local` steps; for `martha` steps, either call the underlying function as a tool (`martha functions call <name> --args …`) or implement an equivalent in your runtime.
232
+ - **Delegate the whole thing**: skip the projection entirely and `martha workflows execute <name> --inputs '…'` — Martha runs it cloud-side and you just read the outcome.
233
+ - **Mix and match**: follow the skill for structure but call Martha functions where useful.
234
+
235
+ Default `execute_on` mapping: `llm` → `local`; `function` / `wait` / `transform` / `agent_loop` / `choice` → `martha`. Override per node by setting `execute_on` in the node config.
236
+
237
+ > **Naming note:** the spec called this command `workflows export <name>`, but `workflows export` was already taken by the bulk-YAML export alias. The Phase 2 command is `project` instead.
238
+
239
+ ### Agents CRUD + Provisioning + Access
240
+
241
+ ```bash
242
+ martha agents list [--inactive] [--limit 50]
243
+ martha agents get <name> # Includes auth_method, status, llm_config, granted functions/workflows
244
+ martha agents create --name <n> --model <m> --prompt "system prompt" \
245
+ [--description "..."] [--temperature 0.7] [--max-tokens 4096] \
246
+ [--type cloud|external] [--auth service-account|api-key] \
247
+ [--tags code-review,python] [--local-tools filesystem,git]
248
+ martha agents update <name> [--model <m>] [--prompt "..."] [--description "..."]
249
+ martha agents delete <name> [--hard] [--yes]
250
+ martha agents versions <name>
251
+ martha agents rollback <name> <version>
252
+ ```
253
+
254
+ **Auth provisioning (Slice 3B — unified):**
255
+
256
+ ```bash
257
+ # Provision auth for the first time, switch methods, or rotate credentials
258
+ martha agents provision-auth <agent> --method service-account
259
+ martha agents provision-auth <agent> --method api-key
260
+
261
+ # Backward-compat alias for provision-auth --method=api-key
262
+ martha agents generate-key <agent>
263
+ ```
264
+
265
+ `provision-auth` shows credentials **once** — copy them immediately. Output is `Cache-Control: no-store` and never logs the secret.
266
+
267
+ | Method | Returns | Use when |
268
+ |---|---|---|
269
+ | `service-account` | `client_id` + `client_secret` (export to `MARTHA_CLIENT_ID`/`MARTHA_CLIENT_SECRET`) | Long-running agents that need token refresh. Recommended. |
270
+ | `api-key` | Single `martha_ak_...` token (export to `MARTHA_TOKEN`) | One-shot scripts, CI jobs, debugging. |
271
+
272
+ **Function grants:**
273
+
274
+ ```bash
275
+ martha agents add-function <agent> <function> [--set key=value] # config_overrides
276
+ martha agents remove-function <agent> <function>
277
+ martha agents functions <agent> # List granted functions
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Tasks: queue + executor lifecycle
283
+
284
+ Tasks are the unit of async work. Two flows operate on the same `tasks` table from different angles:
285
+
286
+ - **Admin flow:** humans (or agents acting as humans) create, monitor, cancel tasks
287
+ - **Executor flow:** external agents poll, claim, heartbeat, complete
288
+
289
+ ### Admin commands
290
+
291
+ ```bash
292
+ martha tasks list [--status open|claimed|running|completed|failed|cancelled|stale|poisoned] \
293
+ [--agent <id>] [--priority low|medium|high|urgent] [--search QUERY] [--limit 50]
294
+ martha tasks stats # Counts per status
295
+
296
+ martha tasks create --agent <agent-id> --goal "instruction text" \
297
+ [--title "label"] [--priority medium] [--context '{"key":"val"}'] \
298
+ [--team <team-id>] [--assigned-agent <agent-id>] [--sticky] \
299
+ [--lease-timeout 600] [--max-retries 3]
300
+
301
+ martha tasks view <id> # Full task: outcome, error, executor, lease, tracker info
302
+ martha tasks cancel <id> # Transitions to cancelled; signals Temporal workflow if running
303
+ ```
304
+
305
+ **Team routing:**
306
+ - Without `--team`/`--assigned-agent` → task lives in tenant queue, any agent can claim
307
+ - `--team <id>` → routes via team's `routing_strategy` (round_robin assigns to next active member at create time)
308
+ - `--assigned-agent <id>` → pinned to that agent; only it can claim
309
+ - `--sticky` → assignment survives the agent going offline (default: re-route on offline)
310
+
311
+ **Tracker linkage (Phase 4):**
312
+ Tasks created from a tracker webhook automatically get `external_ref` (e.g. `ENG-423`) and `tracker_type` (`linear`/`github`/`gitlab`). On status changes, the auto-provisioned trigger pushes back to the tracker. To create a tracker-linked task manually, use the API directly — there's no `--tracker-ref` flag yet.
313
+
314
+ ### Executor commands (the agent side)
315
+
316
+ ```bash
317
+ # 1a. Discover available work — ordered by priority then age (polling)
318
+ martha tasks poll [--agent <id>] [--limit 10]
319
+
320
+ # 1b. Long-poll via SSE — connection stays open, prints one event per assignment
321
+ # (Phase 2 / Slice 2A). Survives reconnect via Last-Event-ID; sends
322
+ # ": keepalive" comment heartbeats every ~5s so intermediaries don't drop.
323
+ martha tasks watch --team <name-or-id> [--json] [--max-reconnects N]
324
+
325
+ # 2. Atomic claim — sets executor_id from your auth context, starts lease
326
+ martha tasks claim <id> [--lease-timeout 300]
327
+
328
+ # 3. Heartbeat — renew lease, optionally save checkpoint
329
+ martha tasks heartbeat <id>
330
+ martha tasks heartbeat <id> --checkpoint '{"step": 3, "progress": 0.75}'
331
+
332
+ # 4. Report completion
333
+ martha tasks complete <id> --outcome '{"result": "done", "files_changed": ["src/foo.py"]}'
334
+ ```
335
+
336
+ **Claim lifecycle:** `open` → `claimed` (after `claim`) → `running` (when work starts; auto-set on first heartbeat after claim) → `completed`
337
+
338
+ **Lease semantics:**
339
+ - Default lease: 300s (5min). Override with `--lease-timeout` on claim.
340
+ - Heartbeat **before `lease_expires_at`** or the reaper marks task `stale` and re-queues.
341
+ - Recommended cadence: heartbeat every 30–60s.
342
+ - Heartbeats from a non-executor return 403. Heartbeats after lease expiry return 409.
343
+
344
+ **Checkpoint resume:**
345
+ If `martha tasks claim <id>` returns a task with `retry_count > 0` and a `last_checkpoint`, a previous executor failed mid-work. Resume from the checkpoint instead of starting over. Inspect:
346
+ ```bash
347
+ martha tasks view <id> --json | jq '.last_checkpoint, .retry_count'
348
+ ```
349
+
350
+ **Error handling cheat sheet:**
351
+
352
+ | Code | Meaning | Action |
353
+ |---|---|---|
354
+ | `409 Conflict` on claim | Task already claimed | Skip and try the next polled task |
355
+ | `409 Conflict` on heartbeat | Lease expired | Stop work, do NOT complete (another agent will pick it up) |
356
+ | `403 Forbidden` on heartbeat | You're not the executor | Stop work |
357
+ | `404 Not Found` | Task was hard-deleted | Stop work |
358
+ | `423 Locked` | Concurrent claim attempt in flight | Retry once after 100ms |
359
+
360
+ ### Standard executor loop
361
+
362
+ Two flavours — pick polling for simplicity or `watch` for low-latency assignment:
363
+
364
+ **Polling (works on any team / unassigned pool):**
365
+
366
+ ```bash
367
+ #!/usr/bin/env bash
368
+ set -euo pipefail
369
+ export MARTHA_TOKEN="${MARTHA_TOKEN:?need token}"
370
+
371
+ while true; do
372
+ TASK_JSON=$(martha tasks poll --json --limit 1 | jq -c '.[0] // empty')
373
+ if [ -z "$TASK_JSON" ]; then
374
+ sleep 10
375
+ continue
376
+ fi
377
+
378
+ TASK_ID=$(echo "$TASK_JSON" | jq -r '.id')
379
+ GOAL=$(echo "$TASK_JSON" | jq -r '.goal')
380
+
381
+ # Atomic claim — bail if someone else got it
382
+ if ! martha tasks claim "$TASK_ID" --json >/dev/null 2>&1; then
383
+ continue
384
+ fi
385
+
386
+ # Background heartbeat every 30s for the duration
387
+ ( while sleep 30; do martha tasks heartbeat "$TASK_ID" --json >/dev/null || break; done ) &
388
+ HB_PID=$!
389
+
390
+ # Do the work — replace with your agent's actual logic
391
+ RESULT=$(echo "Working on: $GOAL" | your-agent-here)
392
+
393
+ kill $HB_PID 2>/dev/null || true
394
+ martha tasks complete "$TASK_ID" --json --outcome "$(jq -n --arg r "$RESULT" '{result: $r}')"
395
+ done
396
+ ```
397
+
398
+ **SSE watch (Phase 2 / Slice 2A — for low-latency, real-time tracker-driven work):**
399
+
400
+ ```bash
401
+ #!/usr/bin/env bash
402
+ set -euo pipefail
403
+ export MARTHA_TOKEN="${MARTHA_TOKEN:?need token}"
404
+
405
+ # `watch` writes one JSON line per assignment to stdout; reconnects automatically
406
+ # with Last-Event-ID so no work is missed across transient network drops.
407
+ martha tasks watch --team code-review --json | while IFS= read -r LINE; do
408
+ TYPE=$(echo "$LINE" | jq -r '.type // empty')
409
+ [ "$TYPE" = "task.assigned" ] || continue
410
+ TASK_ID=$(echo "$LINE" | jq -r '.data.id')
411
+
412
+ if ! martha tasks claim "$TASK_ID" --json >/dev/null 2>&1; then
413
+ # Lost the race to another team member — keep watching
414
+ continue
415
+ fi
416
+
417
+ ( while sleep 30; do martha tasks heartbeat "$TASK_ID" --json >/dev/null || break; done ) &
418
+ HB_PID=$!
419
+
420
+ RESULT=$(your-agent-here "$TASK_ID")
421
+
422
+ kill $HB_PID 2>/dev/null || true
423
+ martha tasks complete "$TASK_ID" --json --outcome "$(jq -n --arg r "$RESULT" '{result: $r}')"
424
+ done
425
+ ```
426
+
427
+ **When to choose which:**
428
+ - **Polling**: simplest; tolerates seconds of latency; works without a team scope.
429
+ - **Watch**: best for trackers (Linear/GitHub webhooks → instant assignment) and high-throughput teams. Requires `--team`. Server enforces team membership for agent service accounts.
430
+
431
+ ---
432
+
433
+ ## Teams
434
+
435
+ Teams group agents and define how incoming work is distributed.
436
+
437
+ ```bash
438
+ martha teams create --name "code-review" [--description "..."] [--routing round_robin|manual|external]
439
+ martha teams list
440
+ martha teams view <name-or-id> # Members, current task counts, routing config
441
+ martha teams update <name-or-id> [--name "..."] [--routing manual]
442
+ martha teams delete <name-or-id> [--yes]
443
+ martha teams add-member <team> <agent-name> [--role member|lead]
444
+ martha teams remove-member <team> <agent-id>
445
+ ```
446
+
447
+ **Routing strategies:**
448
+
449
+ | Strategy | Behavior | Best for |
450
+ |---|---|---|
451
+ | `manual` | Admin sets `assigned_agent_id` explicitly | Heterogeneous teams, deliberate routing |
452
+ | `round_robin` | Auto-assigns to next active member at task creation | Homogeneous worker pools (5x ork instances) |
453
+ | `external` | Tasks land in queue; external orchestrator (e.g. Linear, GitHub Actions) decides who claims | Tracker-driven workflows |
454
+
455
+ **Setting up an external agent on a team:**
456
+
457
+ ```bash
458
+ # 1. Create the agent definition
459
+ martha agents create --name ork-worker-1 --type external --description "ork instance #1" \
460
+ --tags code-review,refactoring
461
+
462
+ # 2. Provision auth — service account is recommended for long-running agents
463
+ martha agents provision-auth ork-worker-1 --method service-account
464
+ # Copy client_id + client_secret immediately
465
+
466
+ # 3. Create the team if it doesn't exist
467
+ martha teams create --name code-review --routing round_robin
468
+
469
+ # 4. Add the agent to the team
470
+ martha teams add-member code-review ork-worker-1
471
+
472
+ # 5. On the agent host
473
+ export MARTHA_CLIENT_ID="<client_id>"
474
+ export MARTHA_CLIENT_SECRET="<client_secret>"
475
+ martha auth login --service-account
476
+ martha tasks poll --json
477
+ ```
478
+
479
+ ---
480
+
481
+ ## Connections + Vault credentials
482
+
483
+ A **Connection** is a stored credential record for an integration. Auth values live in HashiCorp Vault keyed by `(scope, scope_id, service_name)`. The DB only has metadata. Tracker connections include adapter-specific config (team_id, repository, project_id) stored as a flat dict in Vault — `resolve_connection_config()` is the single merge point for tracker adapter calls.
484
+
485
+ The CLI doesn't yet have a top-level `connections` subcommand — manage them via the admin UI at `/settings` (Trackers tab) or via the API:
486
+
487
+ ```bash
488
+ # List connections (uses your token's tenant_id scope)
489
+ curl -s -H "Authorization: Bearer $(martha auth token)" \
490
+ "$MARTHA_API_URL/api/admin/connections" | jq
491
+
492
+ # List available tracker adapters + their config_schema
493
+ curl -s -H "Authorization: Bearer $(martha auth token)" \
494
+ "$MARTHA_API_URL/api/admin/trackers" | jq
495
+
496
+ # Create a Linear connection (full config dict in JSON, stored as one Vault entry)
497
+ curl -s -X POST -H "Authorization: Bearer $(martha auth token)" \
498
+ -H "Content-Type: application/json" \
499
+ -d '{
500
+ "integration_name": "linear",
501
+ "name": "production",
502
+ "auth_type": "api_key",
503
+ "credential_value": "{\"api_key\":\"lin_api_xxx\",\"team_id\":\"uuid-here\"}",
504
+ "is_default": true
505
+ }' \
506
+ "$MARTHA_API_URL/api/admin/connections"
507
+
508
+ # Test a connection (calls adapter.test_connection() with merged config)
509
+ curl -s -X POST -H "Authorization: Bearer $(martha auth token)" \
510
+ "$MARTHA_API_URL/api/admin/connections/<connection-id>/test" | jq
511
+
512
+ # Delete (also removes from Vault and deprovisions tracker triggers if applicable)
513
+ curl -s -X DELETE -H "Authorization: Bearer $(martha auth token)" \
514
+ "$MARTHA_API_URL/api/admin/connections/<connection-id>"
515
+ ```
516
+
517
+ **Tracker connection auto-provisioning:** When you create a connection where `integration_name` matches a tracker (`linear`/`github`/`gitlab`), the backend automatically provisions:
518
+ 1. A `webhook_definition` (returns one-time `webhook_url` + `webhook_secret` in the create response)
519
+ 2. Three trigger definitions (managed by `tracker:{type}`):
520
+ - `sync-task-status-{type}` — fires on `task.*` events with `external_ref` set
521
+ - `sync-task-activity-{type}` — fires on `task.activity.created`
522
+ - `inbound-tracker-{type}` — fires on `webhook.received` with matching `webhook_name`
523
+
524
+ After creating the connection, paste the `webhook_url` + `webhook_secret` into your tracker's webhook configuration page (Linear/GitHub/GitLab settings).
525
+
526
+ ---
527
+
528
+ ## Triggers + Events + Webhooks
529
+
530
+ Triggers wire events to workflow or platform-function dispatches.
531
+
532
+ ```bash
533
+ # Triggers
534
+ martha triggers list [--active] [--limit 50]
535
+ martha triggers create -f trigger.yaml
536
+ martha triggers get <name>
537
+ martha triggers update <name> -f trigger.yaml
538
+ martha triggers delete <name> [--yes]
539
+ martha triggers test <name> --event '{"type": "task.completed", "data": {...}}' # Dry-run
540
+
541
+ # Events (browse what's flowing)
542
+ martha events list [--type task.*] [--since 1h] [--limit 50]
543
+ martha events types # Available event types with sample payloads
544
+ martha events emit --type custom.event --data '{"foo": "bar"}' # Manual emit (admin only)
545
+
546
+ # Webhooks
547
+ martha webhooks list
548
+ martha webhooks create --name <n> [--description "..."] # Returns one-time secret
549
+ martha webhooks rotate-secret <name> # New secret, invalidates old
550
+ martha webhooks delete <name> [--yes]
551
+ ```
552
+
553
+ **Trigger YAML:**
554
+
555
+ ```yaml
556
+ name: notify-on-task-completion
557
+ event_type: task.completed
558
+ event_filter:
559
+ data:
560
+ priority: ["urgent", "high"]
561
+ target_type: workflow # or 'platform_function'
562
+ target_name: send-completion-email
563
+ input_mapping:
564
+ task_id: "{{event.data.task_id}}"
565
+ recipient: "carlos@nomadriver.co"
566
+ is_active: true
567
+ max_concurrent: 5 # Concurrent dispatches per tenant
568
+ dedup_key: "{{event.data.task_id}}"
569
+ dedup_window_seconds: 300
570
+ ```
571
+
572
+ **Filter operators:** Each filter value is a list. Within a list, ANY match counts. Across keys, ALL must match. Operators: literal equality, `[{"exists": true}]`, `[{"prefix": "foo"}]`, `[{"in": [...]}]`, `[{"regex": "..."}]`.
573
+
574
+ ---
575
+
576
+ ## Documents (RAG)
577
+
578
+ Document collections are tenant-scoped containers that ingest files (PDF, DOCX, MD, etc.) and serve them for retrieval.
579
+
580
+ ```bash
581
+ # Collections
582
+ martha documents collections [--inactive] [--limit 50]
583
+ martha documents collection <id> # Stats, ingestion status, total size
584
+ martha documents create-collection --name "My Docs" [--description "..."]
585
+
586
+ # Document lifecycle
587
+ martha documents upload <collection-id> <file> [--wait] [--follow] # --follow streams ingestion progress
588
+ martha documents list <collection-id> [--inactive] [--limit 50]
589
+ martha documents get <doc-id> # Full metadata + chunk count
590
+ martha documents status <doc-id> [--follow] # Ingestion progress: validate → parse → embed → finalize
591
+ martha documents download <doc-id> [--expires 3600] # Presigned URL (default 1h)
592
+ martha documents delete <doc-id> [--yes]
593
+ martha documents reingest <doc-id> [--wait] [--follow] # Re-run ingestion (after pipeline upgrade)
594
+ martha documents page-images <doc-id> [--expires 3600] # Presigned URLs for VLM-described pages
595
+
596
+ # Search & query
597
+ martha documents search <collection-id> "search query" [--max-results 10] # Hybrid keyword+vector
598
+ martha documents query <collection-id> "question" [--max-chunks 10] [--model claude-sonnet-4-5] # RAG with answer
599
+ ```
600
+
601
+ **Ingestion stages (visible in `status --follow`):**
602
+ 1. `validate` — MIME type, size limits, content scan
603
+ 2. `parse_and_chunk` — Docling extraction + HybridChunker + page classification
604
+ 3. `enrich` — embed + VLM describe (visual pages) + ColPali index (parallel, all non-fatal)
605
+ 4. `finalize` — write to `document_chunks`, mark ready
606
+
607
+ A failed enrich step does NOT fail the document — it falls back to keyword-only search.
608
+
609
+ ---
610
+
611
+ ## Approvals (human-in-the-loop)
612
+
613
+ Approvals are pause-points inside workflows. The `approval_gate` workflow node creates an `ApprovalCase`; downstream nodes wait until a human resolves it.
614
+
615
+ ```bash
616
+ martha approvals list [--status pending|approved|rejected|expired] [--assigned-to USER] [--limit 50]
617
+ martha approvals stats
618
+ martha approvals get <id> # Context summary, workflow context
619
+ martha approvals approve <id> [--comment "looks good"]
620
+ martha approvals reject <id> [--comment "needs more data"]
621
+ ```
622
+
623
+ The workflow execution resumes within seconds of approval/rejection (Temporal signal).
624
+
625
+ ---
626
+
627
+ ## Citations (RAG provenance)
628
+
629
+ Citations link an LLM response to specific document chunks it consulted. They survive their source documents (no FK).
630
+
631
+ ```bash
632
+ # Browse citations for an execution / message / approval
633
+ martha citations list --execution <id>
634
+ martha citations list --message <id>
635
+ martha citations get <citation-id> # Quoted text, page range, verification status
636
+ martha citations stats # Total / verified / failed / pending
637
+ martha citations verify <citation-id> # Re-runs exact/normalized/fuzzy matching
638
+ ```
639
+
640
+ Available verification states: `verified`, `failed`, `pending`, `unverified`, `out_of_provenance`.
641
+
642
+ ---
643
+
644
+ ## Clients (chat API consumers)
645
+
646
+ Clients are the consumer-side identity for the chat API (web frontend, SMS sender, voice line). Each Client has allowlists for which functions, workflows, and agents the client's sessions can use.
647
+
648
+ ```bash
649
+ martha clients list [--limit 50]
650
+ martha clients get <name-or-id>
651
+ martha clients create --name <n> [--system-prompts '{"default": "..."}'] [--keycloak-client-id <id>]
652
+ martha clients update <name-or-id> [--name <n>] [--system-prompts '...']
653
+ martha clients delete <name-or-id> [--force] [--yes] # --force drops sessions
654
+
655
+ # Access grants — what definitions this client's sessions can use
656
+ martha clients grant <client> function|workflow|agent <def-name> [--config key=value]
657
+ martha clients revoke <client> function|workflow|agent <def-name>
658
+ martha clients access <name-or-id> # All grants for this client
659
+ ```
660
+
661
+ ---
662
+
663
+ ## Sessions + Chat
664
+
665
+ ```bash
666
+ martha sessions list [--search QUERY] [--limit 20] [--type api|sms|whatsapp]
667
+ martha sessions get <session-id> # Messages + tool calls
668
+ martha sessions delete <session-id> [--yes]
669
+
670
+ # Interactive chat (REPL)
671
+ martha chat [--session <id>] [--client <name>] [--message "text"] [--show-tools]
672
+
673
+ # Single-message mode
674
+ martha chat --client web --message "What's the weather?" # Creates new session, prints response
675
+ ```
676
+
677
+ `--show-tools` prints each tool call + result inline so you can debug agent reasoning.
678
+
679
+ ---
680
+
681
+ ## Integrations (OpenAPI specs + plugins)
682
+
683
+ Integrations are external API surfaces. Three types: `core` (`@platform_function`), `plugin` (Martha-published), `connected` (OpenAPI spec import), `custom` (manual function definitions).
684
+
685
+ ```bash
686
+ martha integrations list # All four sections
687
+ martha integrations specs [--inactive] # OpenAPI specs only
688
+ martha integrations sync <spec-id> [--force] # Re-pull and reconcile (--force skips hash check)
689
+ martha integrations import-openapi --source <url> --name <name> \
690
+ [--prefix <p>] [--auth-scheme bearer] [--auth-value '${API_KEY}'] \
691
+ [--include-tags tag1,tag2] [--exclude-tags admin] [--dry-run]
692
+ martha integrations plugins # Installed plugins
693
+ martha integrations plugin <name> # Plugin manifest + resources
694
+
695
+ # Plugin proxy — call plugin endpoints directly (debugging)
696
+ martha integrations proxy <plugin> GET|POST|PUT|DELETE <path> [--data '{}'] [--query 'k=v']
697
+ ```
698
+
699
+ **OpenAPI import flow:**
700
+ 1. `--dry-run` first to see what functions would be created
701
+ 2. Resolve any naming conflicts (use `--prefix`)
702
+ 3. Re-run without `--dry-run` to apply
703
+ 4. Use `martha agents add-function` to grant new functions to agents
704
+ 5. Use `martha integrations sync <spec-id>` to refresh after the upstream API changes
705
+
706
+ ### Function authentication patterns
707
+
708
+ Functions imported from OpenAPI inherit auth from the spec's `securitySchemes`. To bridge to Vault credentials, set `credential_source` on the function's `auth` block:
709
+
710
+ ```yaml
711
+ kind: Function
712
+ name: example-create-order
713
+ definition:
714
+ endpoint: https://example.com/orders
715
+ http_method: POST
716
+ auth:
717
+ scheme: bearer
718
+ header: Authorization
719
+ credential_source: example_prod # Vault key for the credential
720
+ # value: null — left null; resolved at runtime from Vault
721
+ ```
722
+
723
+ `FunctionHandler._resolve_credentials()` walks 4 tiers in priority order:
724
+ 1. **Session** — `vault.get_credential("sessions", session_id, credential_source)`
725
+ 2. **Client** — `vault.get_credential("clients", str(client_id), credential_source)`
726
+ 3. **Tenant by credential_source** — direct Vault lookup
727
+ 4. **Tenant by Connection** — looks up `Connection.credential_source` for `integration_name`, then Vault
728
+
729
+ If all 4 miss, falls back to baked-in `auth.value` (legacy). Use `martha agents provision-auth` for agent-scoped credentials, or the connections API for integration-scoped ones.
730
+
731
+ ---
732
+
733
+ ## Operational commands
734
+
735
+ ```bash
736
+ martha status # Connection + auth + Vault + Temporal health summary
737
+ martha models [--provider anthropic|openai|google] # Available LLM models with cost
738
+ martha messaging health # Infobip/Twilio status
739
+ martha messaging send-sms --to <number> --from <sender> --content "text"
740
+ martha config show # Resolved config (token-redacted)
741
+ martha config set <key> <value> # Update profile config
742
+ martha config profiles # List + active marker
743
+ martha config use <profile> # Switch active profile
744
+ ```
745
+
746
+ ---
747
+
748
+ ## Patterns (cookbook)
749
+
750
+ ### Create + grant + execute a workflow end-to-end
751
+
752
+ ```bash
753
+ # 1. Define functions + workflow declaratively
754
+ cat > pipeline.yaml <<'EOF'
755
+ kind: Function
756
+ name: fetch-orders
757
+ definition:
758
+ endpoint: https://api.example.com/orders
759
+ http_method: GET
760
+ auth:
761
+ scheme: bearer
762
+ credential_source: example_prod
763
+ ---
764
+ kind: Workflow
765
+ name: daily-orders-summary
766
+ definition:
767
+ nodes:
768
+ - id: fetch
769
+ type: function
770
+ config: { function_name: fetch-orders }
771
+ - id: summarize
772
+ type: llm
773
+ config:
774
+ prompt: "Summarize: {{steps.fetch.output}}"
775
+ model: claude-sonnet-4-5
776
+ edges: [{source: fetch, target: summarize}]
777
+ EOF
778
+
779
+ martha definitions apply -f pipeline.yaml --yes
780
+
781
+ # 2. Set up the credential (one-time per tenant)
782
+ # Use the admin UI at /settings, or POST to /api/admin/connections
783
+
784
+ # 3. Execute
785
+ martha workflows execute daily-orders-summary --inputs '{}' --wait --follow
786
+ ```
787
+
788
+ ### External agent on a team — full setup
789
+
790
+ ```bash
791
+ # Admin side
792
+ martha agents create --name ork-1 --type external --tags refactoring
793
+ martha agents provision-auth ork-1 --method service-account --json
794
+ # Copy client_id + client_secret from output
795
+
796
+ martha teams create --name refactoring-team --routing round_robin
797
+ martha teams add-member refactoring-team ork-1
798
+
799
+ # Agent side
800
+ export MARTHA_CLIENT_ID=<from above>
801
+ export MARTHA_CLIENT_SECRET=<from above>
802
+ export MARTHA_API_URL=https://martha.nomadriver.co
803
+ martha auth login --service-account
804
+ martha tasks poll --json # Should return open team-scoped tasks
805
+ ```
806
+
807
+ ### Tracker integration — Linear
808
+
809
+ ```bash
810
+ # 1. Create a Linear connection via admin UI: /settings > Trackers > Linear > Connect
811
+ # Provide: API Key (lin_api_...), Team ID (UUID from team URL)
812
+ # On success, copy the webhook_url + webhook_secret
813
+
814
+ # 2. Configure Linear webhook
815
+ # Linear settings > API > Webhooks > New webhook
816
+ # URL: <webhook_url>, Secret: <webhook_secret>
817
+ # Subscribe to: Issues, Comments
818
+
819
+ # 3. Verify the auto-created triggers
820
+ martha triggers list --active | grep linear
821
+ # Expect: sync-task-status-linear, sync-task-activity-linear, inbound-tracker-linear
822
+
823
+ # 4. Test by creating a Linear issue — Martha should create a corresponding task
824
+ martha tasks list --json | jq '.[] | select(.tracker_type == "linear")'
825
+ ```
826
+
827
+ ### Reingest documents after VLM upgrade
828
+
829
+ ```bash
830
+ COLL=<collection-id>
831
+ martha documents list $COLL --json | jq -r '.[] | .id' | while read DOC; do
832
+ martha documents reingest $DOC --wait
833
+ done
834
+ ```
835
+
836
+ ### Debug a failed task
837
+
838
+ ```bash
839
+ TASK=<id>
840
+ martha tasks view $TASK --json | jq '{status, error_message, retry_count, last_checkpoint, executor_id}'
841
+
842
+ # Look at the workflow execution if any
843
+ EXEC=$(martha tasks view $TASK --json | jq -r '.workflow_execution_id')
844
+ [ -n "$EXEC" ] && martha workflows execution $EXEC
845
+
846
+ # Check audit log for the executor
847
+ SVC=$(martha tasks view $TASK --json | jq -r '.executor_id')
848
+ martha events list --since 24h --json | jq ".[] | select(.data.executor_id == \"$SVC\")"
849
+ ```
850
+
851
+ ### Bulk grant many functions to an agent
852
+
853
+ ```bash
854
+ AGENT=my-agent
855
+ martha integrations specs --json | jq -r '.[] | select(.name == "linear") | .id' | while read SPEC; do
856
+ martha functions list --source openapi --json | jq -r ".[] | select(.spec_id == \"$SPEC\") | .name" | while read FN; do
857
+ martha agents add-function $AGENT $FN
858
+ done
859
+ done
860
+ ```
861
+
862
+ ---
863
+
864
+ ## Troubleshooting
865
+
866
+ ### "auth failure" (exit 2)
867
+ - `martha auth status` — check the token isn't expired or for a different tenant
868
+ - For service accounts: confirm `MARTHA_CLIENT_SECRET` matches the value from the most recent `provision-auth`
869
+ - For human users: re-login (`martha auth login --username ... --password ...`)
870
+
871
+ ### "not found" (exit 3) when the entity exists in the UI
872
+ - Tenant mismatch — your token belongs to a different tenant than where the entity lives
873
+ - Run `martha auth status` to see your `tenant_id`, then check the entity's tenant in the admin UI
874
+
875
+ ### Workflow execution stuck in "running"
876
+ - `martha workflows execution <id> --follow` — see which node is blocking
877
+ - `martha approvals list --status pending` — common cause is a waiting `approval_gate`
878
+ - For agent_loop nodes: check the agent's tool grants and credential resolution chain
879
+
880
+ ### Function returns 401 from upstream API
881
+ - Check the function's `auth.credential_source` matches a Vault entry: `martha auth token` then `curl /api/admin/connections | jq` to find the connection
882
+ - Verify the connection's `status` is `active` (rotate if expired)
883
+ - For OAuth2 connections: a 401 may trigger automatic token refresh; check `events list --type oauth.refresh.*`
884
+
885
+ ### Task heartbeat returns 409 unexpectedly
886
+ - Lease expired — your agent didn't heartbeat in time. Default lease is 300s; reduce work granularity or increase lease.
887
+ - Another agent claimed it after a stale event. Inspect: `martha tasks view <id> --json | jq '{executor_id, lease_expires_at, retry_count}'`
888
+
889
+ ### Tracker webhook not creating tasks
890
+ - Check the inbound trigger is active: `martha triggers list | grep inbound-tracker`
891
+ - Check recent webhook events: `martha events list --type webhook.received --since 1h`
892
+ - Look for HMAC signature failures in API logs (per-webhook secret mismatch)
893
+ - Verify the connection still has `status: active`
894
+
895
+ ### "DuplicateColumn" alembic error on deploy
896
+ - Migration trying to add a column that already exists. Check if a previous migration in the chain added it via a different path (this happened with `s4t5u6v7w8x9` and `u6v7w8x9y0z1`). Make the duplicate migration idempotent with `IF NOT EXISTS`.
897
+
898
+ ---
899
+
900
+ ## Conventions
901
+
902
+ - **Always pass `--json`** when scripting. Human output may include color codes and is not parse-stable.
903
+ - **Always pass `--yes`** in non-TTY contexts. The CLI errors instead of silently cancelling — this prevents CI from completing without doing anything.
904
+ - **Tenant comes from the token**, never from a flag. To switch tenants, switch profiles.
905
+ - **Time fields are ISO 8601 with timezone** in JSON output. Local-format dates appear only in human output.
906
+ - **Pagination:** all `list` commands accept `--limit` (default 50, max 200) and most accept `--offset`. There's no cursor pagination yet.
907
+ - **Soft delete is default.** `delete` without `--hard` marks `is_active=false` and keeps history. `--hard` purges. Tombstoned entities don't appear in `list` unless you pass `--inactive`.