Skip to content

Build your first agent ​

This guide takes one project from "we have a backlog" to a small digital worker: a governed agent that reads sources on a schedule, recommends backlog items, and — once it has earned the right, in episodes a person fires — drafts roadmap items and stories into that project's own definition store. The project's build loop picks them up once a person says they're ready. By the end you will have:

  • an outcome, a use case and stories the worker exists to serve;
  • a declared worker (an agent_solution) compiled into a running, sandboxed deployment;
  • a trigger that fires it, and one episode you ran by hand to see it work;
  • a recommendation you responded to, and a read of what the evaluation actually measures;
  • a worker promoted up the autonomy ladder, by evidence, to the point where it can draft;
  • a draft in your definition store that is always a draft until an admin or approver says otherwise, and a feature your project's own loop built from it;
  • optionally, the same worker exported and run outside Weftra, under Hermes.

The worked example throughout is a research worker: it reads a handful of sources you declare — documentation, a changelog, a feed, whatever your own solution names — and turns what it finds into draft backlog items for the project it serves. The sources and the subject are yours to choose; nothing below names a real project, vendor or competitor.

Way to drive WeftraWhat it isCredential
DashboardThe web UI at http://localhost:3100An admin key or session (see "If something goes wrong" for why an approver's key is not enough here)
APIcurl against the same routes the dashboard usesA project key (fa_...) for definition work; a named admin or approver key for everything that compiles, triggers, promotes or exports a worker
In an IDE or agent consoleClaude Code over MCP, or claude.ai/ChatGPT as remote connectors, holding a project keyThe project key only — there is no MCP tool for anything admin/approver-only in this guide

Almost every step past the first two is admin/approver-only, and recorded against a named person — not the shared instance admin key. A worker earns the right to act, but a person decides whether its suggestion counts and whether its draft becomes real work.

Before you start ​

  • A running Weftra instance and an enrolled project whose loop you want the worker to eventually serve — this is the worker's source project. If you don't have one yet, the chess walkthrough gets you from nothing to an enrolled project with a project key; come back here once you have one.

  • A named admin user, not just the shared ADMIN_API_KEY. Compiling a worker, arming a trigger, firing an episode, responding to a recommendation, promoting the worker and exporting it are recorded against a specific person, and those routes refuse the shared instance key. Create one once, if you don't have one:

    bash
    curl -s -X POST "$WEFTRA/api/users" \
      -H "Authorization: Bearer $ADMIN_API_KEY" -H "Content-Type: application/json" \
      -d '{"email": "you@example.com", "name": "Your Name", "role": "admin"}' \
      | jq '{id, api_key}'

    Save the api_key — it is shown only this one time. An approver linked to the source project (see "Switching to PO approval with an approver" in the chess walkthrough) can make every $UKEY call below except the admin-only ones: setting the worker repository, compiling (Step 3), writing a draft action (Step 7), reading the worker's drafts with full provenance (Step 8) and establishing the loop (Step 10).

  • A worker repository on the source project. The worker in this guide has an act step, and a worker with an act step cannot be declared repository-less, so compiling it needs the source project's worker_repo_url: a repository your instance can clone, which the worker project is enrolled against. Only an admin can set it, and it has no dashboard field:

    bash
    curl -s -X PATCH "$WEFTRA/api/projects/$PROJECT_ID" \
      -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
      -d '{"worker_repo_url": "https://github.com/your-org/research-worker"}' | jq '.worker_repo_url'

Set these once:

bash
export WEFTRA=http://localhost:3100
export PROJECT_ID=<your source project's id>
export PROJECT_API_KEY=fa_...   # the source project's own key
export UKEY=fa_...              # your named admin key

Step 1: Define the outcome, use case and stories ​

