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
26 changes: 19 additions & 7 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ responsibilities isolated.

```text
main
├── config + doctor + assistant init
├── paths + config + doctor + assistant init
├── gateway
│ ├── channel
│ │ ├── imessage
Expand Down Expand Up @@ -174,15 +174,26 @@ Push uses separate stores because they have different update and query needs:

| Store | Purpose |
| --- | --- |
| `state.json` | small atomically replaced map of channel cursors and backend session IDs |
| `push.db` | transactional conversation, delivery, approval compatibility, and job-run history |
| `<state_path>.slack-inbox.db` | Slack Socket Mode inbox committed before envelope acknowledgement |
| `audit.jsonl` | append-only operational audit events |
| `$PUSH_HOME/state.json` | small atomically replaced map of channel cursors and backend session IDs |
| `$PUSH_HOME/push.db` | transactional conversation, delivery, approval compatibility, and job-run history |
| `$PUSH_HOME/state.json.slack-inbox.db` | Slack Socket Mode inbox committed before envelope acknowledgement |
| `$PUSH_HOME/audit.jsonl` | append-only operational audit events |
| `$PUSH_HOME/run/` | advisory job locks |
| `$PUSH_HOME/cache/` | disposable agent handoff files |
| assistant Git repository | user-owned identity, context, evals, jobs, and project resources |

Runtime databases, secrets, locks, sessions, and logs must stay outside the
assistant repository.

[`src/paths.rs`](src/paths.rs) is the single owner of runtime locations.
`PUSH_HOME` selects the root and defaults to `~/.push`. Config, startup, jobs,
history, audit, and channels consume the resolved `PushPaths` value instead of
reconstructing filenames. The config, database, state, audit, run-lock, inbox,
and cache locations are derived from that root. Explicit legacy state,
database, audit, and run-lock settings replace only their matching derived
locations. `assistant_root` remains independent, and overlap with the runtime
root fails closed.

---

## 3. Process Lifecycle
Expand All @@ -192,8 +203,9 @@ The entry point is [`src/main.rs`](src/main.rs).
### Step 1: Parse the Command

The binary supports the long-running gateway plus `init`, `doctor`, `reload`,
and job management commands. Commands that need configuration resolve
`--config`, defaulting to `~/.push/config.toml`.
and job management commands. Commands that need configuration use `--config`
when present. `--config` selects a file without changing `PUSH_HOME`;
otherwise the config is `$PUSH_HOME/config.toml`.

### Step 2: Load and Validate Configuration

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ push init ~/Code/assistant
This creates a Git repository containing `SOUL.md`, shared instructions in
`AGENTS.md`, a `CLAUDE.md` reference to those instructions, `context/`, `evals/`,
`jobs/`, and a versioned Push capability skill for Claude Code, Codex, and Pi.
It then records the repository path in `~/.push/config.toml`.
It then records the repository path in `$PUSH_HOME/config.toml`. `PUSH_HOME`
defaults to `~/.push`.

Edit `~/.push/config.toml` to connect a chat channel. A small Telegram setup
Edit `$PUSH_HOME/config.toml` to connect a chat channel. A small Telegram setup
looks like this:

```toml
Expand Down
41 changes: 35 additions & 6 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Configuration

Push reads TOML from `~/.push/config.toml` by default. Pass `--config <path>`
to use a different file for a gateway, doctor, init, or job command.
Push owns one runtime root. `PUSH_HOME` selects it and defaults to `~/.push`.
The default config is `$PUSH_HOME/config.toml`. Pass `--config <path>` to use a
different file for a gateway, doctor, init, or job command.

