@agentled/cli
Version:
CLI for Agentled — manage workflows, apps, and knowledge from the command line. Zero context-window cost for AI agents.
162 lines (114 loc) • 11 kB
Markdown
# 00 — Design Principles
> The four-pillar contract every Agentled workflow follows. Read this **before authoring any step.**
The other docs in this folder (`01-trigger-design`, `02-dedup-gates`, …) are tactical playbooks for specific shapes. This one is the contract underneath them: the four invariants every workflow you author has to satisfy, and the questions to answer before you write a single step.
If you remember nothing else from this folder, remember the four pillars and the three questions.
---
## The four pillars
### 1. Idempotency — re-running is safe
Every workflow you author must be re-runnable on the same input without producing duplicates, double-charges, or partial state. This is a property of the *workflow*, not the platform.
The two implementations:
- **Polling intake** — every trigger that reads from an external source (Gmail, Slack, a feed, a webhook replay) starts with a **dedup gate**. The default for email is a Gmail label gate (`-label:processed`) plus `GMAIL_ADD_LABEL` at the end of the loop. See [02-dedup-gates](02-dedup-gates.md).
- **KG writes** — use `kg.upsert-rows` with an explicit `userKey` (the LinkedIn URL, the domain, the order id — whatever is stable). Same `userKey` = same row across runs, O(1), no table scan. Reach for `kg.add-rows` only when duplicates are *acceptable*. See [12-event-driven-workflow-groups](12-event-driven-workflow-groups.md) §"Status is the API".
> **Test:** if you ran your workflow twice with the same input, would the world look the same as if you ran it once? If no, you have a missing dedup gate.
### 2. Small scope — one workflow owns one stage
A workflow that "sources leads, enriches them, scores them, drafts outreach, sends email, and updates the CRM" is not a workflow — it's a system that needs five. Symptoms of an over-scoped workflow:
- A failed step at minute 20 forces re-running steps 1–4 to retry step 5.
- The execution log spans 200+ rows and you can't tell which stage failed.
- Two upstream sources both want to feed in, but they can't share infrastructure.
- The "outreach" step needs different scheduling than the "sourcing" step.
The default decomposition rule: **one workflow per stage in the business process.** Stages are the things a human would describe with a different verb — *source*, *enrich*, *score*, *outreach*, *follow up*, *report*. Each stage gets its own workflow, its own trigger, its own retry semantics. See [05-child-workflow-contracts](05-child-workflow-contracts.md) for child workflows and [12-event-driven-workflow-groups](12-event-driven-workflow-groups.md) for multi-workflow systems.
> **Test:** can a human point at your workflow and say in one sentence what it does, without saying "and" twice? If not, split it.
### 3. KG list is the connector between workflows
When one workflow finishes and another begins, **the handoff is a Knowledge Graph list row, not a direct call**. The producing workflow writes (`kg.upsert-rows` with `status: "new"`); the consuming workflow reads (`kg.read-list` with `filters: { status: "new" }`) and updates (`kg.update-rows` to set `status: "processed"`).
Why a KG list and not `call-workflow`?
| KG-list handoff | Direct call |
|---|---|
| Producer doesn't know which consumers exist | Producer hardcodes consumer ID |
| Consumer can be paused, replaced, or duplicated freely | Pausing the consumer breaks the producer |
| Backfill = widen the status filter | Backfill = re-run the producer |
| State is durable and inspectable in the UI | State exists only in the execution log |
| Multiple producers can write to one list | Each producer wires up its own caller chain |
Use `call-workflow` (Pattern 11/12) when a child workflow is a **pure function** — same input always yields the same output, and the parent immediately consumes the return value. Use the KG list as connector for everything else.
> **Test:** if you swap one of your workflows for a different implementation tomorrow, do the others keep working without code changes? If no, you have a brittle direct-call dependency.
### 4. Design as data — persist decisions in KG text
The workflow JSON tells you *what* the workflow does. It does not tell you *why* it does it that way. Why this trigger? Why this scoring rubric? Why a dedup label and not a userKey? Without those answers, the next agent session — or the next human — rebuilds your reasoning from scratch every time.
Save the rationale **in the platform**, not in your local scratch folder. Two places, two purposes:
**Working memory (local, per-group)** — `workflow-groups/<group-slug>/design.md`, `decisions/`, `worklog.md`. The agent's scratch space while building. See [12-event-driven-workflow-groups](12-event-driven-workflow-groups.md) §"Folder layout".
**Durable memory (KG text, per-workspace)** — call the `upsert_knowledge_text` MCP tool when you reach a non-trivial decision. The decision survives across agent sessions, users, and CLI reinstalls. Example:
```text
upsert_knowledge_text({
key: "design.<group-slug>.scoring-rubric",
value: "Scoring rubric for the <group> funnel.\n\nDimensions: team (40), traction (30), market (30).\nThreshold: score >= 70 → outreach; 50-69 → nurture; <50 → reject.\nWhy: validated against 200 hand-scored seed-stage rows. See decisions/0003.\nLast review: 2026-04."
})
```
Use the `design.<group-slug>.<topic>` key convention so a later agent can `recall_memory({ key: "design.<group-slug>.scoring-rubric" })` or `search_memories({ query: "scoring rubric", scope: "workspace" })` without guessing.
What to upsert as `design.*` text:
| Topic | Key suffix | What to capture |
|-------|-----------|-----------------|
| Group goal & operating principles | `.charter` | The 1-paragraph goal and the 3–5 operating principles |
| ICP / scoring rubric | `.scoring-rubric` | Dimensions, weights, thresholds, the data the thresholds were calibrated on |
| KG list shapes | `.kg-lists` | List keys, `userKeyField`, state machines, who reads/writes |
| Approval gate policies | `.approvals` | Which steps gate, who approves, escalation rules |
| Channel / outreach voice | `.outreach-voice` | Tone, forbidden phrases, brand rules |
| Rejected alternatives | `.tradeoffs` | What you considered and didn't ship, and why |
> **Test:** if a new agent picks up this group tomorrow with no chat history, can it answer "what is the rubric and why does it look like this?" by reading what's in KG text? If no, you owe a `design.*` upsert.
---
## The three questions before you author
Answer all three out loud (or in `design.md`) before the first `add_step`. If you can't answer, you don't have enough context — orient the workspace first (`agentled workspace inspect`, `list_workflows`, `list_knowledge_lists`).
1. **What is the unit of work?** One company? One lead? One inbound email? One edition? The unit determines the loop scope, the KG row, the `userKey`, and the retry granularity.
2. **What is the completion signal?** A KG row status flip? A downstream trigger firing? A human approval? A milestone with no successor? "Completion" must be a thing the platform can observe — not an intent in the agent's head.
3. **Is this workflow part of a group?** If the user describes more than one verb ("source *and* enrich", "enrich *and* outreach"), the answer is yes and you owe a group manifest before you write step 1. See [12-event-driven-workflow-groups](12-event-driven-workflow-groups.md).
---
## Decision tree: which shape do I author?
```
Is the user describing one verb, one input, one output?
├─ YES, and it's truly one-shot → Single workflow, milestone end.
│ Scaffolds: minimal, ai-with-tools, list-match-email.
│
├─ YES, but the same logic will be reused by other workflows → Child workflow.
│ Terminal step is `return`, not `milestone`.
│ Scaffold: child-with-return.
│ Pattern: 05.
│
└─ NO, the user is describing a process with multiple stages →
│
├─ Stages share state and run on different cadences → Two (or more) workflows
│ connected by a KG list. Producer upserts with status: "new"; consumer
│ reads by status, processes, updates status.
│ Scaffolds: source-to-kg + kg-process-update.
│ Pattern: 12, 15.
│
└─ One workflow needs to fan out the same operation per item with deterministic
results → Orchestrator + child via call-workflow inside a loop.
Scaffold: orchestrator-kg-loop.
Pattern: 11.
```
---
## The 30-second sanity check
Before you publish, walk this checklist. Each "no" is a blocker.
- [ ] If this workflow is re-run on the same input, does it produce the same state? (Pillar 1)
- [ ] Can I describe what this workflow does in one short sentence without "and"? (Pillar 2)
- [ ] If the next workflow in the chain is paused or replaced, does this one still do its job? (Pillar 3)
- [ ] Are the design rationale, ICP/rubric, and list shapes saved as `design.<group>.*` in KG text? (Pillar 4)
- [ ] Did I answer the three questions before writing step 1?
- [ ] Did I pick the shape from the decision tree, not invent a new one?
- [ ] Does the workflow pass `agentled workflows validate <wfId>` with zero errors and zero warnings?
- [ ] Did I run the [Dry-Run Protocol](../../../CLAUDE.md) (`validate_workflow` → `test_*_action` per key step → check `{{steps.X.field}}` refs against captured payloads) before any `start_workflow`?
If all eight pass, ship it. If not, fix before publish — the platform won't catch a missing rationale or a misnamed list key for you.
---
## Where to go next
| If you're doing… | Read |
|---|---|
| Choosing a trigger | [01-trigger-design](01-trigger-design.md) |
| Building a polling intake | [02-dedup-gates](02-dedup-gates.md) |
| Iterating without burning credits | [03-credit-efficiency](03-credit-efficiency.md) |
| Looping over a KG list | [04-loop-patterns](04-loop-patterns.md) |
| Building a child workflow | [05-child-workflow-contracts](05-child-workflow-contracts.md) |
| Conditional routing | [06-conditional-routing](06-conditional-routing.md) |
| Handling errors / skips / waits | [07-error-handling](07-error-handling.md) |
| Sending email with approval | [08-composed-email-approval](08-composed-email-approval.md) |
| Rendering reports + KG storage | [09-reports-and-knowledge-storage](09-reports-and-knowledge-storage.md) |
| Researching a person | [10-person-research-ladder](10-person-research-ladder.md) |
| Researching a company | [11-company-research-ladder](11-company-research-ladder.md) |
| Designing a multi-workflow group | [12-event-driven-workflow-groups](12-event-driven-workflow-groups.md) |
And before you start: `agentled workflows scaffold --list` to see every starter shape, then `agentled workflows scaffold <slug> --out my-pipeline.json` to bootstrap.