The worker has to point at something you defined first: an outcome (the result you want), a use case (who's affected and how), and stories (the testable slices). This is the same product-definition store every project gets.

In the dashboard ​

Open the Product tab (/product.html), pick your project, and click + New Outcome. Fill in the statement and its metric, baseline and target, and click Create. On the new outcome's card, click + Item (a roadmap item), then on that card + Use case, then on the use case's card + Story — each button nests the new artifact under the one you clicked from. Fill in each form's fields (a roadmap item needs a summary; a use case needs an actor, a goal and a main scenario; a story needs a statement and at least one acceptance criterion) and click Create each time.

With the API ​

bash
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts" \
  -H "Authorization: Bearer $PROJECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "outcome", "title": "A backlog that reflects what the sources say", "body": {
    "statement": "Reduce how long a topic the sources raised sits with no drafted backlog item",
    "metric": "weeks since the last worker-drafted item, measured weekly",
    "baseline": "unbounded (no worker yet)", "target": "1 week or less", "horizon": "this quarter"
  }}' | jq '{lineage_id, status}'
# save the outcome's lineage_id as $OUTCOME_ID

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts" \
  -H "Authorization: Bearer $PROJECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "roadmap_item", "title": "Backlog coverage from read sources",
       "body": {"summary": "Turn what the worker reads into backlog items a maintainer reviews",
                 "outcome_lineage_ids": ["'"$OUTCOME_ID"'"]},
       "parent_lineage_id": "'"$OUTCOME_ID"'"}' | jq '{lineage_id, status}'
# save as $ROADMAP_ID

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts" \
  -H "Authorization: Bearer $PROJECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "use_case", "title": "A maintainer reviews what the sources raised",
       "body": {"actor": "the project maintainer",
                 "goal": "spend review time deciding, not summarizing, what the sources raised",
                 "main_scenario": ["The worker reads the declared sources on its schedule.",
                                    "It drafts a roadmap item or story citing what it read.",
                                    "A maintainer reviews the draft like any other and decides."]},
       "parent_lineage_id": "'"$ROADMAP_ID"'"}' | jq '{lineage_id, status}'
# save as $USE_CASE_ID

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts" \
  -H "Authorization: Bearer $PROJECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "story", "title": "Draft a story from a source the worker read this week",
       "body": {
         "statement": "As the maintainer, I want the week'"'"'s sources turned into a reviewable story so I do not have to read every source myself first.",
         "acceptance_criteria": [
           {"text": "Given the worker'"'"'s scan finds a topic with no existing roadmap coverage, when its draft step runs, then a new story appears in the definition store at status draft, citing the source it read."},
           {"text": "Given that drafted story, when a maintainer opens the Definition-of-Ready review, then it grades against the same rubric as a story a person wrote."}
         ],
         "use_case_lineage_id": "'"$USE_CASE_ID"'", "outcome_lineage_id": "'"$OUTCOME_ID"'"
       },
       "parent_lineage_id": "'"$USE_CASE_ID"'"}' | jq '{lineage_id, status}'
# save as $STORY_ID

Every kind's body fields are closed and bounded — an unrecognized field is rejected rather than dropped. See "Product Definition — Outcomes, Roadmap, Use Cases & Stories" in the User Guide for the full field reference and every kind's shape.

In an IDE or agent console ​

The outcome, use case and story each have a dedicated tool; there is no tool for a roadmap item (declare that one in the dashboard or with the API, then come back):

product_propose_outcome { title, body }
product_propose_use_case { title, body, parent_lineage_id }
product_propose_story { title, body, parent_lineage_id }
product_list { kind?, status? }
product_get { lineage_id, version? }

Each propose tool creates a draft through the same route curl uses; product_list and product_get read back what you created.

Step 2: Pass the Definition of Ready ​

A worker may only point at an outcome or a story that has already passed Weftra's own quality gate — the Definition of Ready. It checks things like "does the outcome state a measurable target" and "does every acceptance criterion resolve to an observable assertion," against a rubric fixed in Weftra itself.

You do not need to get the roadmap item or the use case to ready — only the outcome and the story need to pass, because those are the two the worker's declaration will point at.

In the dashboard ​

