@widgex/wgx 0.0.0-stage → 0.4.1

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.
Files changed (130) hide show
  1. package/LICENSE.txt +49 -0
  2. package/README.md +33 -2
  3. package/ThirdPartyNotices.txt +4802 -0
  4. package/package.json +29 -4
  5. package/python/XACTOR.md +146 -0
  6. package/python/browser_actor_host.py +456 -0
  7. package/python/cdp_page.py +363 -0
  8. package/python/harness_check.py +77 -0
  9. package/python/requirements.txt +1 -0
  10. package/python/test_line_endings.py +185 -0
  11. package/python/xactor/__init__.py +18 -0
  12. package/python/xactor/__main__.py +226 -0
  13. package/python/xactor/api.py +882 -0
  14. package/python/xactor/bridge.py +784 -0
  15. package/python/xactor/dom.py +117 -0
  16. package/python/xactor/man/add_card.md +89 -0
  17. package/python/xactor/man/agent-vs-fork.md +108 -0
  18. package/python/xactor/man/agent.md +333 -0
  19. package/python/xactor/man/ask_model.md +110 -0
  20. package/python/xactor/man/ask_reference.md +56 -0
  21. package/python/xactor/man/ask_user.md +103 -0
  22. package/python/xactor/man/attach_app.md +595 -0
  23. package/python/xactor/man/attach_ide.md +65 -0
  24. package/python/xactor/man/attach_ide_surface.md +31 -0
  25. package/python/xactor/man/await_user.md +214 -0
  26. package/python/xactor/man/bash.md +142 -0
  27. package/python/xactor/man/browser-driver.md +137 -0
  28. package/python/xactor/man/browser_activity.md +53 -0
  29. package/python/xactor/man/browser_attach.md +84 -0
  30. package/python/xactor/man/browser_close_tab.md +50 -0
  31. package/python/xactor/man/browser_detach.md +50 -0
  32. package/python/xactor/man/browser_exec.md +66 -0
  33. package/python/xactor/man/browser_focus_tab.md +47 -0
  34. package/python/xactor/man/browser_hybrid.md +47 -0
  35. package/python/xactor/man/browser_open.md +50 -0
  36. package/python/xactor/man/browser_open_tab.md +58 -0
  37. package/python/xactor/man/browser_plumbing.md +107 -0
  38. package/python/xactor/man/browser_screenshot.md +56 -0
  39. package/python/xactor/man/browser_snapshot.md +52 -0
  40. package/python/xactor/man/browser_tabs.md +67 -0
  41. package/python/xactor/man/bulletin.md +88 -0
  42. package/python/xactor/man/capture_page.md +54 -0
  43. package/python/xactor/man/cd.md +64 -0
  44. package/python/xactor/man/chess_move.md +53 -0
  45. package/python/xactor/man/children.md +89 -0
  46. package/python/xactor/man/close_task.md +14 -0
  47. package/python/xactor/man/compact.md +75 -0
  48. package/python/xactor/man/drive.md +140 -0
  49. package/python/xactor/man/drive_space.md +72 -0
  50. package/python/xactor/man/driving.md +168 -0
  51. package/python/xactor/man/edit_file.md +112 -0
  52. package/python/xactor/man/finding.md +194 -0
  53. package/python/xactor/man/fork_agent.md +45 -0
  54. package/python/xactor/man/glob.md +83 -0
  55. package/python/xactor/man/grep.md +115 -0
  56. package/python/xactor/man/harness.md +165 -0
  57. package/python/xactor/man/harness_check.md +70 -0
  58. package/python/xactor/man/harness_format.md +257 -0
  59. package/python/xactor/man/highlight.md +66 -0
  60. package/python/xactor/man/instruct.md +111 -0
  61. package/python/xactor/man/ls.md +63 -0
  62. package/python/xactor/man/man.md +86 -0
  63. package/python/xactor/man/merge_wiz.md +40 -0
  64. package/python/xactor/man/notebook.md +98 -0
  65. package/python/xactor/man/overview.md +89 -0
  66. package/python/xactor/man/permissions.md +137 -0
  67. package/python/xactor/man/phase.md +150 -0
  68. package/python/xactor/man/powershell.md +74 -0
  69. package/python/xactor/man/profiles.md +60 -0
  70. package/python/xactor/man/project_drive.md +49 -0
  71. package/python/xactor/man/read_bulletin.md +89 -0
  72. package/python/xactor/man/read_file.md +90 -0
  73. package/python/xactor/man/relay.md +216 -0
  74. package/python/xactor/man/reply.md +72 -0
  75. package/python/xactor/man/reply_message.md +78 -0
  76. package/python/xactor/man/report_bug.md +118 -0
  77. package/python/xactor/man/request_answer.md +142 -0
  78. package/python/xactor/man/request_merge_decisions.md +121 -0
  79. package/python/xactor/man/request_operator_action.md +152 -0
  80. package/python/xactor/man/request_review.md +122 -0
  81. package/python/xactor/man/request_user_action.md +126 -0
  82. package/python/xactor/man/return_card.md +71 -0
  83. package/python/xactor/man/run.md +99 -0
  84. package/python/xactor/man/sandbox.md +144 -0
  85. package/python/xactor/man/session_fork.md +74 -0
  86. package/python/xactor/man/session_harness.md +109 -0
  87. package/python/xactor/man/session_join.md +102 -0
  88. package/python/xactor/man/session_land.md +73 -0
  89. package/python/xactor/man/session_nudge.md +73 -0
  90. package/python/xactor/man/session_rest.md +73 -0
  91. package/python/xactor/man/session_scope.md +84 -0
  92. package/python/xactor/man/session_terminate.md +58 -0
  93. package/python/xactor/man/session_token.md +78 -0
  94. package/python/xactor/man/sessions.md +93 -0
  95. package/python/xactor/man/set_harness.md +15 -0
  96. package/python/xactor/man/spawn_swarm.md +80 -0
  97. package/python/xactor/man/submit_evidence.md +54 -0
  98. package/python/xactor/man/swarm_brief.md +30 -0
  99. package/python/xactor/man/task_status.md +82 -0
  100. package/python/xactor/man/tasks.md +47 -0
  101. package/python/xactor/man/todo_write.md +104 -0
  102. package/python/xactor/man/update_card.md +70 -0
  103. package/python/xactor/man/use_profile.md +90 -0
  104. package/python/xactor/man/use_widget.md +82 -0
  105. package/python/xactor/man/wait_for_task.md +105 -0
  106. package/python/xactor/man/widget-model.md +284 -0
  107. package/python/xactor/man/widgets.md +59 -0
  108. package/python/xactor/man/write_file.md +76 -0
  109. package/python/xactor/man/writing-a-widget.md +142 -0
  110. package/python/xactor/man/xylon.md +120 -0
  111. package/python/xactor/man/xylon_blame.md +80 -0
  112. package/python/xactor/man/xylon_read.md +131 -0
  113. package/python/xactor/man/xylon_search.md +125 -0
  114. package/python/xactor/manual.py +195 -0
  115. package/python/xactor/parent_watch.py +115 -0
  116. package/python/xactor/plan_mode.py +350 -0
  117. package/python/xactor/sandbox.py +856 -0
  118. package/python/xactor/seals.py +438 -0
  119. package/python/xactor/shell_hints.py +210 -0
  120. package/python/xactor/states/__init__.py +13 -0
  121. package/python/xactor/states/base.py +523 -0
  122. package/python/xactor/states/registry.py +274 -0
  123. package/python/xactor/stdlib_api.py +6345 -0
  124. package/python/xactor/widgets/__init__.py +1 -0
  125. package/python/xactor/widgets/github_tokens.py +314 -0
  126. package/python/xactor/widgets/google_aistudio.py +365 -0
  127. package/settings-schema.json +403 -0
  128. package/viewer/index.css +160 -0
  129. package/viewer/index.js +15 -0
  130. package/wgx.mjs +273753 -0
