Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ rename / release-pipeline wiring.
| Agent runtime | `graph/agent.py`, `server.py` | LangGraph `create_agent()` wired to the A2A handler, with streaming token capture for cost-v1 |
| LLM gateway | `graph/llm.py` | OpenAI-compatible client pointed at LiteLLM — swap models by editing the gateway config, not the fork |
| Subagents | `graph/subagents/config.py` | DeerFlow-pattern delegation via a `task()` tool; one placeholder `worker` ships |
| Starter tools | `tools/lg_tools.py` | Keyless general tools (`current_time`, `calculator` safe AST eval, `web_search` via DuckDuckGo, `fetch_url`) plus memory tools (`memory_ingest`, `memory_recall`, `memory_list`, `memory_stats`, `daily_log`) bound to the bundled store |
| Starter tools | `tools/lg_tools.py` | Twelve tools default-on: 4 keyless general (`current_time`, `calculator` safe AST eval, `web_search` via DuckDuckGo, `fetch_url`) + 5 memory (`memory_ingest`, `memory_recall`, `memory_list`, `memory_stats`, `daily_log`) bound to the KB store + 3 scheduler (`schedule_task`, `list_schedules`, `cancel_schedule`) bound to the scheduler backend |
| Knowledge store | `knowledge/store.py` | sqlite + FTS5 (LIKE fallback). One `chunks` table for operator notes, daily-log entries, and conversation findings. Default-on; turn off with `middleware.knowledge: false` |
| Scheduler | `scheduler/` | `schedule_task` / `list_schedules` / `cancel_schedule` tools backed by either a bundled sqlite scheduler or a Workstacean adapter (env-selected). Multi-agent-safe — every job is namespaced by `AGENT_NAME`. See [Schedule future work](./docs/guides/scheduler.md) |
| Eval harness | `evals/` | Side-effect-verified A2A test harness — audit log + reply text + KB state. `python -m evals.runner` against a running agent. See [Eval your fork](./docs/guides/evals.md) |
Expand Down
4 changes: 3 additions & 1 deletion docs/guides/customize-and-deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ Replace with the skills your agent actually advertises over A2A. The `name` and

## 5. (Optional) Add domain tools

`tools/lg_tools.py` ships with `current_time`, `calculator`, `web_search`, `fetch_url`. Keep the ones you want, drop the rest, add your own. Update `get_all_tools()` at the bottom. Any tool returned from there becomes a checkbox in the wizard and drawer automatically.
`tools/lg_tools.py` ships with `current_time`, `calculator`, `web_search`, `fetch_url` plus 5 memory tools (`memory_ingest`, `memory_recall`, `memory_list`, `memory_stats`, `daily_log`) bound to the bundled `KnowledgeStore`. The 3 scheduler tools (`schedule_task`, `list_schedules`, `cancel_schedule`) are wired in separately by `server.py::_build_scheduler` when the scheduler backend is enabled. Keep the ones you want, drop the rest, add your own. Update `get_all_tools()` at the bottom of `tools/lg_tools.py`. Any tool returned from there (or from `_build_scheduler_tools`) becomes a checkbox in the wizard and drawer automatically.