On the outcome's card and the story's card, click Propose ready. If a finding comes back, fix the field it names and click it again — a new version resets readiness, so you can iterate.

With the API ​

bash
# See what the gate would say, without changing anything
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts/$OUTCOME_ID/review" \
  -H "Authorization: Bearer $PROJECT_API_KEY" | jq '{verdict, findings}'

# If it's clean, make it ready (same review, but sets status on a pass)
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts/$OUTCOME_ID/propose-ready" \
  -H "Authorization: Bearer $PROJECT_API_KEY" | jq '{artifact: .artifact.status, verdict: .review.verdict}'

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts/$STORY_ID/propose-ready" \
  -H "Authorization: Bearer $PROJECT_API_KEY" | jq '{artifact: .artifact.status, verdict: .review.verdict}'

Both routes accept a project key for an ordinary (non-worker-drafted) artifact — there is no separate "approve readiness" role here; the gate's own verdict decides. That changes for a worker's own draft, in Step 9.

In an IDE or agent console ​

product_review { lineage_id }
product_propose_ready { lineage_id }

Same routes, same rubric. See "Definition of Ready — the quality gate" in the User Guide for what each kind's rubric checks and how multi-round reviews are carried forward.

Step 3: Declare the worker and compile it to a shadow deployment ​

Now declare the worker itself: an agent_solution artifact naming which outcome and story it serves, the steps it runs (classed observe, recommend or act), the authority it's allowed (which capabilities, which hosts), and its starting autonomy. A compile enrolls a worker at the base of the autonomy ladder — shadow or advisory; anything higher is refused. For this worker the choice is already made: a declaration with an act step is refused at advisory when the artifact is written, so the example declares shadow and climbs later, in Step 7.

The declaration below leaves workspace at its default, a repository-backed worker, because it has an act step; that is why "Before you start" set worker_repo_url. (A worker with only observe and recommend steps may declare workspace: "none" and needs no repository — but it can never draft.)

In the dashboard ​

The Product tab's + New worker from a workflow button starts an interview — describe your current process in plain English, answer its classification questions, and it derives a declaration of the shape shown below. There is no form for writing the JSON body directly. Make the artifact ready with the same Propose ready button as Step 2. Compiling has no dashboard control; use the API.

With the API ​

bash
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts" \
  -H "Authorization: Bearer $PROJECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "agent_solution", "title": "Stale-coverage research worker", "body": {
    "purpose": {"outcome_lineage_ids": ["'"$OUTCOME_ID"'"], "story_lineage_ids": ["'"$STORY_ID"'"]},
    "workflow": {
      "triggers": [{"kind": "manual", "spec": "fired by hand, or on a schedule armed after compile"}],
      "steps": [
        {"id": "scan", "description": "Read the declared sources for topics the backlog does not cover", "action_class": "observe", "escalate_when": []},
        {"id": "flag", "description": "Recommend a roadmap item or story worth adding", "action_class": "recommend", "escalate_when": ["two sources disagree"]},
        {"id": "draft", "description": "Draft the recommended item into the backlog as a draft", "action_class": "act", "escalate_when": ["the Definition-of-Ready gate cannot grade the draft"]}
      ]
    },
    "authority": {
      "capabilities": ["worker:propose_recommendation", "worker:write_evidence", "worker:propose_definition_draft"],
      "egress_hosts": ["docs.example.com"], "data_scopes": [], "engine_profile": "claude-code"
    },
    "autonomy": "shadow",
    "evaluation": {"success_measures": [{"outcome_lineage_id": "'"$OUTCOME_ID"'", "observation_kind": "weekly backlog coverage"}]},
    "promotion_policy": {
      "advisory": {"min_samples": 10, "min_agreement": 0.8}, "draft": {"min_samples": 10, "min_agreement": 0.8, "max_refused_act": 0},
      "act_reversible": {"min_samples": 30, "min_agreement": 0.8, "min_effect_observed": 10, "max_refused_act": 0, "max_open_escalations": 0}
    }
  }}' | jq '{lineage_id, status}'
