diff --git a/mkdocs/docs/core/commands/builtin.md b/mkdocs/docs/core/commands/builtin.md index 321e3947..c9fa7e01 100644 --- a/mkdocs/docs/core/commands/builtin.md +++ b/mkdocs/docs/core/commands/builtin.md @@ -1,22 +1,23 @@ # Built-in commands -Every slash command shipped with CraftBot, with its aliases, subcommands, and behavior. All of them work in both [interfaces](../interfaces/index.md). The one marked otherwise says so. +Every slash command shipped with CraftBot, with its aliases, subcommands, and behavior. All of them work in both [interfaces](../interfaces/index.md). The two marked otherwise say so. At a glance: | Command | Aliases | What it does | |---|---|---| | [`/help [command]`](#help) | `/h`, `/?` | List commands, or detail one | -| [`/clear`](#clear) | `/cls` | Clear the current chat session | -| [`/reset`](#reset) | — | Reset agent state and clear history | +| [`/clear`](#clear) | `/cls` | Clear the chat and action log | +| [`/clear-tasks`](#clear-tasks) | `/cleartasks` | Remove finished tasks from the task panel | +| [`/reset`](#reset) | (none) | Reset agent state and clear history | | [`/exit`](#exit) | `/quit`, `/q` | Shut down CraftBot | -| [`/menu`](#menu) | — | Open the settings menu (browser only) | -| [`/provider [name] [key]`](#provider) | — | View or change the LLM provider | -| [`/mcp `](#mcp) | — | Manage MCP servers | -| [`/skill `](#skill) | — | Manage skills | -| [`/cred `](#cred) | — | Credentials and integration status | +| [`/menu`](#menu) | (none) | Open the settings menu (browser only) | +| [`/provider [name] [key]`](#provider) | (none) | View or change the LLM provider | +| [`/mcp `](#mcp) | (none) | Manage MCP servers | +| [`/skill `](#skill) | (none) | Manage skills | +| [`/cred `](#cred) | (none) | Credentials and integration status | | [`/update [--check]`](#update) | `/upgrade` | Check for and install updates | -| [`/tokens`](#tokens) | — | Show this session's token usage | +| [`/tokens`](#tokens) | (none) | Show this session's token usage | Beyond these, the registry also holds [integration commands](#integration-commands) (`/gmail`, `/slack`, ...) and [skill commands](#skill-commands) (`/pdf`, `/docx`, ...), covered at the end. @@ -31,11 +32,15 @@ The leading slash on the argument is optional (`/help mcp` and `/help /mcp` both ## /clear -Clears the conversation of the session it's typed in: the persisted chat messages plus the agent-side session state (event stream, todos, run budgets), so a restart won't resurrect the cleared chat. Other sessions, dashboard data, and the session's lifetime [token counters](#tokens) are unaffected. Use it when one conversation is cluttered. Use [`/reset`](#reset) when the *agent* needs a fresh start. +Clears the chat transcript and the action log in the current interface, and also drops the agent's persisted conversation memory, so a restart won't resurrect the cleared chat. Task history and dashboard data are unaffected. Use it when the conversation is cluttered. Use [`/reset`](#reset) when the *agent* needs a fresh start. + +## /clear-tasks + +Removes tasks whose status is completed, failed, or cancelled from the task panel, along with their child actions. Running and waiting tasks are preserved, and dashboard usage data and task history are untouched. Requires an action panel, so it's effectively a browser command. In the CLI it reports that no action panel is available. ## /reset -Resets the agent to its initial state: deletes every chat session (a fresh main session is created), clears the action history and conversation context, restores the agent's markdown files in `agent_file_system/` from their templates, rebuilds the memory index, and clears dashboard usage data. Workspace outputs are wiped too, except Living UI projects, which are preserved. Saved settings and credentials are **not** affected. Feedback arrives as system messages while the reset runs in the background. +Resets the agent to its initial state: clears the current task, action history, and conversation context, and wipes the chat view. Saved settings and credentials are **not** affected. Feedback arrives as system messages while the reset runs in the background. ## /exit @@ -43,7 +48,7 @@ Stops the agent cleanly and ends the session. In [service mode](../../start/serv ## /menu -Opens the settings menu. Browser only, and hidden from the `/help` list. In the CLI it points you to `/help` instead. (In practice you'll click **Settings** in the sidebar; the command exists mainly for keyboard-first use.) +Opens the settings menu. Browser only. In the CLI it points you to `/help` instead. (In practice you'll click **Settings** in the sidebar; the command exists mainly for keyboard-first use.) ## /provider @@ -55,8 +60,6 @@ View or switch the LLM provider without opening settings. /provider anthropic sk-ant-... # switch and set the key in one line ``` -Bare `/provider` masks the key as its first four and last four characters (`sk-a...abcd`). - Accepted names: | Name | Provider | Key | @@ -72,7 +75,7 @@ Accepted names: | `openrouter` | OpenRouter | `OPENROUTER_API_KEY` | | `remote` | Ollama (local) | none | -The change is saved to `settings.json` and the LLM client reinitializes immediately, with no restart — a genuine provider change also resets the per-session model caches, while re-running the command with nothing changed is a no-op. Switching providers clears any model override so the new provider starts on its default model. Model selection, base URLs, and subscription login live in **Settings → Model**; see [LLM providers](../providers/llm.md). +The change is saved to `settings.json` and the LLM reinitializes immediately, with no restart. Model selection, base URLs, and subscription login live in **Settings → Model**; see [LLM providers](../providers/llm.md). ## /mcp @@ -105,7 +108,7 @@ Manage [skills](../concepts/skills.md). `/skill` with no arguments prints usage. |---|---| | `list [--all]` | List enabled skills; `--all` includes disabled ones | | `info ` | Description, version, author, path, and the skill's actions | -| `enable ` / `disable ` | Toggle a skill — this also registers/unregisters its slash command | +| `enable ` / `disable ` | Toggle a skill; this also registers/unregisters its slash command | | `install ` | Install from a local directory | | `install ` | Install from a GitHub/GitLab URL | | `create [description]` | Scaffold a new skill | diff --git a/mkdocs/docs/core/commands/cli-anything.md b/mkdocs/docs/core/commands/cli-anything.md index d5ebfa93..e381992f 100644 --- a/mkdocs/docs/core/commands/cli-anything.md +++ b/mkdocs/docs/core/commands/cli-anything.md @@ -56,7 +56,7 @@ The skill's instructions (`skills/cli-anything/SKILL.md`) contain a routing tabl 6. **Report** in a sentence or two: what was produced and where. -Every step runs as a shell action, so you can watch the whole flow (version checks, installs, the command itself) in the [activity view](../interfaces/browser.md#activity), and it all lands in [logs](../concepts/logs.md). +Every step runs as a shell action, so you can watch the whole flow (version checks, installs, the task command) in the [task panel](../interfaces/browser.md#tasks), and it all lands in [logs](../concepts/logs.md). The skill hard-bans the failure modes of driving desktop apps directly: no `.exe` suffixes, no hardcoded `C:\Program Files\...` paths, no `&&` command chaining, no raw `soffice`/`gimp`/`blender` invocations. The harness resolves app locations and flags per platform, which is what makes the same task work on all three OSes. @@ -73,7 +73,7 @@ CLI-anything is the first choice, not the only one. If a harness command fails a - **File paths:** give the agent full paths to input files (`C:\Users\you\Desktop\photo.jpg`, `/home/user/photo.jpg`) for the smoothest run. !!! warning "It installs software" - By design, this skill can install real applications on your machine (silently, with license agreements auto-accepted) and run them with your privileges. Each install is a visible shell action in the activity view, and installs are attempted at most once. If you don't want the agent installing anything, keep the skill disabled or preinstall the apps you care about. + By design, this skill can install real applications on your machine (silently, with license agreements auto-accepted) and run them with your privileges. Each install is a visible shell action in the task panel, and installs are attempted at most once. If you don't want the agent installing anything, keep the skill disabled or preinstall the apps you care about. ## Related diff --git a/mkdocs/docs/core/commands/index.md b/mkdocs/docs/core/commands/index.md index 6790d8c2..2bb4a143 100644 --- a/mkdocs/docs/core/commands/index.md +++ b/mkdocs/docs/core/commands/index.md @@ -1,6 +1,6 @@ # Commands -Commands are slash-prefixed inputs (`/help`, `/provider anthropic sk-...`, `/skill enable pdf`) that the [UI layer](../interfaces/ui-layer.md) intercepts before anything reaches the agent. They're how you configure providers, integrations, skills, and MCP servers (instantly, without spending tokens or waking the agent) and they work identically in the [browser](../interfaces/browser.md) and the [CLI](../interfaces/cli.md). +Commands are slash-prefixed inputs (`/help`, `/provider anthropic sk-...`, `/skill enable pdf`) that the [UI layer](../interfaces/ui-layer.md) intercepts before anything reaches the agent. They're how you configure providers, integrations, skills, and MCP servers (instantly, without spending tokens or starting a task) and they work identically in the [browser](../interfaces/browser.md) and the [CLI](../interfaces/cli.md).
@@ -32,7 +32,7 @@ The registry is populated from four sources at startup: | Kind | Examples | Where they come from | |---|---|---| -| **Built-in** | `/help`, `/provider`, `/mcp`, `/skill`, `/cred`, `/update` | Shipped in `app/ui_layer/commands/builtin/` — always present | +| **Built-in** | `/help`, `/provider`, `/mcp`, `/skill`, `/cred`, `/update` | Shipped in `app/ui_layer/commands/builtin/`; always present | | **Integration** | `/gmail`, `/slack`, `/telegram_bot`, `/notion` | One per available [integration](../../integrations/index.md), each with `connect` / `disconnect` / `status` plus integration-specific subcommands | | **Skill** | `/pdf`, `/docx`, `/pptx` | One per **enabled** [skill](../concepts/skills.md); registered and unregistered live as you toggle skills | | **Agent-provided** | varies | Commands the agent runtime registers programmatically, wrapped into the same registry | diff --git a/mkdocs/docs/core/concepts/actions-and-action-sets.md b/mkdocs/docs/core/concepts/actions-and-action-sets.md index cce6fdab..948e84a6 100644 --- a/mkdocs/docs/core/concepts/actions-and-action-sets.md +++ b/mkdocs/docs/core/concepts/actions-and-action-sets.md @@ -23,9 +23,9 @@ An action is a Python function with an `@action` decorator that registers it at | `description` | What the LLM reads to decide when to pick this action | | `input_schema` / `output_schema` | Parameter and result contracts, shown to the LLM | | `action_sets` | Which sets contain this action (an action can be in several) | -| `mode` | Interface visibility — which interface contexts offer the action; `"ALL"` means everywhere | +| `mode` | Interface visibility (which interface contexts offer the action); `"ALL"` means everywhere | | `execution_mode` | `"internal"` (in-process) or `"sandboxed"` (isolated venv) | -| `platforms` | `windows` / `linux` / `darwin` / `all` — see platform dispatch below | +| `platforms` | `windows` / `linux` / `darwin` / `all`; see platform dispatch below | | `requirement` | pip packages the action needs; installed automatically before it runs | | `parallelizable` | Whether it may run alongside other actions in one turn (`False` for writes, state changes, `send_message`) | | `irreversible` | Marks side effects that can't be undone once they reach the outside world (send email, post publicly) | @@ -58,7 +58,7 @@ When a task starts, one LLM call selects both the [skills](skills.md) and the ac This compile-once design is deliberate: during execution there is no retrieval step and no searching for tools. The task's vocabulary is a fixed list the router reads directly. -The list can still change, though. Mid-run, the agent can call `list_action_sets`, `add_action_sets`, and `remove_action_sets` (all in `core`, so always available) to expand or trim its own vocabulary when it discovers it needs something. These calls appear in the activity view when a run discovers mid-way that it needs another capability. +The list can still change, though. Mid-task, the agent can call `list_action_sets`, `add_action_sets`, and `remove_action_sets` (all in `core`, so always available) to expand or trim its own vocabulary when it discovers it needs something. These calls appear in the action panel when a task discovers mid-way that it needs another capability. ## Per-turn selection @@ -66,17 +66,17 @@ Every iteration of the [agent loop](agent-loop.md), the router makes **one LLM c ```json {"reasoning": "...", "actions": [{"action_name": "web_search", "parameters": {...}}, - {"action_name": "update_todos", "parameters": {...}}]} + {"action_name": "task_update_todos", "parameters": {...}}]} ``` Rules applied to that list before execution: - **Parallel execution.** Multiple actions in one decision run concurrently, up to 10 per batch. - **Non-parallelizable wins alone.** If any selected action has `parallelizable=False`, it runs by itself and the rest are dropped with an error the agent sees next turn. -- **Format errors retry, then abort.** Malformed LLM output gets up to 3 retries with the parse error fed back. After that the run halts rather than wasting tokens. -- **Run-ending actions end the run.** When the only selected actions are a final `send_message` or `end_turn`, the run is over; anything else queues a continuation for the next turn. +- **Format errors retry, then abort.** Malformed LLM output gets up to 3 retries with the parse error fed back. After that the task aborts rather than wasting tokens. +- **Conversation mode is narrow.** Outside a task, the candidates are only `send_message`, `task_start`, `ignore`, plus messaging actions for connected platforms. Real work requires a task. -Each execution logs `action_start` / `action_end` events to the session's [event stream](event-stream.md), which is what the activity view in the browser renders live. +Each execution logs `action_start` / `action_end` events to the task's [event stream](event-stream.md), which is what the action panel in the browser renders live. ## Internal vs sandboxed execution diff --git a/mkdocs/docs/core/concepts/agent-file-system.md b/mkdocs/docs/core/concepts/agent-file-system.md index fa5c95e1..ba12fb12 100644 --- a/mkdocs/docs/core/concepts/agent-file-system.md +++ b/mkdocs/docs/core/concepts/agent-file-system.md @@ -13,15 +13,17 @@ The directory is seeded from templates in `app/data/agent_file_system_template/` | File | Who writes it | May I edit it? | What it does | |---|---|---|---| -| `SOUL.md` | You (agent only on your explicit request) | **Yes — the main personality knob** | Personality, tone, behavior. Injected into the system prompt every turn | +| `SOUL.md` | You (agent only on your explicit request) | **Yes, the main personality knob** | Personality, tone, behavior. Injected into the system prompt every turn | | `USER.md` | Onboarding wizard; agent, after confirming with you | **Yes** | Your profile: identity, timezone, communication preferences, life goals | | `FORMAT.md` | You | **Yes** | Formatting standards the agent reads before generating any document | -| `GLOBAL_LIVING_UI.md` | You | **Yes** | Global design preferences for every [Living UI](../../living-ui/index.md) project — colors, theme, enforced rules | -| `AGENT.md` | Ships with CraftBot; agent appends learned operational fixes | Yes, carefully | The agent's versioned operations manual — runtime, errors, integrations, conventions. The agent greps it by `## ` | -| `PROACTIVE.md` | `recurring_*` actions and the planners | Prefer the actions; preserve the `` markers | Recurring proactive tasks plus the planner's Goals / Plan / Status — see [Proactive mode](../modes/proactive.md) | -| `MEMORY.md` | Memory processor only (nightly job) | **No** | Distilled long-term memory, one timestamped fact per line — see [Memory](memory.md) | +| `GLOBAL_LIVING_UI.md` | You | **Yes** | Global design preferences for every [Living UI](../../living-ui/index.md) project: colors, theme, enforced rules | +| `AGENT.md` | Ships with CraftBot; agent appends learned operational fixes | Yes, carefully | The agent's versioned operations manual: runtime, errors, integrations, conventions. The agent greps it by `## ` | +| `PROACTIVE.md` | `recurring_*` actions and the planners | Prefer the actions; preserve the `` markers | Recurring proactive tasks plus the planner's Goals / Plan / Status; see [Proactive mode](../modes/proactive.md) | +| `MEMORY.md` | Memory processor only (nightly job) | **No** | Distilled long-term memory, one timestamped fact per line; see [Memory](memory.md) | | `EVENT.md` | Event stream manager | **No** | Append-only chronological log of every event (actions, messages, errors) | | `EVENT_UNPROCESSED.md` | Event stream manager | **No** | Staging buffer of events awaiting the nightly memory run; cleared after each run | +| `TASK_HISTORY.md` | Appended on every `task_end` | **No** | One summary section per finished task: status, timestamps, outcome, skills used | +| `CONVERSATION_HISTORY.md` | Event stream manager | **No** | Rolling transcript of every user ↔ agent exchange, never auto-cleared | | `MISSION_INDEX_TEMPLATE.md` | Static template | **No** | Copied into `workspace/missions//INDEX.md` when a mission starts | The "No" files are harness-managed. Hand-editing them creates inconsistencies the agent can't recover from: the memory pipeline expects `MEMORY.md` in its exact line format, and the event logs are the ground truth other subsystems replay. Read them freely, but never write to them. @@ -42,20 +44,20 @@ A useful side-effect to know: `AGENT.md`, `PROACTIVE.md`, `MEMORY.md`, `USER.md` ## workspace/ -Everything the agent produces lands under `agent_file_system/workspace/`. Four zones with different lifecycles: +Everything a task produces lands under `agent_file_system/workspace/`. Four zones with different lifecycles: ```text workspace/ -├── Persistent outputs — reports, exports, +├── Persistent task outputs: reports, exports, │ anything you asked for. Never auto-cleaned. -├── sessions// Per-session scratch: drafts, downloads, -│ intermediate state. Created with the session; -│ removed only when the session is deleted. -├── missions// Multi-run initiatives. INDEX.md (from the -│ template) records goal, findings, next steps — -│ it's what a future run reads to restore context. +├── tmp// Per-task scratch: drafts, downloads, +│ intermediate state. Auto-created when the task +│ starts; auto-deleted on task end AND at startup. +├── missions// Multi-session initiatives. INDEX.md (from the +│ template) records goal, findings, next steps; +│ it's what a future task reads to restore context. │ Never auto-cleaned. -└── living_ui/_/ Living UI projects — self-contained apps managed +└── living_ui/_/ Living UI projects: self-contained apps managed by their own lifecycle actions. Don't rename or delete these by hand. ``` @@ -63,15 +65,15 @@ workspace/ The practical rules: - **Deliverables go in the workspace root.** That's where "save it as frameworks.md" ends up, and where you go looking for outputs. -- **`sessions/` is scratch space.** If a run saved something there that you want to keep, ask for it to be moved to the workspace root. -- **Missions are for work bigger than one run** (a job hunt, a research program). The mission's `INDEX.md` is the durable state. Individual runs come and go. +- **Anything in `tmp/` is disposable by design.** If a task saved something there that you want, move it out before the task ends. +- **Missions are for work bigger than one task** (a job hunt, a research program). The mission's `INDEX.md` is the durable state. Individual tasks come and go. ## Configuration and limits - **Location:** `agent_file_system/` in the project root. The template lives at `app/data/agent_file_system_template/`. - **Edits apply on the next trigger**, with no restart. `SOUL.md` in particular takes effect on the very next turn. - **Reset:** `/reset` deletes the markdown files and re-copies the templates. Workspace contents are handled separately, and Living UI projects are preserved by the generic reset (they have their own teardown). -- **Growth:** `EVENT.md` auto-rotates on size, and `EVENT_UNPROCESSED.md` is cleared by each successful memory run. +- **Growth:** `EVENT.md` auto-rotates on size. `CONVERSATION_HISTORY.md` and `TASK_HISTORY.md` grow indefinitely, and `EVENT_UNPROCESSED.md` is cleared by each successful memory run. - **Not for secrets:** API keys and credentials live in `app/config/settings.json` and `.credentials/`, not in these markdown files. ## Next diff --git a/mkdocs/docs/core/concepts/agent-loop.md b/mkdocs/docs/core/concepts/agent-loop.md index 3816c0b9..a4ff63db 100644 --- a/mkdocs/docs/core/concepts/agent-loop.md +++ b/mkdocs/docs/core/concepts/agent-loop.md @@ -1,93 +1,98 @@ # Agent loop -The agent loop is the cycle CraftBot runs every time something wakes it up: claim a [trigger](triggers.md), fold in everything else that's due, let the LLM pick actions, execute them, and queue the follow-up. This cycle explains why runs tick forward one step at a time, why the agent can wait days for your reply without burning tokens, and why a restart doesn't lose work in flight. +The agent loop is the cycle CraftBot runs every time something wakes it up: claim a [trigger](triggers.md), route it to a workflow, let the LLM pick actions, execute them, and queue the follow-up. This cycle explains why tasks tick forward one step at a time, why the agent can wait hours for your reply without burning tokens, and why a restart doesn't lose work in flight. ## Overview -CraftBot does not run continuously. The agent sleeps until a trigger fires (your message, a schedule, a run's own "continue" note) then runs **exactly one turn** and goes back to sleep. The design has three properties: +CraftBot does not run continuously. The agent sleeps until a trigger fires (your message, a schedule, a task's own "continue" note) then runs **exactly one turn** and goes back to sleep. The design has three properties: -1. **One trigger batch, one turn.** A turn is a single pass through the loop: the LLM picks one or more [actions](actions-and-action-sets.md), CraftBot executes them, and the results land on the [event stream](event-stream.md). Everything due for the session at that moment folds into the same turn (see [Triggers](triggers.md)). -2. **Continuation is a new trigger, not a loop.** A ten-step run is not a `while` loop held in memory. Each turn that doesn't end the run enqueues a fresh `run_continuation` trigger for the same session. The next turn picks it up. Waiting is just a trigger with a `fire_at` timestamp in the future. -3. **State lives outside the process.** Progress is recorded in the session's todos, its event stream, and the durable trigger queue, so a crash or restart between turns re-delivers the pending trigger and the run resumes where it left off. +1. **One trigger, one turn.** A turn is a single pass through the loop: the LLM picks one or more [actions](actions-and-action-sets.md), CraftBot executes them, and the results land on the [event stream](event-stream.md). +2. **Continuation is a new trigger, not a loop.** A ten-step task is not a `while` loop held in memory. Each turn ends by enqueuing a fresh continuation trigger for the same session. The next turn picks it up. Waiting is just a trigger with a `fire_at` timestamp in the future. +3. **State lives outside the process.** Progress is recorded in the task's todos, its event stream, and the durable trigger queue, so a crash or restart between turns re-delivers the pending trigger and the task resumes where it left off. -This is also why several sessions can run "at once": each [session](task-sessions.md) has its own trigger queue and its own serial consumer loop, and their turns interleave (up to 3 turns execute concurrently across sessions, one at a time within each). +This is also why several tasks can run "at once": their triggers interleave through the same loop, each turn scoped to its own [session](task-sessions.md). ## The outer loop -Each session's consumer loop drives its turns: +A single consumer drives everything: | Step | What happens | |---|---| -| 1. Claim | The loop waits for the next due trigger in its session's queue, drains everything else already due, and merges the batch into one turn; all merged rows are marked claimed | +| 1. Claim | `trigger_service.next()` waits for the next due trigger and marks its durable record as claimed | | 2. React | `agent.react(trigger)` runs one full turn (everything below) | -| 3. Settle | On success the trigger rows are `ack()`ed (done); on an exception they are `nack()`ed, which retries them with backoff | +| 3. Settle | On success the trigger is `ack()`ed (done); on an exception it is `nack()`ed, which retries it with backoff | -A crash between claim and settle leaves the triggers claimed. The next boot re-delivers them. The guarantee is *at-least-once*, never silently lost. The details are on the [Triggers](triggers.md) page. +A crash between claim and settle leaves the trigger claimed. The next boot re-delivers it. The guarantee is *at-least-once*, never silently lost. The details are on the [Triggers](triggers.md) page. !!! note "Implementation files" - The per-session consumer loops are `SessionRuntimeManager` in `app/triggers/runtime.py` (batch merging is `_merge_triggers` in the same file). The turn itself is `AgentBase.react()` in `app/agent_base.py`. Claim/ack/nack live in `app/triggers/service.py`. + The consumer is `_consume_triggers()` in `app/ui_layer/controller/ui_controller.py`. The turn itself is `AgentBase.react()` in `app/agent_base.py`. Claim/ack/nack live in `app/triggers/service.py`. -## Inside a turn: react() +## Inside a turn: routing -`react()` runs a fixed sequence — there is no routing and no per-task workflow fork; every turn goes through the same pipeline: +`react()` checks the trigger, then the session's state, in a fixed order. First match wins: -| Order | Step | What happens | +| Order | Condition | What runs | |---|---|---| -| 1 | Restart notice | If the trigger is a restart notice, the prebuilt "I was restarted" message goes to chat and the turn returns — no LLM call | -| 2 | Resolve the session | The trigger's `session_id` names its session directly | -| 3 | Workflow pre-check | For `memory` / `proactive_*` triggers: skip the turn if no work is due; otherwise load the workflow's skills and action sets onto the session for this run (see [Special workflows](../modes/special-workflows.md)) | -| 4 | Announce | The turn's cause — or the aggregated checklist of causes — is logged onto the event stream, along with any queued user messages | -| 5 | Run bookkeeping | If the trigger starts a new run (a user message, a schedule — anything but a continuation), the run's budgets and state are reset | -| 6 | Pipeline | select → prepare → execute → finalize (below) | +| 1 | Trigger is a restart notice | Posts the prebuilt "I was restarted" message to chat and returns; no LLM call | +| 2 | Trigger source is `memory` | Memory workflow: spawns a task that distills recent events into long-term [memory](memory.md) | +| 3 | Trigger source is `proactive_heartbeat` / `proactive_planner` | [Proactive](../modes/proactive.md) workflow: collects due recurring tasks or runs a planner | +| 4 | Task waiting for your reply, and this trigger carries no message | Re-schedules the wait for another 3 hours and returns; the task keeps sleeping | +| 5 | Session has a running **complex** task | Complex-task workflow: todo-driven, approval-gated | +| 6 | Session has a running **simple** task | Simple-task workflow: linear, auto-completing | +| 7 | Anything else | Conversation workflow: no task exists yet | + +Before steps 4–7, the turn initializes the session and, if the trigger carries a user message routed in mid-task, records it onto the event stream so the LLM sees it. + +The three main workflows (5–7) differ in prompt shape, todo handling, and caching (compared side by side in [Task modes](../modes/index.md)) but they all execute the same four-phase pipeline. ## The turn pipeline -Every turn — main session, chat session, or Living UI session — runs the same four phases: +Every conversation, simple-task, and complex-task turn runs the same four phases: -1. **Select.** One LLM call chooses one or more actions and their inputs, based on the trigger, the current requirements and todos, and the event stream. -2. **Prepare.** Each selected action is resolved by name from the session's loaded action sets and its inputs are bound. -3. **Execute.** The actions run, in parallel when more than one was selected (up to 10 per batch). Every action logs `action_start` / `action_end` events, which is what the action panel in the browser renders live. -4. **Finalize.** The turn's outcome decides the run's fate: if every executed action was terminal — a final `send_message` (without `continue_work=true`) or `end_turn` — the run ends and the session goes idle. Otherwise a **new `run_continuation` trigger** is enqueued for the session and the next turn follows. +1. **Select.** One LLM call chooses one or more actions and their inputs, based on the task instruction, todos, and the event stream. In conversation mode the menu is deliberately tiny: reply, start a task (several in parallel is allowed), or deliberately ignore a message that needs no reaction. +2. **Prepare.** Each selected action is resolved by name from the task's action sets and its inputs are bound. +3. **Execute.** The actions run, in parallel when more than one was selected. Every action logs `action_start` / `action_end` events, which is what the action panel in the browser renders live. +4. **Finalize.** The action output is inspected: did it create a task? ask for a delay (`wait`)? flag `waiting_for_user_reply`? Then a **new continuation trigger** is enqueued for the session (or for each task that a parallel `task_start` created) and the turn ends. -The finalize phase drives multi-step work. A run making twenty tool calls is roughly twenty turns, each handed to the next by a continuation trigger. Between turns the agent is idle, free to run a different session's turn or to sleep. +The finalize phase drives multi-step work. A complex task making twenty tool calls is roughly twenty turns, each handed to the next by a `task_continuation` trigger. Between turns the agent is idle, free to run a different task's turn or to sleep. ## What happens when you send a message Putting it together, end to end: -1. Your message is durably recorded as a `user_message` trigger for the [session](task-sessions.md) you typed it in. Messages arriving from a connected platform land in the main session. -2. The session's loop claims it — together with anything else due for that session — and calls `react()`. -3. A new run starts. For a quick request the agent just answers: the final `send_message` ends the run. For substantial work it records requirements, acknowledges you, plans todos, and works turn by turn (see [How runs scale](../modes/index.md)). -4. If the agent needs your answer before it can continue, it asks the question as its final message. The run ends and the session sleeps at zero cost. Your reply wakes a **new run in the same session** — the shared event stream carries the context over. +1. Your message is durably recorded, then [session routing](task-sessions.md) decides whether it continues an existing task or opens a fresh session. +2. A `user_message` trigger fires. The consumer claims it and calls `react()`. +3. No task is running for the fresh session, so the conversation workflow runs: the LLM either answers directly (`send_message`) or calls `task_start`. +4. If a task started, finalize queues a continuation trigger. Each subsequent turn works a todo, until the agent sends you a result and (for complex tasks) waits for your approval before `task_end`. +5. If the agent asked you something mid-task, the task flips to waiting-for-reply and its trigger sleeps. Your answer routes back and wakes it immediately. ## Watch it run -- **In the browser.** The chat, todo list, and action panel are a live rendering of the loop: each visible action is one entry in a turn's execute phase. -- **In the logs.** Every app start writes a folder under `logs/` ([Logs](logs.md)). Grep for the loop's own tags: +- **In the browser.** The task card, todo list, and action panel are a live rendering of the loop: each visible action is one entry in a turn's execute phase. +- **In the logs.** Every run writes to `logs/` ([Logs](logs.md)). Grep for the loop's own tags: ```bash -grep -E "\[REACT\]|\[ACTION\]|SessionRuntime" logs//all.log +grep -E "\[REACT\]|\[WORKFLOW|\[ACTION\]|\[TRIGGER" logs/.log ``` ```text -[SessionRuntime] Loop started for session main [REACT] starting... -[ACTION] Ready to run 1 action(s): ['send_message'] -[SessionRuntime] Aggregated 2 queued trigger(s) (user_message, run_continuation) into one turn for main +[WORKFLOW: CONVERSATION] Query: what's the weather in Tokyo +[ACTION] Ready to run 1 action(s): ['task_start'] +[TRIGGER] Creating new trigger for session: 4f2c1a ``` - **On disk.** Every event a turn produces is also appended to `agent_file_system/EVENT.md` ([Event stream](event-stream.md)). ## Limits and error handling -- **Per-run budgets.** Each run counts its actions and tokens. At 100% of either limit the run pauses and you get a Continue/Stop choice in chat; picking Continue resets the counters and the run resumes on the next trigger. Token accounting bills only *uncached* tokens, so warm-cache runs go much further than raw usage suggests. -- **Waiting costs nothing.** A run that ends with a question leaves nothing scheduled and invokes no LLM while it waits — the next message simply wakes the session. -- **Force-stop.** You can stop a run from the UI: the in-flight turn is cancelled, child processes are killed, and queued continuation triggers are purged. Queued user messages and schedules stay. The next message starts a fresh run. -- **Errors don't kill the loop.** Exceptions inside a turn are caught by `react()` itself, logged, and surfaced to the affected session. The session's loop keeps running. Failures that escape a turn entirely cause a `nack()`: retry with exponential backoff, then a dead-letter message in chat rather than silent loss (see [Triggers](triggers.md)). -- **Feature switches.** Disabling memory or proactive mode in settings makes their triggers no-ops: the workflow pre-check (step 3 above) returns without doing anything. +- **Per-task budgets.** Each task counts its actions and tokens. At 80% of either limit the agent gets a warning event telling it to wrap up. At 100% the task pauses and you get a Continue/Abort choice in chat. Nothing runs unbounded. +- **Waiting costs nothing.** A task waiting for your reply re-schedules itself in 3-hour hops without invoking the LLM (step 4 in the routing table). +- **Errors don't kill the loop.** Exceptions inside a turn are caught by `react()` itself, logged, and surfaced to the affected session. The consumer keeps running. Failures that escape a turn entirely cause a `nack()`: retry with exponential backoff, then a dead-letter message in chat rather than silent loss (see [Triggers](triggers.md)). +- **Feature switches.** Disabling memory or proactive mode in settings makes their triggers no-ops. Routing steps 2 and 3 return without doing anything. ## Next - [Triggers](triggers.md): everything that wakes the loop, and what survives a restart -- [Sessions](task-sessions.md): the lanes runs execute in, and what each one owns +- [Task sessions](task-sessions.md): how messages find the right task, and how tasks live and end - [Event stream](event-stream.md): the record each turn reads from and writes to -- [How runs scale](../modes/index.md): quick requests vs substantial work, compared +- [Task modes](../modes/index.md): conversation vs simple vs complex, compared diff --git a/mkdocs/docs/core/concepts/context-engine.md b/mkdocs/docs/core/concepts/context-engine.md index f7a91807..7a9d145f 100644 --- a/mkdocs/docs/core/concepts/context-engine.md +++ b/mkdocs/docs/core/concepts/context-engine.md @@ -7,8 +7,8 @@ Every LLM call is two halves: | Half | Contents | Changes between calls? | Cached? | |---|---|---|---| -| **Static prefix** (system prompt) | Agent identity, your profile, personality, policy, environment, file-system map | No — byte-identical within a session | Yes — provider KV cache | -| **Dynamic tail** (user prompt) | The decision template, current task, conversation history, live event stream, your query | Yes — every call | Only incrementally | +| **Static prefix** (system prompt) | Agent identity, your profile, personality, policy, environment, file-system map | No; byte-identical within a session | Yes (provider KV cache) | +| **Dynamic tail** (user prompt) | The decision template, current task, conversation history, live event stream, your query | Yes, every call | Only incrementally | The split is the whole design. LLM providers cache a prompt *prefix*: as long as the opening bytes of a call are identical to a previous call, those tokens are nearly free and fast. So the engine pushes everything stable to the front and everything volatile to the back. A follow-up call in a long task pays full price only for the events that happened since the last call, not for the agent's entire identity again. @@ -20,15 +20,15 @@ The engine assembles the system prompt from fixed sections in a fixed order: | # | Section | What it contains | You control it via | |---|---|---|---| -| 1 | Agent info | Capabilities, task system, working ethic, format standards | — (built-in) | +| 1 | Agent info | Capabilities, task system, working ethic, format standards | Nothing (built-in) | | 2 | User profile | Your `USER.md`, verbatim | Edit [`USER.md`](agent-file-system.md) | -| 3 | Soul | Your `SOUL.md`, verbatim — personality and tone | Edit [`SOUL.md`](agent-file-system.md) | +| 3 | Soul | Your `SOUL.md`, verbatim: personality and tone | Edit [`SOUL.md`](agent-file-system.md) | | 4 | Language instruction | "Use the user's preferred language" rule | Language preference in `USER.md` | -| 5 | Policy | Safety, privacy, prompt-injection defense | — (built-in) | +| 5 | Policy | Safety, privacy, prompt-injection defense | Nothing (built-in) | | 6 | Role info | Agent name + role persona | [Onboarding](../../start/onboarding.md) sets the name | -| 7 | Environment | Timezone, working directory, OS — stable facts only | — (detected) | -| 8 | File system | Map of `agent_file_system/` — what each file is for | — (built-in) | -| 9 | Base instruction | One-line closing instruction | — (built-in) | +| 7 | Environment | Timezone, working directory, OS (stable facts only) | Nothing (detected) | +| 8 | File system | Map of `agent_file_system/`: what each file is for | Nothing (built-in) | +| 9 | Base instruction | One-line closing instruction | Nothing (built-in) | Sections 2 and 3 are read from disk at prompt-build time, which is why editing `USER.md` or `SOUL.md` changes behavior on the very next call, with no restart. It also means an edit invalidates the cached prefix once. The first call after the edit pays full price, then caching resumes. The prompt templates behind each section are covered in [Prompts](prompts.md). diff --git a/mkdocs/docs/core/concepts/event-stream.md b/mkdocs/docs/core/concepts/event-stream.md index c681afc1..ffcea6c7 100644 --- a/mkdocs/docs/core/concepts/event-stream.md +++ b/mkdocs/docs/core/concepts/event-stream.md @@ -16,12 +16,12 @@ Every event carries a typed category. This is a closed set. Consumers route on i | Event type | Recorded when | |---|---| | `user_message` | You send a message (locally or via a connected platform) | -| `agent_message` | The agent replies — this is what appears as a chat bubble | +| `agent_message` | The agent replies; this is what appears as a chat bubble | | `reasoning` | The LLM explains why it picked the next action(s) | | `action_start` / `action_end` | An action begins / finishes; carries the action name, a paired id, and structured input/output | -| `trigger` | A trigger woke the session (what caused this run/turn) | +| `task_start` / `task_end` | A task's boundaries; `task_end` carries the final status | | `todos` | The todo list changed | -| `waiting_for_user` | The run ended on a question for you | +| `waiting_for_user` | The task paused for your reply | | `relevant_memories` | Memory retrieval injected context pointers | | `system` / `error` | Harness notices and failures | | `internal` | Bookkeeping the UI hides | @@ -30,14 +30,14 @@ Every event carries a typed category. This is a closed set. Consumers route on i The chat and the action panel are direct projections of streams: -- The UI watches all streams (one per session) and routes each event **by its `event_type` only**: `agent_message` becomes a chat bubble, `action_start`/`action_end` become the live activity rows, `todos` updates the checklist, `waiting_for_user` flips the status bar. +- The UI watches all streams (main + every task) and routes each event **by its `event_type` only**: `agent_message` becomes a chat bubble, `action_start`/`action_end` become the live action rows, `todos` updates the checklist, `waiting_for_user` flips the status bar. - `action_start` and `action_end` share an `action_id`, so the panel can pair them even when several copies of the same action run in parallel. - Events may carry a shorter `display_message` for the UI while keeping the full `message` for the LLM and for debugging. Nothing happens off the record: if the agent did it, there is an event for it, and the UI shows the ones that concern you. !!! note "Implementation files" - The event model and type enum are `agent_core/core/event_stream/event.py`. The per-stream mechanics (tail, summary, snapshots) are `agent_core/core/impl/event_stream/event_stream.py`. Stream creation per session and the file logging below are `EventStreamManager` in `agent_core/core/impl/event_stream/manager.py`. + The event model and type enum are `agent_core/core/event_stream/event.py`. The per-stream mechanics (tail, summary, snapshots) are `agent_core/core/impl/event_stream/event_stream.py`. Stream creation per task and the file logging below are `EventStreamManager` in `agent_core/core/impl/event_stream/manager.py`. ## EVENT.md and EVENT_UNPROCESSED.md @@ -45,8 +45,8 @@ Every event is also appended to markdown files in `agent_file_system/` (see [Age | File | Contents | |---|---| -| `EVENT.md` | The complete history — every event from every stream, in `[YYYY/MM/DD HH:MM:SS] [kind]: message` format. Auto-rotated when it grows too large. | -| `EVENT_UNPROCESSED.md` | The staging buffer for the [memory pipeline](memory.md) — the subset of events awaiting distillation into `MEMORY.md`, cleared after each processing run. | +| `EVENT.md` | The complete history: every event from every stream, in `[YYYY/MM/DD HH:MM:SS] [kind]: message` format. Auto-rotated when it grows too large. | +| `EVENT_UNPROCESSED.md` | The staging buffer for the [memory pipeline](memory.md): the subset of events awaiting distillation into `MEMORY.md`, cleared after each processing run. | Routine event kinds that the memory processor would always discard (action starts/ends, reasoning, todos, errors, waiting notices, memory-retrieval pointers) are filtered out at write time, so `EVENT_UNPROCESSED.md` contains only dialogue and meaningful state changes. During a memory-processing task the buffer is frozen entirely, so the processor's own events can't loop back into it. @@ -96,11 +96,11 @@ tail -f agent_file_system/EVENT.md - The summarization thresholds (30k trigger / 10k keep) are constructor defaults of the stream, not user settings. They are tuned to balance context quality against per-turn cost. - A summary is lossy by design. Recent events are exact. Older history is the LLM's condensation of it. Durable facts belong in [memory](memory.md), not in the stream. -- A session's stream lives as long as the session. The permanent records are `EVENT.md` and whatever memory distilled. +- Task streams are removed when their task ends. The permanent records are `EVENT.md`, `TASK_HISTORY.md`, and whatever memory distilled. ## Next - [Agent loop](agent-loop.md): the producer: every turn writes here -- [Sessions](task-sessions.md): why each session gets its own stream +- [Task sessions](task-sessions.md): why each task gets its own stream - [Context engine](context-engine.md): how snapshots and deltas reach the LLM - [Memory](memory.md): how events become long-term memory diff --git a/mkdocs/docs/core/concepts/logs.md b/mkdocs/docs/core/concepts/logs.md index 7cabb50e..ed5a7cae 100644 --- a/mkdocs/docs/core/concepts/logs.md +++ b/mkdocs/docs/core/concepts/logs.md @@ -3,26 +3,26 @@ When the agent does something unexpected (a task stalls, a schedule doesn't fire, an action errors) the logs are the ground truth. Every run writes a timestamped folder under `logs/` at the project root, capturing what every subsystem did, down to module and line number. ## Overview -CraftBot logs with **Loguru**, and each process run gets **one folder**: `logs//` (e.g. `logs/20260717085754/`). Inside, the same stream is split by *session* and by *who was speaking*: +CraftBot logs with **Loguru**, and each process run gets **one folder**: `logs//` (e.g. `logs/20260717085754/`). Inside, the same stream is split three ways by *who was speaking*: | File | Contains | Read it when | |---|---|---| -| `all.log` | Everything, interleaved in true time order — every session, main agent and every sub-agent | You're debugging anything that crosses sessions or agents, or just want the full picture. **Start here** | -| `/session.log` | One folder per session; that session's own lines. The main session's folder is `main/` | You want one lane's story without the noise of the others | -| `/.log` | One file per sub-agent, inside the session that spawned it | A specific delegated job misbehaved — see [Sub-agents](sub-agents.md) | +| `main.log` | Only the main agent (plus framework startup) | You want the primary agent's story without sub-agent noise | +| `all.log` | Everything, interleaved in true time order: main agent and every sub-agent | You're debugging anything that crosses agents, or just want the full picture. **Start here** | +| `sub__.log` | One file per sub-agent spawned during the run (e.g. `sub_research_agent_2a707e74.log`) | A specific delegated job misbehaved; see [Sub-agents](sub-agents.md) | -The split works through attribution tags: every line carries a `session` field and an `agent` field (`main` for the main agent, `sub::` for lines emitted inside a sub-agent's run, including its actions and LLM calls). The per-session and per-sub-agent files are filtered views of the same stream. `all.log` keeps the cross-session ordering that the filtered files lose. +The split works through an attribution tag: every line carries an `agent` field: `main` for the main agent, `sub::` for lines emitted inside a sub-agent's run (including its actions and LLM calls). `main.log` and the per-sub-agent files are filtered views of the same stream. `all.log` keeps the cross-agent ordering that the filtered files lose. ## Reading a line ``` -2026-07-17 02:17:32.811 | INFO | main | main | app.scheduler.manager:initialize:83 - [SCHEDULER] Initialized with 5 schedule(s) -^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -timestamp level session agent module:function:line message +2026-07-17 02:17:32.811 | INFO | main | app.scheduler.manager:initialize:83 - [SCHEDULER] Initialized with 5 schedule(s) +^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +timestamp level agent module:function:line message ``` - **Level:** `DEBUG` < `INFO` < `WARNING` < `ERROR`. The file threshold is INFO, and the harness narrates generously at INFO, so most context is captured by default. -- **Session / agent:** which lane and which worker emitted the line. +- **Agent:** `main` or `sub::` (the per-sub-agent files omit this column because the filename already says it). - **module:function:line** points at the exact source location. Open the module and jump to the line for full context. - Errors include full tracebacks (`backtrace` and `diagnose` are enabled). @@ -34,9 +34,9 @@ Most subsystems prefix their messages with a bracketed tag, which makes grep the | Tag | Covers | |---|---| -| `[REACT]` | The agent loop — each trigger consumed, each reaction; `[REACT ERROR]` for caught loop-level exceptions | +| `[REACT]` | The agent loop: each trigger consumed, each reaction; `[REACT ERROR]` for caught loop-level exceptions | | `[ACTION]` | Action preparation and execution | -| `[SESSION]` | Session lifecycle and caches | +| `[TASK]` | Task lifecycle: create, update, end | | `[MEMORY]` | Memory indexing, processing, retrieval. See [Memory](memory.md) | | `[MCP]` | MCP server init, connection, tool calls | | `[SCHEDULER]` | Schedule loops: sleep-until times, wakes, fires. See [Scheduling](scheduling.md) | @@ -51,11 +51,11 @@ Find the newest run first, since it's the one you almost always want: cd logs && ls -t | head -2 # newest run folders ``` -**Why did a run fail?** Errors first, then rewind for the story leading up to them: +**Why did a task fail?** Errors first, then rewind for the story leading up to them: ```bash grep -n "ERROR" logs//all.log | tail -20 -grep -n "\[REACT ERROR\]" logs//all.log +grep -n "\[REACT ERROR\]\|\[TASK\]" logs//all.log ``` Then open `all.log` at the line numbers you found and read upward. The `[ACTION]` and `[REACT]` lines just before an error usually name the exact action and input that broke. @@ -75,7 +75,7 @@ tail -f logs//all.log | grep "\[SCHEDULER\]" You'll see each loop's `sleeping until
-## Runs & workflows +## Task modes -The agent scales its process to the size of the work. [Runs](modes/index.md) maps how: [quick requests](modes/simple-task.md) get direct answers, [substantial work](modes/complex-task.md) gets requirements, todos, and verification, [background workflows](modes/special-workflows.md) run system jobs like memory and planning, and [proactive](modes/proactive.md) is where the agent proposes work on its own. +How the loop behaves depends on the task's mode. [Task modes](modes/index.md) maps the decision. Each mode then has a full reference: [simple](modes/simple-task.md), [complex](modes/complex-task.md), the system-initiated [special workflows](modes/special-workflows.md), and [proactive](modes/proactive.md), where the agent proposes work on its own. ## Interfaces -CraftBot has two interfaces over one shared engine: the [browser UI](interfaces/browser.md) (the default: multi-session chat, live activity view, Living UI tabs, settings) and the [CLI](interfaces/cli.md) (terminal chat for scripting and headless machines). The [UI layer](interfaces/ui-layer.md) page explains the shared architecture. Overview and comparison: [Interfaces](interfaces/index.md). +CraftBot has two interfaces over one shared engine: the [browser UI](interfaces/browser.md) (the default: chat, live action panel, Living UI tabs, settings) and the [CLI](interfaces/cli.md) (terminal chat for scripting and headless machines). The [UI layer](interfaces/ui-layer.md) page explains the shared architecture. Overview and comparison: [Interfaces](interfaces/index.md). ## Commands diff --git a/mkdocs/docs/core/interfaces/browser.md b/mkdocs/docs/core/interfaces/browser.md index 13c842d9..858196af 100644 --- a/mkdocs/docs/core/interfaces/browser.md +++ b/mkdocs/docs/core/interfaces/browser.md @@ -1,6 +1,6 @@ # Browser interface -The browser interface is CraftBot's default: a React web UI where you chat with the agent across multiple sessions, watch its actions execute live, browse the agent's workspace, and configure everything without touching a config file. If you followed the [Quickstart](../../start/quickstart.md), this is what opened at `http://localhost:7925`. +The browser interface is CraftBot's default: a React web UI where you chat with the agent, watch tasks and actions execute live, browse the agent's workspace, and configure everything without touching a config file. If you followed the [Quickstart](../../start/quickstart.md), this is what opened at `http://localhost:7925`. ## Launch @@ -25,11 +25,12 @@ A sidebar on the left, the active page on the right. The sidebar (collapsible) h | Sidebar item | What it opens | |---|---| -| **New Chat** | Starts a fresh chat session | -| **Chats** | The session list: **Main** plus every chat session you've created, with busy/unread indicators. Sessions auto-title from the first exchange; the options menu renames, clears, or deletes one | +| **Chat** | The main conversation view (the home page) | +| **Tasks** | Full-page view of every task and its actions | | **Dashboard** | Usage metrics and agent activity | | **Workspace** | File browser for the agent's workspace | -| **Living UI** (one tab per project) | The running [Living UI](../../living-ui/index.md) app, rendered in-page; **Add Living UI** starts a new one | +| One tab per [Living UI](../../living-ui/index.md) project | The running Living UI app, rendered in-page | +| **Add Living UI** | Opens the create dialog for a new Living UI project | | **Settings** | All settings pages (pinned at the bottom) | Above Settings sits a small toolbar with the version number, a **Playbooks** button, a light/dark theme toggle, and GitHub/Discord links. @@ -38,11 +39,15 @@ On the very first launch you won't see any of this yet. A full-screen [onboardin ## Chat -The core surface. Messages stream in with markdown rendering; you can attach files to a message (with a preview before sending) and the agent can send files back into the chat. Typing `/` opens a command autocomplete listing every [slash command](../commands/builtin.md), including one entry per enabled skill. Reply directly on a specific message bubble to answer that exact question. A stop control force-stops the current run. An animated mascot reflects what the agent is currently doing. +The core surface. Messages stream in with markdown rendering; you can attach files to a message (with a preview before sending) and the agent can send files back into the chat. Typing `/` opens a command autocomplete listing every [slash command](../commands/builtin.md), including one entry per enabled skill. -## Activity +Beside the conversation sits a resizable panel showing active and recent tasks. From it you can reply directly into a task's session, confirm a task complete, cancel it, resume it, or delete it. An animated mascot sits above the chat and reflects what the agent is currently doing. -While the agent works, an always-visible **Working** row shows the live run, and the activity view expands it: every action renders with its inputs and outputs, chunked per run. Long values are collapsible, and action-specific renderers format things like file writes and searches readably. Past activity is restored when you reopen a session. +## Tasks + +The full-screen version of the task panel. Every task expands into its action list, and every action renders as a card with its inputs and outputs. Long values are collapsible, and action-specific renderers format things like file writes and searches readably. Buttons per task: reply, complete, cancel, resume, delete. + +Completed tasks have one extra feature: a **skill creator** that turns a finished task into a reusable [skill](../concepts/skills.md), or folds it into an existing one, so the agent can repeat the workflow on demand. ## Dashboard @@ -61,10 +66,10 @@ Eight pages, selected from a category rail: | **General** | Agent identity and general behavior | | **Proactive** | [Proactive mode](../modes/proactive.md) configuration | | **Memory** | The agent's memory | -| **Model** | LLM provider, API keys, model selection — the UI equivalent of `/provider` (see [LLM providers](../providers/llm.md)) | +| **Model** | LLM provider, API keys, model selection: the UI equivalent of `/provider` (see [LLM providers](../providers/llm.md)) | | **MCPs** | Add, edit, enable/disable [MCP servers](../../integrations/mcp.md) | | **Skills** | Browse and toggle [skills](../concepts/skills.md) | -| **Integrations** | Connect [external services](../../integrations/index.md) — token entry and OAuth flows | +| **Integrations** | Connect [external services](../../integrations/index.md): token entry and OAuth flows | | **Living UI** | [Living UI](../../living-ui/index.md) project settings | ## Playbooks @@ -80,7 +85,7 @@ Each Living UI project the agent builds appears as its own sidebar tab and rende | Port | Serves | |---|---| | `7925` | Frontend (the page you open) | -| `7926` | Agent backend — WebSocket plus HTTP API, proxied through the frontend | +| `7926` | Agent backend: WebSocket plus HTTP API, proxied through the frontend | Both bind to **localhost** only, and the browser UI has **no built-in authentication**. Anyone who can reach the port controls your agent. Never expose these ports directly to the internet. For access from another machine, tunnel: diff --git a/mkdocs/docs/core/interfaces/cli.md b/mkdocs/docs/core/interfaces/cli.md index f1352f0c..c4e4d54d 100644 --- a/mkdocs/docs/core/interfaces/cli.md +++ b/mkdocs/docs/core/interfaces/cli.md @@ -20,7 +20,7 @@ Type messages to chat, `/commands` to configure, `/exit` (or Ctrl+C / EOF) to qu Alternative entry points: ```bash -python -m app.main # agent driver directly — CLI is its default mode +python -m app.main # agent driver directly; CLI is its default mode python -m app.main --provider anthropic --api-key sk-ant-... # override settings.json for this run python craftbot.py start --cli # run the CLI agent as a background service ``` diff --git a/mkdocs/docs/core/interfaces/index.md b/mkdocs/docs/core/interfaces/index.md index 21ef6050..ac1057bc 100644 --- a/mkdocs/docs/core/interfaces/index.md +++ b/mkdocs/docs/core/interfaces/index.md @@ -8,7 +8,7 @@ CraftBot has exactly two user-facing interfaces (the **browser UI** and the **te --- - The default. React web UI with multi-session chat, live activity view, dashboard, workspace browser, and full settings. Requires Node.js. + The default. React web UI with chat, task panel, dashboard, workspace browser, and full settings. Requires Node.js. - :material-console:{ .lg .middle } __[CLI](cli.md)__ @@ -30,8 +30,8 @@ CraftBot has exactly two user-facing interfaces (the **browser UI** and the **te |---|---|---| | Launch | `python run.py` | `python run.py --cli` | | Requirements | Python + Node.js 18+ | Python only | -| Chat | Multiple sessions, streaming, markdown, attachments | Plain text, line by line | -| Activity visibility | Live activity view with per-action inputs/outputs | Inline one-line status per action | +| Chat | Streaming, markdown, attachments | Plain text, line by line | +| Task & action visibility | Live task panel with per-action inputs/outputs | Inline one-line status per task/action | | Settings | Full settings pages (Model, MCPs, Skills, Integrations, ...) | Commands only (`/provider`, `/mcp`, `/skill`, `/cred`) | | [Living UI](../../living-ui/index.md) apps | Rendered in-app, one tab per project | Not displayable | | Best for | Daily use, watching work happen, setup | Servers, SSH, machines without Node.js | @@ -56,7 +56,7 @@ For an always-on assistant that starts at login and survives closing the termina ## One brain, two skins -Both interfaces are thin adapters over `app/ui_layer/`. A single `UIController` owns the event bus, state store, and command registry; the browser and CLI adapters just render what flows through it. That's why every slash command works identically in both, and why switching interfaces never loses agent state. Memory, sessions, and settings live with the agent, not the interface. Details in [UI layer](ui-layer.md). +Both interfaces are thin adapters over `app/ui_layer/`. A single `UIController` owns the event bus, state store, and command registry; the browser and CLI adapters just render what flows through it. That's why every slash command works identically in both, and why switching interfaces never loses agent state. Memory, tasks, and settings live with the agent, not the interface. Details in [UI layer](ui-layer.md). ## Next diff --git a/mkdocs/docs/core/interfaces/ui-layer.md b/mkdocs/docs/core/interfaces/ui-layer.md index 38fa029d..a866bfbb 100644 --- a/mkdocs/docs/core/interfaces/ui-layer.md +++ b/mkdocs/docs/core/interfaces/ui-layer.md @@ -14,7 +14,7 @@ At the center sits **`UIController`** (`app/ui_layer/controller/ui_controller.py | `UIStateStore` | Reactive store for UI state (agent status, panels, GUI flags) | | `CommandRegistry` | Every slash command, with names and aliases | | `CommandExecutor` | Parses `/command args`, resolves it in the registry, runs it | -| Active adapter | The one interface currently rendering — CLI or browser | +| Active adapter | The one interface currently rendering: CLI or browser | The adapters live in `app/ui_layer/adapters/`: `base.py` defines the `InterfaceAdapter` contract, `cli_adapter.py` renders with ANSI text, and `browser_adapter.py` runs the backend server and streams JSON over WebSocket to the React frontend. Each adapter fills in component protocols (chat, action panel, status bar, input, footage, menu from `app/ui_layer/components/protocols.py`) in its own medium. That's the entire difference between the interfaces. @@ -27,7 +27,7 @@ render: agent events ─→ EventTransformer ─→ EventBus ─→ active ada input: adapter ─→ UIController.submit_message() ─→ CommandExecutor ─→ agent ``` -**Agent → screen.** Everything the agent does (messages, action results, run-state changes) becomes a typed `UIEvent` on the bus: `USER_MESSAGE`, `AGENT_MESSAGE`, `ACTION_START`, `ACTION_END`, `RUN_STATE_CHANGED`, `AGENT_STATE_CHANGED`, and friends. The adapter subscribes and renders each in its own way: the CLI prints a formatted line, the browser pushes a WebSocket frame that updates the chat or activity view. This is the same [event stream](../concepts/event-stream.md) that feeds the logs. +**Agent → screen.** Everything the agent does (messages, task starts, action results, state changes) becomes a typed `UIEvent` on the bus: `USER_MESSAGE`, `AGENT_MESSAGE`, `TASK_START`, `TASK_END`, `ACTION_START`, `ACTION_END`, `AGENT_STATE_CHANGED`, `FOOTAGE_UPDATE`, and friends. The adapter subscribes and renders each in its own way: the CLI prints a formatted line, the browser pushes a WebSocket frame that updates the chat or task panel. This is the same [event stream](../concepts/event-stream.md) that feeds the logs. **You → agent.** Whatever you type lands in `UIController.submit_message()`, which does one important thing first: it offers the text to the `CommandExecutor`. If the message starts with `/` it's handled as a [command](../commands/index.md) and never reaches the agent. Commands belong to the interface, not the conversation. Otherwise the controller emits the user-message event and routes the text to the agent. @@ -51,7 +51,7 @@ The UI layer also owns everything that must behave the same across interfaces: | Directory | What it is | |---|---| | `themes/` | Theme definitions and the per-adapter styling contract (ANSI codes for CLI, styles for browser) | -| `settings/` | The settings backends — provider, model, MCP, skill, memory, proactive, Living UI — used by both the settings pages and the `/provider`, `/mcp`, `/skill` commands | +| `settings/` | The settings backends (provider, model, MCP, skill, memory, proactive, Living UI), used by both the settings pages and the `/provider`, `/mcp`, `/skill` commands | | `onboarding/` | The shared first-run wizard flow that the browser renders as pages and the CLI as numbered prompts | | `metrics/` | The usage collector behind the browser dashboard | | `components/Mascot/` | The animated mascot shown on the browser chat page | diff --git a/mkdocs/docs/core/modes/complex-task.md b/mkdocs/docs/core/modes/complex-task.md index 575f8223..129080eb 100644 --- a/mkdocs/docs/core/modes/complex-task.md +++ b/mkdocs/docs/core/modes/complex-task.md @@ -1,125 +1,138 @@ -# Substantial work +# Complex task mode -Substantial work is anything that needs a plan: multi-step work, file deliverables, irreversible operations, "projects". The agent locks a requirement contract before it even acknowledges you, works through a live phase-prefixed todo list, verifies its own output against the contract, and delivers with a final message that ends the run. This page is the complete behavior reference; for the guided version, see [Your first task](../../start/first-task.md). +Complex task mode is how CraftBot handles anything that needs a plan: multi-step work, file deliverables, irreversible operations, "projects". The agent locks a requirement contract, works through a live phase-prefixed todo list, verifies its own output, and (the defining feature) **does not close the task until you approve the result**. -## When the agent scales up +For the guided version, see [Your first task](../../start/first-task.md#walkthrough-2-a-complex-task). This page is the complete behavior reference. -The agent gives a request the full treatment when any of these hold: +## When the agent picks complex + +The agent chooses `task_mode: "complex"` at `task_start` (it's also the default) when any of these hold: - The plan has more than ~3 actions. -- The output is a file or artifact you should review. +- The output is a file or artifact you should review and approve. - The work touches external state: it sends messages on your behalf, makes purchases, or modifies third-party data. -- You call it a "project", or it spans multiple sessions or days. +- The work spans multiple sessions or days. + +## The state machine -## The shape of a substantial run +A well-run complex task moves through a fixed sequence: ``` -trigger (your message) +task_start(task_mode="complex") │ ▼ -set_requirement(...) ← FIRST move: lock what "done" must contain +set_requirement(...) ← FIRST move: lock what "done" must contain │ ▼ -send_message(continue_work=true) ← acknowledge you immediately, one sentence +send_message ← acknowledge you immediately │ ▼ -update_todos(...) ← the full plan, all "pending", phase-prefixed +task_update_todos(...) ← the full plan, all "pending", phase-prefixed │ ▼ loop { mark ONE todo "in_progress" execute actions that advance it (parallel within a todo is fine) mark it "completed" - discovered missing info? → add a fresh "Collect:" todo + discovered missing info? → add a "Collect:" todo, revert } │ ▼ -set_requirement(...) ← Verify: re-score every item satisfied / violated +send_message ← final result + explicit approval request │ ▼ -send_message ← final result + artifact paths; ends the run +wait for your reply ← task pauses; nothing blocks + │ + ▼ +task_end ← only after your explicit approval ``` ### The requirement contract -Before anything else, the agent calls `set_requirement`: a list of checkable items, each with a `dimension` (content, structure, length, format, ...), a specific falsifiable `requirement`, a concrete `done_when` test, and a `status` (`pending` / `satisfied` / `violated`). This is distinct from the todo list: todos are the *steps*, requirements are the *contract* for the finished output, and substantial work needs both. - -During the Verify phase the agent calls `set_requirement` again with every item re-scored. A `violated` item means rework before delivery. Each call replaces the whole list (it never appends), and the current list is pinned into the agent's context every turn, rendered with `[SAT]` / `[VIO]` / `[ ]` markers, so it survives even when older events are summarized away. +Before anything else, the agent calls `set_requirement`, a list of checkable items, each with a `dimension` (content, structure, length, format, ...), a specific falsifiable `requirement`, a concrete `done_when` test, and a `status` (`pending` / `satisfied` / `violated`). This is distinct from the todo list: todos are the *steps*, requirements are the *contract* for the finished output. During the Verify phase the agent re-scores every item. A `violated` item means rework before it asks for your approval. The list is pinned into the agent's context every turn, so it survives even when older events are summarized away. -Practical upshot for you: constraints you state in the first message ("under 2 pages", "cite sources") get encoded here, which is why stating them up front beats correcting after delivery. +Practical upshot for you: constraints you state in the first message ("under 2 pages", "cite sources") get encoded here, which is why stating them up front beats correcting at the approval gate. ### Todos and phase prefixes -The plan lives in the run's todo list, updated via `update_todos`. Each todo has a `content`, a `status` (`pending` / `in_progress` / `completed`), and starts with one of five phase prefixes: +The plan lives in the task's todo list, updated via `task_update_todos`. Each todo has a `content`, a `status` (`pending` / `in_progress` / `completed`), and must start with one of six mandatory phase prefixes: | Prefix | Meaning | |---|---| +| `Acknowledge:` | Restate your goal in the agent's own words | | `Collect:` | Gather inputs: read files, search, ask you, list integrations | | `Execute:` | Do the work: generate, transform, send, write | -| `Verify:` | Check the output meets the goal: re-read files, run tests, smoke-test | -| `Deliver:` | Present the result to you | +| `Verify:` | Check the output meets the goal: re-read files, run tests | +| `Confirm:` | Present the result to you for approval | | `Cleanup:` | Remove temp files, restore state, close connections | Rules the agent is held to: -- Exactly **one** todo is `in_progress` at a time. Always. -- Todos are marked `completed` only after the actions ran, never before. There is no batch-completing. +- Exactly **one** todo is `in_progress` at a time. Each turn works the current in-progress todo (or the first pending one if none is in progress). +- Todos are marked `in_progress` before the work and `completed` right after. There is no batch-completing. - Verify is never skipped for todos that produce files or change external state. -- Missing information discovered mid-Execute becomes a fresh `Collect:` todo. The agent collects rather than guesses. +- Cleanup never happens before you've signed off at Confirm. +- Missing information discovered mid-Execute becomes a fresh `Collect:` todo. The agent reverts rather than guesses. -The plan is revisable at any time: the agent adds, reorders, rewords, or drops todos as it learns, and you can watch all of this live in the todo list. +The plan is revisable at any time: the agent adds, reorders, rewords, or drops todos as it learns. You can watch all of this live in the todo list on the task card, which shows the current progress. -## Delivery, and approval as a question +## The approval gate -The final message summarizes what was done and lists artifacts with paths. Sending it ends the run: `continue_work=true` marks a progress update and keeps the run alive, while omitting it makes the message final. That flag is the run terminator; there is no separate completion action, and the agent never delivers a result and keeps working in the same message. +Complex tasks do not close themselves. The final message summarizes what was done, lists artifacts with paths, and explicitly asks for approval. Reply "looks good" and the agent runs Cleanup and calls `task_end(status="complete")`. Point out what's wrong and it keeps working *in the same task* with full context. Rejection is a revision loop, not a restart. There is no timeout that auto-approves. An unanswered task just waits (see below). -When the agent wants your sign-off before an irreversible step (sending an email on your behalf, purchasing, deleting), it makes the question its final message. The run ends, and your reply wakes a **new run in the same session** with the full event-stream context, so "yes, send it" continues seamlessly. The same pattern covers revision: reply with what's wrong and the new run picks the work back up with everything it knew before. There is no timeout and nothing is left running while you decide. +## Waiting, steering, and parallel work -## Steering and parallel work - -- **Steering.** Messages you send while a run is working fold into its next turn (all due triggers for a session aggregate into one checklist), so new requirements adjust the todos and "stop" winds the work down. You can also force-stop a run from the UI at any time. -- **Unrelated work** belongs in another session: sessions run independently in parallel, one turn at a time each. See [Sessions](../concepts/task-sessions.md). -- **Spin-offs.** Independent side-work gets deferred or parallelized with `schedule_task`: `schedule="immediate"` starts a separate run within seconds, and expressions like `"tomorrow at 9am"` or `"every day at 7am"` defer it. See [Scheduling](../concepts/scheduling.md). +- **Pauses.** When the agent asks you something with `wait_for_user_reply=true`, the task's `waiting_for_user_reply` flag pauses trigger scheduling. Your reply routes back into the task automatically. If nothing arrives, a silent wait trigger re-queues every 3 hours, so the task idles indefinitely without consuming tokens. +- **Steering.** Messages you send about the running task route into it: new requirements adjust the todos, and "stop" winds the work down. +- **Unrelated messages** don't interrupt, because [session routing](../concepts/task-sessions.md) sends them to a fresh conversation, and multiple tasks run side by side. ## Action and token limits -Every run carries per-run counters (`action_count`, `token_count`) checked each turn against `max_actions_per_task` and `max_tokens_per_task`. Defaults live in `agent_core/core/state/types.py`: **150 actions** and **6,000,000 tokens** per run. +Every task carries per-task counters (`action_count`, `token_count`) checked after each turn. Defaults live in `app/config.py`: **500 actions** and **12,000,000 tokens** per task. -At 100% of either limit the run pauses: you get a chat message with **Continue** / **Stop** options, no continuation is queued, and the session sits idle until you pick one (or send a new message). Choosing **Continue** resets both counters to zero and the run resumes; while paused, the agent issues no actions. There is no advance warning before the gate. +| Threshold | What happens | +|---|---| +| 80% of either limit | A warning event tells the agent to wrap up, deliver the best partial result, or ask you whether to abort | +| 100% of either limit | The task **pauses**: you get a chat message with **Continue** / **Abort** buttons, the task card shows *paused*, and a long-delay trigger keeps the task alive while it waits | -Token accounting bills only **uncached** tokens: each turn adds `tokens_used - cached_tokens` to the counter, so warm-cache runs stretch much further than raw usage suggests. See [Context engine](../concepts/context-engine.md). +Choosing **Continue** resets both counters to zero and resumes the task, and **Abort** ends it. While paused, the agent issues no actions. The decision is entirely yours. ## Failure paths - **Recoverable errors.** Failed actions return `status: "error"`. The agent retries transient failures once, changes approach on semantic failures, and escalates to you with one specific question when blocked. -- **Impossible work.** When the work can't be done (missing access, contradictory requirements), the agent's final message summarizes what it tried, includes any salvageable partial result, and ends the run. It never fabricates success. -- **Fatal LLM failures.** Repeated consecutive LLM call failures halt the run and surface an error message. Fix the provider configuration, then send any chat message (for example "continue") to resume. See [LLM providers](../providers/llm.md). +- **Deliberate abort.** When the work is impossible (missing access, contradictory requirements), the agent summarizes what it tried, sends any salvageable partial result, and calls `task_end(status="abort")`. +- **Fatal LLM failures.** Repeated consecutive LLM call failures cancel the task automatically and surface an error dialog. Fix the provider configuration, then retry. See [LLM providers](../providers/llm.md). + +## What happens at the end + +On `task_end`: + +- A record is appended to `agent_file_system/TASK_HISTORY.md` with the task name, ID, status (`completed` / `cancelled` / `failed`), timestamps, a one-paragraph summary, the original instruction, and the skills and action sets used. +- The task's scratch directory `agent_file_system/workspace/tmp//` is cleaned automatically. Deliverables saved to `agent_file_system/workspace/` persist. See [Agent file system](../concepts/agent-file-system.md). -## Where the output goes +## Mode is fixed -- Files you should keep land in `agent_file_system/workspace/`. -- Drafts and intermediate state go to the session's scratch directory, `agent_file_system/workspace/sessions//`, which persists for the session's life and is removed when the session is deleted. -- Multi-run initiatives get a **mission**: a directory at `agent_file_system/workspace/missions//` anchored by an `INDEX.md` (goal, status, key findings, next steps) that lets any future run restore context and pick up where the last one stopped. The agent scans the missions directory at the start of every substantial run. See [Agent file system](../concepts/agent-file-system.md). +A task never changes mode after it starts. Simple work discovered inside a complex task just gets done. Complex work discovered inside a simple task ends the simple task and spawns a complex one (see [Simple task](simple-task.md#when-the-work-grows-mid-task)). There is no demotion from complex to simple. ## Caching -Substantial runs benefit most from prompt caching: the loop can span dozens of turns, and each one reuses the cached context prefix, appending only the new events. Combined with uncached-only token billing, this is what keeps long runs affordable. See [Context engine](../concepts/context-engine.md). +Complex tasks benefit most from **session-level prompt caching**: the loop can span dozens of turns, and each one reuses the cached context prefix, appending only the new events. See [Context engine](../concepts/context-engine.md). -## Observing a substantial run +## Observing a complex task | Where | What you see | |---|---| -| Todo list | The live plan with phase prefixes and per-todo status | -| Action panel | Every action with inputs and results, including `update_todos` and `set_requirement` calls | -| Chat | Acknowledgement, milestone updates, questions, the delivery message, limit dialogs | -| `agent_file_system/EVENT.md` | The main session's full event log | -| `logs//all.log` | Grep `[REACT]` for the run flow, `[ACTION]` for execution, `[LIMIT]` for limit events | +| Task panel | The task card with its live todo list and status (running / paused / completed) | +| Action panel | Every action with inputs and results, including `task_update_todos` and `set_requirement` calls | +| Chat | Acknowledgement, milestone updates, questions, the approval request, limit dialogs | +| `agent_file_system/TASK_HISTORY.md` | The end-of-task record | +| `logs//main.log` | Grep `[TASK]` for lifecycle, `[ACTION]` for execution, `[LIMIT]` for limit events | More on logs in [Logs](../concepts/logs.md). ## Related -- [Quick requests](simple-task.md): the path for 1-3 action work -- [Runs overview](index.md): how the agent scales its process -- [Sessions](../concepts/task-sessions.md): reply routing and parallel sessions -- [Triggers](../concepts/triggers.md): aggregation and the continuation machinery underneath runs +- [Simple task](simple-task.md): the mode for 2–3 action work +- [Task modes overview](index.md): how the mode gets picked +- [Task sessions](../concepts/task-sessions.md): reply routing and parallel tasks +- [Triggers](../concepts/triggers.md): the wait/re-queue machinery underneath pauses diff --git a/mkdocs/docs/core/modes/index.md b/mkdocs/docs/core/modes/index.md index 5a174045..fd9ee6be 100644 --- a/mkdocs/docs/core/modes/index.md +++ b/mkdocs/docs/core/modes/index.md @@ -1,49 +1,65 @@ -# Runs +# Task modes -Every message you send (and every scheduled event that fires) wakes the agent for exactly one **run**: a stretch of work inside a **session** that starts on a trigger and continues turn by turn until the agent delivers its result or decides no reply is needed. There is one pipeline for all work, and the agent scales its process to the size of the request: a quick question gets a direct answer; a "project" gets a requirement contract, a live todo plan, and a verification pass before delivery. This page is the map; the linked pages are the full treatment. +Every message you send (and every scheduled event that fires) becomes exactly one kind of turn: a **conversation**, a **simple task**, a **complex task**, or one of two system-initiated **special workflows**. This page is the decision map: how CraftBot picks, what each mode is for, and where to read the full behavior of each. -If you haven't run anything yet, do [Your first task](../../start/first-task.md) first. It walks you through one quick request and one substantial piece of work live. These pages are the reference treatment. +If you haven't run a task yet, do [Your first task](../../start/first-task.md) first. It walks you through one simple and one complex task live. These pages are the reference treatment. -## Sessions and runs +## How a message becomes a mode -The unit of work is a **session** (main, chat, or Living UI). Each session has its own event stream, its own durable trigger queue, and a serial consumer loop: one turn at a time per session, with different sessions running independently in parallel. How sessions and reply routing work is covered in [Sessions](../concepts/task-sessions.md). +Routing happens at the top of the agent loop (`react()` in `app/agent_base.py`), in a fixed order: -A **run** is one wake of a session: +1. **Memory trigger?** A scheduled memory-processing event short-circuits everything else and runs the [memory workflow](special-workflows.md#the-memory-workflow). +2. **Proactive trigger?** A heartbeat or planner event runs the [proactive workflow](special-workflows.md#the-proactive-workflow). +3. **Is a task already running for this session?** If yes, the message routes into that task: the complex-task workflow if the task's mode is `complex`, the simple-task workflow if `simple`. +4. **Otherwise: conversation mode.** -1. A trigger fires: your message, a scheduled event, or a workflow event. Everything currently due for that session folds into a single turn, presented to the agent as a numbered checklist ([Triggers](../concepts/triggers.md)). -2. The agent works turn by turn through the same four-phase beat every time: select actions, prepare them, execute them, finalize ([Agent loop](../concepts/agent-loop.md)). -3. The run ends when the only action(s) the agent selects are **terminal**: a final `send_message` (one without `continue_work=true`) or `end_turn`. Any other turn queues a continuation trigger and the next turn follows. +Two things follow from this order. Special workflows never compete with your messages. They are separate turns with their own trigger types. And *you* never pick a mode: the routing is decided by session state, and the task mode itself is decided by the agent when it calls `task_start` with `task_mode: "simple"` or `"complex"` (default `complex`). How triggers and sessions drive this is covered in [Agent loop](../concepts/agent-loop.md), [Triggers](../concepts/triggers.md), and [Task sessions](../concepts/task-sessions.md). -You never pick a mode and neither does the agent, because there are no modes: every turn runs the same pipeline, and the agent decides how much process the work deserves. +## The three user-facing modes -## How the agent scales its process +| | Conversation | Simple task | Complex task | +|---|---|---|---| +| Exists when | No task running | `task_start` with `simple` | `task_start` with `complex` | +| For | Chat, routing, clarification | Quick, obvious work | Multi-step work needing a plan | +| Can do | Reply, start tasks, ignore | Full action surface of its action sets | Full action surface of its action sets | +| Todo list | n/a | No | Yes: live, phase-prefixed | +| Typical length | One turn | 2–3 actions | Many actions, many turns | +| Ends | n/a | By itself, after delivering the result | Only after **you approve** the result | +| Prompt caching | Prefix caching only | Session caching | Session caching | -| Signal in your request | What the agent does | -|---|---| -| Quick lookup, single answer, 1-3 obvious actions | Executes the action(s) and replies; the reply is the final message and ends the run | -| Input that needs no reply (an emoji ack, third-party noise) | `end_turn`: the run ends silently | -| Multi-step work, file deliverables, irreversible operations, "projects" | Locks a requirement contract (`set_requirement`), acknowledges you, plans with `update_todos`, works phase by phase, verifies, then delivers | +Conversation mode is deliberately narrow: the agent can reply (`send_message`), start one or more tasks with `task_start` (several in parallel is allowed, so "research A and B" becomes two tasks at once), or deliberately `ignore` a message that needs no response (mostly relevant for group-chat integrations, where not every message is addressed to the agent). It cannot touch files, browse, or call integrations. All of that requires a task. + +## Which task mode does the agent pick? -The two paths in detail: +The agent decides at `task_start` based on the size of the request: + +| Signal in your request | Mode picked | +|---|---| +| Quick lookup, single answer, one obvious action | Simple | +| Result is the reply itself, nothing for you to review | Simple | +| More than ~3 actions, research, planning | Complex | +| Output is a file or artifact you should approve | Complex | +| Irreversible external effects (sends, purchases, config changes) | Complex | +| "Project"-scale or multi-session work | Complex | -- [Quick requests](simple-task.md): how small asks flow, and when the agent stays silent -- [Substantial work](complex-task.md): the requirement contract, the todo phases, verification, and delivery +The mode is fixed for the task's lifetime. If a simple task turns out to be bigger than expected, the agent doesn't silently keep going. It ends the simple task with the partial result and schedules a complex follow-up. Details on both pages: -## Asking you something +- [Simple task](simple-task.md): lifecycle, auto-completion, what happens when the work grows +- [Complex task](complex-task.md): the todo state machine, requirement contract, approval gate, limits -There is no approval gate wired into the machinery and no waiting state. When the agent needs your input (including sign-off before an irreversible step), it makes the question its **final message**: the run ends, the session sleeps, and your reply wakes a **new run in the same session**. Because the session's event stream carries the full history, the new run picks up exactly where the old one left off. A follow-up after delivery works the same way, which makes revision a continuation rather than a restart. +## The special workflows -## Workflow runs +Two turns are never started by you: -Some runs are never started by you. Memory processing, the proactive heartbeat, and the day/week/month planners run **in the main session** on the scheduler's clock: each run temporarily loads a dedicated skill (plus the action sets it needs), does its work silently, and unloads everything when the run ends. +- **Memory**: a nightly (3 AM) distillation run that turns the day's events into long-term memory. See [Special workflows](special-workflows.md) and [Memory](../concepts/memory.md). +- **Proactive**: heartbeats every 30 minutes and day/week/month planners that let the agent execute and plan recurring work on its own. See [Special workflows](special-workflows.md) for the mechanics and [Proactive](proactive.md) for the full user guide. -- [Workflow runs](special-workflows.md): schedules, what each workflow loads, and the silent-execution rules -- [Proactive](proactive.md): the full user guide to recurring tasks, permission tiers, and the planners +Both workflows do little themselves: they check their enable switch, then create an ordinary simple task loaded with a dedicated skill. The work itself runs through the same simple-task machinery described above. ## Related - [Your first task](../../start/first-task.md): the tutorial version of this page -- [Agent loop](../concepts/agent-loop.md): the cycle every turn runs on -- [Triggers](../concepts/triggers.md): what wakes the agent and how due triggers aggregate -- [Sessions](../concepts/task-sessions.md): parallel sessions and message routing +- [Agent loop](../concepts/agent-loop.md): the cycle every mode runs on +- [Triggers](../concepts/triggers.md): what wakes the agent and carries the routing type +- [Task sessions](../concepts/task-sessions.md): how parallel tasks and message routing work - [Scheduling](../concepts/scheduling.md): the scheduler that fires memory and proactive triggers diff --git a/mkdocs/docs/core/modes/proactive.md b/mkdocs/docs/core/modes/proactive.md index bb309ba0..5f7e9f7d 100644 --- a/mkdocs/docs/core/modes/proactive.md +++ b/mkdocs/docs/core/modes/proactive.md @@ -1,13 +1,13 @@ # Proactive -Proactive behavior lets CraftBot work without being asked: a morning inbox summary, a weekly report, a daily plan for your day. These are recurring tasks the agent executes on schedule and, very conservatively, proposes on its own. It is conservative by design: the agent executes what's in its task registry and asks before adding anything to it. +Proactive mode lets CraftBot work without being asked: a morning inbox summary, a weekly report, a daily plan for your day. These are recurring tasks the agent executes on schedule and, very conservatively, proposes on its own. It is conservative by design: the agent executes what's in its task registry and asks before adding anything to it. ## The moving parts -Three pieces, all covered mechanically in [Workflow runs](special-workflows.md): +Three pieces, all covered mechanically in [Special workflows](special-workflows.md): - **`agent_file_system/PROACTIVE.md`**: the registry. Recurring task definitions (as YAML blocks) plus a Goals / Plan / Status section the planners maintain. -- **The heartbeat**: fires every 30 minutes at `:00` and `:30`. Collects every due task from PROACTIVE.md and executes them in one heartbeat run. +- **The heartbeat**: fires every 30 minutes at `:00` and `:30`. Collects every due task from PROACTIVE.md and executes them in one `Heartbeat` task. - **The planners**: a day planner (7 AM daily), week planner (Sunday 5 PM), and month planner (1st, 8 AM) that review recent activity and update the Goals / Plan / Status section. Proposing *new* recurring tasks is a rare, approval-gated side effect. All schedules live in `app/config/scheduler_config.json` and can be edited or toggled per entry (see [Scheduling](../concepts/scheduling.md)). @@ -20,7 +20,7 @@ The master switch is `proactive.enabled` in `app/config/settings.json` (default: { "proactive": { "enabled": true } } ``` -When disabled, heartbeat and planner triggers log and skip, and no runs start. The nightly memory run has its own `memory.enabled` toggle and is unaffected, and one-off tasks you scheduled explicitly still fire. +When disabled, heartbeat and planner triggers log and skip, and no tasks are created. The nightly memory run has its own `memory.enabled` toggle and is unaffected, and one-off tasks you scheduled explicitly still fire. One hard dependency: **schedules only fire while CraftBot is running.** On a laptop that sleeps at night, a 7 AM planner never fires. For dependable proactive behavior, run the agent persistently (see [Service mode](../../start/service-mode.md)). @@ -61,7 +61,7 @@ outcome_history: [] | `permission_tier` | yes | `0`–`3`, see below | | `run_count` | auto | Execution counter, maintained by the system | | `conditions` | no | e.g. `weekdays_only`, `market_hours_only`, `user_available` | -| `instruction` | yes | Multi-line, step-by-step spec (the most important field) | +| `instruction` | yes | Multi-line, step-by-step spec; the most important field | | `outcome_history` | auto | Last 5 run results (timestamp, result, success) | The instruction quality determines execution quality. Write exact steps, name the sources (which integration, which file), define the output format, and say what to do when data is missing. "Check emails and summarize important ones" is a bad instruction. A numbered eight-step procedure is a good one. The template at the top of PROACTIVE.md carries a full worked example. @@ -91,17 +91,17 @@ In practice, recurring tasks should be tier 0 or 1. The heartbeat executes silen ## What a heartbeat run actually does -When due tasks exist, the heartbeat run (loaded with the `heartbeat-processor` skill): +When due tasks exist, the `Heartbeat` task (running the `heartbeat-processor` skill): 1. Reads all enabled recurring tasks and confirms which are due. 2. Evaluates each against its conditions and a five-dimension rubric: Impact, Risk, Cost, Urgency, Confidence, each scored 1–5. Total 18+ executes, 13–17 may warrant asking you first, and below 13 skips this round. -3. Executes each passing task **inline** if it's quick and tier 0/1, or **spins it off as its own run** (via `schedule_task` with `schedule="immediate"`) when it needs multi-step execution or action sets the heartbeat doesn't carry. -4. Records the outcome to the task's `outcome_history` via `recurring_update_task`. The history is kept to the last 5 entries, which the planners read to decide whether a task is worth keeping or tuning. -5. Ends with `end_turn`, or with a tier-1 notification as its final message. Tier-1 notifications arrive as chat messages prefixed with a star, and nothing waits for a reply. +3. Executes each passing task **inline** if it's quick and tier 0/1, or **schedules it as a separate task** (via `schedule_task`) when it needs multi-step execution or action sets the heartbeat doesn't carry. +4. Records the outcome to the task's `outcome_history`. The history is kept to the last 5 entries, which the planners read to decide whether a task is worth keeping or tuning. +5. Ends silently. Tier-1 notifications arrive as chat messages prefixed with a star, and nothing waits for a reply. ## The approval model -The agent never quietly grants itself new recurring work. The planner skills are built around a hard conservatism rule: a new recurring task may only be suggested if you explicitly asked for the automation or demonstrably did the same thing at least three times. Most planner runs are expected to produce zero suggestions. When a planner does suggest one, the suggestion is a question and the planner's run ends with it. If you approve, your reply wakes a new run that adds the task; a rejection, or no reply at all, means nothing is ever added. +The agent never quietly grants itself new recurring work. The planner skills are built around a hard conservatism rule: a new recurring task may only be suggested if you explicitly asked for the automation or demonstrably did the same thing at least three times. Most planner runs are expected to produce zero suggestions. When a planner does suggest one, it messages you and waits. On approval it adds the task, and on rejection (or ~20 hours of silence) it drops the idea and ends without adding anything. Your broader preferences come from [onboarding](../../start/onboarding.md): a proactivity level (low waits for instructions, medium suggests when relevant, high proactively suggests) and **approval categories**, the kinds of actions the agent must always ask before taking on your behalf (sending messages, scheduling, file changes, purchases, or everything). These are written into `USER.md` under *Prefer Proactive Assistance* and *Approval Required For*, and the agent reads them when deciding how to act. @@ -122,11 +122,11 @@ Your broader preferences come from [onboarding](../../start/onboarding.md): a pr | Task exists but never fires | `enabled: false`, malformed YAML, or missing/damaged markers | Validate against the template format | | Ran but did the wrong thing | Vague `instruction` | Rewrite as numbered, specific steps; check `outcome_history` for what it actually did | -Ground truth for any run: each task's `outcome_history` in PROACTIVE.md, and `[PROACTIVE]` lines in `logs//all.log` (see [Logs](../concepts/logs.md)). +Ground truth for any run: the `Heartbeat` / planner task cards in the task panel, and `[PROACTIVE]` lines in `logs//main.log` (see [Logs](../concepts/logs.md)). ## Related -- [Workflow runs](special-workflows.md): how heartbeat and planner triggers are routed and executed +- [Special workflows](special-workflows.md): how heartbeat and planner triggers are routed and executed - [Scheduling](../concepts/scheduling.md): the scheduler behind the heartbeat, and one-off scheduled tasks - [Service mode](../../start/service-mode.md): keeping the agent alive so schedules fire - [Memory](../concepts/memory.md): the other system-initiated workflow diff --git a/mkdocs/docs/core/modes/simple-task.md b/mkdocs/docs/core/modes/simple-task.md index 8000828d..3e522c86 100644 --- a/mkdocs/docs/core/modes/simple-task.md +++ b/mkdocs/docs/core/modes/simple-task.md @@ -1,75 +1,90 @@ -# Quick requests +# Simple task mode -Most day-to-day requests fit in a short answer or one to three actions: look something up, send one message, convert one file. For these, the agent skips all ceremony. There is no todo list, no requirement contract, and no planning step. It does the work, delivers the result, and the delivery itself ends the run. This page is the complete behavior reference for that path; for the guided version, see [Your first task](../../start/first-task.md). +Simple task mode is CraftBot's lightweight path for work that fits in two or three actions: look something up, send one message, convert one file. There is no todo list, no planning step, and no approval gate. The agent does the work, delivers the result, and ends the task by itself. Most day-to-day requests run this way. -## When a request stays quick +For the guided version, see [Your first task](../../start/first-task.md#walkthrough-1-a-simple-task). This page is the complete behavior reference. -Nothing is chosen up front. The agent simply keeps the process minimal when all of these hold: +## When the agent picks simple -- The work is completable in roughly 1-3 actions (a weather lookup, a calculation, a single search-and-summarize, one message send). +The mode is chosen once, when the agent calls `task_start` with `task_mode: "simple"`. You don't set it, and it doesn't change for the task's lifetime. The agent is trained to pick simple when all of these hold: + +- The work is completable in roughly 2–3 actions (weather lookup, calculation, a single search-and-summarize, one message send). - The result **is** the reply, with no file or artifact you need to review. - Nothing irreversible happens externally (no purchases, no destructive writes). -Anything heavier gets the full [substantial work](complex-task.md) treatment: a requirement contract, a todo plan, and verification. +Anything heavier routes to [complex task](complex-task.md) instead. If the agent guessed wrong and answered actionable work in plain conversation, phrase the request as a deliverable and it will start a task. ## Lifecycle ``` -trigger (your message) +task_start(task_mode="simple") ← from conversation mode + │ + ▼ +(optional) send_message ← brief acknowledgement + │ + ▼ +execute the 1–3 work actions ← may be parallel in one turn │ ▼ -execute the 1-3 work actions ← may be parallel in one turn +send_message ← deliver the result │ ▼ -send_message ← the result; final message, ends the run +task_end ← auto-completes; no approval gate ``` -Rules the agent follows on this path: +Rules the agent follows in this mode: + +- **No todos.** Simple tasks never call `task_update_todos` and never use phase prefixes. The work is small enough that planning would only slow it down. +- **Never ends silently.** The final `send_message` with the result always precedes (or accompanies) `task_end`. +- **Auto-completion.** Unlike complex tasks, there is no confirmation step. The task card flips to completed the moment the agent calls `task_end`. + +Each turn of a running simple task goes through the same four-phase beat as every other workflow (select actions, prepare them, execute them, finalize) but action selection uses a streamlined simple-task prompt with no todo-management instructions. See [Agent loop](../concepts/agent-loop.md). + +## What the agent can do inside a simple task -- **No todos.** Quick runs never call `update_todos` and never use phase prefixes. The work is small enough that planning would only slow it down. -- **Never ends silently after doing work.** A run that produced something always ends with a final `send_message` summarizing the result. -- **The reply is the terminator.** A `send_message` without `continue_work=true` is what ends the run; there is no separate completion action. +Starting a task is what unlocks the real action surface. Conversation mode can only reply, start tasks, or ignore. At `task_start`, CraftBot automatically selects **action sets** (groups of related actions) and **skills** based on your request; those selections are locked in when the task starts. Mid-task, the agent can add or remove action sets via the `action_set_management` action, but skills cannot be swapped. A different skill means a new task. See [Actions and action sets](../concepts/actions-and-action-sets.md) and [Skills](../concepts/skills.md). -## When no reply is needed at all +One structural limit: `task_start` cannot be called from inside a task. If a simple task needs to spawn separate work, the agent uses `schedule_task` with `schedule="immediate"` instead. -Some inputs deserve no response: an emoji-only acknowledgement, or noise from a group-chat integration where the message isn't addressed to the agent. For these the agent calls `end_turn`, which ends the run silently. `end_turn` is only for inputs that need no response; using it to skip a deserved reply is a hard rule violation, and it refuses to fire while a Living UI project is still building. +## Caching and cost -## What the agent can do inside a quick run +Simple tasks use **session-level prompt caching**: across the task's few turns, the context prefix is reused and only new events are appended, so multi-turn execution stays cheap. Conversation mode, by contrast, gets prefix caching only. Combined with the short action count, this makes simple tasks the fastest and cheapest way CraftBot does real work. See [Context engine](../concepts/context-engine.md). -Any loaded action is callable on any turn, and the agent can expand or shrink its own action surface in place: `add_action_sets` / `remove_action_sets` load and unload action-set bundles, and `use_skill` / `unload_skill` do the same for skills, all mid-run. The new actions appear in the next turn's prompt. So a quick request that needs one integration action is still quick: the agent loads the set, fires the action, and replies. See [Actions and action sets](../concepts/actions-and-action-sets.md) and [Skills](../concepts/skills.md). +## Waiting for you -To spin off separate work without holding up the reply, the agent uses `schedule_task` with `schedule="immediate"`, which queues a trigger that starts its own run within seconds. +A simple task can pause on you: if the agent sends a question with `wait_for_user_reply=true`, the task's `waiting_for_user_reply` flag is set and trigger scheduling pauses. Your next reply routes straight back into the task ([session routing](../concepts/task-sessions.md)). If no reply arrives, the agent re-queues a silent wait trigger every 3 hours, so the task idles without consuming tokens. -## Follow-ups +## When the work grows mid-task -There is no waiting state. Once the final message lands, the session sleeps. Your next message wakes a **new run in the same session**, and the session's event stream carries the full context over, so "actually, make that Celsius" just works. See [Sessions](../concepts/task-sessions.md). +Simple mode has a deliberate escape hatch, and it is *not* silently chaining more actions. If the agent discovers mid-task that the job is bigger than simple: -## When the work grows mid-run +1. It stops, delivers the partial result via `send_message`, and calls `task_end`. +2. It schedules the remainder as a new complex task: `schedule_task(schedule="immediate", mode="complex", ...)`. -Nothing is locked in at the start, so a request that turns out bigger than it looked doesn't need to be restarted or handed off. The agent scales up in place: it locks the deliverable with `set_requirement`, sends a one-line acknowledgement (`send_message` with `continue_work=true`), lays out a phase-prefixed plan with `update_todos`, and continues as [substantial work](complex-task.md). Independent side-work it discovers along the way gets spun off with `schedule_task(schedule="immediate", ...)` as its own run. +The task's `mode` field never changes mid-task. A task that started simple ends simple, and the heavier work gets a proper complex task with todos and an approval gate. ## Limits and failure paths -Quick runs sit on the same safety machinery as everything else, even though they rarely hit it: +Simple tasks run on the same safety machinery as complex ones, even though they rarely hit it: -- **Per-run counters.** Every run tracks `action_count` and `token_count` against `max_actions_per_task` and `max_tokens_per_task` (defaults 150 actions and 6,000,000 tokens, in `agent_core/core/state/types.py`). At 100% of either, the run pauses and you get a Continue/Stop choice in chat. Details in [Substantial work](complex-task.md#action-and-token-limits). -- **Errors.** Failed actions return `status: "error"` and the agent adapts: one retry for transient failures, a changed approach otherwise. If it can't recover, the final message tells you what failed and why. -- **Fatal LLM failures.** Repeated consecutive LLM call failures (bad key, exhausted credits) halt the run to prevent infinite retries. You'll see a clear error message, and sending any new chat message (for example "continue") resumes normally once the provider configuration is fixed. See [LLM providers](../providers/llm.md). +- **Per-task counters.** Every task tracks `action_count` and `token_count`. Defaults are 500 actions and 12,000,000 tokens per task (`app/config.py`). At 80% the agent gets a warning event telling it to wrap up; at 100% the task pauses and you get a Continue/Abort choice in chat. Details in [Complex task limits](complex-task.md#action-and-token-limits). +- **Errors.** Failed actions return `status: "error"` and the agent adapts (retry once for transient failures, change approach otherwise). If it can't recover, it tells you what failed and ends the task with `task_end(status="abort")`. +- **Fatal LLM failures.** Repeated consecutive LLM call failures (bad key, exhausted credits) cancel the task automatically to prevent infinite retries. You'll see a clear error dialog, and fixing the provider configuration is on you. See [LLM providers](../providers/llm.md). -## Observing a quick run +## Observing a simple task | Where | What you see | |---|---| -| Chat | The final reply (and nothing else: no narration, no status pings) | -| Action panel | Each action with inputs and results, nothing hidden | -| `agent_file_system/EVENT.md` | The main session's full event log | -| `logs//all.log` | Grep `[REACT]` for the run flow, `[ACTION]` for execution | +| Task panel | A task card with no todo list; flips to completed on `task_end` | +| Action panel | Each action with inputs and results; nothing is hidden | +| `agent_file_system/TASK_HISTORY.md` | A summary record appended when the task ends (name, status, summary, instruction, skills, action sets) | +| `logs//main.log` | Grep `[TASK]` for lifecycle events, `[ACTION]` for execution; the simple-task workflow logs at debug level | More on logs in [Logs](../concepts/logs.md). ## Related -- [Substantial work](complex-task.md): the path for everything that needs a plan -- [Runs overview](index.md): how the agent scales its process -- [Sessions](../concepts/task-sessions.md): reply routing and parallel sessions +- [Complex task](complex-task.md): the mode for everything that needs a plan +- [Task modes overview](index.md): the decision map +- [Task sessions](../concepts/task-sessions.md): parallel tasks and reply routing - [Agent loop](../concepts/agent-loop.md): the shared turn cycle diff --git a/mkdocs/docs/core/modes/special-workflows.md b/mkdocs/docs/core/modes/special-workflows.md index b070e7e4..47e38cdc 100644 --- a/mkdocs/docs/core/modes/special-workflows.md +++ b/mkdocs/docs/core/modes/special-workflows.md @@ -1,65 +1,63 @@ -# Workflow runs +# Special workflows -Some runs are never started by a user message: **memory processing**, the **proactive heartbeat**, and the **proactive planners**. They arrive as typed triggers from the scheduler and run **in the main session**, like any other run, with one twist: at run start the workflow's dedicated skill and action sets are loaded onto the session, and at run end they are unloaded again, so the main session's prompt doesn't accumulate background skills permanently. +Two kinds of turns are never started by a user message: **memory processing** and **proactive processing**. They arrive as typed triggers from the scheduler, are checked *before* any task or conversation routing in the agent loop, and each one does the same thing: it creates an ordinary simple task loaded with a dedicated skill, then lets the normal task machinery do the work. This page covers the mechanics. For what the memory pipeline actually distills, see [Memory](../concepts/memory.md). For the full user guide to proactive behavior, see [Proactive](proactive.md). -## How they differ from your runs +## How they differ from user tasks -| | Run from your message | Workflow run | +| | User task | Special workflow task | |---|---|---| -| Started by | Your message | Scheduler trigger | -| Trigger source | `USER_MESSAGE` | `MEMORY`, `PROACTIVE_HEARTBEAT`, `PROACTIVE_PLANNER` | -| Capabilities | Whatever the agent loads as it works | Workflow skill + action sets, loaded for the run, unloaded at run end | -| Acknowledgement | Agent acknowledges you | None: silent by design | -| Run end | Final `send_message` delivers to you | `end_turn`, or a tier-1 notification as the final message | +| Started by | Your message → `task_start` | Scheduler trigger → handler creates the task | +| Trigger type | Untyped / message payload | `memory_processing`, `proactive_heartbeat`, `proactive_planner` | +| Acknowledgement | Agent acknowledges you | None; silent by design | +| Approval gate | Complex tasks wait for you | Never waits; ends silently with `task_end` | | Enable switch | Always on | `memory.enabled` / `proactive.enabled` in settings | -| Concurrency | Per-session serialization | The same per-session serialization: the main session runs one turn at a time, and everything due folds into the next turn | +| Concurrency | Parallel tasks allowed | A workflow lock blocks overlapping runs | -Before a workflow run starts, a **pre-check** decides whether there is anything to do. If not (memory disabled, empty buffer, no due proactive tasks), the turn is skipped entirely and no run happens. If the aggregated trigger batch also carried user messages, those are still processed; workflow triggers never swallow your input. +The silence is deliberate and comes from the skills these tasks load: their instructions explicitly override the normal "acknowledge immediately, confirm before ending" rules, because a background run that waited for confirmation every 30 minutes would pile up forever. The one exception: a planner that wants to *suggest* a new recurring task does message you and waits for approval (see [Proactive](proactive.md#the-approval-model)). -The silence is deliberate and comes from the skills these runs load: their instructions explicitly override the normal "acknowledge immediately" communication rules, because a background run that messaged you every 30 minutes would be noise. The one exception: a planner that wants to *suggest* a new recurring task does message you, as a question that ends its run (see [Proactive](proactive.md#the-approval-model)). - -You can still watch these runs like any other: their actions show in the action panel, their events land in the main session's stream, and they log under the `[MEMORY]` and `[PROACTIVE]` tags in `logs//all.log`. +You can still watch these tasks like any other: they appear in the task panel (named `Heartbeat`, `Day Planner`, ...), their actions show in the action panel, they append to `TASK_HISTORY.md`, and they log under the `[MEMORY]` and `[PROACTIVE]` tags in `logs//main.log`. ## The memory workflow -Fires on a `MEMORY`-source trigger. By default that is the scheduler entry `memory-processing` (`every day at 3am` in `app/config/scheduler_config.json`), plus a one-off replay at startup if `EVENT_UNPROCESSED.md` still holds unprocessed events from before the last shutdown. +Fires when a trigger carries `type: "memory_processing"`. By default that is the scheduler entry `memory-processing` (`every day at 3am` in `app/config/scheduler_config.json`), plus a one-off replay at startup if `EVENT_UNPROCESSED.md` still holds unprocessed events from before the last shutdown. -The pre-check runs a short checklist before starting anything: +The handler runs a short checklist before creating anything: 1. **Enabled?** If `memory.enabled` is off in settings, skip. 2. **Anything to do?** If `EVENT_UNPROCESSED.md` is missing or holds no event lines, skip. -3. **Pruning needed?** If `MEMORY.md` has grown past its configured maximum item count, the pruning instruction is folded into the same run. +3. **Already running?** A `memory_processing` workflow lock guarantees one run at a time. If a slow previous run is still going when the trigger fires, the new trigger is dropped and the next scheduled fire picks up the work. +4. **Pruning needed?** If `MEMORY.md` has grown past its configured maximum item count, the run also gets a pruning phase. -The run then loads the `memory-processor` skill (with the `file_operations` action set). It reads `EVENT_UNPROCESSED.md`, scores each event for long-term value, checks for duplicates, writes the keepers to `MEMORY.md`, and clears the buffer. While it runs, the event stream sets a skip flag so the run's own events don't loop back into the unprocessed buffer; the flag resets when the run ends. The distillation semantics (what gets kept, the fact format, pruning) are covered in [Memory](../concepts/memory.md). +It then creates a simple-mode task with the `memory-processor` skill. That task reads `EVENT_UNPROCESSED.md`, scores each event for long-term value, checks for duplicates, writes the keepers to `MEMORY.md`, and clears the buffer. While it runs, the event stream sets a skip flag so the memory task's own events don't loop back into the unprocessed buffer; the flag and the lock are both released when the task ends. The distillation semantics (what gets kept, the fact format, pruning) are covered in [Memory](../concepts/memory.md). ## The proactive workflow -Fires on `PROACTIVE_HEARTBEAT` or `PROACTIVE_PLANNER` triggers. Both are skipped entirely when `proactive.enabled` is off in settings. Two variants: +Fires when a trigger carries `type: "proactive_heartbeat"` or `"proactive_planner"`. Both are skipped entirely when `proactive.enabled` is off in settings. Two variants: ### Heartbeat -The scheduler fires it every 30 minutes, at `:00` and `:30` (cron `0,30 * * * *`). The pre-check collects every due recurring task from `PROACTIVE.md` across all frequencies (hourly, daily, weekly, monthly). If nothing is due, the turn is skipped and no run happens. Otherwise **one** heartbeat run starts, loaded with the `heartbeat-processor` skill and the action sets `file_operations`, `proactive`, `web_research`. Its instruction summarizes what's due (e.g. "Due tasks: 2 daily, 1 weekly (3 total)"), and the run executes each due item: quick tier-0/1 items inline, heavier items spun off as their own runs via `schedule_task(schedule="immediate", ...)`. Each item's outcome is recorded to its `outcome_history` via `recurring_update_task`, and the run ends with `end_turn` or with a tier-1 notification as its final message. +The scheduler fires it every 30 minutes, at `:00` and `:30` (cron `0,30 * * * *`). The handler collects every due recurring task from `PROACTIVE.md` across all frequencies (hourly, daily, weekly, monthly). If nothing is due, it returns silently and no task is created. Otherwise it creates **one** unified simple-mode task named `Heartbeat`, with action sets `file_operations`, `proactive`, `web_research` and the `heartbeat-processor` skill. The instruction summarizes what's due (e.g. "Due tasks: 2 daily, 1 weekly (3 total)"), and the task executes each due item and records its outcome. ### Planner -Three scheduler entries fire it: `day-planner` (every day at 7 AM), `week-planner` (every Sunday at 5 PM), and `month-planner` (the 1st of the month at 8 AM). Each starts a run loaded with the matching `day-planner` / `week-planner` / `month-planner` skill plus the `file_operations` and `proactive` action sets (a planner skill can declare more, e.g. the day planner also pulls in scheduler, calendar, and web access). The planner reviews recent interactions and updates the Goals / Plan / Status section of `PROACTIVE.md`. Rarely, and only with your approval, it proposes new recurring tasks. +Three scheduler entries fire it: `day-planner` (every day at 7 AM), `week-planner` (every Sunday at 5 PM), and `month-planner` (the 1st of the month at 8 AM). Each creates a simple-mode task named `Day Planner` / `Week Planner` / `Month Planner` with action sets `file_operations`, `proactive` and the matching `day-planner` / `week-planner` / `month-planner` skill. The planner reviews recent interactions and updates the Goals / Plan / Status section of `PROACTIVE.md`. Rarely, and only with your approval, it proposes new recurring tasks. ## Trigger routing summary -| Trigger source | Loads | Skipped when | -|---|---|---| -| `MEMORY` | `memory-processor` skill + `file_operations` | Memory disabled, or no unprocessed events | -| `PROACTIVE_HEARTBEAT` | `heartbeat-processor` + `file_operations`, `proactive`, `web_research` | Proactive disabled, or nothing due | -| `PROACTIVE_PLANNER` | `-planner` + `file_operations`, `proactive` | Proactive disabled | -| anything else | Normal run, nothing pre-loaded | never pre-checked | +| Trigger `type` | Handler | Creates | Skipped when | +|---|---|---|---| +| `memory_processing` | Memory workflow | Simple task, `memory-processor` skill | Memory disabled, no events, or lock held | +| `proactive_heartbeat` | Proactive heartbeat | One `Heartbeat` simple task | Proactive disabled, or nothing due | +| `proactive_planner` | Proactive planner | ` Planner` simple task | Proactive disabled | +| anything else | Normal routing | n/a | n/a | All schedules live in `app/config/scheduler_config.json` and can be toggled individually (see [Scheduling](../concepts/scheduling.md)). Because they're clock-driven, none of them fire while CraftBot isn't running. For reliable nightly and weekend runs, keep the agent up via [service mode](../../start/service-mode.md). ## Related - [Proactive](proactive.md): the full guide: PROACTIVE.md format, permission tiers, the approval model -- [Memory](../concepts/memory.md): what the memory run actually does with your events -- [Triggers](../concepts/triggers.md): trigger anatomy and per-session queues -- [Agent loop](../concepts/agent-loop.md): where the workflow pre-check lives +- [Memory](../concepts/memory.md): what the memory task actually does with your events +- [Triggers](../concepts/triggers.md): trigger anatomy and the priority queue +- [Agent loop](../concepts/agent-loop.md): where the short-circuit routing lives diff --git a/mkdocs/docs/core/providers/index.md b/mkdocs/docs/core/providers/index.md index 4c3baba1..281418d8 100644 --- a/mkdocs/docs/core/providers/index.md +++ b/mkdocs/docs/core/providers/index.md @@ -24,7 +24,7 @@ There is no code to touch. The active provider, model overrides, and keys all li 2. **`/provider` command**: `/provider anthropic sk-ant-...` switches provider and key in one line from chat. 3. **Settings → Model**: the full surface, with per-capability providers, model overrides, base URLs, connection testing, and subscription sign-in. -Changes made through the wizard, the command, or Settings reinitialize the model client immediately, with no restart — and every model change, including a same-provider model swap, needs that reinitialize, because the live client never re-reads `settings.json` on its own. If you hand-edit `settings.json`, restart the agent to pick it up. +Changes made through the wizard, the command, or Settings reinitialize the model client immediately, with no restart. If you hand-edit `settings.json`, restart the agent to pick it up. Every provider has a default model, so picking a provider is enough to start. Set an explicit model only when you want something other than the default. `model.llm_model`, `model.vlm_model`, and friends override per capability. diff --git a/mkdocs/docs/core/providers/llm.md b/mkdocs/docs/core/providers/llm.md index e07b089d..6fd2c2c2 100644 --- a/mkdocs/docs/core/providers/llm.md +++ b/mkdocs/docs/core/providers/llm.md @@ -10,11 +10,11 @@ The LLM handles every plan, reply, and tool call the agent makes. CraftBot suppo | **OpenAI** | `openai` | `OPENAI_API_KEY` | `gpt-5.2-2025-12-11` | Also works with a ChatGPT Plus/Pro/Team [subscription](subscription-auth.md) | | **Google Gemini** | `gemini` | `GOOGLE_API_KEY` | `gemini-2.5-pro` | API base and version overridable; also the default video-generation provider | | **BytePlus** | `byteplus` | `BYTEPLUS_API_KEY` | `seed-2-0-pro-260328` | Default base `https://ark.ap-southeast.bytepluses.com/api/v3`, overridable | -| **Ollama (local)** | `remote` | None — just a server URL | `llama3.2:3b` | Default `http://localhost:11434`; free, fully local | +| **Ollama (local)** | `remote` | None, just a server URL | `llama3.2:3b` | Default `http://localhost:11434`; free, fully local | | **Grok (xAI)** | `grok` | `XAI_API_KEY` | `grok-3` | `https://api.x.ai/v1`; also works with a SuperGrok [subscription](subscription-auth.md) | | **DeepSeek** | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek-chat` | `https://api.deepseek.com`; text only, no vision | -| **Moonshot** | `moonshot` | `MOONSHOT_API_KEY` | `kimi-k2.5` | `https://api.moonshot.cn/v1`; geo-restricted — see quirks | -| **MiniMax** | `minimax` | `MINIMAX_API_KEY` | `MiniMax-Text-01` | `https://api.minimax.chat/v1`; geo-restricted — see quirks | +| **Moonshot** | `moonshot` | `MOONSHOT_API_KEY` | `kimi-k2.5` | `https://api.moonshot.cn/v1`; geo-restricted (see quirks) | +| **MiniMax** | `minimax` | `MINIMAX_API_KEY` | `MiniMax-Text-01` | `https://api.minimax.chat/v1`; geo-restricted (see quirks) | | **Z.ai (GLM)** | `glm` | `ZAI_API_KEY` | `glm-5.2` | `https://api.z.ai/api/paas/v4`, OpenAI-compatible | | **Sakana (Fugu)** | `fugu` | `SAKANA_API_KEY` | `fugu` | `https://api.sakana.ai/v1`, OpenAI-compatible; LLM only | | **OpenRouter** | `openrouter` | `OPENROUTER_API_KEY` | `anthropic/claude-sonnet-4.5` | `https://openrouter.ai/api/v1`; one key, hundreds of models | @@ -27,9 +27,9 @@ The key names above are the conventional identifiers shown in the settings inter | You want | Pick | Why | |---|---|---| | Best out-of-box agent quality | **Anthropic**, **OpenAI**, or **Gemini** | Flagship hosted models; CraftBot's prompt-caching paths are tuned for them | -| Zero cost, full privacy | **Ollama** (`remote`) | Runs on your hardware, no key, no tokens billed — quality depends on the model you run | +| Zero cost, full privacy | **Ollama** (`remote`) | Runs on your hardware, no key, no tokens billed; quality depends on the model you run | | One key, many models | **OpenRouter** | Access Claude, GPT, Gemini, Kimi, and more through a single account; models are addressed as `vendor/model` slugs | -| Pay with a subscription you already have | **OpenAI** or **Grok** in subscription mode | Your ChatGPT Plus/Pro/Team or SuperGrok quota powers the agent — see [Subscription authentication](subscription-auth.md) | +| Pay with a subscription you already have | **OpenAI** or **Grok** in subscription mode | Your ChatGPT Plus/Pro/Team or SuperGrok quota powers the agent; see [Subscription authentication](subscription-auth.md) | | Existing AWS billing / compliance | **Bedrock** | Claude models under your AWS account, IAM roles, and region controls | | Low-cost hosted | **DeepSeek**, **GLM**, **Moonshot**, **MiniMax** | Cheap and capable; check the quirks below for vision support and geo-restrictions | @@ -61,8 +61,6 @@ You can also hand-edit `settings.json` (restart afterwards): `llm_model: null` means "use the provider's default from the registry". Switching providers clears any model override so the new provider starts on its own default. Per-capability overrides (`vlm_model`, `image_gen_model`, `video_gen_model`) work the same way (see [settings.json](../configuration/config-json.md)). -One rule to remember: **every model change requires a reinitialize** — the live LLM client holds its provider and model from construction and never re-reads `settings.json` per call, so a hand-edit alone changes nothing until you restart. `/provider` and a Settings → Model save both trigger the reinitialize for you (it's a no-op when nothing actually changed), and this applies to same-provider model swaps too. A true provider change resets the per-session model caches; a same-provider reinitialize preserves them. - **Connection testing.** The test in Settings → Model sends a tiny request against your exact configured model, so a typo in the model ID fails at test time instead of at first real use. When no model is set it uses a known-good default from `app/config/connection_test_models.json`. If a provider deprecates its test model, update that file. ## Per-provider quirks @@ -94,10 +92,10 @@ When enabled, CraftBot tracks token usage in a sliding 60-second window and bloc | Grok returns `400` on every call | xAI signals rejected/expired bearers with 400, not 401 | Test the key in Settings → Model; if using a subscription, reconnect it | | Model-not-found error | Model override typo, or the default model isn't enabled on your account | Run the connection test (it validates the exact model ID); set an explicit model in Settings → Model | | `429 Too Many Requests` | Provider rate limit | Enable [slow mode](#slow-mode); lower the TPM limit; or switch providers | -| Moonshot / MiniMax unreachable | Geo-restricted direct API | Add an OpenRouter key — CraftBot proxies these providers through it automatically | +| Moonshot / MiniMax unreachable | Geo-restricted direct API | Add an OpenRouter key; CraftBot proxies these providers through it automatically | | Ollama: connection refused | Server not running, or wrong URL | `ollama serve`, then check `endpoints.remote_model_url` | | New API key saved but ignored (OpenAI / Grok) | A connected subscription takes precedence over the key | Disconnect the subscription in Settings → Model, then save | -| The run halts with a provider-failure message | The failure guard tripped — a non-retryable error (bad key, no credits, wrong model, misconfigured provider, content filter) halts on the first failure; transient errors get 5 attempts first | Fix the cause, then send any chat message ("continue") to resume. See [Provider troubleshooting](../../reference/troubleshooting/providers.md) | +| Repeated failures, agent backs off | 5 consecutive LLM errors trip the failure guard | See [Provider troubleshooting](../../reference/troubleshooting/providers.md) | ## Related diff --git a/mkdocs/docs/core/providers/subscription-auth.md b/mkdocs/docs/core/providers/subscription-auth.md index 99bf23a8..d925a660 100644 --- a/mkdocs/docs/core/providers/subscription-auth.md +++ b/mkdocs/docs/core/providers/subscription-auth.md @@ -8,13 +8,11 @@ Subscription auth is a layer in front of the model factory. Before building a cl | Provider | Qualifying plans | Endpoint in subscription mode | |---|---|---| -| **OpenAI (ChatGPT)** | Plus, Pro, Team (Enterprise/Business also accepted) | `https://chatgpt.com/backend-api/codex` — CraftBot translates its calls to the Responses API this backend serves | -| **Grok (xAI)** | SuperGrok, X Premium+ | `https://api.x.ai/v1` — same host as API-key mode | +| **OpenAI (ChatGPT)** | Plus, Pro, Team (Enterprise/Business also accepted) | `https://chatgpt.com/backend-api/codex`; CraftBot translates its calls to the Responses API this backend serves | +| **Grok (xAI)** | SuperGrok, X Premium+ | `https://api.x.ai/v1` (same host as API-key mode) | A free ChatGPT account can complete the sign-in, but you'll get a warning and every model call will fail until you upgrade or switch back to API-key auth. -The Codex backend serves everything CraftBot needs: the agent's action decisions ride ordinary JSON-mode completions, which work transparently. Only native tool-calls and streaming are unsupported there, and CraftBot uses neither on this path, so agent behavior under a subscription matches API-key mode. - **Model list narrows.** Under a ChatGPT subscription only the Codex-accepted models are reachable: `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, and `gpt-5.3-codex-spark`. If your configured model isn't one of them, CraftBot substitutes `gpt-5.4` and logs a warning. Set one of the accepted models in Settings → Model to silence it. Grok subscriptions serve `grok-4-0709` and `grok-3`. ## Connect @@ -57,10 +55,10 @@ Anthropic's terms of service explicitly forbid third-party applications from usi | Symptom | Cause | Fix | |---|---|---| -| Browser opens but Connect never completes | The loopback callback didn't fire — port `1455` (ChatGPT) or `56121` (Grok) is busy, or the provider showed a code page | Free the port and retry, or use the paste-back field in Settings when the code page appears | +| Browser opens but Connect never completes | The loopback callback didn't fire: port `1455` (ChatGPT) or `56121` (Grok) is busy, or the provider showed a code page | Free the port and retry, or use the paste-back field in Settings when the code page appears | | Repeated sign-in loop | A stale pending attempt | Click Connect again to start a fresh attempt, then complete it in one go | -| "ChatGPT connected — this account has no Plus/Pro/Team plan" | Free-tier account | Upgrade the subscription, or use an API key instead | -| Calls fail with `429` after working fine | Subscription quota exhausted — neither provider exposes a live quota endpoint, so this is how you find out | Wait for the quota window to reset, or disconnect and fall back to an API key | +| "ChatGPT connected: this account has no Plus/Pro/Team plan" | Free-tier account | Upgrade the subscription, or use an API key instead | +| Calls fail with `429` after working fine | Subscription quota exhausted; neither provider exposes a live quota endpoint, so this is how you find out | Wait for the quota window to reset, or disconnect and fall back to an API key | | Grok: `400` "OAuth2 access token could not be validated" | Expired/revoked token (xAI returns 400, not 401) | Reconnect from Settings → Model | | "subscription session expired and refresh failed" | Refresh token no longer valid | Reconnect from Settings → Model | | Configured model silently changed (OpenAI) | Model not in the Codex-accepted list; `gpt-5.4` substituted | Pick `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, or `gpt-5.3-codex-spark` in Settings | diff --git a/mkdocs/docs/core/providers/vlm-and-media.md b/mkdocs/docs/core/providers/vlm-and-media.md index ba89730b..cf3669b3 100644 --- a/mkdocs/docs/core/providers/vlm-and-media.md +++ b/mkdocs/docs/core/providers/vlm-and-media.md @@ -33,7 +33,7 @@ The `generate_image` action turns a text prompt into a PNG. Two providers suppor | Provider | Default model | Notes | |---|---|---| -| **OpenAI** | `gpt-image-2` | Three canvas sizes only — 16:9 and 9:16 requests map to the nearest fit (1536×1024 / 1024×1536); tops out at ~1536px regardless of the requested resolution | +| **OpenAI** | `gpt-image-2` | Three canvas sizes only; 16:9 and 9:16 requests map to the nearest fit (1536×1024 / 1024×1536); tops out at ~1536px regardless of the requested resolution | | **Google Gemini** | `gemini-3-pro-image` | Native negative prompts; reference images act as style guidance | Set the provider with `model.image_gen_provider` (default `openai`) and override the model with `model.image_gen_model`. If unset, generation follows your VLM provider when it can generate images, otherwise falls back to OpenAI. @@ -69,8 +69,8 @@ Generation is long-running: the action blocks while it submits, polls, and downl | "VLM not available" | LLM provider has no vision model (DeepSeek, Fugu) and no `vlm_provider` set | Set `vlm_provider` to a vision-capable provider and configure its key | | `generate_image` / `generate_video` errors about provider | Configured provider doesn't support that capability | Set `image_gen_provider` to `openai`/`gemini`, `video_gen_provider` to `gemini`/`openai`/`byteplus` | | Generated image ignores 16:9 | OpenAI has no true 16:9 canvas | Use Gemini for exact wide/tall ratios | -| `understand_video` falls back to keyframes | No Google API key configured | Add a `GOOGLE_API_KEY` — the native path activates even if Gemini isn't your VLM provider | -| Video generation appears stuck | Generation blocks by design | Wait — typical calls take 60–300 s; check logs for polling progress | +| `understand_video` falls back to keyframes | No Google API key configured | Add a `GOOGLE_API_KEY`; the native path activates even if Gemini isn't your VLM provider | +| Video task appears stuck | Generation blocks by design | Wait; typical runs are 60–300 s, and logs show polling progress | More cases: [Provider troubleshooting](../../reference/troubleshooting/providers.md). diff --git a/mkdocs/docs/develop/architecture.md b/mkdocs/docs/develop/architecture.md index 888ca0ee..e1c38083 100644 --- a/mkdocs/docs/develop/architecture.md +++ b/mkdocs/docs/develop/architecture.md @@ -10,7 +10,7 @@ CraftBot is split into a reusable engine and a concrete application that wires t `app/` is the CraftBot application. It supplies a concrete runtime around the engine: the interface layer (`app/ui_layer/`), external communications (`app/external_comms/`, `craftos_integrations/`), the scheduler (`app/scheduler/`), configuration (`app/config/`), and the built-in actions and skills the agent ships with. `AgentBase` in `app/agent_base.py` is the object that holds all of this together. -Many `app/*.py` modules are thin bindings over engine classes. `app/context_engine.py` is a pure re-export of `agent_core.core.impl.context.ContextEngine`. `app/session/session_manager.py` owns session lifecycle (per-session event streams, workspace dirs, persistence for crash recovery) on top of the engine's session implementation. This is the general pattern: the engine provides behavior, the app injects the concrete dependencies. +Many `app/*.py` modules are thin bindings over engine classes. `app/context_engine.py` is a pure re-export of `agent_core.core.impl.context.ContextEngine`. `app/task/task_manager.py` defines a `TaskManager` that subclasses the engine's `agent_core.core.impl.task.TaskManager` and passes CraftBot-specific hooks (the `STATE` singleton, per-task event streams, session persistence for crash recovery). This is the general pattern: the engine provides behavior, the app injects the concrete dependencies. ## Directory layout @@ -20,15 +20,15 @@ CraftBot/ │ ├── __init__.py public API: re-exports engine classes │ └── core/ │ ├── protocols/ typing.Protocol definitions the host satisfies -│ ├── registry/ component registries +│ ├── registry/ component registries (get_task_manager, ...) │ ├── action_framework/ @action decorator + ActionRegistry singleton │ ├── impl/ default implementations of every subsystem │ │ ├── action/ ActionExecutor, ActionManager, ActionRouter -│ │ ├── session/ session implementation +│ │ ├── task/ TaskManager │ │ ├── event_stream/ EventStream, EventStreamManager │ │ ├── memory/ skill/ mcp/ settings/ trigger/ context/ │ ├── prompts/ PromptRegistry + prompt string constants -│ ├── state/ trigger.py event_stream/ core data types +│ ├── state/ task/ trigger.py event_stream/ core data types │ └── llm/ provider clients and model factory │ ├── app/ the CraftBot application (concrete runtime) @@ -39,7 +39,7 @@ CraftBot/ │ ├── scheduler/ SchedulerManager: fires due schedules │ ├── external_comms/ inbound platform listeners and bridges │ ├── context_engine.py re-export of engine ContextEngine -│ ├── session/ SessionManager: session lifecycle + workspace dirs +│ ├── task/ TaskManager subclass with app hooks │ ├── state/ STATE singleton, StateManager │ ├── config/ settings.json and per-feature config files │ └── data/ @@ -161,7 +161,7 @@ For a new contributor, read in this order: 1. [Agent loop](../core/concepts/agent-loop.md): the single cycle every trigger runs through. 2. [Triggers](../core/concepts/triggers.md): what wakes the loop and what survives a restart. -3. [Sessions](../core/concepts/task-sessions.md): the lanes work runs in. +3. [Task sessions](../core/concepts/task-sessions.md): how a message finds the right task. 4. [Event stream](../core/concepts/event-stream.md): the record each turn reads and writes. 5. [Actions and action sets](../core/concepts/actions-and-action-sets.md): the unit of work the LLM selects. 6. [Custom action](custom-action.md): write and register your first action. diff --git a/mkdocs/docs/develop/custom-action.md b/mkdocs/docs/develop/custom-action.md index 615917eb..990cc234 100644 --- a/mkdocs/docs/develop/custom-action.md +++ b/mkdocs/docs/develop/custom-action.md @@ -165,7 +165,7 @@ Every import your function uses must go inside the function body. Only the `from The registry extracts each action's source with the `inspect` module and runs the extracted body on its own. Names defined at module level, including top-level imports, are not in scope when the body runs. A helper referenced through a module-level import raises `NameError` at call time even though the file imported cleanly at startup. This is the single most common mistake when writing actions. ```python -# WRONG — module-top import; raises NameError at call time +# WRONG: module-top import; raises NameError at call time from app.data.action.integrations._helpers import run_client @action(name="do_thing", ...) @@ -173,7 +173,7 @@ async def do_thing(input_data: dict) -> dict: return await run_client("service", "method") -# RIGHT — import inside the function body +# RIGHT: import inside the function body @action(name="do_thing", ...) async def do_thing(input_data: dict) -> dict: from app.data.action.integrations._helpers import run_client diff --git a/mkdocs/docs/develop/custom-agent.md b/mkdocs/docs/develop/custom-agent.md index b3af1989..334252fb 100644 --- a/mkdocs/docs/develop/custom-agent.md +++ b/mkdocs/docs/develop/custom-agent.md @@ -24,9 +24,9 @@ Each agent lives in its own folder under `agent_bundle/agents//`. The `ads ``` agent_bundle/agents/ads-specialist/ -├── agent.yaml # Manifest — what the agent is and ships with -├── soul.md # Persona + decision rules — becomes SOUL.md, always in context -├── role.md # Deep playbooks + SOTA tool reference — becomes AGENT.md, grepped on demand +├── agent.yaml # Manifest: what the agent is and ships with +├── soul.md # Persona + decision rules; becomes SOUL.md, always in context +├── role.md # Deep playbooks + SOTA tool reference; becomes AGENT.md, grepped on demand ├── USE_CASES.md # What the agent covers and what it can execute (ships, not in context) ├── SOURCES.md # Section-to-source provenance map (ships, not in context) ├── reference/ # Downloaded research + INVENTORY.md + SOTA_USE_CASES.md diff --git a/mkdocs/docs/guides/github-pr-review.md b/mkdocs/docs/guides/github-pr-review.md index 13d4b525..e3bc03a2 100644 --- a/mkdocs/docs/guides/github-pr-review.md +++ b/mkdocs/docs/guides/github-pr-review.md @@ -95,7 +95,7 @@ What do you have scheduled? Pause the daily PR review. ``` -These map to the `scheduled_task_list` and `schedule_task_toggle` actions. A real code review is multi-step work, so the scheduled run plans it with todos and verifies before delivering. Schedule expressions, one-time versus recurring runs, and what happens after downtime are all covered in [Scheduling](../core/concepts/scheduling.md). +These map to the `scheduled_task_list` and `schedule_task_toggle` actions. Because a real code review is multi-step work, set the schedule to run as a complex task so it plans and checks its work. Schedule expressions, one-time versus recurring runs, and what happens after downtime are all covered in [Scheduling](../core/concepts/scheduling.md). ## Step 4: what the review does diff --git a/mkdocs/docs/guides/index.md b/mkdocs/docs/guides/index.md index 08810f19..75bd8844 100644 --- a/mkdocs/docs/guides/index.md +++ b/mkdocs/docs/guides/index.md @@ -29,7 +29,7 @@ You do not need to read the guides in order, and they do not build on each other Guides are recipes, not reference. They show one good path to one outcome and skip the alternatives. When you want the full picture of a mechanic, follow the links out to the reference and concept pages: - [Scheduling](../core/concepts/scheduling.md) for schedule expressions, one-time versus recurring tasks, and the schedule actions. -- [Runs](../core/modes/index.md) for how the agent scales quick requests versus substantial work. +- [Task modes](../core/modes/index.md) for how simple and complex tasks differ. - [Proactive mode](../core/modes/proactive.md) for recurring tasks the agent plans and runs on its own. - [Integrations](../integrations/index.md) for the setup, actions, and configuration of every connector. - [Skills](../core/concepts/skills.md) for how skills are structured, invoked, and shared. diff --git a/mkdocs/docs/guides/telegram-assistant.md b/mkdocs/docs/guides/telegram-assistant.md index d48b5939..ae9260b8 100644 --- a/mkdocs/docs/guides/telegram-assistant.md +++ b/mkdocs/docs/guides/telegram-assistant.md @@ -41,17 +41,17 @@ CraftBot validates the token, stores it locally, and starts a listener that poll ## Step 3: how Telegram messages reach the agent -A Telegram message is not a separate, cut-down chat channel. It enters the agent the same way a browser message does, so everything you already know about runs applies. +A Telegram message is not a separate, cut-down chat channel. It enters the agent the same way a browser message does, so everything you already know about tasks applies. Here is the path a message takes: 1. **The listener receives it.** While connected, CraftBot long-polls Telegram for new messages. Only messages that carry text are forwarded, and each is dispatched once. 2. **It becomes a trigger.** The incoming message is turned into a `user_message` trigger, the same durable record a browser message creates. Triggers are written to disk before they run, so a message is not lost if CraftBot restarts mid-handling. See [Triggers](../core/concepts/triggers.md). -3. **It lands in the main session.** Platform messages go to the agent's main session, and if several arrive while it's busy they fold into one turn. See [Sessions](../core/concepts/task-sessions.md). -4. **The run works the normal way.** The agent scales its process to the request: a small ask gets a direct answer; real work gets requirements, a todo plan, and step-by-step execution — identically to a request from the browser. See [Runs](../core/modes/index.md). -5. **Replies come back to Telegram.** The run records that your message came from Telegram, so its updates and results go back to the chat you wrote from, not to the browser. +3. **It routes to a session.** The message runs through session routing exactly like a browser message. If it is a small ask or a question, it stays in conversation mode. If it asks for real work, the agent opens a task. If you are answering something a running task asked, it routes back into that task. This is the same automatic routing described in [Task sessions](../core/concepts/task-sessions.md). +4. **The task runs the normal way.** Once a task starts, it plans, calls actions, and works through its todos identically to a task you started in the browser. See [Task modes](../core/modes/index.md). +5. **Replies come back to Telegram.** The task records that it started from Telegram, so its messages and results go back to the chat you wrote from, not to the browser. -Two consequences are worth stating plainly. Long runs keep working after you lock your phone, because the work lives in the agent, not in the chat window, and you get the result when it is done. And work started from Telegram can send you progress updates and the final answer in that same chat as it goes. +Two consequences are worth stating plainly. Long tasks keep running after you lock your phone, because the work lives in the agent, not in the chat window, and you get the result when it is done. And a task started from Telegram can send you progress updates and the final answer in that same chat as it goes. ## Step 4: send your first task from Telegram @@ -64,19 +64,19 @@ write a one-page summary, and send it back to me here as a file. Watch what happens: -1. The agent acknowledges and starts working. A request this size gets a requirement contract and a live todo list. -2. It runs actions to research and write the summary. You can open the browser later to see the full activity view, but you do not have to. +1. The agent opens a task and starts working. A request this size becomes a complex task with a live todo list. +2. It runs actions to research and write the summary. You can open the browser later to see the full action panel, but you do not have to. 3. When it finishes, it sends the summary into the Telegram chat and, because you asked for a file, attaches it as a document. -You can steer the work from your phone while it runs. Send a follow-up like: +You can steer the task from your phone while it runs. Send a follow-up like: ``` Focus on automotive use, not consumer electronics. ``` -Your message arrives in the same session, folds into the agent's next turn, and the agent adjusts course. The mechanics are in [Sessions](../core/concepts/task-sessions.md). +Because a task is already active for this chat, routing sends this into the running task instead of starting a new one, and the agent adjusts. If you instead send something unrelated, routing opens a fresh conversation so it does not derail the task. You never pick where a message goes. Routing decides, and it is deliberately conservative about interrupting running work. The rules are in [Task sessions](../core/concepts/task-sessions.md). -**Checkpoint:** you sent work from Telegram, received the summary and the attached file in the chat, and a follow-up message changed what the agent did. +**Checkpoint:** you sent a task from Telegram, received the summary and the attached file in the chat, and a follow-up message changed what the running task did. ## Step 5: tune it for personal use @@ -84,7 +84,7 @@ For a personal assistant you talk to alone, restrict the bot to your own direct **Direct messages only.** In **Settings → Integrations → Telegram Bot**, turn on **Private DMs only** (`self_messages_only`). With it on, only messages from one-to-one private chats reach the agent. Group, supergroup, and channel messages are dropped before dispatch. This keeps the bot focused on you and ignores any group it happens to be in. The listener re-reads this setting on every message, so the change applies without reconnecting. -**Chat versus work over chat.** Nothing changes on Telegram. A plain question ("what's on my plate today?") gets a direct answer. A request with a deliverable gets the full structured treatment. You do not send commands to switch — you phrase the message as a chat or as work, and the agent scales its process accordingly. See [Runs](../core/modes/index.md). +**Conversation versus task over chat.** Nothing about modes changes on Telegram. A plain question ("what's on my plate today?") is answered in conversation mode. A request with a deliverable opens a task. You do not send commands to switch. You phrase the message as a chat or as work, and the agent routes accordingly. See [Task modes](../core/modes/index.md). **Let the agent message you first.** With [proactive mode](../core/modes/proactive.md) on, the agent runs recurring work on a schedule and can push the result to you in Telegram. Set up a recurring task by asking in plain language, for example: @@ -102,7 +102,7 @@ You can also drop the bot into a group so a team shares one agent. This is optio 2. Add the bot to the group like any other member. 3. In @BotFather, disable Privacy Mode for the bot. With Privacy Mode on, a bot only receives messages that address it directly, so it will not see normal group chatter. Re-add the bot to the group after changing this. -In a busy group you rarely want the agent reacting to every line. Two behaviors help. Address the bot directly (mention it or reply to its message) so it is clear a message is for it. And the agent can deliberately end a turn silently (`end_turn`) for messages that need no response, which is exactly the group case where most messages are people talking to each other, not to the agent. See [Quick requests](../core/modes/simple-task.md). +In a busy group you rarely want the agent reacting to every line. Two behaviors help. Address the bot directly (mention it or reply to its message) so it is clear a message is for it. And in conversation mode the agent can deliberately ignore a message that needs no response, which is exactly the group case where most messages are people talking to each other, not to the agent. Conversation mode's `ignore` option is described in [Task modes](../core/modes/index.md). ## Troubleshooting @@ -120,4 +120,4 @@ In a busy group you rarely want the agent reacting to every line. Two behaviors - [Telegram (Bot)](../integrations/telegram-bot.md): the full action list, configuration, and setup reference - [Telegram (User)](../integrations/telegram-user.md): connect your own account for a personal self-messaging inbox - [Proactive mode](../core/modes/proactive.md): recurring tasks the agent runs and reports on its own -- [Sessions](../core/concepts/task-sessions.md): where messages land and how runs work, in the browser and over chat +- [Task sessions](../core/concepts/task-sessions.md): how messages route to the right task, in the browser and over chat diff --git a/mkdocs/docs/index.md b/mkdocs/docs/index.md index fd21f2ec..badc8a3b 100644 --- a/mkdocs/docs/index.md +++ b/mkdocs/docs/index.md @@ -41,13 +41,13 @@ That single command installs dependencies, registers CraftBot to start at login,
-- :material-brain:{ .lg .middle } __Work that scales to the request__ +- :material-brain:{ .lg .middle } __Task execution in modes__ --- - Quick requests get direct answers. Bigger requests get a requirement contract, a live todo list, and step-by-step execution you can watch. Run several chat sessions side by side; each keeps its own context. + Quick requests run as lightweight simple tasks. Bigger requests become complex tasks with a live todo list and a confirmation step before closing. You never manage sessions. CraftBot routes each message to the right conversation or running task. - [:octicons-arrow-right-24: Runs](core/modes/index.md) + [:octicons-arrow-right-24: Task modes](core/modes/index.md) - :material-lightning-bolt-outline:{ .lg .middle } __1,100+ built-in actions__ @@ -110,6 +110,6 @@ That single command installs dependencies, registers CraftBot to start at login, ## Project status - **License:** [MIT](https://github.com/CraftOS-dev/CraftBot/blob/main/LICENSE): free to use, host, and monetize (credit required for distribution). -- **Website:** [craftbot.live](https://craftbot.live/) — product site and cloud hosting. +- **Website:** [craftbot.live](https://craftbot.live/): product site and cloud hosting. - **Community:** [GitHub](https://github.com/CraftOS-dev/CraftBot) · [Discord](https://discord.gg/ZN9YHc37HG) · [Living UI marketplace](https://craftos.net/marketplace) - **Maintainers:** [CraftOS](https://craftos.net/) and contributors. Active development, weekly improvements. diff --git a/mkdocs/docs/integrations/github.md b/mkdocs/docs/integrations/github.md index b25b9957..1de1dd5c 100644 --- a/mkdocs/docs/integrations/github.md +++ b/mkdocs/docs/integrations/github.md @@ -8,7 +8,7 @@ The GitHub integration connects the agent to your GitHub account with a personal |---|---| | GitHub account | The agent acts as this account for every API call | | Personal access token (classic) | Generate at [github.com/settings/tokens](https://github.com/settings/tokens) with the `repo` scope | -| `workflow` scope | Optional — needed only for the workflow actions (trigger, cancel, re-run) | +| `workflow` scope | Optional; needed only for the workflow actions (trigger, cancel, re-run) | | Network access | CraftBot calls `api.github.com` over HTTPS | ## Setup @@ -102,7 +102,7 @@ The 107 GitHub actions are grouped into action sets (`github_issues`, `github_pu | `get_github_repo` | Get repository metadata (default branch, description, stars) | | `create_github_repo` | Create a new repository | | `update_github_repo` | Update repository settings (name, visibility, default branch, archive) | -| `delete_github_repo` | Delete a repository — irreversible, requires admin scope | +| `delete_github_repo` | Delete a repository (irreversible, requires admin scope) | | `fork_github_repo` | Fork a repository under your account or an organization | | `list_github_forks` | List forks of a repository | | `list_github_collaborators` | List collaborators and their permissions | diff --git a/mkdocs/docs/living-ui/a2app-protocol.md b/mkdocs/docs/living-ui/a2app-protocol.md new file mode 100644 index 00000000..65b689c1 --- /dev/null +++ b/mkdocs/docs/living-ui/a2app-protocol.md @@ -0,0 +1,294 @@ +# The A2App protocol + +A2App is the contract a Living UI presents to **any** agent: CraftBot, an external agent, or a plain script. It exists because letting a model write to a database it has to guess about fails in a specific, repeatable way: the agent guesses a collection name, the database accepts a bad value with a silent HTTP 200, and the model reports success for something that never happened. + +The design principle, applied throughout: **a property that matters is enforced by the system, not requested of the model.** A2App answers each failure with a structural guarantee: + +```mermaid +flowchart LR + D["Describe
the app publishes its own
data model and verbs"] + G["Guard
the app refuses writes
it cannot store correctly"] + R["Receipt
the system reports what
changed, not the model"] + D --> G --> R +``` + +Everything every client needs lives **inside the app**: schema, type rules, operating conventions, guard, audit. CraftBot's own tooling (the `lui` CLI, its actions, its skills) is one client of the same surface; an agent that has never heard of CraftBot gets the same information and the same protection. This is verified, not aspirational: an app can be driven end to end with `curl` and one token. + +## The surface + +| Endpoint | Purpose | +|---|---| +| `GET /api/_a2app` | Identity: which app this is, which contract it speaks, its clock | +| `GET /api/_a2app/describe` | The data model: entities, fields, types, plus operating conventions | +| `GET /api/_ops` | The verb surface: every declared operation with typed parameters | +| `GET /api/collections/{entity}/records` | Read, with PocketBase's `filter`, `sort`, `page`, `perPage`, `expand` | +| `POST/PATCH/DELETE /api/collections/{entity}/records[/{id}]` | Guarded record writes (token required) | +| `POST /api/ops/{name}` | Invoke a declared operation | +| `GET /api/_jobs/{jobId}` | Poll a long-running job started by a `job` operation | + +### Identity + +```json +{ "a2app": true, "protocol": "1.0", "adapterVersion": "1.6.0", + "app": { "id": "cde3d7c6", "name": "Kanban Board", "pbVersion": "0.39.7" }, + "schemaVersion": "sv_c28c7d1f", + "serverNow": "2026-07-30 09:15:00Z", "serverTzOffsetMinutes": 60 } +``` + +Unauthenticated, and more than a greeting. PocketBase answers **HTTP 200 for any unknown path**, so a status code can never tell you whether the thing on a port is a Living UI, a different Living UI whose port shifted, an unrelated dev server, or a stale process. The identity call is the only reliable probe: + +| Field | The failure it prevents | +|---|---| +| `a2app: true` | Talking to something that is not a Living UI. Absent, or HTML came back: stop | +| `app.id` | Writing to the wrong app. Identity survives a port change; confirm it matches the app the user meant | +| `protocol` / `adapterVersion` | Contract versus implementation. The contract stays stable while the adapter gains fixes; a client can detect a known bug or a stale app | +| `pbVersion` | The filter grammar is PocketBase's and therefore part of this contract; this says which dialect you get | +| `schemaVersion` | Writing against a stale schema. Cache `describe` against this fingerprint and re-fetch when it changes | +| `serverNow` / `serverTzOffsetMinutes` | The app's clock and zone, so a client can tell whether its own clock agrees before sending date-based writes | + +### Describe + +`GET /api/_a2app/describe` returns entities, fields, and conventions, generated from the **live** schema on every request so it cannot drift: + +```json +{ "entities": { + "cards": { + "label": "title", + "records": "/api/collections/cards/records", + "fields": { + "title": { "type": "string", "required": true, "max": 255 }, + "list": { "type": "ref", "required": true, "entity": "lists" }, + "due_date": { "type": "datetime" }, + "priority": { "type": "enum", "values": ["none","low","medium","high","urgent"] } } } }, + "operations": [ ... ], + "conventions": { ... } } +``` + +The type vocabulary is closed and deliberately backend-neutral, so a client written against it works unchanged against any other backend: + +| Type | Wire form | +|---|---| +| `string` / `number` / `boolean` | JSON primitive | +| `datetime` | `"2026-07-30"` or `"2026-07-30 00:00:00.000Z"` | +| `enum` | one of `values` | +| `ref` | a 15-char record id of `entity` | +| `list` / `list` | array of the above | +| `json` / `binary` | any JSON value / file upload (multipart) | + +A `string` carrying `"format": "YYYY-MM-DD"` is a **day key**: a date stored as text to avoid timezone drift, validated as a date despite its type. `label` names the field a human uses to identify a record (resolve `"To Do"` to an id with one filtered GET). `readOnly` fields are server-managed; write-only fields (passwords) are never advertised. + +`conventions` carries the rules for driving the app *well*, not just legally: prefer a declared operation over raw collection writes, confirm anything marked `destructive`, write only what the app's own UI would, and if the app cannot express what was asked, say so rather than approximating it into a field that means something else. These rules live in the app, not in CraftBot, precisely so an external agent gets them too. + +### Operations + +Collections are the app's nouns; **operations are its verbs**: the actions the app's author decided outsiders may invoke. They are declared in the project's `operations.json`, validated by the build gate (every declared route must exist, every route should be declared), and discovered at `GET /api/_ops`. + +Each operation declares a unique name (`items.clear-done`), a description, **typed parameters** (string/number/boolean, with `required`, `default`, and `enum` constraints), and an executor: + +| Executor | Meaning | +|---|---| +| `http` | The normal case: a server hook route. Parameters become the JSON body (POST/PUT/PATCH) or query string (GET/DELETE) | +| `crud` | A declarative pointer to parameterized collection CRUD, optionally with a fixed filter. No hook code needed | +| `job` | Long-running work. The route returns `{"jobId": ...}` immediately; progress is polled at `GET /api/_jobs/{jobId}` | + +Two flags change how hosts treat an operation: + +- `destructive: true` marks operations that delete or overwrite data. Hosts confirm with the user before running one, and future access grants may require per-call consent for them. +- `schedule` (`"every 15m"`, `"daily 09:00"`, `"hourly"`) declares recurring work the app wants run. + +### The write path + +A write passes through four checks, ordered so each catches what the one before it cannot see: + +```mermaid +flowchart TB + S["Agent wants:
add a todo, due tomorrow"] + C1["1. Client coercion
'tomorrow' → 2026-07-31
'To Do' → a record id"] + C2["2. Middleware guard
bad dates, unknown fields,
wrong types, bad enums"] + C3["3. PocketBase
required fields, relation ids"] + C4["4. Read-back
did every value land?"] + OK[("Stored")] + RCPT["Receipt
generated from the record"] + ERR["Rejected
machine code + every violation"] + + S --> C1 --> C2 --> C3 --> OK --> C4 --> RCPT + C1 -.->|unresolvable| ERR + C2 -.->|invalid| ERR + C3 -.->|invalid| ERR + C4 -.->|value missing| ERR +``` + +Each layer sits where it does for a measured reason: coercion of human input happens on the **client** (which has the clock and locale to do it); the guard runs in **middleware before PocketBase coerces the body**, because after coercion a garbage value and a deliberate "clear this field" are indistinguishable; PocketBase keeps what it already does well (required fields, relation ids); and the **read-back** is the backstop for anything the first three did not anticipate: a value that was accepted but did not land is an error (`not_stored`), never a partial success. + +### Authentication + +Two independent checks, not a menu; depending on who you are and what you touch, you may need neither, either, or both. + +**Check 1: may you write at all?** The discriminator is the `Origin` header, for a mechanical reason: a browser always attaches `Origin` to a write, and a program never does. + +| Where the write comes from | `Origin` | Needs | +|---|---|---| +| The app's own web UI | its own loopback origin | nothing | +| CraftBot, an agent, `curl` | none sent | **`X-LUI-Token`** | +| Any other website you have open | a foreign origin | **refused (403)** | + +That third row is the attack this closes: without it, any web page you happened to have open could read and write every Living UI on your machine. The token lives at `.agent-token` in the project directory (0600, created at launch). It is a credential you can deliberately hand to an outside agent, not a sandbox: handing out write access is an act, never ambient. + +**Check 2: who are you acting as?** Only on `authMode: multi-user` apps, where operations run as a principal: `/api/ops/*` additionally requires a PocketBase auth token, the same one the app's own users sign in with. A script hitting an operation on a multi-user app carries both headers; on a single-user app, check 2 does not exist. + +**Attribution, not authentication.** Writes also carry `X-LUI-Agent: `. It is self-asserted and verifies nothing; it exists so `logs/agent-actions.jsonl` records which agent wrote what when several agents share an app. Useless against malice, exactly right against confusion. + +### Reading and writing + +Reads use PocketBase's `filter`, `sort`, `page`, `perPage`, `expand`. The filter grammar is PocketBase's and is pinned with the PocketBase version as part of the contract: an upgrade that changes filter semantics is a breaking change here. + +Writes follow four rules: + +- **Dates are ISO 8601.** Relative words like `"tomorrow"` are rejected; resolve them before sending. +- **References are record ids.** Resolve a label with a filtered GET first. More than one match is **ambiguous, not a choice**: ask or fail, never pick. (Every kanban board seeds its own "To Do" list; multi-match is the normal case, not an edge case.) +- **Retries carry `Idempotency-Key`.** A replay returns 409 naming the record the first attempt created (reject-duplicate, scoped per entity, 24h). +- **Everything is validated and read back.** See the write path above. + +### Errors + +Branch on `code`, never on prose. Every rejection carries the `a2app: true` marker (distinguishing it from PocketBase's own errors) and lists **every** violation, so one round trip fixes them all: + +```json +{ "a2app": true, "ok": false, + "code": "invalid_date", "field": "due_date", + "expected": "an ISO 8601 date", "got": "tomorrow", + "violations": [ { "code": "invalid_date", "field": "due_date" }, + { "code": "unknown_field", "field": "bogus" } ] } +``` + +| Code | Meaning | +|---|---| +| `unknown_field` | Not a field of this entity (the message lists the valid ones) | +| `read_only_field` | Server-managed | +| `invalid_date` / `invalid_daykey` | Not ISO 8601 / not `YYYY-MM-DD` | +| `invalid_string` / `invalid_number` / `invalid_boolean` / `invalid_enum` | Wrong type | +| `not_stored` | Accepted but absent from storage: failed, not partial | +| `duplicate_request` | This `Idempotency-Key` already produced a record (409) | + +Plus transport-level `403 forbidden origin` and `401` for a missing agent token or missing authentication. Every one of these was once a silent HTTP 200. + +### Receipts + +The model does not report what happened. When the agent writes to an app, the confirmation you read (*Added "Eat chicken" to To Do, due Fri 31 Jul*) is generated by CraftBot **from the stored record**. If the agent claims a change when nothing was written, its message is withheld and the discrepancy is handed back to it to correct; you never see a system component contradicting your assistant. + +## Transports + +The contract is HTTP; every transport is a client of it. For CraftBot's agent the **`lui` CLI is the primary way to drive an app**, and **raw HTTP is the fallback** for the cases the CLI does not cover. What exists today and what is planned: + +| Transport | Status | Role | +|---|---|---| +| **The `lui` CLI** | Available | **Primary.** CraftBot's client: resolves the project's port, authenticates, validates parameters against the live schema, coerces human input (`"tomorrow"` to a date, `"To Do"` to a record id), and prints readable errors. Nothing gates other shell-capable agents from using it too | +| **HTTP** | Available | **The contract, and the fallback.** Four calls and one credential drive any app from any language; no CraftBot, no CLI, no Node required. CraftBot drops down to it when the CLI cannot express a call | +| **Realtime subscriptions** | Available | PocketBase realtime, used by every app's own UI to live-update without polling. Fast agents can subscribe the same way | +| **MCP gateway** | Planned | One installed gateway exposing every local Living UI to MCP-speaking agents that have no shell (Claude Desktop, IDEs). This is what turns "any agent" from possible into practical | +| **App-to-agent queue** | Planned | The reverse direction; see below | + +What the CLI adds is client-side work any client can do for itself: + +| The CLI does | An agent without it | +|---|---| +| `"tomorrow"` to an ISO date | resolves it itself before sending | +| `"To Do"` to a record id | one filtered GET, exactly as `describe.conventions` documents | +| schema discovery | reads `describe`, the same endpoint the CLI reads | +| readable errors | reads `code` from the JSON | + +### Why the CLI, and not MCP + +For an agent that has a shell, the CLI beats an MCP gateway on every axis that matters here: + +- **No middleman.** The CLI is a thin client that talks straight to the app. An MCP gateway is a long-running server between agent and app: one more process to install, keep alive, and keep in sync. +- **Always current, no registration.** The CLI reads the app's live `describe` on use, so a schema change is visible immediately. An MCP gateway must expose per-app tool definitions, which means registering every app and re-syncing tools whenever a schema changes. +- **Cheaper in context.** CLI calls cost a command line. Loading every app's operations as MCP tool schemas puts the whole surface in the agent's context up front; a measured predecessor of this design cost tens of thousands of extra tokens per write compared to reading the schema on demand. +- **Composable.** The CLI pipes, scripts, and loops like any other command, so multi-step operations stay in one shell session instead of many round trips through a gateway. +- **Same guarantees either way.** Guard, receipts, and errors live in the app, so the CLI adds convenience without creating a privileged path that MCP clients would lack. + +MCP still has a place, which is why the gateway is on the roadmap: it is for agents that have **no shell at all**. For those clients MCP is the only practical transport; for everything else it is an extra hop. + +## Bidirectional communication + +Today the direction is agent-drives-app. The planned reverse direction lets a **button in the app ask an agent to do something**: "Summarise this board" inserts a request into a queue collection inside the app; a linked agent claims it, does the work, and writes the result back. + +```mermaid +sequenceDiagram + participant UI as App UI + participant App as Living UI + participant Ag as Any linked agent + + UI->>App: user clicks "Summarise this board" + App->>App: insert into the request queue + Note over App,Ag: realtime subscription for fast agents,
plain polling for simple ones + Ag->>App: claim the request + Ag->>Ag: do the work + Ag->>App: write the result + App->>UI: result appears +``` + +The mechanism is deliberately boring (a queue collection, because any agent can poll REST and almost none can receive a webhook), and the security shape is the point. An app that can ask an agent to act, combined with third-party apps, means an app someone else wrote could drive an agent that holds your mail, calendar, and payment integrations. So this direction ships only with: + +- a **declared capability vocabulary** in the manifest: exactly which agent actions this app may request, +- **consent at install**, per capability, in words the user can evaluate, +- **no arbitrary passthrough**, ever. + +## Beyond one machine + +Everything above assumes the app is on loopback, on your own machine, where "any process running as you" is already trusted. Deploying an app somewhere reachable changes the questions from *is this a program?* to *which program, acting for whom, allowed to do what?* The designed (not yet built) answers: + +| Loopback today | Deployed | +|---|---| +| Origin guard | TLS plus allowed origins | +| One shared `.agent-token` | Per-agent capability tokens | +| `X-LUI-Agent`, self-asserted | Agent keypairs, verified | +| All-or-nothing access | Scopes derived from `describe`, granted with user consent | + +An agent's identity becomes a self-generated keypair (no registry to join; identity is free, trust is granted per app by its owner). Access becomes a **grant** binding three things: which agent key, which user it acts for, and which scopes; effective permission is the user's own permission intersected with the granted scopes, so an agent can never exceed the person it acts for. Scopes are derived straight from `describe` (`op:`, `data::read|write`), so the thing an agent reads to learn an app is the same thing the consent screen is built from. Tokens are short-lived and refreshed by proof of possession; consent is always human; anything `destructive` can demand confirmation per call; every call is audited and any grant is revocable in one click. + +The current loopback pieces are the single-agent case of the same shapes, which is why none of this requires unpicking what exists. + +## Any technology + +Today the adapter is a PocketBase hook. The planned any-stack path makes the same surface available for an app written in anything (Django, Rails, Next.js, a SaaS REST API): + +1. **Point CraftBot at the app**: import it, or have CraftBot build it in the stack you asked for. +2. **CraftBot probes it** and writes a **mapping**: which entities exist, which fields they have *in protocol types*, and how to reach them. The mapping is data, not code, which is what makes third-party adapters reviewable. +3. **CraftBot verifies the mapping** with real reads and writes. A mapping that does not actually work is rejected rather than shipped. +4. **A shared runtime** reads the mapping and serves `describe`, the guard, the error contract, and receipts: the same surface this page documents. + +After step 4, an agent cannot tell the difference, and a pipeline that cannot map an API says so explicitly rather than emitting a plausible-looking wrong mapping. + +## Where knowledge lives + +The rule that keeps the protocol honest. When something is added, it goes in the row it belongs to: + +| Knowledge | Lives in | Why | +|---|---|---| +| Entities, field types, enum values | the app (`describe`) | every client needs it; generated live so it cannot drift | +| How to drive the app well | the app (`describe.conventions`) | an external agent has no access to CraftBot's skills | +| What is a valid value | the app (the guard) | the last line every caller passes | +| Coercing human input into stored values | the client | it holds the context (clock, locale) the app's runtime lacks | +| The `lui` CLI, CraftBot's actions | CraftBot | one client's tooling | +| How to build or evolve an app | a skill, loaded per run | only relevant to the run doing it | + +## Status + +| Capability | State | +|---|---| +| Identity, describe, guard, read-back backstop | Built | +| Agent token, origin guard, operations auth, rate limits | Built | +| Idempotency (`Idempotency-Key`) | Built | +| System-authored receipts and the false-claim gate | Built | +| CLI as a thin client (verified: an app is drivable with `curl` alone) | Built | +| Adapter delivery at create, install, import, and every launch | Built | +| MCP gateway (one install, every agent) | Planned | +| App-to-agent queue, capabilities, consent | Planned | +| Any-technology mapping and shared runtime | Planned | +| Deployed identity: keypairs, grants, scopes | Designed | + +## Next + +- [The Living UI framework](framework.md): the stack that serves this surface, and how operations are declared +- [Managing apps](managing.md): how CraftBot itself operates apps through this protocol diff --git a/mkdocs/docs/living-ui/framework.md b/mkdocs/docs/living-ui/framework.md new file mode 100644 index 00000000..8dbe89c3 --- /dev/null +++ b/mkdocs/docs/living-ui/framework.md @@ -0,0 +1,141 @@ +# The Living UI framework + +Every Living UI app is built on one stack, from one blueprint: a **React frontend** on a vendored component kit, and a **single PocketBase process** as database, API, and realtime server. One app, one process, one port. This page covers the stack, the ownership contract, how the agent builds and evolves an app (schema, operations, frontend), the bridge that gives apps LLM and integration access without keys, and the pipeline that decides whether an app is allowed to launch. + +## The stack + +| Layer | What it is | +|---|---| +| Frontend | React + Vite, styled with Tailwind utilities and kit design tokens; components follow shadcn conventions | +| Component kit | A versioned kit vendored into every project (`frontend/src/kit/`): realtime data hooks, entity presets, theme packs, UI primitives, and a console relay that pipes browser errors into the project's logs. System-managed; apps import from its public API and never edit it | +| Backend | PocketBase: collections, REST API, realtime subscriptions, JS migrations, and server hooks (JavaScript) | +| Operations layer | `operations.json` declares the app's public verbs; hook routes implement them; `GET /api/_ops` makes them discoverable | +| A2App adapter | System hooks inside the app that publish its schema, guard every write, and speak the [A2App protocol](a2app-protocol.md). Stamped into the app at create, install, import, and **every launch**, so existing apps pick up adapter fixes | +| CraftBot bridge | A system module hook routes can call to reach the host's LLM and connected integrations, with capability grants and zero keys in the app | + +Ports are allocated per app in the `3100-3199` range. An app never depends on CraftBot at runtime: a Living UI needs only the PocketBase binary, and bridge-backed features degrade gracefully when the host is absent. + +## Project anatomy + +Projects live at `agent_file_system/workspace/living_ui/_/`: + +```text +_/ +├── manifest.json Identity, ports, authMode, capabilities, run pipeline. +│ System-managed; the source of truth. Never rename a +│ project directory by hand. +├── LIVING_UI.md Per-project plan/index: entities, operations, external +│ data sources, ownership map. The agent keeps it current. +├── operations.json Declared verbs (non-system entries are editable) +├── reference/ +│ └── requirements.md The BINDING spec. Verification drives the app against +│ this file, feature by feature. +├── frontend/ +│ └── src/app/ All app UI code (editable) +│ src/kit/ The vendored kit (system-managed) +│ main.tsx, config.gen.ts, build configs (system-managed) +├── pb/ +│ ├── pb_hooks/ ops.pb.js + new *.pb.js (editable) +│ │ _a2app*.js, _system.pb.js, +│ │ _craftbot_bridge.js (system-managed) +│ └── pb_migrations/ One migration per schema change (editable) +├── logs/ pocketbase.log, frontend_console.log, agent-actions.jsonl +├── .agent-token The A2App write credential (0600, created at launch) +└── .superuser Machine superuser for administrative API access + (0600; never printed, copied, or shipped) +``` + +**Ownership is a contract, not a convention.** Every file has exactly one owner. The validation gate **hashes the system-managed files and fails the build if the agent touched them**. Need different behavior from a kit component? Wrap it in `app/` code; never edit kit files. + +## The requirements spec + +`reference/requirements.md` is the binding definition of what the app does. It is written during the creation interview, and **every evolution appends a dated bullet to its `## Changes` section**: verification checks the running app against this file, so a stale spec produces a wrong verdict. If you want to know what an app is supposed to do, this file is the answer. + +## How the agent builds + +The build loop is fixed, and each step has rules the gate enforces: + +```text +1. Read reference/requirements.md and LIVING_UI.md +2. Schema first → one migration per change in pb/pb_migrations/ +3. Operations → hook route + operations.json entry per public verb +4. Frontend → compose kit parts in frontend/src/app/ +5. Validate → the gate; fix and repeat until clean +6. Debug → logs/frontend_console.log and logs/pocketbase.log +``` + +### Schema + +- One JS migration per change; an already-applied migration is never edited. +- Every record carries `id`, `created`, `updated` (autodate fields, following the starter migration's pattern). +- **Collection rules are the security boundary**, set from the project's auth mode in `manifest.json`: open rules for `authMode: none` (single-user), authenticated rules for `authMode: multi-user`. In multi-user apps, owner-scoped data uses a relation to `users` with rules like `owner = @request.auth.id`. + +### Operations + +Anything an outside agent should be able to *do* to the app is declared in `operations.json` and implemented as a hook route. The gate enforces both directions: every declared `http`/`job` operation must match a registered route, and it warns about routes that were never declared. Operation kinds, parameters, `destructive` marking, and scheduling are part of the protocol contract; see [Operations](a2app-protocol.md#operations). + +### External data, and the CraftBot bridge + +Apps reach the outside world two ways, both from server hooks only (never frontend `fetch`, where CORS breaks and keys would be public): + +- **The CraftBot bridge** for the host's services. Hook routes call `bridge.callLLM(prompt, system)` and `bridge.callIntegration('slack', 'POST', '/chat.postMessage', ...)` through the system module, with **zero API keys in the app**. The bridge fails closed on grants: the integration must be listed under `capabilities.integrations` in the system-managed manifest, and only integrations the user actually connected in CraftBot work. Outside CraftBot the bridge degrades gracefully (`callLLM` returns empty, `callIntegration` returns a 503-shaped status): apps skip the feature rather than crash. +- **Direct `$http.send`** for anything public (weather, stocks, any third-party API), always with a timeout. Base URLs stay as string literals because the gate derives the app's declared `external_hosts` egress capability from them. A non-200 from a source degrades to a clean error and the UI's offline/empty state; generated stand-in data is never substituted for a real source. Periodic syncs use `cronAdd` on the same hook modules. + +One constraint shapes all hook code: **handler callbacks run in isolated VMs** that cannot see their own file's scope, so shared logic lives in plain modules and is `require()`d inside each callback. + +### Frontend + +The frontend is composed from the kit's public API, and the kit does the heavy lifting: + +- **Data** is realtime by default: `useCollection('items', { sort: '-created' })` subscribes, so apps never poll and never reload. Writes go through the kit's client wrapper, which surfaces errors as toasts automatically. +- **Entity presets** collapse the common surfaces: `EntityForm` and `EntityTable` take a field/column spec and produce a validated create/edit form (reference fields become live dropdowns) and a live, sortable table with row actions and delete confirmation, both wired to a collection. +- **Primitives and hooks**: typed inputs (number, date, debounced search, tags), overlays (`useConfirm`, drawers, dropdown row actions, tooltips), data display (sparklines, mini bar charts, sortable lists, file/image upload), `useDebounce`, `useHotkey`. +- **Auth**: in multi-user projects the shell wraps the app in a login gate; `useAuth()` exposes the current user. +- **Styling**: Tailwind plus kit tokens (`var(--lui-*)`), never hardcoded colors, because theming is host-owned and must survive style-pack and dark-mode switches. +- **Required UX** (from the global design rules): empty states with a next action, loading states, confirmation dialogs for destructive actions, toasts on CRUD, responsive layout. + +## The build pipeline + +An app reaches its live URL only through two gates: + +```mermaid +flowchart LR + CODE["Code written
dedicated build session"] --> GATE["Validation gate
types · build ·
migrations on a fresh DB ·
ops structure/routing ·
ownership hashes"] + GATE --> BOOT["Boot
PocketBase + frontend + health"] + BOOT --> WV["walk_verify
headless browser drives every
feature against requirements.md
"] + WV -->|pass| LIVE(["Live"]) + GATE -.->|source-annotated errors| CODE + WV -.->|defect report| CODE +``` + +- **The validation gate** runs before anything boots: TypeScript must compile, the frontend must build, migrations must apply on a fresh database, the operations manifest must validate and route correctly, and the system-managed files must be untouched. Errors come back source-annotated, and a circuit breaker stops a build that keeps failing on the identical error. +- **walk_verify** is a [sub-agent](../core/concepts/sub-agents.md) that opens the running app in a headless browser and exercises it feature by feature against `reference/requirements.md`, folding server-side errors from `pocketbase.log` into its defect reports. Its verdict is `pass`, `incomplete`, `defects`, `blocked`, or `unparseable`, and a clean pass is the **only** way a build completes. On a first build, a pass marks the app delivered; on an evolution, it flips the staging copy live (see [Managing apps](managing.md#evolving-an-app)). + +The principle behind both gates, and behind the [protocol](a2app-protocol.md) itself: **a property that matters is enforced by the system, not requested of the model.** An app that does not demonstrably work in a real browser is not announced as working. + +## Development commands + +The agent runs the framework's CLI during builds (production serving is always the host's job, driven by the manifest's install/build/start/health pipeline): + +```text +lui validate the gate; run after every meaningful change +lui dev PocketBase + Vite HMR for development +lui kit-sync re-vendor the kit (only when instructed) +lui pb path the pinned PocketBase binary +``` + +## Logs + +First stop when an app misbehaves: + +| File | Contains | +|---|---| +| `/logs/pocketbase.log` | Server side: hooks, migrations, API errors, bridge calls | +| `/logs/frontend_console.log` | The browser console (`console.error`/`warn` and uncaught errors), relayed automatically by the kit | +| `/logs/agent-actions.jsonl` | Every agent write, attributed to the agent id that made it | + +## Next + +- [The A2App protocol](a2app-protocol.md): the contract this stack serves to agents +- [Managing apps](managing.md): operating and evolving a delivered app +- [Sub-agents](../core/concepts/sub-agents.md): how walk_verify runs diff --git a/mkdocs/docs/living-ui/index.md b/mkdocs/docs/living-ui/index.md index 3a33989c..dd692027 100644 --- a/mkdocs/docs/living-ui/index.md +++ b/mkdocs/docs/living-ui/index.md @@ -1,17 +1,86 @@ # Living UI -Living UI is CraftBot's app-builder: describe an app in chat and the agent designs, codes, tests, and launches a full web application that runs locally, embedded in the CraftBot browser interface. +Living UI is CraftBot's application platform. Describe an app in chat and the agent designs, codes, verifies, and launches a real web application (database, API, realtime web UI) that runs locally and renders inside the CraftBot browser interface. The app is not a demo artifact: after delivery the agent **operates** it on your behalf, **evolves** it when you ask for changes, and any other agent or script can drive it through an open protocol. -!!! warning "Section under construction" - The Living UI system is being finalized and this section will be written once it lands. - Until then, the fastest way to explore is to open the **Living UI** tab in the browser - interface and create an app from the modal. +Two pieces make that trustworthy: -## What will be documented here +
-- **Overview**: what Living UI is and when to use it -- **Creating apps**: the creation flow from a chat request or the Create modal -- **Architecture**: how a generated app is structured and launched -- **Components & theming**: the component library and global design preferences -- **Managing apps**: modifying, restarting, and operating running apps -- **Importing & sharing**: bringing in external projects and sharing apps on your network +- :material-cube-outline:{ .lg .middle } __[The Living UI framework](framework.md)__ + + --- + + What a generated app is made of and how the agent builds and evolves it: a React frontend on a vendored component kit, a single PocketBase backend, a declared operation surface, an LLM/integration bridge, and a build pipeline that refuses to launch anything it has not verified in a real browser. + +- :material-swap-horizontal:{ .lg .middle } __[The A2App protocol](a2app-protocol.md)__ + + --- + + The contract every Living UI presents to *any* agent: the app describes its own data model and verbs, guards every write it cannot store correctly, and the system (not the model) reports what changed. Covers the transports available today and the ones on the roadmap. + +- :material-play-circle-outline:{ .lg .middle } __[Managing apps](managing.md)__ + + --- + + Operating a delivered app's data and verbs, evolving it safely through a staging copy, restarting, importing, converting foreign apps, and the marketplace. + +
+ +## Build, evolve, operate + +The agent's relationship with a Living UI has three distinct capabilities, and the platform enforces the boundaries between them: + +| Capability | What it means | What guarantees it | +|---|---|---| +| **Build** | Turn a requirements interview into a working app: schema, verbs, UI | The validation gate plus browser verification; an app that does not demonstrably work is never announced as working | +| **Evolve** | Change a delivered app's code and schema on request | A staging copy with cloned data; the live app is replaced only by a verified successor | +| **Operate** | Act on the app's data and declared verbs in seconds ("add a todo for tomorrow" becomes a row) | The A2App protocol: schema discovery, write guards, and system-authored receipts | + +The distinction between operating and evolving is decided per request by the agent, and it matters: a data write never triggers a rebuild, and a code change never touches live data until it verifies. See [Managing apps](managing.md). + +## Creating an app + +Two entry points, one pipeline: + +- **Chat.** Ask for the app you want: "build me a kanban board for my freelance projects". The agent runs a requirements interview; if questions remain open, a form pops up in the browser to collect the answers. +- **Add Living UI** in the browser sidebar opens the creation wizard directly: name, description, layout, theme, and reference files. + +The build then runs in the project's own dedicated session, so your chat stays free. You watch progress live in the project's sidebar tab, and the app is announced ready only after it has passed the [build pipeline](framework.md#the-build-pipeline): validation gate, boot, and feature-by-feature browser verification against the requirements spec. + +```mermaid +flowchart LR + C["Create
requirements interview"] --> B["Build
dedicated session"] + B --> G["Validation gate
types · build · migrations ·
ops · ownership
"] + G --> W["walk_verify
real browser, every feature"] + W --> D(["Delivered
live URL, sidebar tab"]) + D --> OP["Operate
data + declared verbs"] + D --> MOD["Evolve
staging copy → verify → live"] + MOD --> G +``` + +## What a running app offers + +Every running Living UI is one local process on its own port that presents the same surface to every client: + +- **A web UI** for you, rendered in the app's sidebar tab (and reachable directly in any browser), updating in realtime as data changes. +- **The A2App protocol** for agents: self-describing schema (`GET /api/_a2app/describe`), a discoverable verb surface (`GET /api/_ops`), guarded record CRUD, and machine-readable errors. +- **Declared operations**: the verbs the app's author decided outsiders may invoke, including long-running jobs and scheduled work. +- **An audit trail**: every agent write is attributed and logged per project. + +CraftBot drives this surface primarily through its `lui` CLI, falling back to raw HTTP when the CLI cannot express a call; external agents drive the identical surface with plain HTTP. Transports, including why the CLI is preferred over an MCP gateway and the planned app-to-agent direction, are covered in [the protocol page](a2app-protocol.md#transports). + +## Design preferences + +`agent_file_system/GLOBAL_LIVING_UI.md` holds your universal design rules (colors, theme behavior, always-enforced component and UX rules) and is applied to every project the agent builds. Per-project decisions and agreed overrides live in each project's own `LIVING_UI.md`. Theming itself is host-owned: apps use kit tokens rather than hardcoded colors, so style packs and dark mode keep working across every app, and each project tab has a theme picker. + +## Where apps live + +- **In the browser:** one sidebar tab per project, with creation progress, clarifying-question forms, and the theme picker. +- **On disk:** `agent_file_system/workspace/living_ui/_/`; see [project anatomy](framework.md#project-anatomy). +- **At runtime:** one process per app on a port in the `3100-3199` range. An app depends only on the PocketBase binary, not on CraftBot: it keeps working, protocol included, wherever it runs. + +## Next + +- [The Living UI framework](framework.md): the stack, the build loop, and the verification gates +- [The A2App protocol](a2app-protocol.md): the full agent-facing contract, transport options, and the roadmap +- [Managing apps](managing.md): operate, evolve, restart, import diff --git a/mkdocs/docs/living-ui/managing.md b/mkdocs/docs/living-ui/managing.md new file mode 100644 index 00000000..cc6c3cc2 --- /dev/null +++ b/mkdocs/docs/living-ui/managing.md @@ -0,0 +1,98 @@ +# Managing apps + +A delivered Living UI is a live application with your real data in it. Everything that happens to it afterward falls into two categories with very different mechanics: **operating** (data and verb calls through the [A2App protocol](a2app-protocol.md): instant, no rebuild) and **evolving** (code and schema changes, which go through a staging copy and full re-verification before they touch the live app). This page covers both, plus restarting, importing, converting foreign apps, the marketplace, and multi-agent use. + +## Operate or evolve + +The agent decides which category a request is, per request; nothing is routed in advance: + +| You say | Category | What happens | +|---|---|---| +| "add a todo for tomorrow" | Operate | One validated write. Seconds | +| "clear all the done items" | Operate | One declared operation, confirmed first if marked destructive | +| "summarise this week's entries" | Operate | Reads plus (if the app declares one) an operation | +| "add a priority filter to the board" | Evolve | Staging copy, code, validation gate, browser verification, then live | + +The boundary is enforced, not just encouraged. Getting it wrong used to be expensive: a data write that triggers the build machinery rebuilds a live app and drives a browser over your real records. Build skills therefore load **per run**, chosen by the agent from the request, and a plain write never touches them. + +## Operating an app + +Ask in chat; the app's sidebar tab or the main chat both work. Under the hood the agent follows the discovery ladder every A2App client follows: + +```text +1. Identity GET /api/_a2app confirm it is the right app, cache-check the schema +2. Verbs GET /api/_ops what can this app DO? +3. Declared op exists → run it destructive ops are confirmed with you first +4. No op → record CRUD read freely; write only what the app's own UI offers +5. Would require new code → that is an evolution, not an operation; the agent says so +``` + +The agent walks this ladder through the **`lui` CLI**, its primary transport: the CLI resolves the port, authenticates, and validates parameters against the live schema. Raw HTTP is the fallback for calls the CLI cannot express; see [Transports](a2app-protocol.md#transports) for the hierarchy and why the CLI is preferred over an MCP gateway. + +Three properties make this safe to do casually: + +- **The write is guarded.** Bad values are refused with machine-readable errors; nothing lands half-stored. See [the write path](a2app-protocol.md#the-write-path). +- **The confirmation is a receipt.** The line you read is generated from the stored record, not from the model's belief about what it did. +- **The UI updates in place.** Apps subscribe to their data in realtime, so a write appears on screen without a reload. + +Long-running work runs as a **job**: the operation returns a `jobId` immediately and the agent polls its status rather than blocking. Operations can also declare a **schedule** (`"every 15m"`, `"daily 09:00"`), for work the app wants run recurrently. + +Because the surface is the open protocol, you are not limited to CraftBot: any agent or script holding the app's token can operate the same data the same way, and every write is attributed in `logs/agent-actions.jsonl` so shared use stays untangled. + +## Evolving an app + +Code changes to a delivered app never touch it directly: + +```mermaid +flowchart LR + REQ["Change request"] --> STG["Staging copy
cloned data, hidden port"] + STG --> CODE["Agent edits code
+ appends to requirements.md"] + CODE --> GATE["Validation gate"] --> WV["walk_verify
headless browser"] + WV -->|pass| FLIP["Staging flips live"] + WV -.->|defects| CODE +``` + +- The agent loads a build skill for the run, works on a **staging copy** with a disposable clone of the app's data on a hidden port, and follows the same [build loop](framework.md#how-the-agent-builds) as a first build: schema migrations first, operation declarations, kit-composed UI, gate after every meaningful change. +- The change is appended to `reference/requirements.md` under `## Changes`, keeping the binding spec current; verification checks the app against that file, so a stale spec would produce a wrong verdict. +- Only a clean verification verdict flips staging to live. A failed change never replaces the working app, and your real data is never the test bed. + +Mid-arc writes to the live app's real data are refused while an evolution is in flight, so the two paths cannot interleave. + +## Restarting + +Ask the agent to restart an app (or use its tab). A restart runs the full launch pipeline: dependency check, validation gate, boot (PocketBase plus frontend), health check. Launch also re-stamps the [A2App adapter](a2app-protocol.md) and refreshes the agent token, which is how apps a user already had pick up adapter fixes; delivery at create, install, import, **and every launch** is what keeps the whole installed base current. + +## Importing, converting, and the marketplace + +| Source | What happens | +|---|---| +| **Marketplace** | Pre-built apps installed by id from the app's tab or chat. As-is installs skip browser verification; asking for adaptations turns the install into a build | +| **A Living UI project** (ZIP, folder, git URL) | Registered as a new delivered project: credentials stripped, kit re-vendored, adapter stamped, launch verification queued in its own session | +| **Any other app** (foreign stack) | Registered as an **external app**: it runs under CraftBot with a `craftbot.json` run config (install/build/start/health verbs), logs to `logs/app.log`, but does not speak the protocol. Ask the agent to **convert** it and it rebuilds the app on the Living UI framework: the original source is kept in `reference/source/`, requirements are synthesized from it, then the normal build pipeline runs | + +## Multi-agent use + +An app is a surface several agents can share deliberately: + +- **Give an agent access** by handing it the project's `.agent-token` (and, on a multi-user app, an account to act as). Handing out the token is the act that grants write access; nothing is ambient. +- **Tell writers apart** via `X-LUI-Agent` attribution in `logs/agent-actions.jsonl`. It defends against confusion, not malice; on one machine, everything running as you is already inside the trust boundary. +- The planned per-agent identity model (keypairs, scoped grants, per-call consent for destructive operations) extends this to deployed apps; see [Beyond one machine](a2app-protocol.md#beyond-one-machine). + +## Troubleshooting + +| Symptom | Check | +|---|---| +| App tab is blank or erroring | `/logs/frontend_console.log`; the browser console is captured there automatically | +| Writes fail or data looks wrong | `/logs/pocketbase.log` for server-side causes; a protocol rejection names the field and rule in its [error code](a2app-protocol.md#errors) | +| Not sure the thing on a port is your app | `GET /api/_a2app`; check `a2app: true` and `app.id`, never the status code | +| An operation seems missing | `GET /api/_ops` is the authoritative verb list; if the verb is not there, the app does not offer it and adding it is an evolution | +| A build or evolution keeps failing | Gate errors are source-annotated, and a circuit breaker stops identical-error loops; the build session's activity shows the last gate output | +| A bridge-backed feature does nothing | The integration must be connected in CraftBot and granted in the app's manifest capabilities; outside CraftBot, bridge features degrade to skipped | +| Which agent changed my data? | `/logs/agent-actions.jsonl` records every write with the agent id that made it | + +Do not rename or move a project directory by hand: `manifest.json` is the source of truth for identity and ports, and the platform manages the directory. And never print or copy `.superuser`; it is the machine-level administrative credential. + +## Next + +- [The A2App protocol](a2app-protocol.md): the contract behind every operate request +- [The Living UI framework](framework.md): the build loop and pipeline behind every evolution diff --git a/mkdocs/docs/reference/agent-md-files.md b/mkdocs/docs/reference/agent-md-files.md index 9303fab5..0648e6ac 100644 --- a/mkdocs/docs/reference/agent-md-files.md +++ b/mkdocs/docs/reference/agent-md-files.md @@ -17,9 +17,11 @@ This page is the per-file format reference. It documents each file's sections, f | `MEMORY.md` | Memory processor (nightly job) | No | No. Surfaces through `memory_search` retrieval | | `EVENT.md` | Event stream manager | No | No | | `EVENT_UNPROCESSED.md` | Event stream manager | No | No. Read by the nightly memory run | +| `TASK_HISTORY.md` | Appended on every `task_end` | No | No | +| `CONVERSATION_HISTORY.md` | Event stream manager | No | No | | `MISSION_INDEX_TEMPLATE.md` | Static template | No | No | -The injection facts match the [Context engine](../core/concepts/context-engine.md): the system prompt embeds `USER.md` and `SOUL.md` verbatim and includes a map of the file system, but not the full text of `AGENT.md`. Distilled memory reaches the agent through retrieval, described in [Memory](../core/concepts/memory.md). The files marked "No" under editing are harness-managed. Their formats are contracts that other subsystems depend on, so read them freely but do not hand-edit them. +The injection facts match the [Context engine](../core/concepts/context-engine.md): the system prompt embeds `USER.md` and `SOUL.md` verbatim and includes a map of the file system, but not the full text of `AGENT.md`. Distilled memory reaches a task through retrieval, described in [Memory](../core/concepts/memory.md). The five files marked "No" under editing are harness-managed. Their formats are contracts that other subsystems depend on, so read them freely but do not hand-edit them. ## AGENT.md @@ -189,6 +191,30 @@ The staging buffer of events awaiting the nightly memory run. **Runtime use.** Input to the memory pipeline. It is indexed for memory search. +## CONVERSATION_HISTORY.md + +The rolling transcript of user and agent exchanges. + +**Structure.** `# Conversation History`, an `Agent DO NOT edit this file.` line, an `## Overview`, and a `## Conversations` section. + +**Who maintains it.** The event stream manager records each exchange. The file is never auto-cleared and grows indefinitely. + +**Edit guidance.** Do not hand-edit it. + +**Runtime use.** Provides context for follow-up turns. + +## TASK_HISTORY.md + +One summary per completed task. + +**Structure.** `# Task History`, an `Agent DO NOT edit this file.` line, an `## Overview`, and a `## History` section that receives one summary block per finished task (status, timestamps, outcome, and related detail). + +**Who maintains it.** Appended automatically on every `task_end`. See [Task sessions](../core/concepts/task-sessions.md). + +**Edit guidance.** Do not hand-edit it. It grows indefinitely. + +**Runtime use.** A record for review and for memory search. + ## MISSION_INDEX_TEMPLATE.md The template for a multi-session mission's index file. diff --git a/mkdocs/docs/reference/events.md b/mkdocs/docs/reference/events.md index 46280977..d7f17382 100644 --- a/mkdocs/docs/reference/events.md +++ b/mkdocs/docs/reference/events.md @@ -1,32 +1,31 @@ # Event types -An event is one entry in a [session](../core/concepts/task-sessions.md)'s event stream: a message, a typed category, a severity, and optional structured fields. The [event stream](../core/concepts/event-stream.md) concept page explains the stream mechanics (one stream per session, the verbatim tail plus rolling summary, how snapshots reach the model). This page is the per-type catalogue: what each `EventType` value means, what fields an event record carries, and how consumers route on them. +An event is one entry in a [task session](../core/concepts/task-sessions.md)'s event stream: a message, a typed category, a severity, and optional structured fields. The [event stream](../core/concepts/event-stream.md) concept page explains the stream mechanics (one stream per session, the verbatim tail plus rolling summary, how snapshots reach the model). This page is the per-type catalogue: what each `EventType` value means, what fields an event record carries, and how consumers route on them. -The type is a closed set. It is defined as the `EventType` enum in `agent_core/core/event_stream/event.py` and has exactly 12 values. Consumers route on this field and never on message text. +The type is a closed set. It is defined as the `EventType` enum in `agent_core/core/event_stream/event.py` and has exactly 13 values. Consumers route on this field and never on message text. ## Event types -The "UI render" column describes what the browser event transformer (`app/ui_layer/events/transformer.py`) produces for each type. Several types are deliberately stream-only: they are context for the model, not for you. - -| `EventType` | Emitted when | UI render | -|---|---|---| -| `user_message` | A message arrives from you, entered locally or routed from a connected platform. | Shown as a chat bubble, emitted directly by the controller. The transformer suppresses the stream echo to avoid a double render. | -| `agent_message` | The agent sends a chat reply (the `send_message` action). | Chat bubble. The event's `continue_work` flag tells the UI whether this is a mid-run progress update (keep the "Working…" indicator up) or a final, run-ending reply. | -| `system` | The harness posts a notice: a status change, a warning, or a loop-detection notice upgraded to `system`. | System notice line. | -| `error` | The agent surfaces a failure worth showing. | Error notice line. | -| `reasoning` | The LLM emits its rationale for the next action(s). | Reasoning block, keyed so repeated updates group together. | -| `action_start` | Immediately before an action runs. | Action row opens. Suppressed when `action_name` is the internal control action `end_turn`. | -| `action_end` | After an action finishes. | Action row closes; status is read from `action_output["status"]`, not from message text. Suppressed for the same internal action name. | -| `trigger` | A non-user trigger's instruction is written into the stream when its turn claims it (user messages enter as `user_message` instead). | Hidden. The chat-side announcement is emitted separately by the runtime, so the stream copy never double-posts. | -| `waiting_for_user` | The agent parks a question for you in the stream. | Hidden by the transformer; the question itself reaches you as an `agent_message`. | -| `relevant_memories` | Memory retrieval injects recall pointers into the stream. | Hidden. It is context for the model, not for you. | -| `todos` | The run's todo list changes. | Hidden by the transformer; the checklist renders from session state on a separate path. | -| `internal` | Bookkeeping the agent records for itself, including the Continue/Stop limit-choice notice. | Hidden. | +The "UI render" column describes what the browser event transformer (`app/ui_layer/events/transformer.py`) produces for each type. The "Conversational" column marks the two types recorded into the cross-task conversation buffer (`record_conversation_message`) that injects recent dialogue into new tasks. Everything else is stream-only or transient. + +| `EventType` | Emitted when | UI render | Conversational | +|---|---|---|---| +| `user_message` | A message arrives from you, entered locally or routed from a connected platform. | Shown as a chat bubble, emitted directly by the controller. The transformer suppresses the stream echo to avoid a double render. | Yes | +| `agent_message` | The agent sends a chat reply (the `send_message` action). | Chat bubble. | Yes | +| `system` | The harness posts a notice: a status change, a warning, or a loop-detection notice upgraded to `system`. | System notice line. | No | +| `error` | The agent surfaces a failure worth showing. | Error notice line. | No | +| `reasoning` | The LLM emits its rationale for the next action(s). | Reasoning block, keyed so repeated updates group together. | No | +| `action_start` | Immediately before an action runs. | Action row opens. Suppressed when `action_name` is an internal control action (`task_start`, `ignore`). | No | +| `action_end` | After an action finishes. | Action row closes; status is read from `action_output["status"]`, not from message text. Suppressed for the same internal action names. | No | +| `task_start` | A task is created. | Task-start marker. | No | +| `task_end` | A task finishes. | Task-end marker carrying `task_status` (`completed`, `error`, or `cancelled`). | No | +| `waiting_for_user` | A task pauses for your reply or approval. | Waiting indicator on the task. | No | +| `relevant_memories` | Memory retrieval injects recall pointers into the stream. | Hidden. It is context for the model, not for you. | No | +| `todos` | The task's todo list changes. | Hidden by the transformer; the checklist renders from task state on a separate path. | No | +| `internal` | Bookkeeping the agent records for itself. | Hidden. | No | Every type above is documented; the enum contains no others. Producers must pass `event_type` explicitly at `log()` time. An event that reaches the transformer with no `event_type` renders as nothing, which flags an unmigrated producer. -Alongside the closed-set type, each event also carries a free-text `kind` label that appears in `EVENT.md` lines. One `kind` worth knowing: `action_error` marks an action that was **dropped before execution** for violating a parallel-execution constraint (for example, two `send_message` calls in one batch). An action that ran and failed is a normal `action_end` whose output carries `status: "error"`. - ## Event record fields Each event is an `Event` dataclass. The stream wraps it in an `EventRecord` that adds timing and a repeat counter. Fields, from the source: @@ -44,8 +43,8 @@ Each event is an `Event` dataclass. The stream wraps it in an `EventRecord` that | `action_id` | Stable identifier shared by an action's start and end events so they correlate without parsing. | | `action_input` | Structured input payload on `action_start`. | | `action_output` | Structured output payload on `action_end`, including the `status` key the UI reads. | +| `task_status` | `completed`, `error`, or `cancelled` on `task_end`. | | `platform` | Originating or destination platform for chat messages (for example `Telegram`, `CraftBot Interface`). | -| `continue_work` | On `agent_message` only: `true` when the agent sent this as a mid-run progress update and will keep working; `None`/`false` for final replies and non-chat events. | | `repeat_count` | On the wrapping `EventRecord`: how many identical consecutive occurrences collapsed into this record. Defaults to 1. | ## Action event pairing @@ -55,7 +54,7 @@ An `action_start` and its `action_end` share the same `action_id`. That id is ge Two details follow from routing on structured fields rather than text: - **Status comes from the payload.** `action_end` classification reads `action_output["status"] == "error"`. The message string is never inspected, so an action whose output text happens to contain the word "error" is not miscoloured. -- **Internal actions stay hidden.** `action_start` and `action_end` whose `action_name` is `end_turn` produce no UI row. It is control flow (the action that ends a run silently), not user-visible work. +- **Internal actions stay hidden.** `action_start` and `action_end` whose `action_name` is `task_start` or `ignore` produce no UI row. They are control flow, not user-visible work. Repeated identical events do not each get their own record. When the same event recurs, the stream increments `repeat_count` on the existing `EventRecord` instead of appending a new one. The compact line renders the count as an ` xN` suffix, so a stall that logs the same notice fifty times reads as one line with ` x50` rather than fifty lines. @@ -68,7 +67,7 @@ Persisted events written before `event_type` existed have no value for that fiel | Legacy `kind` | Upgraded `event_type` | |---|---| | `action_error` | `action_end` | -| `gui action start` / `gui action end` | `action_start` / `action_end` | +| `task_started` / `task_ended` | `task_start` / `task_end` | | `agent reasoning` | `reasoning` | | `warning`, `loop_detection_warning` | `system` | | `agent message ...` (prefix) | `agent_message` | @@ -80,11 +79,11 @@ This path is for restored data only. New code sets `event_type` at `log()` time Every event is also appended to `EVENT.md`, the complete on-disk history. A subset is additionally staged in `EVENT_UNPROCESSED.md`, the buffer the [memory pipeline](../core/concepts/memory.md) distills. Routine event kinds the memory processor would always discard are filtered out at write time by `SKIP_UNPROCESSED_EVENT_TYPES` (in `agent_core/core/impl/event_stream/manager.py`), so the buffer holds only dialogue and meaningful state changes. -The filtered kinds are `action_start`, `action_end`, the GUI action kinds, `agent reasoning`, `screen_description`, `todos`, `error`, `waiting_for_user`, and `relevant_memories`. Separately, memory-processing runs flip a skip flag (`set_skip_unprocessed_logging`) for their duration, so the distillation run's own events never write to the buffer and cannot loop back into the next run. +The filtered kinds are `action_start`, `action_end`, `reasoning`, `todos`, `error`, `waiting_for_user`, and `relevant_memories`. Task boundaries (`task_start`, `task_end`) are intentionally left in the buffer. Separately, the memory-processing task itself is named in `SKIP_UNPROCESSED_TASK_NAMES`, so its own events never write to the buffer and cannot loop back into the next distillation run. ## Next - [Event stream](../core/concepts/event-stream.md): the concept, the tail-plus-summary mechanics, and on-disk files. -- [Actions and action sets](../core/concepts/actions-and-action-sets.md): the actions that produce `action_start` and `action_end`. +- [Default actions](../core/concepts/default-actions.md): the actions that produce `action_start` and `action_end`. - [Memory](../core/concepts/memory.md): how buffered events become long-term recall. - [Agent MD files](agent-md-files.md): the schema of `EVENT.md` and the other files under `agent_file_system/`. diff --git a/mkdocs/docs/reference/troubleshooting/index.md b/mkdocs/docs/reference/troubleshooting/index.md index 2d2e8e23..1a848da9 100644 --- a/mkdocs/docs/reference/troubleshooting/index.md +++ b/mkdocs/docs/reference/troubleshooting/index.md @@ -10,9 +10,9 @@ Run these four checks in order. They resolve most problems on their own, and the 1. **Check status.** Run `python craftbot.py status`. It reports whether CraftBot is running and whether auto-start is registered. If the process is not running, start it with `python craftbot.py start` and retry the action that failed. -2. **Read the logs.** Every run writes a timestamped folder under `logs/`. Open `logs//all.log` for the full picture across every session and sub-agent, or `logs//main/session.log` for the main session alone. Restarting begins a fresh folder, so a problem from earlier is in an older folder, not the current one. Search for `ERROR` first, then read upward from the match. See [Logs](../../core/concepts/logs.md) for the file layout, the subsystem tags, and grep recipes. +2. **Read the logs.** Every run writes a timestamped folder under `logs/`. Open `logs//all.log` for the full picture across the main agent and every sub-agent, or `logs//main.log` for the main agent alone. Restarting begins a fresh folder, so a problem from an earlier session is in an older folder, not the current one. Search for `ERROR` first, then read upward from the match. See [Logs](../../core/concepts/logs.md) for the file layout, the subsystem tags, and grep recipes. -3. **Check the activity view.** When a single action fails, the activity view in the interface shows the run and its status. A run parked behind a Continue/Stop choice is waiting for your decision, not stuck. An error names the action that broke, which you can then trace in the logs. +3. **Check the action panel.** When a single action fails, the action panel in the interface shows the task and its status. A task that reads `paused` is waiting for your decision, not stuck. A task that reads `error` names the action that broke, which you can then trace in the logs. 4. **Restart.** Run `python craftbot.py restart`. A restart re-reads settings, re-scans the memory index, and clears a stale lock left by a previous crash. If a restart fixes the problem, the logs from before it explain why. diff --git a/mkdocs/docs/reference/troubleshooting/runtime.md b/mkdocs/docs/reference/troubleshooting/runtime.md index 0f06e720..7163651c 100644 --- a/mkdocs/docs/reference/troubleshooting/runtime.md +++ b/mkdocs/docs/reference/troubleshooting/runtime.md @@ -11,8 +11,8 @@ The backend runs on port `7926` and the browser interface on port `7925`. Launch | `Port 7925` or `7926` already in use on launch | Another process, or an earlier CraftBot that did not exit, owns the port | Run `python craftbot.py stop`, then start again. Or launch on other ports with `python run.py --frontend-port PORT --backend-port PORT` | | `npm not found in PATH` | Node.js is missing, and browser mode requires it | Install the Node.js LTS from [nodejs.org](https://nodejs.org/), restart the terminal, and run again. Or use `python run.py --cli`, which needs no Node.js | | Browser opens but the page stays blank | The frontend is still building on first launch, or the build failed | Wait for the first build to finish. If it does not load, check `python craftbot.py logs` for the build error and re-run `python install.py` | -| The interface loads but never connects | The backend on port `7926` is not up | Confirm with `python craftbot.py status`. In the browser network tab, a failed WebSocket means the backend did not start. Read `logs//all.log` for the startup error | -| The agent hangs on startup and never reports ready | A provider connection test is blocking, or a required setting is missing | Read `logs//all.log` for the last line before the stall. A bad API key or an unreachable Ollama URL is the usual cause. See [Provider issues](providers.md) | +| The interface loads but never connects | The backend on port `7926` is not up | Confirm with `python craftbot.py status`. In the browser network tab, a failed WebSocket means the backend did not start. Read `logs//main.log` for the startup error | +| The agent hangs on startup and never reports ready | A provider connection test is blocking, or a required setting is missing | Read `logs//main.log` for the last line before the stall. A bad API key or an unreachable Ollama URL is the usual cause. See [Provider issues](providers.md) | ## Service does not auto-start @@ -32,18 +32,18 @@ When you send a message and nothing comes back, the cause is almost always the b |---|---|---| | No reply at all to a chat message | The backend is down | Run `python craftbot.py status`. Start it if it is not running | | The agent accepts messages but never answers | No model provider is configured | Complete onboarding, or set a key with the `/provider` command. See [Provider issues](providers.md) | -| The agent seems to hang partway through a run | It is running a long action, such as a web fetch or a large file operation | Check the activity view for the running action, and `logs//all.log` for the matching `[ACTION]` line. A single slow action is normal, not a hang | +| The agent seems to hang partway through a task | It is running a long action, such as a web fetch or a large file operation | Check the action panel for the running action, and `logs//all.log` for the matching `[ACTION]` line. A single slow action is normal, not a hang | | The reply stops mid-thought and never resumes | The provider errored repeatedly and the agent backed off | Search the log for repeated provider errors. See [Provider issues](providers.md) | -## Runs +## Tasks -Substantial work runs many actions in a loop. The budget guardrail can park a run, and it is working as designed. +A complex task runs many actions in a loop. Two guardrails can pause or hold a task, and both are working as designed. | Symptom | Cause | Fix | |---|---|---| -| The agent pauses and asks whether to continue or stop | The run hit its action budget (`150` actions) or its token budget (`6,000,000` uncached tokens). The log records the limit under the `[LIMIT]` tag, and the run parks with no continuation queued | Choose **Continue** to reset the counters and resume, or **Stop** to end it. The run waits idle until you pick | -| Work ends much sooner than expected | It reached a limit and you chose to stop, or the agent decided the work was done | Read the log around the last `[REACT]` lines to see how the run ended. Break large work into smaller requests so each stays within budget | -| The agent asks before an irreversible step | It ends the run on a question when a send, purchase, or destructive change needs your sign-off | Answer in chat; your reply wakes the run's session and it continues. See [Substantial work](../../core/modes/complex-task.md) | +| A task pauses and asks whether to continue or abort | The task hit its action budget of `500` actions or its token budget of `12,000,000` tokens. The log records `Action limit reached` or `Token limit reached` under the `[LIMIT]` tag, and the task status becomes `paused` | Choose **Continue** to reset the counters and resume, or **Abort** to end the task. The paused task stays alive for 3 hours waiting for your choice | +| A task ends much sooner than expected | It reached one of the limits above and you chose to abort, or the agent decided the work was done | Read the `[TASK]` lines in the log to see how it ended. Break large work into smaller tasks so each stays within budget | +| A task will not end and keeps asking for approval | An action needs your confirmation before it runs | Answer the prompt in chat. Irreversible actions request approval by design. See [Complex tasks](../../core/modes/complex-task.md) | ## Memory @@ -81,8 +81,8 @@ The agent's own files live under `agent_file_system/` in the project folder. When nothing above matches, the logs are the ground truth. -- **`logs//all.log`** is the full, time-ordered stream across all sessions and agents. Start here. -- **`logs//main/session.log`** is the main session alone, without the noise of other lanes. +- **`logs//all.log`** is the full, time-ordered stream across all agents. Start here. +- **`logs//main.log`** is the main agent alone, without sub-agent noise. - **`craftbot.log`** is the service log for startup and lifecycle, separate from the run logs. Search for `ERROR` first, then read upward. The `[ACTION]` and `[REACT]` lines just before an error usually name what broke. diff --git a/mkdocs/docs/start/first-task.md b/mkdocs/docs/start/first-task.md index ee27facf..2b4d5b26 100644 --- a/mkdocs/docs/start/first-task.md +++ b/mkdocs/docs/start/first-task.md @@ -1,27 +1,26 @@ -# Your first run +# Your first task -Everything CraftBot does for you happens as a **run** inside a chat session: the agent wakes on your message, works turn by turn, and ends the run with its reply. This page shows what quick and substantial runs look like in practice, how to watch and steer a run while it works, and how to phrase requests so they succeed. Work through it once with the agent open next to you. +Everything CraftBot does for you happens inside a **task**. This page shows how tasks start, what the two task modes look like in practice, how to watch and steer a running task, and how to phrase requests so they succeed. Work through it once with the agent open next to you. -## Sessions and runs +## Conversation mode vs task mode -The browser sidebar has a **Chats** group. **Main** is pinned at the top, and the **+** button creates additional chat sessions. Each session is its own conversation with its own context; new chats title themselves from the first exchange, and you can rename, clear, or delete any of them from the session's menu. Sessions run independently, so work in one never blocks another. +When no task is running, the agent is in **conversation mode**. In this state it can do exactly three things: reply to you, start a task, or deliberately ignore a message that needs no response (relevant once group chats are connected). It cannot touch files, browse, or call integrations. Conversation is deliberately cheap and safe. -A **run** is one wake of a session. Your message starts it, the agent works through however many actions the request needs, and the agent's final reply ends it. The session then sleeps until your next message starts a new run in the same conversation, with all the context carried over. There is no `/task` command and nothing to manage: describing work *is* how you start work. +The moment your message asks for something actionable, the agent starts a task on its own. There is no `/task` command and nothing to manage: describing work *is* how you start work. -The same run mechanics scale from tiny to large: +CraftBot picks one of two task modes based on the size of the request: -| | Quick request | Substantial work | +| | Simple task | Complex task | |---|---|---| -| For | Questions, one-off small actions | Multi-step work that needs planning | -| Todo list | No | Yes — live, updated as it works | -| Requirements | No | Yes — a checklist of what "done" must contain | -| Typical length | 1–3 actions, seconds | Many actions, minutes | -| Ends | With the answer | With the delivered result and artifact paths | +| For | Quick, obvious work | Multi-step work that needs planning | +| Todo list | No | Yes: live, updated as it works | +| Typical length | 2–3 actions, seconds | Many actions, minutes | +| Ends | By itself when done | Only after **you confirm** the result | | Example | "Convert this file to PDF" | "Research X, compare, write a report" | -The full mechanics are in [Runs](../core/modes/index.md); here, you'll watch one of each. +The full mechanics are in [Task modes](../core/modes/index.md); here, you'll run one of each. -## Walkthrough 1: a quick run +## Walkthrough 1: a simple task Ask for something small and concrete: @@ -31,13 +30,13 @@ What's the latest stable Python version? Check online, don't guess. What you'll observe: -1. A **Working** row appears under your message: the agent decided this needs a real action (a web search), and the row ticks while it runs. -2. One or two actions fire behind it: `web_search`, maybe a `web_fetch`. Click the row to expand the agent's reasoning and each action with its result. -3. The agent replies with the answer, and that reply ends the run. Quick runs produce no todo list. +1. A task card appears. The agent decided this needs a real action (a web search), so conversation mode won't do. +2. One or two actions fire in the action panel: `web_search`, maybe a `web_fetch`. +3. The agent replies with the answer and the task ends **by itself**. Simple tasks produce no todo list and require no confirmation. Total time: seconds. Most day-to-day requests run like this. -## Walkthrough 2: substantial work +## Walkthrough 2: a complex task Now give it something that requires a plan: @@ -49,73 +48,71 @@ as frameworks.md What you'll observe, stage by stage: -1. **Requirements first.** The agent records a requirements checklist — the concrete things the finished output must contain — before anything else. This is the contract it verifies against at the end. -2. **An immediate acknowledgment.** One sentence in chat confirming it has started, so you're never staring at silence. -3. **A todo list.** The agent plans the work as phase-prefixed todos (Collect, Execute, Verify, Deliver, Cleanup) and checks them off live as it works, exactly one in progress at a time. -4. **Actions fire.** The **Working** row stays visible the whole time. Expand it (or any settled "N Actions executed" row between messages) to see every search, page fetch, and `write_file` with its inputs and results. Nothing happens off-screen. -5. **Questions, maybe.** If the agent hits a decision it can't make (ambiguous requirement, missing credential), it asks you as its final message and the run ends there. Your answer wakes a new run in the same session with full context, and the work continues. -6. **Verification.** Before delivering, the agent re-checks its requirements checklist and marks each item satisfied or violated — a violated item means rework before you see the result. -7. **Delivery.** The final message summarizes what was done and lists the artifact paths. That message ends the run. If something's wrong ("too short, add benchmarks"), just say so — your reply starts a new run in the same session and the agent picks up right where it left off. +1. **Planning.** A task starts and a **todo list** appears. The agent breaks the work into phases you can read: acknowledging the request, collecting information, executing, verifying its own output, confirming with you, cleaning up. Todos update live, so the list shows the task's current progress. +2. **Execution.** Actions fire one after another in the action panel: searches, page fetches, then `write_file`. Every step the agent takes is visible there, with inputs and results. Nothing happens off-screen. +3. **Questions, maybe.** If the agent hits a decision it can't make (ambiguous requirement, missing credential) it messages you and **waits**. The task is paused, not stuck. Answer in chat and it resumes. Your reply routes into the waiting task automatically. +4. **Verification.** Good complex tasks check their own work before reporting completion. Expect to see the agent re-read the file it wrote. +5. **The approval gate.** The agent presents the result and asks you to confirm. This is the defining feature of complex tasks: **they do not close until you accept.** Reply "looks good" and the task ends, or say what's wrong ("too short, add benchmarks") and it keeps working in the same task, with all its context intact. When it's done, find the output at `agent_file_system/workspace/frameworks.md`. -## Where outputs go +## Where task outputs go | Location | What lands there | |---|---| -| `agent_file_system/workspace/` | Final artifacts — documents, data, anything meant to persist | -| `agent_file_system/workspace/sessions//` | Per-session scratch files — kept for the session's life, removed when the session is deleted | +| `agent_file_system/workspace/` | Final artifacts: documents, data, anything meant to persist | +| `agent_file_system/workspace/tmp//` | Per-task scratch files, cleaned automatically when the task ends | | Chat attachments | Files the agent sends you directly with its reply | +| `agent_file_system/TASK_HISTORY.md` | A record of every completed task | Tell the agent where you want things ("save it to the workspace", "send it to me here as a file") and it will comply. Details: [Agent file system](../core/concepts/agent-file-system.md). -## Steering a running run +## Steering a running task -You are not locked out while the agent works: +You are not locked out while a task runs: -- **Add or change requirements mid-flight.** "Also include Flask" folds into the run's very next turn, and the agent adjusts its todos and continues. -- **Answer its questions.** Anything the agent asked, just answer in chat. Your reply wakes the session and the work resumes with context intact. -- **Stop it.** The send button becomes a **Stop** button while a run is in flight. Stopping cancels the in-flight turn and clears the queue; your next message starts fresh. -- **Ask something unrelated.** Create a new chat with the **+** button in the sidebar's Chats group. Each session runs independently, so the research keeps going in one chat while you draft an email in another. +- **Add or change requirements mid-flight.** "Also include Flask" routes into the running task, and the agent adjusts its todos and continues. +- **Answer its questions.** Anything the agent asked, just answer in chat. +- **Stop it.** Tell it to stop or cancel. The message reaches the task and the agent winds the work down instead of finishing it. +- **Ask something unrelated.** An unrelated message does *not* interrupt the task. CraftBot's [session routing](../core/concepts/task-sessions.md) sends it to a separate conversation, and the task keeps running in parallel. You can watch and switch between them in the task panel. -That last behavior means you can queue real work: give one chat a research job, open a second chat, and hand it something else. Two runs proceed side by side, each with its own context. +That last behavior means you can queue real work: give it a research task, then immediately ask it to draft an email. Two tasks run side by side, each with its own context. ## Phrasing requests that succeed The agent works with exactly what you give it. Requests that go well share these traits: -- **Name the deliverable.** "Write a comparison **and save it as frameworks.md**" beats "tell me about frameworks". A concrete artifact gives the run a clear completion criterion — it becomes the requirements checklist. -- **Give constraints up front.** Stating length, format, tone, and sources to prefer or avoid in the first message saves a revision cycle after delivery. +- **Name the deliverable.** "Write a comparison **and save it as frameworks.md**" beats "tell me about frameworks". A concrete artifact gives the task a clear completion criterion. +- **Give constraints up front.** Stating length, format, tone, and sources to prefer or avoid in the first message saves a revision cycle at the approval gate. - **Don't pre-chunk the work.** You don't need to feed steps one at a time. That's what todos are for. Give the whole goal and let it plan. - **Point at inputs explicitly.** Say "using the CSV in my workspace" or attach the file directly. The agent can read chat attachments and workspace files. - **For recurring work, say so.** "Every weekday at 8am, ..." becomes a scheduled task, not a one-off. See [Scheduling](../core/concepts/scheduling.md). -## Useful commands while working +## Useful commands while working with tasks ``` /help # every command -/clear # clear this session's conversation (alias /cls) -/tokens # this session's token usage (input / cached / output / total) +/clear # clear the chat display +/clear-tasks # remove completed/failed tasks from the task panel /skill # manage skills /cred # connected integrations -/reset # delete all chat sessions and clear history (erases current context) +/reset # reset agent state and history (erases current context) ``` Full catalogue: [Built-in commands](../core/commands/builtin.md). -## If a run misbehaves +## If a task misbehaves | Symptom | What to do | |---|---| -| Run hangs on one action | Expand the Working row and read the action's error; most hangs are a missing credential or dependency | -| Agent misunderstood the goal | Say so in chat — course-correcting a run is cheaper than restarting it | -| Result is wrong at delivery | Reply with specifics; the next run in the session continues with full context | -| Run pauses with a Continue/Stop choice | It hit its per-run action or token budget; pick **Continue** to reset the counters and resume, or **Stop** to end it | -| Run failed outright | Check `logs/` (each app run writes `all.log` plus a `session.log` per session); see [Logs](../core/concepts/logs.md) | -| Everything is confused | `/reset` deletes all chat sessions and clears history — a last resort, it erases the current context | +| Task hangs on one action | Open the action in the action panel and read its error; most hangs are a missing credential or dependency | +| Agent misunderstood the goal | Say so in chat; course-correcting a running task is cheaper than restarting it | +| Result is wrong at the approval gate | Reject with specifics; the task continues with full context | +| Task failed outright | Check `logs/` (each run writes `main.log` + `all.log`); see [Logs](../core/concepts/logs.md) | +| Everything is confused | `/reset` clears state and history: a last resort, it erases the current context | ## Next -- [Runs](../core/modes/index.md): quick requests, substantial work, and the background workflows behind them +- [Task modes](../core/modes/index.md): simple, complex, and the special workflows behind them - [Service mode](service-mode.md): keep the agent available when the terminal closes - [Integrations](../integrations/index.md): day-one picks: Telegram, Gmail, Slack - [Learning path](learning-path.md): choose your track from here diff --git a/mkdocs/docs/start/index.md b/mkdocs/docs/start/index.md index 45d618e8..ae77e265 100644 --- a/mkdocs/docs/start/index.md +++ b/mkdocs/docs/start/index.md @@ -1,6 +1,6 @@ # Getting started -This section takes you from an empty machine to a CraftBot that runs in the background, knows who you are, and has completed its first real piece of work. Follow the pages in order. Each one builds on the previous. +This section takes you from an empty machine to a CraftBot that runs in the background, knows who you are, and has completed its first real task. Follow the pages in order. Each one builds on the previous. !!! tip "The fastest path" If you just want it running: `git clone https://github.com/CraftOS-dev/CraftBot.git && cd CraftBot && python craftbot.py install`, then follow the onboarding wizard that opens in your browser. The [Quickstart](quickstart.md) walks through exactly this, with checkpoints at every step. @@ -17,7 +17,7 @@ This section takes you from an empty machine to a CraftBot that runs in the back --- - From zero to a completed first piece of work, with a checkpoint after every step and a failure-recovery table when something doesn't work. + From zero to a completed first task, with a checkpoint after every step and a failure-recovery table when something doesn't work. - :material-rocket-outline:{ .lg .middle } __[Onboarding](onboarding.md)__ @@ -25,11 +25,11 @@ This section takes you from an empty machine to a CraftBot that runs in the back What the first-launch wizard collects (provider, API key, agent name, profile, skills, integrations), what the agent asks afterwards, and where all of it is stored. -- :material-message-text-outline:{ .lg .middle } __[Your first run](first-task.md)__ +- :material-message-text-outline:{ .lg .middle } __[Your first task](first-task.md)__ --- - How work happens as runs inside chat sessions, what quick and substantial runs look like, and how to watch and steer the agent while it works. + How CraftBot decides between conversation, simple task, and complex task, plus how to watch, steer, and confirm a running task. - :material-server:{ .lg .middle } __[Service mode](service-mode.md)__ @@ -52,14 +52,14 @@ This section takes you from an empty machine to a CraftBot that runs in the back | Python 3.10+ | Everything | `python --version` | | Git | Cloning the repository | `git --version` | | Node.js 18+ | Browser interface (default). Auto-installed on Linux. | `node --version` | -| A model provider | Everything. API key from any of [13 providers](../core/providers/llm.md), a ChatGPT/SuperGrok subscription, or local [Ollama](../core/providers/llm.md#remote--ollama) (no key). | — | +| A model provider | Everything. API key from any of [13 providers](../core/providers/llm.md), a ChatGPT/SuperGrok subscription, or local [Ollama](../core/providers/llm.md#remote--ollama) (no key). | (none) | ## Recommended order 1. [Install](install.md): get CraftBot on your machine. -2. [Quickstart](quickstart.md): launch, connect a provider, complete a first piece of work. +2. [Quickstart](quickstart.md): launch, connect a provider, complete a first task. 3. [Onboarding](onboarding.md): understand what the wizard set up (or redo it properly). -4. [Your first run](first-task.md): learn to work with the agent while it works. +4. [Your first task](first-task.md): learn to work with running tasks. 5. [Service mode](service-mode.md): make it permanent. 6. [Learning path](learning-path.md): branch out based on what you want to build. diff --git a/mkdocs/docs/start/install.md b/mkdocs/docs/start/install.md index 667bac5e..9e9729d3 100644 --- a/mkdocs/docs/start/install.md +++ b/mkdocs/docs/start/install.md @@ -16,9 +16,9 @@ Use the automatic path if you want an assistant that's always available. Use the | **Python 3.10+** | Everything. Python 3.9 and below will not work. | `python --version` | | **git** | Cloning the repository | `git --version` | | **Node.js 18+** | The browser interface (default mode). The launcher auto-installs Node.js on Linux; on Windows and macOS install it yourself from [nodejs.org](https://nodejs.org/) (LTS). Not needed for CLI mode. | `node --version` | -| **A model provider** | The agent needs an LLM. Have ready one of: an API key from a [supported provider](../core/providers/llm.md), a ChatGPT/SuperGrok subscription, or a running [Ollama](../core/providers/llm.md) server (free, no key). You enter this during [onboarding](onboarding.md) — you don't need it to install. | — | +| **A model provider** | The agent needs an LLM. Have ready one of: an API key from a [supported provider](../core/providers/llm.md), a ChatGPT/SuperGrok subscription, or a running [Ollama](../core/providers/llm.md) server (free, no key). You enter this during [onboarding](onboarding.md); you don't need it to install. | (none) | | *(optional)* **conda / mamba** | Isolated Python environment. `install.py --conda` offers to install Miniconda if missing. | `conda --version` | -| *(optional)* **Playwright Chromium** | Only the WhatsApp Web integration. Safe to skip; install later with `playwright install chromium`. | — | +| *(optional)* **Playwright Chromium** | Only the WhatsApp Web integration. Safe to skip; install later with `playwright install chromium`. | (none) | ## Path A: automatic install (background service) @@ -158,12 +158,12 @@ Stops CraftBot, removes the auto-start registration, uninstalls the pip packages | Symptom | Cause | Fix | |---|---|---| -| `npm not found in PATH` | Node.js missing (browser mode needs it) | Install the LTS from [nodejs.org](https://nodejs.org/), restart the terminal, run again — or use `python run.py --cli` | +| `npm not found in PATH` | Node.js missing (browser mode needs it) | Install the LTS from [nodejs.org](https://nodejs.org/), restart the terminal, run again, or use `python run.py --cli` | | Dependency install fails | Python < 3.10, no internet, or a stale pip | `python --version`; `pip install --upgrade pip`; retry. Conda path is the most reliable: `python install.py --conda` | | Playwright/Chromium install fails | Optional dependency | Skip it. Everything except WhatsApp Web works. Install later: `playwright install chromium` | | Port `7925`/`7926` already in use | Another process (or an old CraftBot) owns the port | `python craftbot.py stop`, or launch with `--frontend-port` / `--backend-port` | | Browser opens but nothing loads | Frontend still building on first launch | Wait for the first build to finish; check `python craftbot.py logs` | -| Agent doesn't reply to `hello` | No provider configured | Complete onboarding, or set a key via the `/provider` command — see [Quickstart step 2](quickstart.md#step-2-connect-a-model-provider) | +| Agent doesn't reply to `hello` | No provider configured | Complete onboarding, or set a key via the `/provider` command; see [Quickstart step 2](quickstart.md#step-2-connect-a-model-provider) | More cases: [Runtime issues](../reference/troubleshooting/runtime.md). diff --git a/mkdocs/docs/start/learning-path.md b/mkdocs/docs/start/learning-path.md index c40dd4f7..6e17b38a 100644 --- a/mkdocs/docs/start/learning-path.md +++ b/mkdocs/docs/start/learning-path.md @@ -14,10 +14,10 @@ You don't need to read this documentation cover to cover. Find yourself below (b | # | Read | You'll be able to | |---|---|---| -| 1 | [Quickstart](quickstart.md) | Install, connect a provider, complete a first piece of work | +| 1 | [Quickstart](quickstart.md) | Install, connect a provider, complete a task | | 2 | [Onboarding](onboarding.md) | Shape how the agent talks to you; understand `USER.md` | -| 3 | [Your first run](first-task.md) | Watch and steer the agent while it works | -| 4 | [Runs](../core/modes/index.md) | Know why some requests get todo lists and requirement checks | +| 3 | [Your first task](first-task.md) | Watch, steer, and confirm running tasks | +| 4 | [Task modes](../core/modes/index.md) | Know why some requests get todo lists and approval gates | | 5 | [Service mode](service-mode.md) | Keep it running without a terminal open | Stop here and you have a competent daily assistant. Everything else is optional depth. @@ -38,7 +38,7 @@ Stop here and you have a competent daily assistant. Everything else is optional | # | Read | You'll be able to | |---|---|---| -| 1 | [Agent loop](../core/concepts/agent-loop.md) → [Triggers](../core/concepts/triggers.md) → [Sessions](../core/concepts/task-sessions.md) | Trace a message from arrival to action execution | +| 1 | [Agent loop](../core/concepts/agent-loop.md) → [Triggers](../core/concepts/triggers.md) → [Task sessions](../core/concepts/task-sessions.md) | Trace a message from arrival to action execution | | 2 | [Event stream](../core/concepts/event-stream.md) → [Context engine](../core/concepts/context-engine.md) → [Prompts](../core/concepts/prompts.md) | Know exactly what the model sees each turn | | 3 | [Actions & action sets](../core/concepts/actions-and-action-sets.md) | How 1,100+ actions are registered, selected, and executed | | 4 | [Architecture](../develop/architecture.md) | The `agent_core` / `app` split and the data flows between them | @@ -47,13 +47,13 @@ Stop here and you have a competent daily assistant. Everything else is optional ## By goal -**Daily personal assistant.** [Quickstart](quickstart.md) → [Onboarding](onboarding.md) → [Your first run](first-task.md) → [Service mode](service-mode.md) → one messaging integration ([Telegram](../integrations/telegram-bot.md) or [WhatsApp](../integrations/whatsapp-web.md)) → [Scheduling](../core/concepts/scheduling.md) → [Proactive](../core/modes/proactive.md). +**Daily personal assistant.** [Quickstart](quickstart.md) → [Onboarding](onboarding.md) → [Your first task](first-task.md) → [Service mode](service-mode.md) → one messaging integration ([Telegram](../integrations/telegram-bot.md) or [WhatsApp](../integrations/whatsapp-web.md)) → [Scheduling](../core/concepts/scheduling.md) → [Proactive](../core/modes/proactive.md). -**A bot in my team's workspace.** [Quickstart](quickstart.md) → [Slack](../integrations/slack.md) or [Discord](../integrations/discord.md) or [Telegram](../integrations/telegram-bot.md) → [Credentials](../integrations/credentials.md) → [Service mode](service-mode.md) → [Sessions](../core/concepts/task-sessions.md) (how parallel conversations stay separate). +**A bot in my team's workspace.** [Quickstart](quickstart.md) → [Slack](../integrations/slack.md) or [Discord](../integrations/discord.md) or [Telegram](../integrations/telegram-bot.md) → [Credentials](../integrations/credentials.md) → [Service mode](service-mode.md) → [Task sessions](../core/concepts/task-sessions.md) (how parallel conversations route). **Email and calendar automation.** [Quickstart](quickstart.md) → [Gmail](../integrations/gmail.md) / [Outlook](../integrations/outlook.md) → [Google Calendar](../integrations/google-calendar.md) → [Scheduling](../core/concepts/scheduling.md) → [Service mode](service-mode.md). -**The agent builds my tools (Living UI).** [Quickstart](quickstart.md) → [Living UI](../living-ui/index.md) → [Agent file system](../core/concepts/agent-file-system.md) (where projects live) → [Runs](../core/modes/index.md) (how build runs behave). +**The agent builds my tools (Living UI).** [Quickstart](quickstart.md) → [Living UI](../living-ui/index.md) → [Agent file system](../core/concepts/agent-file-system.md) (where projects live) → [Task modes](../core/modes/index.md) (how build tasks behave). **Extend CraftBot with my own capability.** [Actions & action sets](../core/concepts/actions-and-action-sets.md) → [Custom action](../develop/custom-action.md) → [Skills](../core/concepts/skills.md) → [Write a CraftBot skill](../develop/skills/craftbot-skill.md) → [MCP servers](../integrations/mcp.md) (when to plug in instead of build). @@ -70,7 +70,7 @@ When you know the feature but not the page: | Slash commands | [Built-in commands](../core/commands/builtin.md) | | Memory and what it remembers | [Memory](../core/concepts/memory.md) | | Recurring / scheduled work | [Scheduling](../core/concepts/scheduling.md) | -| Self-initiated work | [Proactive](../core/modes/proactive.md) | +| Self-initiated tasks | [Proactive](../core/modes/proactive.md) | | Sub-agents and delegation | [Sub-agents](../core/concepts/sub-agents.md) | | Where files live | [Agent file system](../core/concepts/agent-file-system.md) | | Every action, by category | [Actions catalogue](../core/concepts/default-actions.md) | diff --git a/mkdocs/docs/start/onboarding.md b/mkdocs/docs/start/onboarding.md index 0c89ba40..2eb9fbda 100644 --- a/mkdocs/docs/start/onboarding.md +++ b/mkdocs/docs/start/onboarding.md @@ -3,7 +3,7 @@ Onboarding is how CraftBot goes from a fresh install to an agent that can work for *you specifically*. It has two distinct phases, and each stores your answers in a different place: - **Hard onboarding** is a six-step setup wizard shown on first launch. It configures the things the agent cannot run without (a model provider) plus identity and preferences. Everything it collects is written to config files. -- **Soft onboarding** is a get-to-know-you interview the agent itself runs as its first conversation with you, right after the wizard finishes. It's a normal chat. Your answers are distilled into `agent_file_system/USER.md`, which the agent reads on every prompt from then on. +- **Soft onboarding** is a get-to-know-you interview the agent itself runs as its first task, right after the wizard finishes. It's a normal conversation. Your answers are distilled into `agent_file_system/USER.md`, which the agent reads on every prompt from then on. You can skip most of it and configure everything later from Settings, but the two provider steps are required, and finishing the profile steps noticeably improves how the agent communicates with you. @@ -37,7 +37,7 @@ A compact form that seeds the agent's picture of you: | Language | Full language list, pre-selected from your OS locale | | Location | Auto-detected from your IP (editable); used for timezone- and locale-aware behavior | | Tone | Casual · Formal · Friendly · Professional | -| Proactivity | **Low** — wait for instructions · **Medium** — suggest when relevant · **High** — proactively suggest things | +| Proactivity | **Low**: wait for instructions · **Medium**: suggest when relevant · **High**: proactively suggest things | | Approval required for | Sending messages on your behalf · Creating/modifying schedules · Modifying files · Purchases/payments · All actions | | Preferred platform | Telegram · WhatsApp · Discord · Slack · the CraftBot interface | @@ -55,12 +55,12 @@ Every step except the two provider steps can be skipped, and you can go back to ## Soft onboarding: the agent's interview -Immediately after the wizard, the agent opens a short conversation: it greets you by name and interviews you about identity details, how you like to communicate, what you're working toward, and how hands-on you want it to be. This is a normal chat. Answer as much or as little as you want. +Immediately after the wizard, the agent starts a short conversational task: it greets you by name and interviews you about identity details, how you like to communicate, what you're working toward, and how hands-on you want it to be. This is a normal chat. Answer as much or as little as you want. This phase has two effects: 1. **Your answers persist.** The agent writes them into `agent_file_system/USER.md` (and agent-behavior notes into `AGENT.md`). `USER.md` is injected into the agent's context on every single prompt, which makes it the highest-leverage file in the system for shaping behavior. -2. **It doesn't block anything.** You can give the agent real work mid-interview. It handles the request normally and returns to the interview later. +2. **It doesn't block anything.** You can give the agent real work mid-interview. It routes your task normally and returns to the interview later. You can also edit `USER.md` directly at any time. It's plain markdown, and the agent picks up changes immediately. See [Agent file system](../core/concepts/agent-file-system.md). @@ -69,9 +69,9 @@ You can also edit `USER.md` directly at any time. It's plain markdown, and the a | What | File | |---|---| | Completion state (hard/soft done, timestamps), your name, agent name, avatar | `app/config/onboarding_config.json` | -| Provider, API keys, model settings | `app/config/settings.json` — see [Settings](../core/configuration/config-json.md) | +| Provider, API keys, model settings | `app/config/settings.json`; see [Settings](../core/configuration/config-json.md) | | Enabled skills | `app/config/skills_config.json` | -| Integration credentials | `.credentials/` — see [Credentials](../integrations/credentials.md) | +| Integration credentials | `.credentials/`; see [Credentials](../integrations/credentials.md) | | Your profile (soft onboarding answers) | `agent_file_system/USER.md` | ## Changing things later @@ -93,7 +93,7 @@ To force the wizard to run again from scratch, stop CraftBot and reset the compl | Wizard doesn't appear on first launch | Onboarding already marked complete (e.g. reused config) | Reset flags in `app/config/onboarding_config.json`, restart | | Can't proceed past API key | Key invalid for the chosen provider | Test the key in the provider's console; check for pasted whitespace | | Skill shows "Setup required" | It depends on an unconfigured MCP server | Add the server in [MCP servers](../integrations/mcp.md), then enable the skill | -| Agent never started the interview | Soft onboarding flag already set, or the run was interrupted | Set `soft_completed` to `false` in `onboarding_config.json`, restart | +| Agent never started the interview | Soft onboarding flag already set, or the task was interrupted | Set `soft_completed` to `false` in `onboarding_config.json`, restart | | Location detected wrong | IP geolocation is approximate | Edit the field in the wizard, or fix it later in `USER.md` | ## Next diff --git a/mkdocs/docs/start/quickstart.md b/mkdocs/docs/start/quickstart.md index edc7a193..58dc2716 100644 --- a/mkdocs/docs/start/quickstart.md +++ b/mkdocs/docs/start/quickstart.md @@ -1,6 +1,6 @@ # Quickstart -This page takes you from a fresh clone to a completed first piece of real work, with a checkpoint after every step so you always know whether you're on track. Budget 10–15 minutes. +This page takes you from a fresh clone to a completed first task, with a checkpoint after every step so you always know whether you're on track. Budget 10–15 minutes. **Who this is for:** anyone setting up CraftBot for the first time. If you've already installed and can chat with the agent, skip to [Step 4](#step-4-run-your-first-real-task). @@ -14,9 +14,9 @@ Match your situation to a starting point: | Already installed, agent won't reply | [Step 2: connect a provider](#step-2-connect-a-model-provider) | | Agent replies, want to see real work | [Step 4: first real task](#step-4-run-your-first-real-task) | | No Node.js and can't install it | Steps below, but launch with `python run.py --cli` | -| Don't want to pay for an API | Run [Ollama](https://ollama.com) locally, pick the **Remote** provider in Step 2 — zero tokens spent | +| Don't want to pay for an API | Run [Ollama](https://ollama.com) locally, pick the **Remote** provider in Step 2 (zero tokens spent) | -**Get a normal chat working before you add anything else.** If the agent can't answer `hello`, connecting Slack or scheduling work will not work either. Fix the basics first. +**Get a normal chat working before you add anything else.** If the agent can't answer `hello`, connecting Slack or scheduling tasks will not work either. Fix the basics first. ## Step 1: Install and launch @@ -65,11 +65,11 @@ Before giving the agent work, send something conversational: What can you actually do? ``` -Every message you send wakes the agent for a **run**: it does whatever the message needs, and its reply ends the run. For a question like this, that's the whole story — no actions, no plan, just an answer. The moment your message asks for something actionable, the same run mechanism scales up: actions fire, a plan appears, and the reply arrives when the work is done. There is no command to memorize. +The reply comes from **conversation mode**: no task exists, and the agent's only options are to answer you, start a task, or deliberately ignore a message that needs no action (this matters later, when group-chat integrations are connected). Conversation is cheap and instant. Real work happens in **tasks**, which the agent opens on its own the moment your message asks for something actionable. There is no command to memorize. -Conversations live in **chat sessions**, listed under **Chats** in the sidebar. **Main** is pinned first, and the **+** button opens a new chat, which titles itself from the first exchange. Each session is independent, so you can keep long-running work in one chat and ask unrelated questions in another. Rename, clear, or delete any session from its menu. +You never manage chat sessions. If you have a task running and ask an unrelated question, CraftBot routes the new message to a fresh conversation instead of derailing the task. When you reply to something the task asked you, the answer routes back to that task. This is [session routing](../core/concepts/task-sessions.md), and it's automatic. -**Checkpoint:** you got a conversational answer, straight away, with no "Working" activity row appearing. +**Checkpoint:** you got a conversational answer, and no task appeared in the task panel. ## Step 4: Run your first real task @@ -82,24 +82,25 @@ and save the comparison as frameworks.md Watch what happens, in order: -1. **The agent acknowledges and plans.** A request this size gets an immediate one-sentence acknowledgment, a requirements checklist (what the finished output must contain), and a **todo list** the agent checks off live. You'll see phases like collecting information, executing, verifying. Something trivial ("rename this file") skips all of that and just runs. The difference is explained in [Runs](../core/modes/index.md). -2. **A "Working" row ticks.** An always-visible activity row sits in the conversation while the run is in flight. Expand it to watch each step: `web_search` and `web_fetch` calls for research, then `write_file` for the output. Every action the agent takes is visible there, with inputs and results. Nothing happens silently. -3. **The agent may ask you something.** If it needs a decision, the question is its final message and the run pauses there. Just answer in chat — your reply wakes the agent in the same session, with all the context intact, and the work continues. -4. **The final message delivers.** The agent verifies its output against the requirements it recorded, then ends the run with a summary and the artifact path. If something's wrong, say so — your reply starts a new run in the same conversation and it keeps working with full context. +1. **A task starts.** The agent calls `task_start` and a task card appears. A request this size becomes a **complex task**; something trivial ("rename this file") would run as a lightweight **simple task** without todos or a confirmation step. The difference is explained in [Task modes](../core/modes/index.md). +2. **A todo list appears.** Complex tasks plan their work as todos and check them off live. You'll see phases like collecting information, executing, verifying. +3. **Actions fire.** In the browser's action panel you can watch each step: `web_search` and `web_fetch` calls for research, then `write_file` for the output. Every action the agent takes is visible. Nothing happens silently. +4. **The agent may ask you something.** If it needs a decision, it sends a message and waits. Just answer in chat and the reply routes back into the task. +5. **You confirm completion.** Complex tasks don't close themselves. The agent presents the result and waits for your confirmation before ending the task. Reply that it's done, or point out what's wrong and it keeps working. -The output lands in the agent's workspace: `agent_file_system/workspace/frameworks.md`. That directory is where run artifacts live. The agent can also send files directly into the chat as attachments. See [Agent file system](../core/concepts/agent-file-system.md). +The output lands in the agent's workspace: `agent_file_system/workspace/frameworks.md`. That directory is where task artifacts live. The agent can also send files directly into the chat as attachments. See [Agent file system](../core/concepts/agent-file-system.md). -**Checkpoint:** the agent delivered a final summary, and `frameworks.md` exists in `agent_file_system/workspace/`. +**Checkpoint:** the task completed after your confirmation, and `frameworks.md` exists in `agent_file_system/workspace/`. ## Step 5: Check logs, agent files, and service status These three locations are the first places to check when something behaves unexpectedly. -- **Logs.** Every launch writes a folder under `logs/` containing `all.log` (everything, interleaved) plus a `session.log` per session (the main session's folder is `main`). When the agent behaves unexpectedly, this is the ground truth. See [Logs](../core/concepts/logs.md). -- **The agent's files.** `agent_file_system/` is the agent's own home: `USER.md` (what it knows about you), `MEMORY.md` (long-term memory), `SOUL.md` (personality), `EVENT.md` (the full event log), and the `workspace/` you just used. Open `USER.md`. After [onboarding](onboarding.md) it should describe you. See [Agent file system](../core/concepts/agent-file-system.md). +- **Logs.** Every run writes a folder under `logs/` containing `main.log` (the main agent) and `all.log` (everything, including sub-agents). When the agent behaves unexpectedly, this is the ground truth. See [Logs](../core/concepts/logs.md). +- **The agent's files.** `agent_file_system/` is the agent's own home: `USER.md` (what it knows about you), `MEMORY.md` (long-term memory), `SOUL.md` (personality), `TASK_HISTORY.md`, and the `workspace/` you just used. Open `USER.md`. After [onboarding](onboarding.md) it should describe you. See [Agent file system](../core/concepts/agent-file-system.md). - **Service status.** `python craftbot.py status` tells you whether CraftBot is running and whether auto-start is registered. -**Checkpoint:** you know where logs, memory, and workspace outputs live on disk. +**Checkpoint:** you know where logs, memory, and task outputs live on disk. ## Step 6: Add capabilities one layer at a time @@ -119,8 +120,8 @@ Everything past this point is optional, and each layer works independently. Add | `hello` gets no reply at all | Agent not running, or backend port blocked | `python craftbot.py status`, then `restart`; check ports `7925`/`7926` | | Authentication / 401 / invalid key error | Wrong key, or key doesn't match selected provider | Re-run `/provider `; verify the key works in the provider's own console | | Reply is an error about model not found | Provider default model not available on your account | Set an explicit model in **Settings → Model**; see [LLM providers](../core/providers/llm.md) | -| Work starts but hangs on a step | A needed integration or dependency is missing | Expand the "Working" activity row and read the failing action's error; check `logs/` | -| Agent answers but doesn't do the work | The request read as a question, not a job | Phrase the request as a deliverable: what to produce, where to put it | +| Task starts but hangs on a step | A needed integration or dependency is missing | Open the action panel and read the failing action's error; check `logs/` | +| Agent answers but refuses actionable work | It routed to conversation mode | Phrase the request as a deliverable: what to produce, where to put it | Deeper diagnosis: [Troubleshooting](../reference/troubleshooting/index.md). @@ -135,13 +136,12 @@ python run.py --cli # run in foreground (terminal chat) ``` /provider [name] [key] # view or set the model provider /cred # credentials & connected integrations -/skill # manage skills /help # all commands -/clear # clear this session /tokens # session token usage -/reset # delete all chat sessions and clear history +/skill # manage skills /help # all commands +/clear # clear the chat /reset # reset agent state ``` ## Next - [Onboarding](onboarding.md): what the wizard configured, and the interview the agent runs afterwards -- [Your first run](first-task.md): steering, parallel sessions, and how to phrase work +- [Your first task](first-task.md): steering, parallel tasks, and how to phrase work - [Learning path](learning-path.md): pick a reading track for what you want to build diff --git a/mkdocs/docs/start/service-mode.md b/mkdocs/docs/start/service-mode.md index 9590ead5..27dd804b 100644 --- a/mkdocs/docs/start/service-mode.md +++ b/mkdocs/docs/start/service-mode.md @@ -84,11 +84,11 @@ Inside it: | File | Purpose | |---|---| -| `craftbot.pid` | PID of the running background process — how `status`/`stop` find it | +| `craftbot.pid` | PID of the running background process; how `status`/`stop` find it | | `craftbot.log` | The service's own output. `craftbot.py logs` reads this. Startup is confirmed by the `CRAFTBOT IS READY` marker here. | | `install.json` | What the installer set up (used by `status`, `repair`, `uninstall`) | -The **agent's** logs are separate and richer: each launch writes a folder under `logs/` in the repository (`all.log` for everything interleaved, plus a folder per session holding its own `session.log` and any sub-agent logs), rotated at 50 MB and kept 14 days. When you're debugging agent behavior, read those. When you're debugging "why didn't it start", read `craftbot.log`. See [Logs](../core/concepts/logs.md). +The **agent's** logs are separate and richer: each run writes a folder under `logs/` in the repository (`main.log` for the main agent, `all.log` for everything including sub-agents), rotated at 50 MB and kept 14 days. When you're debugging agent behavior, read those. When you're debugging "why didn't it start", read `craftbot.log`. See [Logs](../core/concepts/logs.md). ## Common setups @@ -114,7 +114,7 @@ ssh -N -L 7925:localhost:7925 you@server # then open http://localhost:7925 lo |---|---|---| | Nothing starts after login | Auto-start entry missing or disabled | `python craftbot.py status`; re-run `install`. Windows: check `schtasks /Query /TN CraftBot`; Linux: `systemctl --user status craftbot` | | `status` says running but UI won't load | Frontend build failed or port conflict | `python craftbot.py logs`; try `restart`; check nothing else owns `7925`/`7926` | -| Worked in foreground, fails as a service | Environment difference — usually keys set as shell env vars | Move keys into the settings UI / `settings.json` (see the systemd warning above) | +| Worked in foreground, fails as a service | Environment difference, usually keys set as shell env vars | Move keys into the settings UI / `settings.json` (see the systemd warning above) | | Scheduled/proactive tasks don't fire | Service not running, or proactive disabled | `craftbot.py status`, then check `proactive.enabled` in [settings](../core/configuration/config-json.md) | | Stale PID / won't stop | Process died without cleanup | Delete `craftbot.pid` in the state directory, then `start` again | | Desktop shortcut opens a blank page | Agent stopped | `python craftbot.py start`, wait for ready, reload | @@ -124,5 +124,5 @@ More: [Runtime issues](../reference/troubleshooting/runtime.md). ## Next - [Scheduling](../core/concepts/scheduling.md): the recurring work that makes always-on worthwhile -- [Proactive mode](../core/modes/proactive.md): let the agent propose its own work +- [Proactive mode](../core/modes/proactive.md): let the agent propose its own tasks - [Integrations](../integrations/index.md): talk to your always-on agent from Telegram, Slack, and everywhere else diff --git a/mkdocs/docs/stylesheets/extra.css b/mkdocs/docs/stylesheets/extra.css index 843490bc..b9454762 100644 --- a/mkdocs/docs/stylesheets/extra.css +++ b/mkdocs/docs/stylesheets/extra.css @@ -1,5 +1,5 @@ /* docs/stylesheets/extra.css - CraftBot docs theme — matches https://craftbot.live/ + CraftBot docs theme: matches https://craftbot.live/ Brand: - orange: #ff4f18 - coal black: #141517 @@ -358,7 +358,7 @@ } /* ============================================================ - 7. Admonitions — orange tip accent + 7. Admonitions: orange tip accent ============================================================ */ .md-typeset .admonition, diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml index 9b052b4b..3528d46a 100644 --- a/mkdocs/mkdocs.yml +++ b/mkdocs/mkdocs.yml @@ -98,7 +98,7 @@ nav: - Concepts: - Agent loop: core/concepts/agent-loop.md - Triggers: core/concepts/triggers.md - - Sessions: core/concepts/task-sessions.md + - Task sessions: core/concepts/task-sessions.md - Event stream: core/concepts/event-stream.md - Actions & action sets: core/concepts/actions-and-action-sets.md - Default actions: core/concepts/default-actions.md @@ -111,11 +111,11 @@ nav: - Agent bundles: core/concepts/agent-bundles.md - Agent file system: core/concepts/agent-file-system.md - Logs: core/concepts/logs.md - - Runs & workflows: + - Task modes: - core/modes/index.md - - Quick requests: core/modes/simple-task.md - - Substantial work: core/modes/complex-task.md - - Background workflows: core/modes/special-workflows.md + - Simple task: core/modes/simple-task.md + - Complex task: core/modes/complex-task.md + - Special workflows: core/modes/special-workflows.md - Proactive: core/modes/proactive.md - Interfaces: - core/interfaces/index.md @@ -137,6 +137,9 @@ nav: - Agent bundle config: core/configuration/agent-config-yaml.md - Living UI: - living-ui/index.md + - The framework: living-ui/framework.md + - A2App protocol: living-ui/a2app-protocol.md + - Managing apps: living-ui/managing.md - Integrations: - integrations/index.md - Credentials: integrations/credentials.md diff --git a/mkdocs/scripts/gen_default_actions.py b/mkdocs/scripts/gen_default_actions.py index 775bfec6..554fc948 100644 --- a/mkdocs/scripts/gen_default_actions.py +++ b/mkdocs/scripts/gen_default_actions.py @@ -194,7 +194,7 @@ def render_row(name, meta): desc = first_sentence(meta["description"]) or "See the source for details." if meta["irreversible"]: desc += " **Irreversible.**" - sets = ", ".join(f"`{s}`" for s in meta["action_sets"]) or "—" + sets = ", ".join(f"`{s}`" for s in meta["action_sets"]) or "n/a" return f"| `{name}` | {sets} | {params} | {desc} |"