@@ -0,0 +1,73 @@
1
+ # api.session_rest(reason=None) -> dict
2
+ <!-- group: Sessions -->
3
+
4
+ Stop a harness re-serving its prompt. Not gated.
5
+
6
+ A harnessized session is handed the same prompt again at the start of every turn.
7
+ This is how that stops. The session stays alive and the harness binding stays
8
+ configured — a human can re-arm it from the Harness Manager without setting it up
9
+ again.
10
+
11
+ ## Options
12
+
13
+ - **`reason`** — a short line saying why you stopped. It is shown to the user in
14
+ the Harness Manager, and it is the only explanation they get. `"done"` tells
15
+ them nothing; `"the migration is merged, so there is nothing left to poll
16
+ for"` tells them whether to re-arm.
17
+
18
+ ## Returns
19
+
20
+ ```python
21
+ {"rested": True, "reason": str | None}
22
+ ```
23
+
24
+ **`{"rested": False}`** when the session is not a harness. It does not raise —
25
+ calling it from a normal session is a no-op that reports itself as one.
26
+
27
+ ## Examples
28
+
29
+ Ending the loop when the work is actually finished:
30
+
31
+ ```python
32
+ if not remaining:
33
+ await api.session_rest("all flaky tests are quarantined; nothing left to triage")
34
+ return "Done — I've stopped the harness."
35
+ ```
36
+
37
+ Stopping on a condition you cannot fix unattended:
38
+
39
+ ```python
40
+ if migration_needs_a_decision:
41
+ await api.session_rest(
42
+ "the schema change needs a call on whether to drop the legacy column; "
43
+ "I can't ask while unattended"
44
+ )
45
+ return "Rested — this needs a human decision before it can continue."
46
+ ```
47
+
48
+ ## Getting it wrong
49
+
50
+ **Using `session_terminate` instead.** Terminating destroys the session: the
51
+ transcript stops being a live thing you can re-arm, and the user has to build
52
+ the harness again from scratch. Resting is the reversible version, and it is
53
+ almost always the one you want.
54
+
55
+ **Never calling it.** A harness with no exit condition runs until it hits its
56
+ token or cycle limit and stops there, unfinished, waiting for a person. If your
57
+ prompt has a notion of "finished" that its machine cannot see, you are the only
58
+ thing that can notice.
59
+
60
+ **Resting because one turn had nothing to do.** A poll that finds no work is a
61
+ normal turn, not a reason to stop. Rest when the *task* is over, not when this
62
+ pass was quiet.
63
+
64
+ **Expecting it to end the turn.** It does not. Code after this call still runs
65
+ and your reply is still delivered. It changes what happens *next* turn.
66
+
67
+ **Calling it from a normal session and not checking the result.** You get
68
+ `{"rested": False}` and no error. If you thought you were a harness, you were
69
+ wrong, and nothing told you but the return value.
70
+
71
+ ## See also
72
+
73
+ `session_harness`, `harness`, `session_terminate`, `sessions`
@@ -0,0 +1,84 @@
1
+ # api.session_scope(scope) -> dict
2
+ <!-- also: scope, rename, session_name, what-am-i-working-on -->
3
+ <!-- group: Talking to the user -->
4
+
5
+ Say what part of the project this session is working on. Not gated.
6
+
7
+ One short phrase, in the words somebody **managing** the work would use — "the
8
+ sign-in flow", "release notarisation", "the kanban board". It becomes the top
9
+ line of this session's card on the board, its row in the sidebar, its editor tab,
10
+ and any queue row it raises.
11
+
12
+ ## You usually do not need to call this
13
+
14
+ A session is already named from whatever it was first asked for, and that is
15
+ almost always right — a person wrote it, so it is in their language by
16
+ construction.
17
+
18
+ Call this when the work turned out to be about something else. A request to
19
+ "have a look at the build" that became notarisation is still shown as *"Have a
20
+ look at the build"* until you say otherwise, and somebody scanning six agents
21
+ will pick the wrong one. That is the whole use: one call, when the name has
22
+ stopped being true.
23
+
24
+ There is nothing to call at the start of a task, and nothing to repeat each turn.
25
+
26
+ ## What this is not
27
+
28
+ **Not what you are doing right now.** That comes from the task list you already
29
+ keep with `todo_write` — the `activeForm` of whatever is `in_progress`. The two
30
+ are different questions with different lifetimes, which is why they are two lines
31
+ and not one string, and why only one of them needs a call.
32
+
33
+ **Not a file, a directory, a branch or a symbol.** `panel/board` and
34
+ `ClaudeSessionService.ts` are accurate and useless here: whoever reads this is
35
+ deciding which agent to look at next, not editing it. A branch is worse than
36
+ useless — every session in a worktree shares one, so it names the checkout and
37
+ tells you nothing about the agent.
38
+
39
+ **Not a rename over the user's head.** If they have renamed this session, theirs
40
+ is shown. Yours is kept and appears if they clear it.
41
+
42
+ ## Options
43
+
44
+ - **`scope`** — the phrase. Long ones are cut at the first clause and then at 44
45
+ characters, so lead with the subject rather than the reason: "Release
46
+ notarisation" beats "Looking into why notarisation fails, which seems to be
47
+ about the keychain".
48
+
49
+ ## Returns
50
+
51
+ ```python
52
+ {"scope": str | None} # what was recorded
53
+ ```
54
+
55
+ ## Examples
56
+
57
+ The case it exists for — the request and the work turned out to be different
58
+ things:
59
+
60
+ ```python
61
+ # Started as "have a look at the build please".
62
+ await api.session_scope("Release notarisation")
63
+ ```
64
+
65
+ Correcting a scope that has drifted, mid-session:
66
+
67
+ ```python
68
+ await api.session_scope("Keychain access under launchd")
69
+ ```
70
+
71
+ ## Getting it wrong
72
+
73
+ **Calling it every turn.** It is a standing claim, not a status. It persists
74
+ across a window reload precisely so it does not need restating.
75
+
76
+ **Naming the code.** "src/ide-widgets/panel" is the register this exists to
77
+ avoid. If the phrase contains a slash or a file extension, it is wrong.
78
+
79
+ **Using it to report progress.** "Finished the parser, now on tests" is two
80
+ statements and neither is a scope. Progress is `todo_write`.
81
+
82
+ ## See also
83
+
84
+ `todo_write`, `request_review`, `agent`, `bulletin`
@@ -0,0 +1,58 @@
1
+ # api.session_terminate(session_id) -> dict
2
+ <!-- group: Sessions -->
3
+
4
+ Stop this session or one of its descendants. Gated.
5
+
6
+ ## Options
7
+
8
+ - **`session_id`** — the session to stop. Yourself, or something you spawned
9
+ (or something *it* spawned, to any depth).
10
+
11
+ Anything outside your own subtree is refused. You cannot terminate your parent,
12
+ a sibling, or an unrelated session.
13
+
14
+ ## Returns
15
+
16
+ ```python
17
+ {"success": True}
18
+ ```
19
+
20
+ ## Examples
21
+
22
+ Cleaning up a child that is no longer needed:
23
+
24
+ ```python
25
+ r = await api.session_join(child_id, timeout=600)
26
+ if r["status"] == "timeout":
27
+ await api.session_terminate(child_id)
28
+ return "That was taking too long; I stopped it."
29
+ return r["lastMessage"]
30
+ ```
31
+
32
+ Abandoning a fork after reading its answer:
33
+
34
+ ```python
35
+ r = await api.session_join(fork_id, timeout=1800)
36
+ await api.session_terminate(fork_id)
37
+ return r.get("lastMessage")
38
+ ```
39
+
40
+ ## Getting it wrong
41
+
42
+ **Terminating yourself to "finish".** Ending your turn is how you finish. This
43
+ call destroys the session, so anything the user says next has nowhere to go. If
44
+ you are a harness and want to stop looping, that is `api.session_rest`, which
45
+ leaves the session alive and re-armable.
46
+
47
+ **Terminating a child you have not joined.** Whatever it was going to report is
48
+ lost. Join first, read the result, then terminate.
49
+
50
+ **Expecting it to undo the child's work.** Files it wrote stay written.
51
+ Termination stops the conversation, not its effects. To see what it changed,
52
+ read its node with `xylon_get_diff`.
53
+
54
+ **Using it on a sibling.** Refused. The subtree rule has no override.
55
+
56
+ ## See also
57
+
58
+ `session_rest`, `session_join`, `sessions`, `xylon_read`, `permissions`
@@ -0,0 +1,78 @@
1
+ # Session tokens: who is calling a tool
2
+
3
+ <!-- group: Concepts -->
4
+ <!-- also: WIDGEX_SESSION_TOKEN, harness identity, identity token, actor, segregation of duties, verify session -->
5
+
6
+ Every command a session runs through `api.run`, `api.bash` or `api.powershell`
7
+ has `WIDGEX_SESSION_TOKEN` in its environment. A tool you write — a ledger CLI,
8
+ a deploy script, a check — can hand that token back to WidgeX and be told which
9
+ session is calling, and which harness and state it is in **right now**.
10
+
11
+ It is optional at both ends. A tool that never looks at it is unaffected.
12
+
13
+ ## Why it exists
14
+
15
+ A tool that enforces who may do what used to learn who was calling from flags
16
+ like `--actor` and `--role`, which the agent writes itself. That catches
17
+ mistakes, not an agent that claims to be someone else. A token is not
18
+ something an agent can type for another session: it is signed with a key that
19
+ exists only in the WidgeX window's memory, and each session only ever sees its
20
+ own.
21
+
22
+ ## Checking one
23
+
24
+ Each WidgeX window writes where to ask into
25
+ `~/.widgex/harness-identity/<issuer>.json`. The issuer is the token's second
26
+ dot-separated field. Post the token to that file's `url`:
27
+
28
+ ```python
29
+ import json, os, pathlib, urllib.request
30
+
31
+ def who_is_calling():
32
+ token = os.environ.get("WIDGEX_SESSION_TOKEN")
33
+ if not token:
34
+ return None # not run from a WidgeX session
35
+ issuer = token.split(".")[1]
36
+ info = json.loads((pathlib.Path.home() / ".widgex" / "harness-identity" / f"{issuer}.json").read_text())
37
+ req = urllib.request.Request(info["url"], data=json.dumps({"token": token}).encode(),
38
+ headers={"content-type": "application/json"})
39
+ with urllib.request.urlopen(req, timeout=5) as r:
40
+ return json.load(r)
41
+ ```
42
+
43
+ A valid token answers:
44
+
45
+ ```json
46
+ {"valid": true, "verified_at": 1792890000000,
47
+ "session_id": "95791dee-…", "node": "proj/repo/issue/session4", "label": "architect",
48
+ "harness": "revenue-desk", "harness_kind": "overlay", "state": "propose",
49
+ "parent_session_id": "b3588d9d-…"}
50
+ ```
51
+
52
+ `harness`, `harness_kind` and `state` are `null` for a session not running one.
53
+ Anything else answers `{"valid": false, "reason": "..."}`. A tool enforcing a
54
+ rule should refuse on that, not fall back to a flag.
55
+
56
+ Read the url from the file, never from an environment variable: the agent
57
+ chooses its commands' environment, and a url it set could point anywhere.
58
+
59
+ ## What it proves
60
+
61
+ - **Which session's commands the call came from.** Session A cannot pass
62
+ itself off as session B.
63
+ - **Its harness and state when you asked**, not when the command started.
64
+
65
+ What it does not prove:
66
+
67
+ - That the session *should* be doing this. Your tool's rules still decide that.
68
+ - Anything against code that sets out to subvert WidgeX on the same account,
69
+ for instance by rewriting the discovery file. The token makes lying mean
70
+ doing that, instead of typing a different `--actor`.
71
+
72
+ ## Lifetime
73
+
74
+ A token is valid for as long as its session exists and the window that issued
75
+ it is open. After a window reload the old tokens stop verifying
76
+ (`"issued by another WidgeX window, or by this one before it reloaded"`), and
77
+ every shell started from then on carries a new one. Sessions running on a
78
+ remote host carry no token.
@@ -0,0 +1,93 @@
1
+ # Sessions
2
+ <!-- group: Concepts -->
3
+
4
+ What a session is, how children relate to parents, and which call makes which.
5
+
6
+ A session is one conversation with one model, running in one working directory,
7
+ occupying one node in a xylon tree. You are a session. Everything you can spawn
8
+ is either a *child session* — its own conversation, its own node, running
9
+ independently — or a *subagent*, which is neither.
10
+
11
+ ## The shapes
12
+
13
+ | Call | Repo | Conversation | Node | Runs |
14
+ |---|---|---|---|---|
15
+ | `agent` | **same** working dir | fresh, briefed by `summary` | child of yours | independently |
16
+ | `session_fork` | **cloned** copy | **copy of yours** | child of yours | independently |
17
+ | `agent` | same | fresh, briefed by `prompt` | none | **blocks you** |
18
+ | `session_harness` | this session | this one | this one | re-serves you |
19
+
20
+ Only `agent` blocks. `extend` and `fork` return as soon as the child exists;
21
+ you wait for one with `session_join` or you do not wait at all.
22
+
23
+ ## Talking to a child
24
+
25
+ At birth a child gets exactly three things:
26
+
27
+ 1. its `prompt`,
28
+ 2. whatever `aux_context` file you attached, inlined into that prompt,
29
+ 3. `summary`, if you passed one (`agent` only).
30
+
31
+ It can also read your side of the tree with `xylon_read_messages('..')` — that
32
+ is the fallback a child is told about when you give it no `summary` — and you
33
+ can read its result with `session_join`.
34
+
35
+ After that, there is a channel: see `relay`. `api.relay_connect(node)` asks any
36
+ session for one and it may decline; `force=True` takes one on a **direct child**
37
+ without asking, and turns your `relay_send` into that child's prompt, blocking
38
+ until it answers. That is how you steer a child that has misread the job,
39
+ instead of waiting for it to finish being wrong.
40
+
41
+ What every agent in this codebase is *currently* working on is a separate
42
+ thing, and is on the `bulletin`.
43
+
44
+ ## Waiting, and not waiting
45
+
46
+ ```python
47
+ child = await api.agent(
48
+ "Port the auth tests to the new fixture API and report what broke.",
49
+ summary="I'm mid-refactor of src/auth; the fixture API changed shape today.",
50
+ )
51
+ r = await api.session_join(child["sessionId"], timeout=600)
52
+ if r["status"] != "done":
53
+ return {"child": child["sessionId"], "status": r["status"], "why": r.get("error")}
54
+ return r["lastMessage"]
55
+ ```
56
+
57
+ Fire and forget is legitimate — a child that will take an hour should not hold
58
+ your turn open. Report the session id and move on.
59
+
60
+ ## Terminating
61
+
62
+ `session_terminate` works on **yourself or a descendant only**. A parent, a
63
+ sibling, or an unrelated session is refused. There is no way to kill something
64
+ that did not come from you.
65
+
66
+ ## Permission
67
+
68
+ `session_fork`, `agent`, `session_terminate` and `session_harness` all prompt
69
+ the user. `session_join` and `session_rest` never
70
+ do — waiting and stopping need no approval.
71
+
72
+ ## Getting it wrong
73
+
74
+ **Spawning a child to do something you could just do.** A child costs a whole
75
+ new context, and the answer has to travel back through `session_join` as a
76
+ single string. For work you want the answer from, `agent` is cheaper and
77
+ returns directly.
78
+
79
+ **Forking when you meant to extend.** A fork clones the repository. Two
80
+ sessions then edit two different copies of the same files, and nothing merges
81
+ them. Fork when the work is speculative or must not touch the user's tree;
82
+ extend when it is the same tree at a different scope.
83
+
84
+ **Assuming a child inherits the conversation.** Only `session_fork` copies the
85
+ history. `agent` gives the child nothing but what you wrote.
86
+
87
+ **Reading `lastMessage` without checking `status`.** A failed child returns an
88
+ empty `lastMessage`, which reads exactly like a child that had nothing to say.
89
+
90
+ ## See also
91
+
92
+ `agent-vs-fork`, `agent`, `session_fork`, `session_join`,
93
+ `session_terminate`, `agent`, `xylon`, `harness`
@@ -0,0 +1,15 @@
1
+ # api.set_harness(agent, harness, brief=None) -> dict
2
+
3
+ <!-- group: Sessions -->
4
+
5
+ Put an agent under a harness — a state machine that drives it state by state.
6
+
7
+ `agent` is an id from `api.tasks()`. `harness` is a harness name from
8
+ `.widgex/harnesses/`. `brief` is the standing instruction the harness re-serves
9
+ above every state; it defaults to what the agent was first asked.
10
+
11
+ If the agent is busy, the harness arms and starts when its current turn ends.
12
+
13
+ ## Returns
14
+
15
+ `{"agent": str, "harness": str, "started": bool}`
@@ -0,0 +1,80 @@
1
+ # api.spawn_swarm(shared_prompt, given, model=None, workflow=None, browser=False) -> dict
2
+
3
+ <!-- also: swarm, agent_swarm -->
4
+ <!-- group: Sessions -->
5
+
6
+ Start an agent swarm: one autonomous agent per prompt in `given`, all working on
7
+ the same kind of problem at once. **Always asks the user**, in every mode —
8
+ Autonomous included — because the card is the only moment a person sees how
9
+ many agents are about to start.
10
+
11
+ Each agent:
12
+
13
+ - is sent your `shared_prompt` first, then a divider, then its own `given` prompt;
14
+ - gets a worktree and a branch of its own, cut from **your task's branch** — the
15
+ worktrees are made in parallel, so twenty cost little more than one;
16
+ - runs Autonomous, on `model` (or yours), under `workflow` if you name a harness.
17
+
18
+ Together they are one **Agent Swarm** row under your task, with counters for
19
+ idle, blocked, running and finished. The user opens the row to read every
20
+ agent's briefs in one stream, message all of them at once, and answer their
21
+ permission requests and questions; and merges, halts, wakes or aborts them from
22
+ the row's menu. When the task is shelved or done, every agent's work is
23
+ committed and its worktree removed.
24
+
25
+ Returns at once once approved; nothing waits on the agents. The approval itself
26
+ waits on a person, so give the exec a generous `timeout_seconds` (600 or more).
27
+ Calling again with the same prompts while that swarm is live hands back the one
28
+ already running (`already: True`) rather than starting it twice.
29
+
30
+ ## Arguments
31
+
32
+ - **`shared_prompt`** — a path to a file, relative to the cwd, or the prompt
33
+ text itself. A single word with an extension that names no file is refused,
34
+ rather than sent to twenty agents as their brief.
35
+ - **`given`** — a list, one entry per agent, each a path or text like
36
+ `shared_prompt`. At most 50.
37
+ - **`model`** — e.g. `'claude-sonnet-5-5'`. `'Claude-Sonnet-5.5'` is accepted as
38
+ the same id. An unknown one is refused with the list, and nothing is made.
39
+ - **`workflow`** — a harness name or a path to its `.json`. It must have a
40
+ terminal state, or the agents could never stop.
41
+ - **`browser`** — whether the agents may use the browser. `False` refuses
42
+ `drive`, `use_widget` and the `browser_*` calls to them.
43
+
44
+ ## Returns
45
+
46
+ ```python
47
+ {"swarm": "swarm-…", "task": "issue-12", "branch": "widgex/…",
48
+ "members": [{"index": 1, "session": "session-40", "node": "…",
49
+ "branch": "widgex/…", "label": "#1 …"}],
50
+ "failed": [{"index": 3, "label": "#3 …", "error": "…"}]}
51
+ ```
52
+
53
+ A member whose worktree could not be made is in `failed`; the rest still start.
54
+
55
+ ## What the agents are told
56
+
57
+ That they are one of N, that the others are on other branches, to report with
58
+ `api.swarm_brief(text)` as they go, and to commit and call `api.request_review`
59
+ when done — which marks them finished on the row instead of filing an inbox row.
60
+
61
+ ## Examples
62
+
63
+ ```python
64
+ files = [f"tasks/{n}.md" for n in range(1, 9)]
65
+ r = await api.spawn_swarm("tasks/shared.md", files, model="claude-sonnet-5-5")
66
+ return f"{len(r['members'])} agents started, {len(r['failed'])} failed"
67
+ ```
68
+
69
+ Prompts as text:
70
+
71
+ ```python
72
+ modules = ["parser", "lexer", "printer"]
73
+ await api.spawn_swarm(
74
+ "Add property-based tests to one module. Keep the public API unchanged.",
75
+ [f"The module is src/{m}.ts." for m in modules],
76
+ )
77
+ ```
78
+
79
+ Run it from an agent in a task, not from the main checkout — the agents are cut
80
+ from the task's branch, and the Coordinator has none.
@@ -0,0 +1,54 @@
1
+ # api.submit_evidence(id, note, files=None) -> dict
2
+ <!-- group: Sessions -->
3
+
4
+ Attach one required artifact to the harness state you are in. Not gated.
5
+
6
+ Some harness states are **evidence gates**: they cannot be left until every
7
+ required artifact has been submitted, no matter what you answer in the
8
+ transition block. The prompt for such a state lists what is still owed, by id.
9
+ This is how you hand it over.
10
+
11
+ Answering the transition questions does not move a harness that still owes
12
+ evidence. That is deliberate — an agent that believes it has finished is exactly
13
+ the one whose word is worth least, and the gate exists so that belief is not the
14
+ thing being tested.
15
+
16
+ ## Options
17
+
18
+ - **`id`** — the artifact id as written in the state's prompt: lowercase, and
19
+ one of the ids that state actually requires. Anything else raises, and the
20
+ message names the ids that were expected. You cannot invent one.
21
+ - **`note`** — the evidence itself. The command you ran and what it printed, not
22
+ a claim that you ran it. An empty note is refused, because a submission that
23
+ says nothing is indistinguishable from not having done the work.
24
+ - **`files`** — optional paths backing the claim.
25
+
26
+ Re-submitting the same id during the same visit to a state **replaces** your
27
+ earlier note rather than adding to it, so re-running a failing test and
28
+ submitting again is the normal way to correct the record.
29
+
30
+ ## Returns
31
+
32
+ ```python
33
+ {"submitted": True, "still_missing": ["diff", "visual"], "state": "evidence"}
34
+ ```
35
+
36
+ `still_missing` is what the gate is *now* waiting for. An empty list means the
37
+ gate is satisfied and the next transition you answer true can move.
38
+
39
+ ## Raises
40
+
41
+ `ValueError` when the session is not running under a harness, when the state
42
+ collects no evidence, or when `id` is not one of that state's artifacts.
43
+
44
+ ## Freshness
45
+
46
+ Submissions are scoped to the current **visit** to the state. If a review sends
47
+ work back and the run later re-enters the gate, last round's proof does not
48
+ count and every required artifact must be submitted again — which is the whole
49
+ point of sending it back.
50
+
51
+ ## See also
52
+
53
+ - `request_review` — say a whole piece of work is ready for a person to look at.
54
+ - `session_rest` — stop, when another cycle would add nothing.
@@ -0,0 +1,30 @@
1
+ # api.swarm_brief(text) -> dict
2
+
3
+ <!-- group: Sessions -->
4
+
5
+ Report progress to your agent swarm's stream. Never asks. Only for an agent
6
+ started by `api.spawn_swarm`; anyone else is refused.
7
+
8
+ The user reads every agent's briefs side by side in one stream, so a brief is
9
+ one or two sentences about **what changed** — not what you are about to try,
10
+ and not a log. Write one when you have started, at each milestone, and before
11
+ you stop. A turn that ends without one has its last message posted in its
12
+ place, which is rarely as clear as one you wrote.
13
+
14
+ Finishing is not a brief: commit your work and call `api.request_review(claim)`.
15
+ That marks you finished on the swarm's row.
16
+
17
+ Messages the user sends to the whole swarm arrive in your tool results or as a
18
+ prompt. Answer them in your reply — the reply is shown in the stream.
19
+
20
+ ## Returns
21
+
22
+ ```python
23
+ {"ok": True, "swarm": "swarm-…"}
24
+ ```
25
+
26
+ ## Example
27
+
28
+ ```python
29
+ await api.swarm_brief("Parser tests in: 14 properties, 2 found real bugs (fixed, see commit).")
30
+ ```
@@ -0,0 +1,82 @@
1
+ # api.task_status(status, detail=None) -> dict
2
+
3
+ <!-- group: Sessions -->
4
+
5
+ Say where your task has got to. Only the task's lead may.
6
+
7
+ The one thing on the user's board that is **told** rather than read.
8
+
9
+ Everything else a card shows is derived from your session: which lane it is in
10
+ comes from your permission mode and whether you have asked anybody to look at
11
+ something, the badge comes from your status, the branch comes from your Issue.
12
+ That works for every question a session can be *observed* to answer.
13
+
14
+ "How is this going" is not one of them. A session three files into a refactor
15
+ and a session stuck on the second look identical from outside — same status,
16
+ same lane, same branch — and you are the only thing in the system that knows
17
+ which one you are.
18
+
19
+ ## Who may call it
20
+
21
+ The task's **lead**: the first agent spun up in it, which is the one answerable
22
+ for the task and whose state the task already shows. `api.tasks()` reports
23
+ `lead` on every task, so you can check rather than assume.
24
+
25
+ An aux agent is refused, and the refusal names the lead. That is not a
26
+ formality — a task is one working copy with one agent answerable for it and any
27
+ number of agents helping, so a status any of them could write would be the group
28
+ talking over the one that answers for it. Report to your lead with
29
+ `api.relay_send` and let it speak for the task.
30
+
31
+ Outside a task it is refused too. The main checkout is the folder the user has
32
+ open; nobody is answerable for it, so a status there would describe the window
33
+ rather than any piece of work. Work that wants a status wants a task:
34
+ `api.agent(raise_=True, title=...)`.
35
+
36
+ ## Options
37
+
38
+ - **`status`** — where the work is, in a few words. *"reading the importer"*,
39
+ *"blocked on the schema"*, *"landing it"*. One line, up to 60 characters,
40
+ because it is drawn on a card about 200px wide. Longer is refused rather than
41
+ silently cut, so you choose the words instead of a slice choosing them.
42
+ - **`detail`** — one sentence more, when the label cannot carry it. Shown when
43
+ somebody hovers the card. Up to 400 characters.
44
+
45
+ ## Returns
46
+
47
+ ```python
48
+ {"task": str, "status": str, "detail": str | None, "at": int}
49
+ ```
50
+
51
+ `task` is the id it was recorded against, and `status` is what was stored after
52
+ being collapsed to a single line.
53
+
54
+ ## When to call it
55
+
56
+ When the answer changes. You have understood the problem; you have started
57
+ building; you are blocked on something; you are landing.
58
+
59
+ It is a **position, not a log**. A line that changes every thirty seconds is a
60
+ line nobody reads, and nothing here is a progress feed — the transcript already
61
+ holds what happened, and `api.todo_write` already holds your own checklist.
62
+
63
+ ## Getting it wrong
64
+
65
+ **Using it to hand work back.** It is not `api.phase_complete`. This says
66
+ where the work is; that says the work is done and asks a human to judge it.
67
+ Writing "finished" here hands nothing back, files nothing on anybody's queue,
68
+ and nobody is asked to look.
69
+
70
+ **Writing it per tool call.** See above: a position, not a log.
71
+
72
+ **Calling it as an aux agent and treating the refusal as a bug.** It names your
73
+ lead because relaying to the lead is the thing to do instead.
74
+
75
+ **Expecting it to move a card between lanes.** Lanes are claims about the world
76
+ and are derived — see `update_card` for the one call that places a card, and
77
+ note that it is for an orchestrator pointing a card at a subagent, which is a
78
+ different job from this.
79
+
80
+ ## See also
81
+
82
+ `tasks`, `update_card`, `phase_complete`, `relay`