# save as $SOLUTION_ID

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts/$SOLUTION_ID/propose-ready" \
  -H "Authorization: Bearer $PROJECT_API_KEY" | jq '{artifact: .artifact.status, verdict: .review.verdict}'

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/compile" \
  -H "Authorization: Bearer $UKEY" | jq '{deployment_id, worker_project_id, autonomy}'
# save deployment_id as $DEPLOYMENT_ID and worker_project_id as $WORKER_PROJECT_ID

Replace docs.example.com with the host your sources live on. Compile is admin-only, and the admin must be a named person: a project key or an approver is refused (401, or 403 on a loopback development instance with no ADMIN_API_KEY set), and the shared ADMIN_API_KEY is refused too (403) — compiling enrolls a brand-new worker project and mints its own key. Compiling the same solution version a second time answers 409 with the existing deployment_id; it never creates a second worker project.

In an IDE or agent console ​

No tool declares an agent_solution, and there is no interview tool; use the dashboard interview or the API for that one artifact. product_propose_ready works on it once declared. No tool compiles — that needs a named admin, and this mode holds a project key; use the API. See "Agent Factory — declaring and compiling a digital worker" in the User Guide for the full shape of an agent_solution, including repo-based workers and what a template-installed chain's egress is bounded by.

Step 4: Arm a trigger and fire an episode ​

A compiled worker does nothing until something fires an episode — one run of one of its steps. A schedule or webhook trigger can fire the scan and flag steps. A trigger can never target an act step — arming one on draft answers 403 — so drafting episodes are always fired by hand, by a named person, and only once the worker has reached the top of the ladder (Steps 7 and 8).

In the dashboard ​

Open the Workers panel (/workers.html), pick the source project, and open the deployment's card. Under Steps, each observe and recommend step has an Arm control: choose schedule (and a cadence like 1h) or webhook, and click Arm. There is no dashboard control to fire one episode by hand; use the API, below.

With the API ​

bash
# Arm an hourly schedule on the observe step
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/$DEPLOYMENT_ID/triggers" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"kind": "schedule", "step_id": "scan", "cadence": {"kind": "every", "value": "1h"}}' \
  | jq '{id, active}'

# Fire the recommend step once, right now, to see it work
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/$DEPLOYMENT_ID/episodes" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"step_id": "flag"}' | jq '{id, episode_deployment_id}'

Both routes refuse a project key (403) and require a named admin or approver — the shared instance key is refused too, because each act is recorded against whoever did it.

In an IDE or agent console ​

No tool reaches triggers or episodes: both are admin/approver-only, and this mode holds a project key. Arm the trigger in the dashboard or with the API, and fire with the API. See "Running a worker in shadow mode" in the User Guide for the full set of trigger kinds (including webhook) and how the scheduler's daily cap and in-flight bound work.

Step 5: Read episodes and the outcome ledger, respond to a recommendation ​

A recommend step's output is a recommendation — a summary, a rationale and the evidence the worker cites. In shadow mode nothing happens on its own: you decide what to do with it, and that decision is the evidence later steps read.

In the dashboard ​

The Workers panel's deployment card lists Open recommendations with Accept and Reject buttons and an optional text field for what you did (or would do) instead. Click one; the recommendation's state updates immediately.

With the API ​

bash
curl -s "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/$DEPLOYMENT_ID/open-recommendations" \
  -H "Authorization: Bearer $UKEY" | jq '.recommendations[] | {recommendation_lineage_id, summary}'

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/$DEPLOYMENT_ID/recommendations/<recommendation lineage id>/respond" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"decision": "accepted", "human_action": "filed the backlog item myself, confirmed the worker was right"}' \
  | jq '{state}'

decision is accepted, rejected, superseded, or effect_observed — states move forward only, and a call that tries to skip or repeat one answers 409. The route refuses a project key (403), including the worker project's own, so a worker can never respond to its own recommendation.