```sh
push doctor
Expand All @@ -12,6 +13,20 @@ Paths beginning with `~` are expanded. Invalid values, unknown fields inside
provider sections, unsafe path overlap, and removed gateway permission settings
fail configuration load with an actionable error.

The path precedence is:

1. `--config` selects the config file when present.
2. `PUSH_HOME` selects the runtime root, even when `--config` selects a file elsewhere.
3. Explicit `state_path`, `database_path`, `audit_log_path`, and
`jobs_run_dir` settings override their matching derived locations.
4. Without `PUSH_HOME`, Push derives the root from `HOME` as `~/.push`.

If neither `PUSH_HOME` nor `HOME` is available, commands that need
configuration fail with an instruction to set `PUSH_HOME`. Help and version
commands still work. Use distinct `PUSH_HOME` values to run isolated Push
installations. `assistant_root` is never derived from `PUSH_HOME`; keep the
assistant repository outside the runtime root.

Create the one assistant repository and persist its root before editing the
rest of the config:

Expand Down Expand Up @@ -241,9 +256,9 @@ requests. Review [permissions and security](security.md) before enabling jobs.
| Setting | Default | Purpose |
| --- | --- | --- |
| `assistant_root` | required for new setups | Canonical root of the one assistant repository; `SOUL.md`, `context/`, `evals/`, and `jobs/` are derived |
| `state_path` | `~/.push/state.json` | Channel cursors and backend session IDs |
| `database_path` | `~/.push/push.db` | Canonical conversation, approval, and job history |
| `audit_log_path` | `~/.push/audit.jsonl` | Structured local audit log |
| `state_path` | `$PUSH_HOME/state.json` | Channel cursors and backend session IDs |
| `database_path` | `$PUSH_HOME/push.db` | Canonical conversation, approval, and job history |
| `audit_log_path` | `$PUSH_HOME/audit.jsonl` | Structured local audit log |
| `audit_log_content` | `false` | Include message and reply content in audit events |

### Jobs
Expand All @@ -252,14 +267,20 @@ requests. Review [permissions and security](security.md) before enabling jobs.
| --- | --- | --- |
| `jobs_agent` | root `agent` | Default jobs backend |
| `jobs_max_timeout` | `"30m"` | Maximum accepted job timeout |
| `jobs_run_dir` | `~/.push/run` | Local advisory locks |
| `jobs_run_dir` | `$PUSH_HOME/run` | Local advisory locks |
| `jobs_max_workers` | `2` | Concurrent scheduled job workers |

Push validates that runtime state, locks, external config files, and job work
directories do not overlap in unsafe ways.
Runtime state and secrets must stay outside the Git-versioned assistant
repository.

Push also derives the Slack recovery inbox as
`$PUSH_HOME/state.json.slack-inbox.db` and uses `$PUSH_HOME/cache` for
disposable agent handoff files. When `state_path` is explicitly set, the Slack
inbox stays beside it as `<state_path>.slack-inbox.db` to preserve existing
installations.

## Complete example

```toml
Expand Down Expand Up @@ -299,3 +320,11 @@ Legacy `assistant_dir` and `jobs_dir` settings remain compatible only when the
jobs path is exactly `<assistant_dir>/jobs`. For separate legacy paths, move
`SOUL.md`, context, and jobs under one directory and replace both settings with
`assistant_root`.

### Runtime path compatibility

Explicit `state_path`, `database_path`, `audit_log_path`, and `jobs_run_dir`
settings remain supported for the rest of the 0.x release line. Push does not
move or rewrite data at those paths. A future removal would require an
announced major release and migration instructions. New installations should
omit them and let `PUSH_HOME` own the whole runtime layout.
2 changes: 1 addition & 1 deletion docs/core-system/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ actionable migration error rather than silently losing identity or jobs.

### Canonical conversation history

`~/.push/push.db` stores every accepted inbound message and every user-visible
`$PUSH_HOME/push.db` stores every accepted inbound message and every user-visible
outbound message, whether produced by a backend or by the gateway. The minimum
logical model is:

Expand Down
14 changes: 11 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ your shell does not already include it. The installer recognizes Intel macOS
and ARM Linux, but it exits unless the latest GitHub release contains a matching
archive.

Push stores its private runtime data under `PUSH_HOME`, which defaults to
`~/.push`. Set an absolute `PUSH_HOME` before `push init` when you want another
location or an isolated second installation:

```sh
export PUSH_HOME="$HOME/.push-work"
```

## Build from source

Use this path on other Rust-supported architectures or when testing `main`:
Expand Down Expand Up @@ -91,7 +99,7 @@ practical structure for identity, context, shared skills, jobs, and evals.
=== "Telegram"

Create a bot with Telegram's `@BotFather`, send it one message, and find
your stable numeric user ID. Then edit `~/.push/config.toml`:
your stable numeric user ID. Then edit `$PUSH_HOME/config.toml`:

```toml
channel = "telegram"
Expand All @@ -109,7 +117,7 @@ practical structure for identity, context, shared skills, jobs, and evals.
=== "iMessage"

Give the terminal or service host Full Disk Access in macOS System
Settings, then edit `~/.push/config.toml`:
Settings, then edit `$PUSH_HOME/config.toml`:

```toml
channel = "imessage"
Expand All @@ -129,7 +137,7 @@ practical structure for identity, context, shared skills, jobs, and evals.

Create a Slack app with Socket Mode, `connections:write`, `im:history`,
`chat:write`, and the `message.im` bot event. Set the two tokens in the
service environment, then edit `~/.push/config.toml`:
service environment, then edit `$PUSH_HOME/config.toml`:

```toml
channel = "slack"
Expand Down
4 changes: 2 additions & 2 deletions docs/jobs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Scheduled example:
+++
version = 1
timeout = "5m"
workdir = "~/.push/workspaces/morning-agenda"
workdir = "~/Code/morning-agenda"

[[triggers]]
id = "weekday-morning"
Expand Down Expand Up @@ -183,7 +183,7 @@ built-in utility tools. The first version evaluates the returned response
without inspecting work-directory artifacts.

Before backend execution, both manual and scheduled starts attempt a
non-blocking OS advisory lock for the job under `~/.push/run/locks/`. The
non-blocking OS advisory lock for the job under `$PUSH_HOME/run/locks/`. The
winning process holds that lock until its run finishes. It then uses one SQLite
transaction to check queued and running state, record the run, and claim the
job. A start that loses the file lock or database claim is recorded as
Expand Down
4 changes: 2 additions & 2 deletions docs/prd.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ user prompt.
| `assistant_root` | Canonical root of the one assistant repository. `SOUL.md`, `context/`, and `jobs/` are derived. |
| `jobs_agent` | Optional default job backend; otherwise uses `agent`. |
| `jobs_max_timeout` | Maximum validated job timeout; defaults to `30m`. |
| `jobs_run_dir` | Local advisory-lock state; defaults to `~/.push/run`. |
| `jobs_run_dir` | Local advisory-lock state; defaults to `$PUSH_HOME/run`. |
| `jobs_max_workers` | Maximum concurrent scheduled jobs; defaults to `2`. |
| `imessage.db_path` | Path to Messages `chat.db`. |
| `poll_interval` | How often to poll. |
Expand All @@ -164,7 +164,7 @@ user prompt.
| `state_path` | JSON state path. |
| `audit_log_path` | Local JSONL audit log path. |
| `audit_log_content` | Whether audit events include message and reply text. |
| `database_path` | Canonical SQLite history path; defaults to `~/.push/push.db`. |
| `database_path` | Canonical SQLite history path; defaults to `$PUSH_HOME/push.db`. |

## Control Commands

Expand Down
3 changes: 2 additions & 1 deletion docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

Push has one gateway command, one diagnostic command, and a small set of job
commands. All commands accept `--config <path>` anywhere in the argument list.
The default is `~/.push/config.toml`.
The default is `$PUSH_HOME/config.toml`; `PUSH_HOME` defaults to `~/.push`.
`--config` changes only the selected config file, not the runtime root.

| Command | Purpose |
| --- | --- |
Expand Down
19 changes: 13 additions & 6 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ service environment using the narrowest policy that works.

| Path | Contains |
| --- | --- |
| `config.toml` | allowlists, routes, paths, and possibly credentials |
| `$PUSH_HOME/config.toml` | allowlists, routes, paths, and possibly credentials |
| `<assistant_root>/` | Git-versioned identity, context, evals, jobs, and optional project skills |
| `~/.push/state.json` | channel cursors and backend session IDs |
| `~/.push/push.db` | conversation history, approvals, and job runs |
| `~/.push/audit.jsonl` | metadata, errors, handles, and optional content |
| `$PUSH_HOME/state.json` | channel cursors and backend session IDs |
| `$PUSH_HOME/push.db` | conversation history, approvals, and job runs |
| `$PUSH_HOME/state.json.slack-inbox.db` | Slack envelopes accepted before gateway processing |
| `$PUSH_HOME/audit.jsonl` | metadata, errors, handles, and optional content |
| `$PUSH_HOME/run/` | local job lock files |
| `$PUSH_HOME/cache/` | disposable agent handoff files |

Keep them on local durable storage with permissions restricted to the service
user. Keep the assistant directory in its own private Git repository. Never
Expand All @@ -75,9 +78,13 @@ variable or move the config outside. When `voice.openai_api_key` is configured,
`push doctor` requires the config file to be private on Unix:

```sh
chmod 600 ~/.push/config.toml
chmod 600 "${PUSH_HOME:-$HOME/.push}/config.toml"
```

Push rejects any overlap between `assistant_root` and `PUSH_HOME`, including
overlap through symlinks or existing ancestors. Keep the runtime root private
to Push and keep the assistant as a separate user-owned Git repository.

## Network exposure

Push opens no inbound server port. iMessage reads local state. Telegram uses
Expand Down Expand Up @@ -110,7 +117,7 @@ IDs, file paths, and backend errors. Protect and rotate it like a service log.
- run `push doctor` as the service user
- keep agent credentials out of TOML and all credentials out of logs
- protect config credentials with mode `0600` on Unix
- use absolute config paths in service files
- set one absolute `PUSH_HOME` in service files
- keep Push state and job work directories separate
- review agent-authored job changes in the assistant repository
- review the audit log after routing or agent permission changes
47 changes: 31 additions & 16 deletions docs/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,14 @@ own the service:

```sh
push init ~/Code/assistant
# Edit ~/.push/config.toml with your channel settings.
# Edit $PUSH_HOME/config.toml with your channel settings.
push doctor
```

Use absolute paths in service files. The service user needs:
Set one absolute `PUSH_HOME` in the service definition. It defaults to
`~/.push` for interactive commands. The service user needs:

- access to the configured `config.toml`
- write access to `state_path`
- write access to `audit_log_path`
- write access to `database_path`
- write access to `jobs_run_dir`
- read and write access to `PUSH_HOME`
- filesystem access to `assistant_root` as allowed by the selected agent
- agent write access to `assistant_root/jobs/` when jobs should be created from chat
- access to the selected `claude`, `codex`, or `pi` executable on `PATH`
Expand All @@ -38,11 +35,13 @@ Use absolute paths in service files. The service user needs:
`OPENAI_API_KEY` in the service environment, plus network access to
`api.openai.com`

`state_path` stores independent cursors for each channel and backend session
ids. `database_path` stores the canonical conversation journal. Chat agents run
from `assistant_root`. Keep these paths on durable storage. Restarting the
service resumes after the last completed row and reuses existing backend
sessions when the backend for that thread has not changed.
`$PUSH_HOME/state.json` stores independent cursors for each channel and backend
session IDs. `$PUSH_HOME/push.db` stores the canonical conversation and job
journal. The audit log, Slack recovery inbox, job locks, and cache are derived
from the same root. Chat agents run from `assistant_root`. Keep `PUSH_HOME` on
durable storage. Restarting the service resumes after the last completed row
and reuses existing backend sessions when the backend for that thread has not
changed.

Keep `assistant_root` in its own Git repository. Keep config secrets, state,
databases, logs, locks, and service credentials outside it.
Expand Down Expand Up @@ -71,15 +70,15 @@ and replace `YOU` with your macOS user name:
<key>ProgramArguments</key>
<array>
<string>/Users/YOU/.local/bin/push</string>
<string>--config</string>
<string>/Users/YOU/.push/config.toml</string>
</array>

<key>WorkingDirectory</key>
<string>/Users/YOU/.push</string>

<key>EnvironmentVariables</key>
<dict>
<key>PUSH_HOME</key>
<string>/Users/YOU/.push</string>
<key>PATH</key>
<string>/Users/YOU/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
Expand Down Expand Up @@ -147,11 +146,12 @@ Wants=network-online.target

[Service]
Type=simple
ExecStart=%h/.local/bin/push --config %h/.push/config.toml
ExecStart=%h/.local/bin/push
WorkingDirectory=%h/.push
Restart=on-failure
RestartSec=10
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=PUSH_HOME=%h/.push
EnvironmentFile=-%h/.config/push/env

[Install]
Expand Down Expand Up @@ -195,7 +195,7 @@ loginctl enable-linger "$USER"
## Manual Jobs

`push job run <name>` executes in the invoking terminal process, not in the
managed service. Use the same config file so the CLI and service share
managed service. Use the same `PUSH_HOME` so the CLI and service share
`push.db`, `<assistant_root>/jobs`, and the local per-job lock directory.
Invalid job files are reported and disabled individually; they do not stop the
messaging service.
Expand Down Expand Up @@ -228,6 +228,21 @@ Ignored messages, completed rows, and setup failures advance the cursor. Rows
newer than an in-flight row do not push the cursor past it until the earlier row
is completed.

## Backup and Recovery

Stop the service before taking a filesystem-level backup. Back up the complete
`PUSH_HOME` directory as one unit so config, cursors, the Slack inbox, canonical
history, audit events, and job delivery state stay consistent. Back up
`assistant_root` separately through its Git repository because it is
user-owned and must not live under `PUSH_HOME`.

To restore, stop the service, restore both locations to separate directories,
set the service `PUSH_HOME` to the restored runtime root, confirm
`assistant_root` in the restored config, then run `push doctor` before starting
the service. If a compatible older config sets `state_path`, `database_path`,
`audit_log_path`, or `jobs_run_dir`, back up and restore those explicit
locations too. The cache directory is disposable and can be omitted.

## Security Notes

Managed services run without a person watching the terminal. An allowed sender
Expand Down
2 changes: 1 addition & 1 deletion docs/slack.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ You can instead set `slack.app_token` and `slack.bot_token` in the private Push
config. Never put tokens in the Git-versioned assistant repository. Run:

```sh
chmod 600 ~/.push/config.toml
chmod 600 "${PUSH_HOME:-$HOME/.push}/config.toml"
push doctor
push
```
Expand Down
Loading
Loading