Metadata-Version: 2.4
Name: sidequestor
Version: 0.2.0
Summary: Sidequestor workspace and triage command shell
License-Expression: Apache-2.0
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: telegram
Requires-Dist: Telethon<2,>=1.44; extra == "telegram"
Dynamic: license-file

# Sidequestor

**Your side quests, handled.** Sidequestor is a local-first Slack sidekick that keeps small
missions moving while you get on with the main storyline. Point it at a workspace, pick your
agent, and it takes the next useful step — then leaves every file, log and decision on disk so
you can always see exactly what it did.

- 🎯 **Quests, not chores** — you describe the mission once; the triage loop watches for what changes.
- 🏠 **Local-first** — your machine, your Slack token, your files. Nothing to host.
- 🔍 **Nothing hidden** — every dispatch leaves a transcript, a timeline entry and a run log.
- 🤖 **Your agent, your call** — `codex` out of the box, or `claude` and `cursor` if you prefer.

## Before you start

A short checklist, so nothing surprises you halfway through:

- macOS with launchd (that is what runs the loop).
- [uv](https://docs.astral.sh/uv/) or [pipx](https://pipx.pypa.io/) to install the `sq` command
  (`brew install uv` or `brew install pipx`). uv also downloads Python ≥ 3.11 when the Mac does
  not have it.
- The command-line tools `jq`, `curl`, `openssl`, `security` and `open`.
- An authenticated `claude`, `codex`, **or** `cursor-agent` CLI before workers start. Setup can
  offer to install a missing Claude Code or Codex CLI if npm is available; Sidequestor never logs in for you.

Telegram user-session watches require the optional `sidequestor[telegram]` extra. The Slack setup
script checks the command-line tools it needs before it runs, and
`yaas-triage/ops/doctor.sh` checks the rest.

## How do I install it?

```bash
# Install the sq command once for your user (or: pipx install sidequestor).
uv tool install sidequestor
# Add the tool's command directory to PATH if the installer asks you to, then open a new terminal.
uv tool update-shell
# Change to an existing project or workspace directory.
cd ~/path/to/existing-workspace
# Optional instead: create a new directory and enter it.
# mkdir -p ~/new-sidequestor-workspace
# cd ~/new-sidequestor-workspace
# Add Sidequestor metadata and configuration to this directory.
sidequestor init .
# Save the ready-to-paste Slack YAML manifest.
sq setup --manifest > slack-app-manifest.yaml
# Run interactive onboarding; bypassPermissions is the default worker permission mode.
sq setup
# Start triage, heartbeat, and the dashboard and print the dashboard URL.
sq start
```

Nothing of yours is moved or overwritten: `sidequestor init .` only adds `.yaas` metadata alongside your existing files. `sq`, `sidequestor`, and `yaas` are all available aliases. The dashboard workspace label opens the current workspace in Cursor when available, or in macOS’s default IDE/file opener. Set `SIDEQUESTOR_IDE_APP` to prefer another application.

One installation serves every workspace, and `sq` works in any new terminal without activating
anything. To add optional extras, reinstall with them, for example
`uv tool install --force 'sidequestor[telegram]'` or
`pipx install --force 'sidequestor[telegram]'`.

A workspace-local virtualenv still works if you prefer one: `python3 -m venv .venv`,
`source .venv/bin/activate`, then `python -m pip install sidequestor`. Its commands are available
only while that environment is active, or by full path such as `.venv/bin/sq`.

Background jobs record the PATH of the terminal that runs `sq start`, with the installation's own
`bin` first, and `sq start` prints the agent CLI they will use. If it reports the CLI as missing,
install it or set `SIDEQUESTOR_PATH` in the workspace `.env` to a PATH that contains it, then run
`sq start` again.

Commands use the current directory when it is an initialized workspace. From elsewhere, pass `--workspace PATH` or set `SIDEQUESTOR_WORKSPACE`; the legacy `YAAS_WORKSPACE` name remains accepted.

## How do I set up the Slack app?

1. Run `sq setup --manifest > slack-app-manifest.yaml`, then paste that YAML at api.slack.com → Create New App → From an app manifest.
2. Choose the workspace and Install to Workspace; an administrator may need to approve it.
3. Confirm that **Agents → Slack Model Context Protocol (MCP) Server** is enabled. The manifest requests this automatically; enable it there manually only if Slack or workspace policy leaves it off.
4. In **OAuth and Permissions → User Token Scopes**, verify the user scopes listed in the generated manifest. There are no bot scopes. `reactions:read` may need workspace-admin approval; without it reaction monitoring silently finds nothing. `reactions:write` is required or lifecycle transitions fail with `missing_scope`.
5. If you grant a scope later, reinstall the app and run `sq setup` again.
6. In Basic Information, copy the App ID and Client ID into `.env`, then complete OAuth.

For an existing app, generate the updated manifest with `sq setup --manifest`, add the newly
requested user scopes in the app's OAuth settings, reinstall the app, and run `sq setup` again to
re-authorize the token. The manifest and OAuth flow use the same scope list in
`setup/yaas-app-config.json`. It includes `channels:write` and `channels:write.invites`
for public-channel management/invitations; these are not needed to create the private
onboarding interface. Sidequestor's channel-creation helper still creates private channels
only. File uploads need `files:write`; attachment downloads need `files:read`.

The wizard never overwrites real values. It fills only blank or placeholder settings, and `sq setup --instructions` never edits files. The selected backend is `codex` by default; the default Codex model is `gpt-6.1-sol` at `high` effort. Setup checks both the Codex and Claude Code CLIs and offers an npm installation for either missing CLI when Codex or Claude is selected. It checks `cursor-agent` when Cursor is selected. npm installation requires Node.js; the Claude Code npm package requires Node.js 22 or later. Installation is optional, but the selected worker CLI must be available before setup starts jobs. CLI login remains a separate step. You can select `cursor` to use the authenticated `cursor-agent` CLI; leave `SIDEQUESTOR_CURSOR_MODEL` unset to use Cursor's default model, or set it to pin a model.

## How do I get guided onboarding?

At the end of `sq setup`, choose `slack`, `terminal`, or `later`. Choosing Slack or terminal creates
one onboarding quest for the workspace; `later` postpones it. The quest reviews what you already have, checks your connections, drafts
your AGENTS.md and skills, proposes quests, and checks in until day 30. It asks before each change.

```bash
# Start or resume onboarding in your Slack DM.
sq onboard --slack
# Start or resume onboarding with your coding agent in this terminal.
sq onboard --terminal [--codex|--claude|--cursor]
```

Running either command again resumes the same quest and switches its channel. The onboarding quest
starts in your Slack DM and offers to create a private channel such as
`#alex-sidequestor-interface`. If you accept, it continues there and sets up a separate
ongoing interface quest, so you can manage Sidequestor in plain language after onboarding
ends. You approve the channel name and any required review in the dashboard; the agent
performs the technical steps. No terminal is needed for routine use.

Text-send permissions stay limited to your verified DM and approved interface channel
(`allow_send_channels`); other actions retain their normal approvals. Slack must be
configured before choosing it. Without Slack, background check-ins are recorded for your next
terminal session. Channel preferences live separately from progress so a worker cannot undo a switch.
The agent selects a verified, granted and watched channel with `sq onboard --slack-channel CHANNEL_ID`;
this selection does not create channels or grant new permissions. `sq onboard --slack-dm`
returns onboarding to the verified owner DM if the channel becomes unavailable.

## How do I create a private Slack channel?

After upgrading, add `groups:write`, `groups:write.invites`, and `groups:write.topic` to
**User Token Scopes**, reinstall the Slack app, and run `sq setup` again to authorize them.
The generated manifest includes these scopes.

```bash
sq slack-channel create '{"interactive":true,"name":"project-planning"}'
sq slack-channel invite '{"interactive":true,"channel_id":"C...","users":["U...","U..."]}'
sq slack-channel topic '{"interactive":true,"channel_id":"C...","topic":"Project planning"}'
sq slack-channel purpose '{"interactive":true,"channel_id":"C...","purpose":"Coordinate the project"}'
```

Creation always sets `is_private: true`. For automated quest work, replace `interactive`
with `quest_id`; the quest needs `allow_send: true` or an exact claimed approval from
`sq slack-channel approval-spec '{"action":"create","name":"project-planning"}'`.
Each operation is separate, so a failed invitation never causes the channel to be recreated.
Exit 3 means the remote outcome is uncertain or the action succeeded but logging failed:
inspect Slack before retrying. No channel is created by upgrading or running setup.

## What permissions does the worker need?

For unattended work, `sq setup` defaults to `bypassPermissions` for Codex and Claude. Press Enter to accept it if a blank permission setting prompts you, or enter another supported permission mode. Claude's bypass mode disables its permission checks; Codex's bypass mode disables approvals and its sandbox, allowing filesystem, shell, and network actions without per-action approval. Cursor uses its own CLI execution mode and Sidequestor auto-approves its MCP calls. Use unattended backends only in a workspace where that behavior is acceptable. Existing nonempty permission settings are preserved. If a permission setting is removed entirely, the runtime's own fallback is `acceptEdits` for Claude or `workspace-write` for Codex.

```bash
# Allow Claude workers to execute unattended tool actions.
SIDEQUESTOR_CLAUDE_PERMISSION_MODE=bypassPermissions
# Allow Codex workers to execute unattended tool actions.
SIDEQUESTOR_CODEX_PERMISSION_MODE=bypassPermissions
```

The optional instruction block targets `CLAUDE.md` for Claude and `AGENTS.md` for every other backend. Neither file is ever created or edited by Sidequestor.

## How do I use Slack files?

`sq slack-file fetch '{"channel_id":"C...","ts":"<message ts>"}'` downloads the images and PDFs
attached to one Slack message into a private temporary directory and prints their local paths.
Open those paths to inspect the attachments. You can also fetch one attachment with
`{"file_id":"F..."}`. Downloads are capped at 20 MiB per file by default, and the command
reports files it skips.

`sq slack-file send '{"quest_id":"<active quest>","channel_id":"C...","file":"/path/to/image.png","initial_comment":"Here is the chart"}'`
posts a local file. A quest needs `allow_send: true` or a claimed approval for this exact file,
comment, and destination. Run `sq slack-file approval-spec '<same JSON>'` to prepare that approval.
For an explicitly requested interactive upload without a quest, use
`sq slack-file send '{"interactive":true,"channel_id":"D...","file":"/path/to/image.png"}'`.
This mode is unavailable inside a dispatch; that check reads an environment variable, so treat
it as a guard against mistakes rather than a security boundary. The packaged `yaas-slack-file` skill is linked into
`skills/`, `.agents/skills/`, and `.claude/skills/` when workspace resources sync.
For a reply, include `"thread_ts":"<parent ts>"`. If sending exits 3, the file may already be
posted; inspect the Slack conversation before trying again.

## How do I configure Notion watches?

Notion polling is opt-in: append `notion` to `SIDEQUESTOR_CHECKER_CONNECTORS` in
your workspace `.env` (for example `slack,email,github,jira,notion`). Create an API-token
connection in Notion, enable **Read content** and **Read comments**, and connect it to the
parent page containing the content to watch. Child pages inherit access. Store its token
in macOS Keychain as service `sidequestor-notion`, account `sidequestor`, or set
`NOTION_TOKEN` in the checker environment. Never put tokens in watch entries or commands.
Optional `NOTION_KEYCHAIN_SERVICE`, `NOTION_KEYCHAIN_ACCOUNT`, and `NOTION_KEYCHAIN_PATH`
select another Keychain item. If no service is configured and the default is missing,
the original `sidequestor-notion-test` item is accepted as a compatibility fallback. No Notion connector or Python dependency is required.

| Watch type | Required target | Coverage |
|---|---|---|
| `notion_page` | `page_id` UUID | Root page properties/content, nested blocks, and comments/replies on the page and blocks. New blocks enroll automatically. |
| `notion_data_source` | `data_source_id` UUID, or single-source `database_id` | Every current and newly discovered item gets the same page/block/comment coverage. |
| `notion_comments` | `block_id` UUID (page IDs are valid) | Comments/replies throughout the selected page/block tree; body edits alone do not trigger it. |

```bash
sq watch QUEST_ID '{"type":"notion_page","page_id":"YOUR-PAGE-UUID","reason":"Spec and discussion changes"}'
sq watch QUEST_ID '{"type":"notion_data_source","database_id":"YOUR-DATABASE-UUID","reason":"Tasks and their discussions"}'
sq watch QUEST_ID '{"type":"notion_comments","block_id":"YOUR-PAGE-UUID","reason":"Discussion only"}'
```

A page with 100 blocks remains **one watch**. Discovery and scanning run in Python,
without LLM tokens; only pending changes dispatch the worker. Comment checks never rely
on page edit timestamps. Comment IDs and content fingerprints detect new comments,
replies and same-minute edits. Repeated changes are coalesced until acknowledged.

**Subpages are opt-in.** `subpage_depth` defaults to `0` (integer 0–100). Set
`include_subpage_content: true` and/or `include_subpage_comments: true` independently
and give a positive depth. `1` includes direct children; `2` also includes grandchildren.
Existing and newly discovered subpages follow the same boundary. A database item's
page starts at depth zero. Ordinary nested blocks do not consume subpage depth.
Child databases, hyperlinks, mentions and relations do not expand the scope; add an
explicit data-source watch for database items.

```json
{"type":"notion_page","page_id":"YOUR-PAGE-UUID","subpage_depth":1,"include_subpage_comments":true,"include_subpage_content":false,"reason":"Root plus direct-child discussions"}
```

**Bounded, resumable scans.** `max_requests` defaults to 8 per invocation (1–50),
`scan_seconds` to 8 (3–30), and `max_entities` to 10000 (1–100000, counting pages,
blocks and comments). Legacy `max_pages` is accepted as a per-invocation request-budget
alias when `max_requests` is absent. Pagination is resumed across invocations, including
comment lists, rather than restarting at a fixed page cap. Breadth-first traversal and
visited-ID tracking prevent loops. The pending inbox has a 10000-resource change limit;
reaching a limit holds further scanning visibly until acknowledged or scope is adjusted.

Scans share a 20-second private API response cache and a connection-level request queue
within the workspace. Rate limits honor `Retry-After`. Requests and CPU still cost time:
a full comment sweep of 100 blocks can require about 100 requests. Larger scopes take
multiple ticks. The dashboard shows scanning/limited/error state, pending tasks, limits
and the last complete scan; incomplete coverage is never presented as a clean full scan. Normal incremental progress returns
a clean result with the delivery cursor unchanged, avoiding stalled-watch backoff;
that result does not mean the tree scan has completed.

**Durable delivery.** Workspace-local `state/notion/collector.sqlite3` stores scan progress,
fingerprints, pending changes and the short-lived cache, with private permissions. Keep
this state when upgrading or backing up; deleting it discards scan/delivery history.
A watch's `last_checked_ts` becomes a local delivery cursor, **not a Notion timestamp**.
Worker acknowledgement advances that cursor; failed/blocked workers retain pending changes.
The worker reads pending targets with the read-only `notion-state.py` helper described in
the dispatch skill. No per-block quest watches are created. Changing depth/comment/content
scope creates a distinct identity; retire and recreate a watch through supported commands
rather than editing watch JSON. Budgets do not change membership identity.

Initial scans baseline older content and conservatively replay the cursor's minute to
avoid losing rounded timestamps. Newly discovered resources after baseline are monitored
automatically, including their available comments. A partial tree scan can deliver a proven
pending-change prefix while traversal continues; the dashboard reports that distinction.

Polling is not an event log. Comments resolved/deleted before their scan, inaccessible
resources, intermediate edits, and ambiguous disappearances cannot be reconstructed.
A missing child is a structure change, not proof of deletion. A 404 means missing content
or missing access. Missing/unsupported descendants become visible coverage gaps while
other branches continue; the collector retries them on later cycles without guessing
that they were deleted. An unavailable selected root still requires configuration repair. Source queries exclude trashed items; a direct
page watch can observe an API-visible trash edit. `include_archived` defaults to `true` for
source archive partitions; set it to `false` to exclude those rows. Server-incomplete source
queries hold visibly and require narrower scope. Wiki data sources need explicit page watches.
No webhooks or OAuth onboarding are required. Production checkers/read bridges never write
Notion content.

For an opt-in live test from a source checkout, run
`python tests/manual/notion_checkers.py --allow-test-writes --page-id PAGE_UUID --database-id DATABASE_UUID`.
This additionally needs Insert/Update content and Insert comments. It creates its own
fixtures in the disposable test parent/database and trashes only those fixtures afterward.

## How do I authorize Telegram or X watchers?

Run from an initialized Sidequestor workspace, or select one explicitly with `--workspace PATH`.
If `sq` reports `command not found` with a uv or pipx install, run `uv tool update-shell` or
`pipx ensurepath` and open a new terminal; with a workspace virtualenv, activate it first.

Telegram watches use your own Telegram user through the official MTProto API. Create an API
application at `my.telegram.org`, then authorize once; the API hash and serialized user session
are stored together in macOS Keychain and no SQLite session file is created.
You may put the non-secret app identifier in the workspace `.env` as `TELEGRAM_API_ID` and
omit `API_ID` from the authorization command.

```bash
uv tool install --force 'sidequestor[telegram]'  # or pipx install --force, or pip install in a venv
sq telegram-auth authorize API_ID  # or omit API_ID when TELEGRAM_API_ID is in .env
sq telegram-auth status
sq telegram-send --peer @chat --message "hello" --quest-id quest-id
sq telegram-send --peer @chat --message "hello" --send --quest-id quest-id --idempotency-key unique-key
```

The authorization command securely prompts for the phone number and API hash, then Telegram asks
for the login code and, when enabled, the account's 2FA password. None of those values are placed
in the command line. `telegram-send` uses that same authorized user session and logs `message_text`
automatically when `--quest-id` is provided. It saves a native Telegram cloud draft by default;
the draft synchronizes to the authorized account's Telegram clients, and a new draft replaces the
existing draft in that dialog. Pass `--send` to deliver instead. Dispatched sends require an active
quest with `allow_send: true` or an exact claimed approval, plus an `idempotency_key`; an
interrupted send is held for inspection instead of retried blindly.

X uses OAuth 2.0 Authorization Code with PKCE to act as the account that approves access. Create
an X Developer App configured as a public/native OAuth 2.0 client, enable the callback
`http://127.0.0.1:8765/callback`, then authorize it. Sidequestor stores the user access and rotating
refresh tokens in Keychain; it does not accept an app-only bearer token.

```bash
sq x-auth authorize YOUR_OAUTH2_CLIENT_ID
sq x-auth status
sq x-auth revoke
```

External direct checkers are enabled by connector. New workspaces default to Slack, email, GitHub,
and Jira; Telegram and X remain dormant until explicitly added in the workspace `.env`:

```bash
SIDEQUESTOR_CHECKER_CONNECTORS=slack,email,github,jira,telegram,x
```

Disabling a connector does not delete its watches or advance their watermarks. The older
`SIDEQUESTOR_SLACK_CHECKERS_ENABLED=0` setting remains an additional Slack-only kill switch.
Connector edits take effect on the next tick; explicitly exported environment variables
still override `.env`. Creating a disabled watch is rejected unless you deliberately pass
`--allow-dormant` to `sq watch` or `sq new-quest` to stage inactive setup. Retired and disabled
watches are excluded from checker-health warnings, and housekeeping clears their old backoff.

Available X polling types are `x_search`, `x_mentions`, `x_user_posts`, `x_home`, and `x_dm`.
They cover recent search, the authorized account's mentions and home timeline, posts from a
selected user ID, and incoming DMs. X only exposes DM events from the last 30 days. These pollers
detect new items, not later edits, deletions, reactions, list changes, Spaces, or notifications.

`sq x-send` can create posts, replies, threads, DMs, and media uploads; delete the account's posts;
and like, repost, bookmark, follow, mute, or block and reverse those actions. Quest-owned writes
require an active quest with `allow_send: true` or an exact claimed `remote_request` approval.
Dispatched writes also require an `idempotency_key`; an interrupted attempt is held for inspection
instead of retried blindly. Availability and billing still depend on the X app's current access tier.

`sq gdoc-comment` adds verified text-anchored comments through the Google Docs API using the
`gws` CLI. It refuses ambiguous anchor text, requires `allow_send` or an exact claimed approval,
and logs quest-owned writes. Each invocation accepts one exact-case anchor and requires a unique
idempotency key. The CLI must be authenticated with Docs and Drive access, and the author must have
comment access. When the API provides a document revision ID, the writer uses it to guard against
concurrent edits. Comments are limited to 2048
UTF-8 bytes. The response must report
`ALL_SAVED` and quote the requested anchor. The surface is enabled by default; set
`SIDEQUESTOR_GDOC_COMMENTS_ENABLED=0` in the workspace `.env` to disable it. See the installed
`yaas-gdoc-anchored-comments` skill for the payload format.

When upgrading from the browser-based writer, authenticate `gws` with Docs access as well as Drive
access. Remove obsolete `SIDEQUESTOR_GDOC_CDP_URL` and `SIDEQUESTOR_GDOC_CHROME_PROFILE` settings
from the workspace `.env`.

To test without permitting a worker to send anything, initialize a disposable workspace, enable
the connectors, create a quest in its dashboard, add a watch, then use `tick --dry-run`:

```bash
WS=/tmp/sidequestor-connector-test
python -m venv "$WS-venv"
"$WS-venv/bin/pip" install -e '.[telegram]'
"$WS-venv/bin/sq" init "$WS" --name connector-test
cp "$WS/.env.example" "$WS/.env"
chmod 600 "$WS/.env"

# Edit $WS/.env: add telegram,x to SIDEQUESTOR_CHECKER_CONNECTORS.
# Add TELEGRAM_API_ID to $WS/.env, then omit the API_ID here.
"$WS-venv/bin/sq" --workspace "$WS" telegram-auth authorize
"$WS-venv/bin/sq" --workspace "$WS" x-auth authorize YOUR_OAUTH2_CLIENT_ID e2e

# Run this in a second terminal. It serves the test UI without starting background triage.
"$WS-venv/bin/sq" --workspace "$WS" dashboard serve 0
```

Create a quest through that dashboard, then add narrowly scoped watches. Add each watch before
posting its unique test marker so the initial watermark cannot ingest older history:

```bash
"$WS-venv/bin/sq" --workspace "$WS" watch QUEST_ID \
  '{"type":"telegram_chat","credential_id":"e2e","peer":"YOUR_TELEGRAM_USER_ID","include_outgoing":true,"filter_keywords":["sq-e2e-unique"]}'
"$WS-venv/bin/sq" --workspace "$WS" watch QUEST_ID \
  '{"type":"telegram_search","credential_id":"e2e","peer":"YOUR_TELEGRAM_USER_ID","query":"sq-e2e-unique","include_outgoing":true}'
"$WS-venv/bin/sq" --workspace "$WS" watch QUEST_ID \
  '{"type":"x_search","credential_id":"e2e","query":"from:YOUR_TEST_ACCOUNT sq-e2e-unique"}'

# Post/send the unique markers, wait 30 seconds for search indexes, then inspect detection.
"$WS-venv/bin/sq" --workspace "$WS" tick --dry-run
```

The dry run should log `DIRTY` without dispatching. A second dry run must rediscover the same
items because dirty watermarks are committed only after successful acknowledgement. Use
`tick --isolated --fake-worker` to acknowledge them safely, then confirm a final dry run is clean.

```bash
# Print the optional block without changing any files.
sq setup --instructions
# Use defaults without OAuth.
sq setup --non-interactive
# Use the lower-level launchd operations when needed.
sq setup --render-only|install|status|uninstall
# Install the production launchd jobs.
sq setup --production install
```

## How do I run and inspect it?

```bash
# Start triage, heartbeat, and dashboard jobs for the current workspace.
# Wait for the dashboard and print its selected free loopback URL.
sq start
# Stop every job for the current workspace; the instance ID is optional here.
sq stop
# Pause now for time away; equivalent to sq stop.
sq pause
# Resume when you return; equivalent to sq start.
sq resume
# List currently running Sidequestor instances and their exact workspaces.
sq instances list
# Include stopped or historical registered workspaces as well.
sq instances list --all
# Validate the current workspace and print its build identity.
sq doctor
# Look up the dashboard URL later without restarting anything.
sq dashboard url
# From another directory, stop one registered workspace explicitly.
sq stop INSTANCE_ID
# Print the installed package build identity.
sq --version
```

`sq pause` stops triage, heartbeat, and the dashboard using the same verified lifecycle
as `sq stop`. It can interrupt an active worker; it does not wait for that worker to
finish. Quests, watermarks, history, and approval state are preserved. The managed jobs
remain stopped across reboots until you explicitly start/resume them. The dashboard is
unavailable while stopped. Resume uses the installation running your command, just like
`sq start`; use the workspace's virtualenv command when it has its own installation.
Existing watches and previously approved work continue normally after resuming.

Use `sq --workspace /path/to/workspace pause` or `resume` to select a workspace, or
`sq pause INSTANCE_ID` to pause a registered instance. `resume --dashboard-port PORT`
can select a specific dashboard port. This first version has no scheduled dates or
automatic resumption and is not a policy lock: an explicit `sq start` or `sq tick`
can still run work.

## How do I get briefings?

Open **Briefings**. If the archive is empty, use **Create a morning briefing quest**
or **Create a one-off briefing quest** to open an editable example in the usual quest
creation dialog. Choose the topic, sources, and timing before submitting; opening an
example does not create or schedule anything. You can also ask an existing quest's
agent to save a Markdown briefing with a `# Title` in `state/briefs/`. Files there
appear automatically in the Briefings archive. Saving a local brief does not authorize
posting or emailing it.

## How do I work on one quest in a terminal?

Select a quest in the dashboard and choose **Open in terminal**. Sidequestor opens a Terminal
window attached to that quest's own session. The session keeps running after you close the
window, and opening it again joins the same session instead of starting another. Sessions are
named `sq-<instance>-<quest-id>`, so the same quest ID in two workspaces never shares one.

The session starts in the quest's folder with a short summary of the quest and these helpers:

```bash
ask "draft a reply with the docs link"   # start your agent with the quest's context
ask --claude "…"                         # use another installed agent this once
ctx                                      # mission and watches
trail                                    # recent timeline
queue "check again tomorrow"             # same as the dashboard's instruction box
help                                     # show the summary again
```

`ask` starts the agent chosen during `sq setup` (`SIDEQUESTOR_AGENT`, Codex by default) in
interactive mode, so you approve its actions as it works. `queue` asks you to confirm first,
and only accepts that confirmation from an interactive terminal. The same commands work
anywhere as `sq ask QUEST_ID …`, `sq ctx QUEST_ID`, and so on.

To create a quest by conversation instead of the form, choose **Create interactively in
Terminal instead** in the dashboard's **Create a quest** dialog. It opens one session for the
workspace with a `create "<mission>"` helper (also `sq create`), which starts your agent on the
quest-creation skill; the agent asks what to watch and shows you the quest before creating it.

The session is a normal shell running as you. It is focused on the quest, not restricted to
it, and it is not recorded in the quest timeline. Install tmux (`brew install tmux`) for the
best experience; without it Sidequestor uses macOS's built-in `screen`, and without either
each click opens a fresh shell. Set `SIDEQUESTOR_TERMINAL_APP` (for example `iTerm`) to use
another terminal app.

Quest context is stored in `context.md` and rendered as Markdown in the dashboard. Agents are
instructed to use headings for distinct topics, bold labels for short attributes, and flat
lists for fields, rules, and steps when creating or updating context. Existing contexts keep
their saved formatting until an agent or you updates them; an upgrade does not reformat them.

## How do I review proposed actions?

Open a manual review item in the dashboard to read its context and formatted proposal.
Use **Edit** to change the source text, then **Preview** to read it again. Read-only items
offer **View source** instead. Dashboard polling preserves unsaved edits and the selected view.
Approve or save an edit to persist your changes; switching views alone does not save them.

The preview supports basic headings, paragraphs, flat lists, emphasis, code spans, and links.
Code-fenced proposals are shown as literal source. Outbound messages use the source text in
their destination's syntax, so Slack and email formatting may differ from the preview.

## How do I upgrade?

```bash
# From a workspace: upgrade, refresh resources, validate, and restart previously running jobs.
sq upgrade

# From elsewhere, select a workspace for the same full lifecycle.
sq --workspace /path/to/workspace upgrade

# Or install one explicit branch, tag, or commit from GitHub.
sq upgrade --source https://github.com/OWNER/sidequestor.git --ref BRANCH
```

`sq upgrade` upgrades the installation running the `sq` command. It detects whether uv, pipx, or
pip in a virtualenv owns that installation and uses the same tool (`uv tool upgrade`,
`pipx upgrade`, or `pip install --upgrade`), keeping any installed extras. If it cannot tell, it
stops before touching anything. `--installer pip` covers pip installs it cannot detect, such as a
venv under a `venvs/` directory or a pyenv, conda, or `--user` interpreter, unless a uv or pipx
receipt or tool directory claims the environment. `--installer uv` or `pipx` can recover from a
missing receipt only when that manager confirms it owns the running environment; Git upgrades
require a valid receipt to preserve installed extras. Pip-managed PyPI upgrades bypass pip's HTTP
cache so a newly published release is not hidden by an older index response. The installed version
is checked after the installer runs; an unchanged version is reported as a no-op, not an upgrade.
When run from an initialized workspace (or with `--workspace PATH`), it stops production jobs only
when they were previously marked running, upgrades the package, then uses a fresh Python process
to sync resources and run `sq doctor`. Previously running jobs restart only after both checks
succeed. The upgrader preserves each instance's actual published dashboard port and waits up to
60 seconds for that port to be released before restarting. Git installs require confirmation
because they install code with the worker's permissions;
pass `--yes` for a non-interactive run. Use `--pre` to consider PyPI pre-releases or
`--no-restart` to leave previously running jobs stopped. Git sources are limited to HTTPS GitHub
repository URLs and require an explicit `--ref`; a commit SHA is reproducible while a branch can
move.

Outside a workspace, `sq upgrade` upgrades only the package. It does not stop or restart any
running jobs, or sync and validate workspace resources. Select an initialized workspace when you
want the full lifecycle. Versions before 0.1.44 require a workspace even for a package-only
upgrade; from a fresh folder, upgrade once with the installing tool directly, then use `sq upgrade`
normally.

You do not need to check for releases yourself. While a workspace's jobs are running, its
heartbeat job asks PyPI once a day whether a newer version exists. When one does, the dashboard
header shows it, `sq doctor` prints it, and you get one desktop notification per new version.
Each detected stable release also creates one Inbox review item. Approve it to run an
independent background upgrade job after the current tick finishes. It stops the workspace,
upgrades, syncs resources, runs `sq doctor`, restarts previously running jobs, and logs the
outcome in Inbox. On failure, the item returns to review with terminal recovery directions.
If validation leaves the service stopped, a desktop notification also points to recovery;
Sidequestor does not restart a package that failed validation.
This handoff currently requires macOS launchd. Do not run `sq upgrade` inside a dispatched
worker: stopping the service can kill that worker. A separate terminal remains a fallback.
Dismissal is remembered, and installed or superseded versions close their old notices.
Click the dashboard's build/version control to check for updates now (limited to once a minute).
It reports checking, an available release, already current, rate limiting, or failure.
Nothing upgrades on its own. Set `SIDEQUESTOR_UPDATE_CHECK=0` in the workspace `.env` to turn the
check off.

Sidequestor also watches for explicit authentication/configuration failures, repeated checker
errors, and failed worker dispatches. It asks the configured harness for a read-only diagnosis
and a repair proposal for review; it does not apply the repair during diagnosis. Proposals
state whether the fix can run headlessly, needs an interactive terminal/browser session, or
needs further investigation. Shared problems go to Inbox; quest-specific problems stay with
their quest. If the harness cannot launch, Sidequestor writes recovery directions directly.
Suspected packaged-code defects instead produce a sanitized email bug report addressed to
`guangmian.kung@circle.com` for manual review. No report is sent without approval. Reports
include the verified running version and a minimal reproduction, not private Slack content
or credentials. Installed package files and watch watermarks are not edited as a workaround;
an upgrade is a remedy only when a released fix has been verified. Reporting a defect is not
proof of recovery: the health issue remains until a real check succeeds.
The Gmail helpers require exact claimed approval for maintainer-directed mail, including
replies, CCs and plus aliases, even in a send-enabled quest. Real replies resolve their
audience before applying this check; drafts remain unsent and ungated.
Inbox is permanent and is restored automatically, including outstanding review watches.
Reaction actions, source/result links where available, and outcomes are recorded in Inbox.

One uv or pipx installation is shared by every workspace, so `sq upgrade` restarts only the
workspace it runs in and lists any other running workspaces that need `sq --workspace PATH start`
to pick up the new version.

The first upgrade to the stable per-user Slack Keychain helper can show one macOS password prompt.
Choose **Always Allow** so background refreshes can use that same helper identity without prompting
again. Run `sq credentials status` to inspect the migration without opening Keychain, or
`sq credentials repair-keychain` in an interactive terminal to retry it. A failed or cancelled
migration leaves the selected instance stopped; after repair, start it normally with `sq start`.

If the installed command itself is broken, upgrade with the installing tool directly
(`uv tool upgrade sidequestor`, `pipx upgrade sidequestor`, or
`python -m pip install --upgrade sidequestor` in the virtualenv), then run `sq sync-resources`,
`sq doctor`, and `sq start`.

`.env`, `settings.json`, `state/`, `logs/`, and your personal `skills/` survive. `.yaas/engine/current/` is wiped and rebuilt every sync; hand-edits there are intentionally lost. An existing `.env` never gains newly added knobs because `provision_env` fills only placeholders, so diff it against `.env.example` after upgrading. Plists embed the installation’s absolute interpreter path: upgrading in place is fine, but recreating the environment (for example `uv tool install --force` with a different Python, or a new venv) means running `sq start` again.

Reaction defaults use standard names: `robot_face`, `hourglass_flowing_sand`, and `white_check_mark`. Pickup is queued before harness launch; durable status delivery retries Slack failures without repeating completed actions. Blocked work shows `warning` (configurable with `SIDEQUESTOR_REACTION_BLOCKED_EMOJI`); exhausted retries create an Inbox review. Conscious no-action outcomes show done. Save reactions remain silent. Existing historical loading-only orphans are not automatically replayed. To retain a custom set, pin `SIDEQUESTOR_REACTION_PROCESS_EMOJI`, `SIDEQUESTOR_REACTION_LOADING_EMOJI`, and `SIDEQUESTOR_REACTION_DONE_EMOJI`.

## Something looks wrong. Now what?

Start with `sq doctor` — it is the cheapest question you can ask. Include its build line when reporting a problem. `sq start` prints the dashboard URL and records it in `state/dashboard-url.txt`; `sq dashboard url` retrieves it later. `sq dashboard serve` remains available as a foreground developer escape hatch, but the normal persistent lifecycle is `sq start` and `sq stop`. Job errors are in `logs/package-*.err.log`.

In the dashboard, **Needs a fix** means a watch has a configuration or access problem and shows a
suggested action. **Investigate** means a checker or worker is backing off and will retry; check
the recorded cause if it persists. **Retrying** means a temporary read interruption or rate limit
will be retried automatically. A healthy long worker run shows **Dispatching**, not **Triage
stale**. **Worker stalled**, **Dispatch overdue**, or **Tick overdue** mean the run needs attention:
check `logs/worker-latest.log` and `logs/triage.log`, then run `sq doctor`. The health monitor waits
for a second sample before sending a macOS notification for transient worker faults or a long gap
between workers. A temporary watcher watermark hold clears from the quest's current blockers
after that watch successfully checks again; a separate unresolved quest blocker stays visible.

## How do I test a checkout?

```bash
# Run the complete unittest suite from the repository checkout.
python -m unittest discover -s tests -p 'test_*.py'
```

For a disposable branch-install test, read `.agents/skills/sidequestor-e2e/SKILL.md`. Before publishing package changes, run `.agents/skills/regression-check/SKILL.md`. Use `.agents/skills/publish-sidequestor-to-pypi/SKILL.md` for a PyPI release, or `.agents/skills/publish-yaas-to-circlefin/SKILL.md` for the signed Circlefin branch publication. Neither publication skill modifies `.git-yaas-v2`.

## License

Apache License 2.0. See `LICENSE`.