In an IDE or agent console ​

No tool reaches recommendations either. Respond from the dashboard or the API. See "Running a worker in shadow mode" in the User Guide for the full decision lifecycle and what advisory autonomy additionally notifies.

Step 6: Read the evaluation ​

Every response you make in Step 5 feeds a running evaluation — counts, never a recommendation's text — that later steps read to decide whether the worker has earned more autonomy.

In the dashboard ​

The deployment card's evaluation grid shows episode counts, recommendation counts, and the agreement rate directly; the Ladder section below it previews the same numbers against the next rung's policy.

With the API ​

bash
curl -s "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/$DEPLOYMENT_ID/evaluation" \
  -H "Authorization: Bearer $UKEY" | jq

Returns episode counts (started/completed/failed/refused_act), recommendation counts (attempted/accepted/rejected/…), an agreement_rate and a cost summary. agreement_rate stays null until at least 10 responded recommendations exist in the window, so you need to repeat Steps 4 and 5 that many times before Step 7's first promotion becomes possible.

In an IDE or agent console ​

No tool reads it. Read it from the dashboard or the API. See "Running a worker in shadow mode" in the User Guide for exactly what each count means and when it resets.

Step 7: Earn autonomy ​

Autonomy is a closed, ordered ladder — shadow → advisory → draft → act_reversible — and promotion is always exactly one rung at a time, decided live against the promotion_policy your solution declared in Step 3. Our worker must reach the top rung, act_reversible, before its draft step can write anything.

What the climb takes, under the policy declared in Step 3: advisory and draft each need at least 10 responded recommendations with at least 80% agreement. act_reversible needs 30 at the same agreement, zero refused act attempts, no open escalations, and at least 10 distinct observed effects.

The worker cannot produce those ten effects itself today. Below act_reversible its draft step is offered no act tool, so the effects come from a person executing stored draft actions once the deployment is at draft — and nothing this worker declares stores one. A step is classed observe, recommend or act, and none of those is offered a tool that writes a draft action. A draft action is a draft_action artifact on the worker project (the worker_project_id compile returned), written through the ordinary artifacts route with that project's own key or an admin key. In practice an admin writes each one by hand, as shown below, and a named admin or approver executes it: ten of them, each with a different title, because an execution that duplicates an existing item writes nothing and counts for nothing. That is evidence of a person's judgement rather than the worker's, and there is no shorter path.

Separately, your operator must have added worker:propose_definition_draft to the WORKER_REVERSIBLE_CAPABILITIES setting in Weftra's own environment — host configuration no key or dashboard control can set. Without it, executing a draft action of that kind answers 403 and the tool stays absent from every episode however far the worker climbs.

In the dashboard ​

The Ladder section shows a Promote to <next level> button, disabled with the exact unmet reason until the policy's floor is cleared, and a Demote control that needs no evidence at all. Each open draft action is listed with its payload and Execute and Discard buttons; there is no control to write one.

With the API ​

bash
# Zero-write preview of whether the next rung would succeed
curl -s "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/$DEPLOYMENT_ID/promotion-eligibility?to_level=advisory" \
  -H "Authorization: Bearer $UKEY" | jq '{eligible, reason}'

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/$DEPLOYMENT_ID/promote" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"to_level": "advisory"}' | jq '{id, autonomy, promoted_from}'

Promotion returns a new deployment id — later calls (evaluation, episodes, triggers, further promotion) all move to the new id; the old one is retired. Repeat the preview-then- promote pair for draft, then for act_reversible, against each new deployment id in turn. Both routes refuse a project key (403). Promote and demote also refuse the shared admin key (403) — only a named admin or a linked approver may do either — while the preview, which writes nothing, answers the shared key too.

Once the deployment is at draft, store a draft action, read it back, and execute it:

bash
# The hash is over the payload as stored: write it compactly, no spaces between tokens
DRAFT_PAYLOAD='{"step_id":"draft","kind":"roadmap_item","title":"Cover the new export format","body":{"summary":"The changelog describes an export format the backlog has no item for"},"evidence_refs":[{"kind":"url","ref":"https://docs.example.com/changelog"}]}'
DRAFT_HASH=$(printf '%s' "$DRAFT_PAYLOAD" | sha256sum | cut -d' ' -f1)
curl -s -X POST "$WEFTRA/api/projects/$WORKER_PROJECT_ID/product/artifacts" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"kind": "draft_action", "title": "Draft a roadmap item", "body": {"step_id": "draft", "action_kind": "propose_definition_draft", "payload_hash": "'"$DRAFT_HASH"'", "payload": '"$DRAFT_PAYLOAD"'}}' \
  | jq '{lineage_id, status}'

curl -s "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/<draft-level deployment id>/open-drafts" \
  -H "Authorization: Bearer $UKEY" | jq '.drafts[] | {lineage_id, content_hash, action_kind, payload}'

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/<draft-level deployment id>/drafts/<draft lineage id>/execute" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"content_hash": "<content_hash from open-drafts>"}' | jq '{executed, effect_ref}'

Writing the draft action needs an admin key (or the worker project's own); executing it needs a named admin or linked approver, at draft or higher, and the exact content_hash you read. The execution writes the roadmap item into the source project as a draft, authored by whoever executed it — not by the worker — and effect_ref is its id. It is made ready exactly like a worker's own draft (Step 9).

In an IDE or agent console ​

No tool reaches promotion, demotion, eligibility or draft actions. Use the dashboard or the API. See "Earning autonomy" in the User Guide for the full ladder table, the policy floors Weftra enforces regardless of what you declare, and how auto-demotion works if a promoted worker ever gets a refused act.

Step 8: The worker's drafts reach the definition store ​

Step 7's executed draft actions already put drafts in the definition store, in a person's name. Once the deployment is act_reversible, the worker's own draft step episodes can call propose_definition_draft: writing a draft outcome, roadmap item, use case or story straight into the source project's own definition store — the same store Step 1 used — citing the evidence it read. The result is always a draft, never anything a build loop can select, however far up the ladder the worker climbs.

No trigger can fire the draft step (Step 4), so fire it by hand each time you want the worker to draft. Below act_reversible the same request is refused:

bash
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/<latest deployment id>/episodes" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"step_id": "draft"}' | jq '{id}'

In the dashboard ​

The Product canvas shows the new artifact on the source project's tree like any other draft, plus a provenance line: "Drafted by worker" and its name, the episode it came from, when, and the sources it cited as plain text. A Worker drafts toggle on the toolbar filters the tree to only these.

With the API ​

bash
curl -s "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts?kind=story" \
  -H "Authorization: Bearer $UKEY" | jq '.artifacts[] | {lineage_id, title, worker_draft}'

This read accepts an admin key or the project's own key, not an approver's. With an admin key, worker_draft carries deployment_id, worker_project_id, worker_name, episode_feature_id, the worker's evidence_refs and last_review. With the source project's own key it carries only origin and drafted_at; the rest belongs to another project and is withheld. A deployment can write at most five drafts per episode and twenty per rolling day, and a draft whose title matches an existing item of the same kind (after trimming and case-folding) writes nothing new and points back at the original.

In an IDE or agent console ​

product_list/product_get read the artifact, including its worker_draft field, with the source project's own key — same tenant-narrowed shape as above. See "Worker drafts — provenance, review, and who may make one ready" and "Outcomes and traceability" in the User Guide for the complete field reference, the exact cap numbers, and how de-duplication is decided.

Step 9: Promote a draft ​

A worker's draft is graded exactly like any other draft — the same Definition-of-Ready review, the same rubric, on demand. What changes is who may make it ready: only an admin or an approver linked to the source project. The source project's own key — the same key that could propose any of its own artifacts ready in Step 2 — is refused.

In the dashboard ​

Open the worker-drafted story's card on the Product canvas and click Propose ready.

With the API ​

bash
# The source project's own key can still see how it grades...
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts/<worker-drafted story id>/review" \
  -H "Authorization: Bearer $PROJECT_API_KEY" | jq '{verdict, findings}'

# ...but cannot make it ready:
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts/<worker-drafted story id>/propose-ready" \
  -H "Authorization: Bearer $PROJECT_API_KEY"
# -> 403 worker_draft_requires_approver — no review ran, nothing changed, nothing was recorded

# An admin or a linked approver can:
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/artifacts/<worker-drafted story id>/propose-ready" \
  -H "Authorization: Bearer $UKEY" | jq '{artifact: .artifact.status, verdict: .review.verdict}'

The refusal fires before any review runs. It does not stop the source project from writing the same text into a brand-new artifact of its own and proposing that one — that artifact is the project's own, attributed to its own key.

In an IDE or agent console ​

product_review works with the project key. product_propose_ready hits the same 403 the API does, because this mode holds a project key; use the dashboard or the API with your named key. See "Worker drafts — provenance, review, and who may make one ready" in the User Guide for exactly what each caller sees and what the refusal message says.

Step 10: The project's loop builds it ​

A ready story — worker-drafted or not — is just a story. If the source project runs a build loop that selects from its definition store, this is where "ready" becomes a feature, a branch and a pull request.

In the dashboard ​

No control. Loop configuration has no dashboard form; use the API.

With the API ​

A project key cannot bring a loop into existence. An admin establishes it first, which is also where the limits it runs under are set:

bash
curl -s -X PATCH "$WEFTRA/api/projects/$PROJECT_ID" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"loop": {"enabled": false, "cadence_seconds": 7200, "budget_usd_per_cycle": 2}}' | jq '.loop'

