Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3f2d28e
feat(friend): trigger contracts for manual, bot and call triggers
IsraelAraujo70 Nov 2, 2026
2bb876d
refactor(friend): clients read workflow triggers as a map keyed by id
IsraelAraujo70 Nov 2, 2026
db9c103
refactor(friend): server reads workflow triggers as a map keyed by id
IsraelAraujo70 Nov 2, 2026
0a79d23
Merge branch 'feat/workflows-triggers-leaf-n1b' into feat/workflows-t…
IsraelAraujo70 Nov 2, 2026
a13a3d8
Merge commit '141f3fac3' into feat/workflows-triggers
IsraelAraujo70 Nov 2, 2026
b93dc7e
feat(friend): journal keeps trigger switches, last events and test de…
IsraelAraujo70 Nov 2, 2026
4f914a1
Merge branch 'feat/workflows-triggers-leaf-n3a' into feat/workflows-t…
IsraelAraujo70 Nov 2, 2026
3b511b1
feat(friend): compiler checks the new trigger kinds
IsraelAraujo70 Nov 2, 2026
0469d6d
Merge branch 'feat/workflows-triggers-leaf-n2' into feat/workflows-tr…
IsraelAraujo70 Nov 2, 2026
2c6f3e1
refactor(friend): compiler imports stay below the module docblock
IsraelAraujo70 Nov 2, 2026
c425c8f
feat(friend): webhook triggers receive the request body and headers
IsraelAraujo70 Nov 2, 2026
f1c80e4
Merge branch 'feat/workflows-triggers-leaf-n4a' into feat/workflows-t…
IsraelAraujo70 Nov 2, 2026
215f2a6
refactor(friend): webhook trigger imports stay below the module docblock
IsraelAraujo70 Nov 2, 2026
f0ee354
feat(friend): one trigger policy decides which fires start a run
IsraelAraujo70 Nov 2, 2026
7d0a2ae
Merge branch 'feat/workflows-triggers-leaf-n3b' into feat/workflows-t…
IsraelAraujo70 Nov 2, 2026
055599b
feat(friend): each workflow trigger has its own switch
IsraelAraujo70 Nov 2, 2026
64384b1
refactor(friend): the trigger policy reads the caller from the payload
IsraelAraujo70 Nov 2, 2026
492a8f6
Merge branch 'feat/workflows-triggers-leaf-n3c' into feat/workflows-t…
IsraelAraujo70 Nov 2, 2026
7a6d271
refactor(friend): bot entries read the documents they already load
IsraelAraujo70 Nov 2, 2026
366bba6
feat(friend): workflow triggers are entry nodes on the canvas model
IsraelAraujo70 Nov 2, 2026
aa1c97b
Merge branch 'feat/workflows-triggers-leaf-n8' into feat/workflows-tr…
IsraelAraujo70 Nov 2, 2026
cacdbe9
fix(friend): the node palette leaves trigger styles out
IsraelAraujo70 Nov 2, 2026
dd8751f
feat(friend): webhook triggers listen for a test event
IsraelAraujo70 Nov 2, 2026
2f9e889
fix(friend): a capture wakes only the listeners present when it ran
IsraelAraujo70 Nov 2, 2026
f6fb250
feat(friend): trigger sources honor each trigger's switch
IsraelAraujo70 Nov 2, 2026
fe75b03
feat(friend): Run now goes through the manual trigger and test runs r…
IsraelAraujo70 Nov 2, 2026
ab5eac3
Merge branch 'feat/workflows-triggers-leaf-n6' into feat/workflows-tr…
IsraelAraujo70 Nov 2, 2026
f8ea691
feat(friend): library workflows declare a manual trigger
IsraelAraujo70 Nov 2, 2026
4ca8455
Merge branch 'feat/workflows-triggers-leaf-n12a' into feat/workflows-…
IsraelAraujo70 Nov 2, 2026
c3bb1f6
feat(friend): bots run workflows through their bot trigger
IsraelAraujo70 Nov 2, 2026
70d01db
Merge branch 'feat/workflows-triggers-leaf-n7' into feat/workflows-tr…
IsraelAraujo70 Nov 2, 2026
36bf071
test(friend): bot trigger refusals, the list entry and trigger switch…
IsraelAraujo70 Nov 2, 2026
9b4f28a
feat(friend): the web canvas draws trigger nodes and their entry edges
IsraelAraujo70 Nov 2, 2026
b1ddf73
feat(friend): the workflow editor edits, switches and tests triggers
IsraelAraujo70 Nov 2, 2026
7c11042
fix(friend): trigger lookups in the editor ignore inherited keys
IsraelAraujo70 Nov 2, 2026
3dfb15f
feat(friend): run pages show their trigger and Run now follows the ma…
IsraelAraujo70 Nov 2, 2026
2ea1880
feat(friend): Android shows and switches workflow triggers
IsraelAraujo70 Nov 2, 2026
3ca87dd
docs(friend): workflow triggers in FORK.md and the design doc
IsraelAraujo70 Nov 2, 2026
62fef9d
Merge branch 'feat/workflows-triggers-leaf-n11' into feat/workflows-t…
IsraelAraujo70 Nov 2, 2026
942827b
fix(friend): trigger data in agent prompts is framed as untrusted
IsraelAraujo70 Nov 2, 2026
517ef7e
Merge branch 'feat/workflows-triggers-leaf-n15' into feat/workflows-t…
IsraelAraujo70 Nov 2, 2026
20ee506
fix(friend): a webhook listen ends when the panel stops listening
IsraelAraujo70 Nov 2, 2026
0b7b19a
fix(friend): new trigger reads are scoped reads; Android switch compiles
IsraelAraujo70 Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,9 @@ On the host, under the T3 home (`~/.t3`):
- `workflows/definitions/` is a git repository with one `<id>.workflow.yaml` per workflow. Every save
is a commit; the server ingests it at startup and picks up hand edits. Library templates ship in
[`apps/server/src/bots/workflows/library/`](apps/server/src/bots/workflows/library/).
- `userdata/bots.sqlite` holds each run's journal (`workflow_facts`) and the run list (`workflow_runs`).
- `userdata/bots.sqlite` holds each run's journal (`workflow_facts`), the run list (`workflow_runs`),
each trigger's last event (`workflow_trigger_samples`) and the deliveries captured as test events
(`workflow_test_deliveries`).
- `workflows/runs/<runId>/` holds the receipts of each command unit (`started.json`, `exit.json`),
the step directories, build artifacts, and the run's worktrees until `retain` lets them go.