The memory tools are dropped automatically when `middleware.knowledge: false`; the scheduler tools when `middleware.scheduler: false`. See [Schedule future work](/guides/scheduler) and [Configuration](/reference/configuration#middleware) for the toggles.

## 6. (Optional) Configure subagents

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/fork-the-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Keep the `<scratch_pad>` / `<output>` protocol block in `prompts.py` — the A2A

## 4. Replace the starter tools

`tools/lg_tools.py` ships with `current_time`, `calculator`, `web_search`, `fetch_url`. Keep what you want, drop the rest, add your own. Update `get_all_tools()` at the bottom of the file.
Twelve tools ship by default: `current_time`, `calculator`, `web_search`, `fetch_url` (keyless general) plus `memory_ingest`, `memory_recall`, `memory_list`, `memory_stats`, `daily_log` (bound to the bundled `KnowledgeStore`) plus `schedule_task`, `list_schedules`, `cancel_schedule` (bound to the scheduler backend). Keep what you want, drop the rest, add your own. Update `get_all_tools()` at the bottom of `tools/lg_tools.py`.

See the [starter tools reference](/reference/starter-tools) for the shapes of the shipped ones.

Expand Down
31 changes: 31 additions & 0 deletions docs/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,37 @@ Session memory is enabled by default. See [architecture § Session memory](/expl

To persist memory across container restarts, mount a volume at whatever `MEMORY_PATH` resolves to. Without a volume the directory is ephemeral.

## Knowledge store

The bundled `KnowledgeStore` (sqlite + FTS5) is enabled by default. See [Configuration § knowledge](/reference/configuration#knowledge) for the full guide.

| Variable | Default | What |
|---|---|---|
| `KNOWLEDGE_DB_PATH` | (unset — uses YAML `knowledge.db_path`) | Runtime override for the sqlite path. Falls back to `~/.protoagent/knowledge/agent.db` when the resolved path is unwritable (e.g. running locally without `/sandbox`). |

To opt out entirely, set `middleware.knowledge: false` in YAML. The memory tools (`memory_ingest`, `memory_recall`, etc.) are dropped from the agent loop when the store is disabled.

## Audit log

| Variable | Default | What |
|---|---|---|
| `AUDIT_PATH` | `/sandbox/audit/audit.jsonl` | Directory + filename of the JSONL audit log written by `AuditMiddleware`. Read by `evals/verify.py` for side-effect assertions. |

## Scheduler

The bundled scheduler is enabled by default. See [Schedule future work](/guides/scheduler) and [Configuration § scheduler](/reference/configuration#scheduler) for the full guide. **Backend selection** is env-driven; **enable/disable** lives in YAML (`middleware.scheduler`) so the drawer can toggle without a restart.

| Variable | Default | What |
|---|---|---|
| `WORKSTACEAN_API_BASE` | (unset) | When set together with `WORKSTACEAN_API_KEY`, swaps the bundled `LocalScheduler` for the `WorkstaceanScheduler` HTTP adapter. |
| `WORKSTACEAN_API_KEY` | (unset) | Auth token sent as `X-API-Key` to Workstacean's `/publish`. |
| `WORKSTACEAN_TOPIC_PREFIX` | `cron.<agent_name>` | Override the bus topic the adapter fires on, when your Workstacean install uses a different convention. |
| `SCHEDULER_DB_DIR` | `/sandbox/scheduler` | Local backend: parent directory for `<agent_name>/jobs.db`. Falls back to `~/.protoagent/scheduler/<agent_name>/jobs.db` when unwritable. |
| `SCHEDULER_INVOKE_URL` | `http://127.0.0.1:<active_port>` | Local backend: where to POST `message/send` when a job fires. Override only if the agent's A2A endpoint isn't on localhost. |
| `SCHEDULER_DISABLED` | (unset) | Runtime escape hatch — set to `1` / `true` to drop the scheduler tools entirely without editing YAML. `middleware.scheduler: false` is the canonical opt-out. |

> **protoLabs operators**: the fleet's Workstacean lives on the `ava` node. `WORKSTACEAN_API_KEY` is in the org's secrets manager under `secret-management → workstacean`.

## Tracing (optional)

| Variable | What |
Expand Down
55 changes: 50 additions & 5 deletions docs/reference/starter-tools.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Starter tools

Nine tools ship in `tools/lg_tools.py`:
Twelve tools ship by default:

- Four keyless general-purpose tools — `current_time`, `calculator`, `web_search`, `fetch_url` — that work without any state.
- Five **memory tools** — `memory_ingest`, `memory_recall`, `memory_list`, `memory_stats`, `daily_log` — bound to the bundled `KnowledgeStore` (sqlite + FTS5, see [Configuration](/reference/configuration#knowledge)).
- Three **scheduler tools** — `schedule_task`, `list_schedules`, `cancel_schedule` — bound to the bundled scheduler backend (local sqlite or the Workstacean adapter, see [Schedule future work](/guides/scheduler)).

`get_all_tools(knowledge_store)` is the registry. When `knowledge_store` is `None` (the store is disabled in config) the memory tools are omitted automatically.
`get_all_tools(knowledge_store, scheduler)` is the registry. When `knowledge_store` is `None` the memory tools are omitted; when `scheduler` is `None` the scheduler tools are omitted. Both backends are constructed by default in `server.py`; opt out via `middleware.knowledge: false` / `middleware.scheduler: false` in `config/langgraph-config.yaml`.

## `current_time`

Expand Down Expand Up @@ -158,6 +159,47 @@ async def daily_log(content: str) -> str

Convenience wrapper around `memory_ingest` that writes to `domain='daily-log'` with today's UTC date as the heading. Same-day entries cluster under the same heading for `memory_list(domain='daily-log')`.

## `schedule_task`

```python
@tool
async def schedule_task(prompt: str, when: str, job_id: str | None = None) -> str
```

Persist a future invocation. The agent receives `prompt` as a fresh turn when the schedule fires.

`when` is either a 5-field cron expression (`"0 9 * * 1-5"` = every weekday at 9am) or an ISO-8601 datetime (`"2026-05-01T15:00:00"` = once at 3pm UTC on May 1). Backends auto-detect.

`job_id` is optional — auto-generated as `<agent_name>-<uuid>` when omitted. You'll need it later for `cancel_schedule`.

Output: `"Scheduled job <id> next at <iso>."` on success. Returns `"Error: ..."` on malformed `when` or backend failure.

Prompts are self-contained — the agent has no memory of the scheduling moment when the task fires, so write the prompt as a fresh turn ("review last week's pipeline incidents and post a summary"), not a reference ("do that thing we discussed").

## `list_schedules`

```python
@tool
async def list_schedules() -> str
```

List the current scheduled jobs for *this* agent. Multi-agent isolation: each agent only sees jobs it created.

Output: one job per line with id, next-fire timestamp, schedule, and prompt preview. Returns `"No scheduled jobs."` when empty.

The Workstacean adapter intentionally returns `[]` (Workstacean owns scheduling state and its `list` action publishes asynchronously to a topic). Run the local backend or query Workstacean directly for live introspection there.

## `cancel_schedule`

```python
@tool
async def cancel_schedule(job_id: str) -> str
```

Cancel a scheduled job by id. Returns `"Canceled <id>."` or `"Error: no such job <id>."`.
Comment on lines +193 to +199
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor spelling inconsistency: "Cancelled" vs "Canceled".

Line 199 documents the output as "Canceled <id>." (American spelling), but the code at tools/lg_tools.py:475 returns f"✓ Cancelled {job_id}" (British spelling). Consider standardizing on one spelling throughout the codebase.

📝 Align documentation with code
-Cancel a scheduled job by id. Returns `"Canceled <id>."` or `"Error: no such job <id>."`.
+Cancel a scheduled job by id. Returns `"✓ Cancelled <id>."` or an error message when the job doesn't exist.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/starter-tools.md` around lines 193 - 199, The doc string for
cancel_schedule and the implementation message are inconsistent: the docs say
"Canceled <id>." but the code returns "✓ Cancelled {job_id}". Update one side so
both use the same spelling (choose either "Canceled" or "Cancelled")—for
example, change the implementation string that currently returns "✓ Cancelled
{job_id}" in the cancel_schedule-related function to match the docs, or update
the docs to match the implementation; ensure you modify the exact formatted
return string (e.g., the f-string "✓ Cancelled {job_id}") and/or the doc text
"Canceled <id>." so they are identical.


Cross-agent cancellation is blocked — `gina-personal` cannot cancel `gina-work`'s jobs even when sharing a sqlite path or a Workstacean install.

## Adding your own

Follow the same pattern:
Expand All @@ -180,13 +222,15 @@ async def my_tool(required_arg: str, optional_arg: int = 5) -> str:
return f"Success: {result}"
```

Then append it to the keyless tool list in `get_all_tools()` — keep the conditional `_build_memory_tools(knowledge_store)` extension below it so the bundled memory tools still ship when a store is configured:
Then append it to the keyless tool list in `get_all_tools()` — keep the two conditional extensions below it so the bundled memory + scheduler tools still ship when their backends are configured:

```python
def get_all_tools(knowledge_store=None):
def get_all_tools(knowledge_store=None, scheduler=None):
tools = [current_time, calculator, web_search, fetch_url, my_tool]
if knowledge_store is not None:
tools.extend(_build_memory_tools(knowledge_store))
if scheduler is not None:
tools.extend(_build_scheduler_tools(scheduler))
return tools
```

Expand All @@ -195,5 +239,6 @@ See [Write your first tool](/tutorials/first-tool) for the full walkthrough.
## Related

- [Configure subagents](/guides/subagents) — tools are allowlisted per subagent
- [Environment variables](/reference/environment-variables) — SSRF allowlist vars affect `fetch_url`
- [Environment variables](/reference/environment-variables) — SSRF allowlist vars affect `fetch_url`; scheduler backend selection lives there too
- [Eval your fork](/guides/evals) — the eval harness exercises every tool listed here end-to-end
- [Schedule future work](/guides/scheduler) — the firing model + multi-agent isolation story behind the scheduler tools
2 changes: 1 addition & 1 deletion docs/tutorials/first-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Walk through the four steps:

1. **Connect to your model.** Paste your API base URL (`https://api.openai.com/v1` for OpenAI direct, `http://localhost:4000/v1` for a local LiteLLM gateway) and API key. Click **Test connection & fetch models** — the dropdown fills with whatever the endpoint actually exposes. Pick one.
2. **Name your agent.** Short lowercase slug (e.g. `product-director`). Pick a persona preset — **Generic Assistant** is the safe default; **Research** / **Coding** / **Blank** are the alternatives — and click **Load preset into SOUL.md**. Edit the loaded text if you want to make it specific to your agent.
3. **Tools & middleware.** All nine starter tools (`current_time`, `calculator`, `web_search`, `fetch_url`, plus the memory tools `memory_ingest` / `memory_recall` / `memory_list` / `memory_stats` / `daily_log`) are enabled by default. Leave **Audit**, **Memory**, and **Knowledge** middleware on — the template ships a working sqlite + FTS5 store under `/sandbox/knowledge/agent.db` (falls back to `~/.protoagent/knowledge/agent.db` outside Docker).
3. **Tools & middleware.** All twelve starter tools are enabled by default — four keyless general (`current_time`, `calculator`, `web_search`, `fetch_url`), five memory (`memory_ingest`, `memory_recall`, `memory_list`, `memory_stats`, `daily_log`), and three scheduler (`schedule_task`, `list_schedules`, `cancel_schedule`). Leave **Audit**, **Memory**, **Knowledge**, and **Scheduler** middleware on — the template ships a working sqlite + FTS5 store under `/sandbox/knowledge/agent.db` and a sqlite-backed scheduler under `/sandbox/scheduler/<agent_name>/jobs.db`, both with `~/.protoagent/...` fallbacks outside Docker.
4. **Optional — you, security, autostart.** Your name makes the agent address you directly. A2A auth token blank for local dev, set it before you expose the port. "Launch this agent automatically on login" installs a macOS LaunchAgent so the server is up after every reboot without remembering to `python server.py`.

Hit **Launch agent**. The wizard closes, the chat UI appears, and the Configuration drawer on the right is now populated with your choices.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/first-tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,5 @@ The template runs tests via `pytest` with `pytest-asyncio` in auto mode — no e
## Where to go next

- [Add a custom skill](/guides/add-a-skill) — advertise new capabilities on the agent card so A2A callers can find them
- [Starter tools reference](/reference/starter-tools) — the shapes of the five tools that ship
- [Starter tools reference](/reference/starter-tools) — the shapes of all twelve tools that ship by default
- [Configure subagents](/guides/subagents) — add specialized delegates beyond the placeholder `worker`
Loading