No field of loop is required, but cadence_seconds, max_pending_merges and budget_usd_per_cycle are admin-only, and a loop with no budget_usd_per_cycle has no per-cycle cost gate — so set them here. Once a loop exists, the project's own key may turn it on and off and choose where it selects from:

bash
curl -s -X PATCH "$WEFTRA/api/project" \
  -H "Authorization: Bearer $PROJECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"loop": {"enabled": true, "backlog_source": "definition_store"}}' | jq '.loop'

The same request against a project with no loop answers 403, and so does a project-key write that names one of the admin-only limits. No cycle runs while enabled is false. Once enabled, each cycle picks one ready story that has no feature already in flight for it (by a declared roadmap-item order, then creation order), drafts a spec for that one story, and submits it through the project's ordinary autonomy mode — a full_auto project queues it immediately; auto_safe/po_approval go through analysis or approval first.

In an IDE or agent console ​

No tool sets loop configuration; use the API. Watch the feature a cycle produces with get_feature_status or list_features like any other feature. See "Selecting from your own definition store" in the User Guide for the full selection policy — ordering, dependencies, serialization lanes and what happens when a story can't be specced — none of which this guide repeats.

Step 11 (optional): Export and run under Hermes ​

Everything above runs inside Weftra. You can also export a compiled deployment as a portable, credential-free package and run it under a separate agent runtime — Hermes is the one this guide and this repository's own examples support today.

In the dashboard ​

The Workers panel's deployment card has an Export package control (pick generic-json or hermes), a Packages list with a Download link per export, and Mint enrollment token on each package row — the token renders once, in a copy box, and is never stored anywhere Weftra can read back.

With the API ​

bash
curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/<latest deployment id>/packages" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" \
  -d '{"shape": "hermes", "profile": {}}' | jq '{id, package_hash}'
# save the package id as $PACKAGE_ID

curl -s -X POST "$WEFTRA/api/projects/$PROJECT_ID/product/agents/$SOLUTION_ID/deployments/<latest deployment id>/packages/$PACKAGE_ID/enroll" \
  -H "Authorization: Bearer $UKEY" -H "Content-Type: application/json" -d '{}' \
  | jq '{enrollment_token, expires_at}'