Expand Down Expand Up @@ -370,8 +372,19 @@ of the Android Bots tab. It lists the bots host's workflows with their last run.

- **Create one.** "From template" copies a ready routine under a new name; "New workflow" starts
blank. A new workflow starts turned off.
- **Run it.** Turn it on, then press "Run now" and fill in the parameters, or keep the defaults.
While it is on, its triggers (a schedule, a new upstream tag) start runs by themselves.
- **Run it.** Press "Run now" and fill in the parameters, or keep the defaults. Run now needs a
manual trigger in the workflow and works even while the workflow is off, so you can try it before
arming anything; a suspended workflow stays blocked until you resume it.
- **Triggers.** Each trigger is a node on the left of the canvas: manual (Run now), bot, called by
another workflow, webhook, schedule, new upstream tag, PR merged, thread finished, workflow
failed. Every trigger starts the whole graph. The workflow switch arms them all; each trigger also
has its own switch in its inspector and on Android. A trigger node shows when it last fired.
- **Webhooks.** A webhook trigger's inspector shows its URL with a copy button and the last event
it received. "Listen for test event" waits two minutes for the next request: that request is
captured, starts no run, and fills the event shown there and the list of `{{ trigger.* }}` paths
you can copy into prompts and params. "Run with this event" then runs the workflow with it. A test
delivery uses up its replay key, so the sender's redelivery of that same request is ignored.
Values from a trigger reach agent prompts marked as untrusted data.
- **Follow a run.** The run page shows each step with its status, cost and the thread its agent
worked in. A failed step can be retried from there.
- **Approve a gate.** A run that needs you stops at a gate and shows the question with its options
Expand All @@ -380,9 +393,11 @@ of the Android Bots tab. It lists the bots host's workflows with their last run.
- **Read the result.** A run ends ready for review (with a draft pull request), with no gain,
blocked, or failed. Merging into `friend/main` stays your step.

