Request Flows
TeamWork is a full web UI with chat channels, file browser, terminal, browser screencast, and content panel. Messages flow through a webhook with rich view context so Prax knows exactly what the user is looking at.
f62d7985
View source ↗
Request Flow — SMS Message
sequenceDiagram
participant U as User (Phone)
participant T as Twilio
participant F as Flask /sms
participant S as SmsService
participant C as ConversationService
participant A as ReAct Agent
participant DB as SQLite
participant W as Workspace
U->>T: Send SMS
T->>F: POST /sms (webhook)
F->>S: process(request)
S->>S: Authorize (phone_to_name_map)
alt PDF attachment
S->>S: Extract markdown (opendataloader-pdf)
S->>W: Save .md to active/, .pdf to archive/
S->>U: Summary via SMS
else Text message
S->>C: reply(from_number, text)
C->>DB: Retrieve conversation history
C->>W: get_workspace_context()
C->>A: Invoke agent (history + input + context)
A->>A: ReAct loop (reason → act → observe)
A-->>W: Workspace tool calls (optional)
A-->>A: Search / PDF / NPR tools (optional)
A->>C: Final response
C->>DB: Save user + assistant messages
C->>S: Return response text
S->>U: Send SMS (chunked if >1600 chars)
end
Request Flow — Discord Message
sequenceDiagram
participant U as User (Discord)
participant D as Discord Bot
participant DS as DiscordService
participant C as ConversationService
participant A as ReAct Agent
participant DB as SQLite
U->>D: Send message (DM or channel)
D->>DS: on_message(message)
DS->>DS: Authorize (discord_allowed_users)
alt File attachment
DS->>DS: Download attachment
DS->>C: reply(discord_user_id, text + attachment context)
else Text message
DS->>C: reply(discord_user_id, text)
end
C->>DB: Retrieve conversation history
C->>A: Invoke agent (history + input + workspace context)
A->>A: ReAct loop (reason → act → observe)
A->>C: Final response
C->>DB: Save user + assistant messages
C->>DS: Return response text
DS->>U: Send Discord message (chunked if >2000 chars)
Request Flow — TeamWork Web UI
TeamWork is a full web UI with chat channels, file browser, terminal, browser screencast, and content panel. Messages flow through a webhook with rich view context so Prax knows exactly what the user is looking at.
sequenceDiagram
participant U as User (Browser)
participant TW as TeamWork (FastAPI)
participant WH as Prax /teamwork/webhook
participant C as ConversationService
participant A as ReAct Agent
participant DB as SQLite
U->>TW: Send message (chat, side chat, or DM)
Note over U: Message includes:
content, channel_id,
active_view, extra_data
TW->>TW: Persist message to DB
TW->>TW: Broadcast via WebSocket
TW->>WH: POST webhook (content, channel_id,
active_view, extra_data)
WH->>WH: Build view context
Note over WH: View-specific behavior:
browser → "PAIRING in shared browser"
terminal → "PAIRING in shared terminal"
content → "browsing the Library"
+ fetch screen state if applicable
alt active_view = "browser"
WH->>TW: GET /api/browser/info
TW-->>WH: Browser screencast status
Note over WH: Prepend: "[LIVE BROWSER — user watching]"
else active_view = "terminal"
WH->>TW: GET /api/terminal/{project}/recent
TW-->>WH: Last ~50 terminal lines
Note over WH: Prepend: "[TERMINAL SCREEN — last 50 lines]"
else active_view = "library"
Note over WH: Extract extra_data.content_context
{project, notebook, slug, title}
WH->>WH: library_service.get_note(...)
Note over WH: Prepend: "[LIBRARY ITEM — viewing this note]
Title + full markdown content"
else active_view = "home"
Note over WH: No extra context needed — Prax knows
the user is on the project dashboard
end
WH->>WH: Set ContextVars (channel_id, active_view)
WH->>C: reply(user_id, prefixed_content)
C->>DB: Retrieve conversation history
C->>A: Invoke agent (history + input + view context)
A->>A: ReAct loop (reason → act → observe)
A->>C: Final response
C->>DB: Save messages
WH->>TW: POST /api/external/reply (response + trace metadata)
TW->>U: WebSocket broadcast → message appears in UI
View Context System
Every message from TeamWork includes an active_view field indicating which panel the user is on. Prax uses this to tailor both the system prompt and its behavior:
active_view |
What Prax Sees | Behavior |
|---|---|---|
"browser" |
Live browser info + screencast status | Uses delegate_browser exclusively — user watches the browser in real-time |
"terminal" |
Last ~50 terminal output lines | Uses sandbox_shell — executes commands immediately, no confirmation |
"library" |
Full library item content injected (when viewing a specific note) + item metadata | Uses library_* tools; discusses the item immediately without re-reading |
"home" |
Active project dashboard visible | Uses library_projects_list + library_tasks_list when asked for status |
"chat" |
No special context | Default behavior with all tools available |
| other | No special context | View label shown but no behavior change |
Library item context tracking
When the user is viewing a specific note in the Library, the frontend passes the selected item’s metadata through the full stack:
LibraryPanel (note selection)
→ ProjectWorkspace (holds state)
→ BrowserChatSidebar (contentContext prop)
→ POST /api/messages { extra_data: { content_context: {project, notebook, slug, title} } }
→ TeamWork forwards extra_data in webhook payload
→ Prax extracts content_context, injects into tool_guidance:
"The user is currently viewing a library item: personal/health/sleep-tips — 'Sleep tips'"
This means when the user says “refine this” or “add a section about X”, Prax knows exactly which note they mean without the user needing to name it. For very long notes, content is truncated with a hint to use library_note_read for the full version.
Direct-edit acknowledgment
When the user edits a note directly in the Library panel, Prax’s
library view tool guidance tells the agent to treat bracketed meta
notes like [I just edited this] as completed actions rather than
instructions — the user is informing Prax of a state change, not
asking for one.
Sandbox Code Execution Flow (direct execution)
Direct code execution — no coding sessions. The multi-round OpenCode coding-session tools (
sandbox_start/sandbox_message/sandbox_review/sandbox_finish/sandbox_abort/sandbox_search/sandbox_execute) were removed (2026-07): the sandbox image no longer ships a coding-agent server.delegate_sandboxis now a headless sub-agent that writes and runs code directly in the container viasandbox_shell— no session lifecycle, no rounds, no archive/replay. Prax also codes natively on the host (run_python,workspace_save/workspace_patch,source_read/source_grep). Available wheneverSANDBOX_ENABLED. See sandbox-execution-boundary.
sequenceDiagram
participant A as Main Agent
participant SB as Sandbox Spoke
participant SS as Sandbox Service
participant D as Docker
participant W as Workspace Git
Note over A: User asks: "Turn this PDF into a beamer presentation with voiceover"
A->>SB: delegate_sandbox("Build beamer deck from PDF")
Note over SB: Headless sub-agent plans and codes directly
SB->>SS: sandbox_shell("cat main.tex > ...")
SS->>D: docker exec (write file)
D-->>SS: ok
SS-->>SB: stdout / exit code
SB->>SS: sandbox_shell("pdflatex main.tex && ...")
SS->>D: docker exec (build)
D-->>SS: build output
SS-->>SB: stdout / exit code
Note over SB: Inspect a file with the line-numbered viewer
SB->>SS: sandbox_view("build.sh")
SS->>D: docker exec (read)
D-->>SS: file contents
SS-->>SB: numbered lines
Note over SB: Copy the artifact back to the user's workspace
SB->>W: write artifact + git commit
SB-->>A: Summary of what was built + artifact path
A->>A: Respond to user via SMS
Scheduled Messages Flow
sequenceDiagram
participant U as User
participant A as Main Agent
participant SchS as Scheduler Service
participant YAML as schedules.yaml
participant APS as APScheduler
participant C as ConversationService
participant SMS as SMS Gateway
Note over U: "Send me French words every 2h weekdays 9-5, I'm in LA"
U->>A: (via SMS)
A->>SchS: schedule_set_timezone("America/Los_Angeles")
SchS->>YAML: Write timezone
A->>SchS: schedule_create("French vocab", prompt, "0 9,11,13,15,17 * * 1-5")
SchS->>YAML: Append schedule entry
SchS->>APS: Register CronTrigger (tz=America/Los_Angeles)
SchS-->>A: Schedule created (id: french-vocab-a1b2c3)
A->>U: "Done! I'll send French words at 9am, 11am, 1pm, 3pm, 5pm PT on weekdays."
Note over APS: Monday 9:00 AM Pacific
APS->>SchS: _on_fire(user, schedule_id, prompt)
SchS->>C: reply(user, "[Scheduled task] Send me 5 French words...")
C->>A: Agent generates fresh vocabulary
A-->>C: "Here are 5 French words: ..."
C-->>SchS: Response text
SchS->>SMS: send_sms(response, user)
SMS->>U: French vocabulary arrives as SMS
SchS->>YAML: Update last_run timestamp
schedules.yaml Format
Each user has a schedules.yaml in their git workspace that both the agent and the user can edit manually:
timezone: America/Los_Angeles
schedules:
- id: french-vocab-a1b2c3
description: French vocabulary practice
prompt: >
Send me 5 new French words with their English translations,
pronunciation guides, and example sentences. Vary the difficulty
and topic each time. Remember what you sent before.
cron: '0 9,11,13,15,17 * * 1-5'
timezone: America/Los_Angeles
enabled: true
created_at: '2026-03-20T09:00:00-07:00'
last_run: '2026-03-20T15:00:00-07:00'
- id: daily-briefing-d4e5f6
description: Morning news briefing
prompt: >
Give me a brief morning briefing: top 3 news headlines,
weather summary, and one interesting fact.
cron: '30 7 * * 1-5'
timezone: America/Los_Angeles
enabled: true
created_at: '2026-03-20T09:05:00-07:00'
last_run: null
Cron field reference (5 fields: minute hour day month weekday):
| Pattern | Meaning |
|---|---|
0 9,11,13,15,17 * * 1-5 |
9am, 11am, 1pm, 3pm, 5pm on weekdays |
30 7 * * * |
Daily at 7:30am |
0 */3 * * 1-5 |
Every 3 hours on weekdays |
0 8 * * 1 |
Every Monday at 8am |
0 20 1,15 * * |
8pm on the 1st and 15th of each month |
Manual editing: Edit the YAML directly in the workspace, then tell the agent “I edited the schedules file” and it will call schedule_reload to pick up changes. All changes are git-committed automatically.