From there, the supported start path is this repository's own harness (examples/agent-factory/hermes/): it exports the package, mints the enrollment token, starts the runtime you point it at, and watches Weftra's ledger for the episode to redeem, call the gateway and report completion. See that directory's README.md for every variable it reads.

In an IDE or agent console ​

No tool exports a package, mints a token, or drives the external-episode routes — this whole step is API/dashboard only. See "Export a worker" and "Run an exported worker" in the User Guide for the full package contents and the external-episode protocol, and examples/agent-factory/README.md for the runnable harness this step uses.

What each mode can do ​

StepDashboardAPI (named admin/approver key, or project key where noted)IDE / agent console (MCP, project key)
Step 1: Define the outcome, use case and storiesYesYes (project key)Outcome, use case, story — yes; roadmap item — no tool
Step 2: Pass the Definition of ReadyYesYes (project key)Yes
Step 3: Declare the worker and compile it to a shadow deploymentDeclare via interview; no control to compile or to set the worker repositoryYes (declare: project key; worker repository and compile: named admin)Declare — no; compile — no
Step 4: Arm a trigger and fire an episodeArm — yes (never on an act step); fire by hand — noYes (named admin/approver)No
Step 5: Read episodes and the outcome ledger, respond to a recommendationYesYes (named admin/approver)No
Step 6: Read the evaluationYesYes (named admin/approver)No
Step 7: Earn autonomyYes, except writing a draft actionYes (named admin/approver; writing a draft action: admin)No
Step 8: The worker's drafts reach the definition storeRead-only viewYes (fire by hand: named admin/approver; read: admin or project key)Read-only (project key)
Step 9: Promote a draftYes (admin session, in practice)Yes (named admin/approver); project key can only reviewproduct_review only — no promote
Step 10: The project's loop builds itNo controlYes (establish the loop: admin; enable and point it: project key)No
Step 11 (optional): Export and run under HermesYesYes (named admin/approver)No

If something goes wrong ​

  • An approver's own key gets "Invalid API key" signing into the Workers panel or the Product canvas. The dashboard's sign-in check validates the key against an admin-only route, so it accepts an admin key but not an approver's. Use an admin key or session in the dashboard, and the approver's key against the API.
  • Creating the agent_solution answers 400 naming workspace. A worker declared workspace: "none" may not have an act step. Leave workspace out, as Step 3 does.
  • Compile answers 400 naming worker_repo_url. The source project has none; an admin sets it (see "Before you start"). Compile answers 409: either the solution is not ready, or this version is already compiled — the response carries its deployment_id.
  • Arming a trigger answers 403 naming an act step. A trigger may not target one; fire that step by hand (Step 8).
  • A project-key loop write answers 403. Either no admin has established a loop on the project yet, or the request names an admin-only limit. See Step 10.
  • propose-ready on a worker's draft answers worker_draft_requires_approver. Expected with a project key: only an admin or an approver linked to the source project can make a worker's draft ready (Step 9).
  • The worker cannot draft even though the deployment reads act_reversible. The tool is offered only to an episode fired on the act step itself, and only if your operator's WORKER_REVERSIBLE_CAPABILITIES names the capability the solution declared (Step 7).
  • promote answers 409 naming a measure. That is the policy working as declared — read the named measure (agreement rate, sample count, open escalations) and either generate more evidence (Steps 4-6) or revisit the floor you declared in Step 3.
  • An episode ends failed with cause no_evidence. An episode that changes nothing in the repository completes on what it recorded: an evidence note or escalation for the scan step, those or a recommendation for flag, and an act with an observed effect for draft. An episode that recorded none of them fails this way.

Keep going ​

  • User Guide: every route, field and guard this walkthrough used, in full, under the section names each step cites.
  • examples/agent-factory/: runnable scripts that exercise this same chain end to end against a live instance, including the Hermes harness Step 11 uses.
  • Chess walkthrough: if you don't yet have an enrolled project to be this worker's source project, start there.

Released under the MIT License.