Any bot can run, cancel or turn on a workflow when you ask in your own message in its conversation,
or when you allow it to manage bots in Settings → Bots. Messages from another bot, scheduled tasks
and workflow steps do not count as your request.
A bot runs a workflow only through the workflow's bot trigger, whose description tells bots when to
use it and whose optional list says which bots may. On top of that, a bot runs, cancels or turns on
a workflow only when you ask in your own message in its conversation, or when you allow it to manage
bots in Settings → Bots. Messages from another bot, scheduled tasks and workflow steps do not count
as your request.

Each workflow is a file in `~/.t3/workflows/definitions` on the bots host. Saving in the editor
commits it there, and a hand edit of the file shows up in the app. Two templates are not ready to run
Expand Down
58 changes: 48 additions & 10 deletions apps/mobile/src/bots/workflows/WorkflowDetailScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as Clipboard from "expo-clipboard";
import { relativeTime } from "../../lib/time";
import { useAtomValue } from "@effect/atom-react";
import * as Schema from "effect/Schema";
import { useState } from "react";
Expand Down Expand Up @@ -98,12 +100,14 @@ function DetailContent({
{document.definition && (
<>
<View className="flex-row flex-wrap gap-2">
<ControlPill
label="Run now"
variant="primary"
disabled={!canRun || !document.valid}
onPress={() => setShowRun(true)}
/>
{document.triggers.some((trigger) => trigger.kind === "manual") && (
<ControlPill
label="Run now"
variant="primary"
disabled={!canRun || !document.valid}
onPress={() => setShowRun(true)}
/>
)}
{document.valid && (
<ControlPill
label="Edit"
Expand All @@ -115,10 +119,44 @@ function DetailContent({
</View>
<WorkflowGraph model={toCanvasModel(document.definition)} />
<ListSection title="Triggers">
{(document.definition.triggers ?? []).map((trigger) => (
<Text key={trigger.id}>
{trigger.id} · {trigger.kind}
</Text>
{document.triggers.map((trigger) => (
<View key={trigger.id} className="gap-2 p-3">
<Text>{trigger.title ?? trigger.id}</Text>
<Text>{trigger.label}</Text>
{trigger.kind !== "manual" && (
<Switch
accessibilityLabel={`Enable ${trigger.title ?? trigger.id}`}
value={trigger.enabled !== false}
disabled={pending || !canEnable}
onValueChange={async (enabled) => {
setPending(true);
await enable({
environmentId,
input: {
workflowId,
triggerId: trigger.id,
enabled,
clientRequestId: Crypto.randomUUID(),
},
});
setPending(false);
}}
/>
)}
{trigger.lastFiredAt && (
<Text>Last event {relativeTime(trigger.lastFiredAt)}</Text>
)}
{trigger.kind === "webhook" && trigger.webhookUrl && (
<>
<Text selectable>{trigger.webhookUrl}</Text>
<ControlPill
label="Copy"
variant="pill"
onPress={() => void Clipboard.setStringAsync(trigger.webhookUrl!)}
/>
</>
)}
</View>
))}
</ListSection>
{showRun && (
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/bots/workflows/WorkflowGraph.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ describe("mobile graph geometry", () => {
it("resolves body coordinates before fitting the graph", () => {
const result = graphGeometry(
{
triggers: [],
entries: [],
nodes: [
{
id: "parent",
Expand Down
Loading
Loading