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
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ PIPELINE_VERBATIM_MAX_CHARS=200000
# reach the bot's own files or another server's.
WORKSPACE_ENABLED=true
# Base directory the per-server workspaces live under. Blank puts it at
# .workspace/ beside the bot; set an absolute path to mount a volume there.
# .workspace/ beside the bot. The Docker image sets this to /data/workspace,
# so a volume mounted at /data (a Railway volume, or `docker run -v`) keeps
# the workspace across redeploys. Set any absolute path to override.
WORKSPACE_ROOT=
# Caps: the largest single file, the whole workspace, and the file count.
WORKSPACE_MAX_FILE_KB=64
Expand Down
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,13 @@ ENV PLUGIN_HTTP_ENABLED=true \
PLUGIN_HTTP_MAX_REDIRECTS=3 \
PLUGIN_HTTP_ALLOW_PRIVATE=false

# Persistent storage. The agent file workspace lives under /data so it
# survives a redeploy: attach a Railway volume (or `docker run -v`) with the
# mount path /data and everything the files.* and shell.run tools write
# persists. With nothing mounted, /data is an ordinary directory and the
# workspace is ephemeral, exactly as before. An --env-file at run time still
# overrides WORKSPACE_ROOT.
RUN mkdir -p /data/workspace
ENV WORKSPACE_ROOT=/data/workspace

CMD ["python", "main.py"]
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,16 @@ boot -- it is idempotent.

```sh
docker build -t archimedes .
docker run --env-file .env archimedes
docker run --env-file .env -v archimedes-data:/data archimedes
```

A `railway.toml` is included for one-click Railway deploys.
The agent file workspace lives under `/data` (`WORKSPACE_ROOT` is set to
`/data/workspace` in the image), so mounting a volume there keeps it across
restarts; without `-v` the workspace is ephemeral.

A `railway.toml` is included for one-click Railway deploys. On Railway,
attach a volume to the service with the mount path `/data` to get the same
persistence.

## Configuration

Expand Down
19 changes: 10 additions & 9 deletions cogs/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ async def _stream_turn(

async def _approver(name: str, args: dict) -> bool:
return await self._collect_tool_approval(
channel_id, user_id, name, args)
placeholder.channel, user_id, name, args)

tool_ctx.approver = _approver
renderer = StreamRenderer(placeholder)
Expand Down Expand Up @@ -454,18 +454,19 @@ async def _approver(name: str, args: dict) -> bool:
return final_text or None

async def _collect_tool_approval(
self, channel_id: int, user_id: int, name: str, args: dict,
self, channel, user_id: int, name: str, args: dict,
) -> bool:
"""Post an Approve / Reject prompt for one gated tool call.

Returns the human decision. A send failure or an unanswered prompt
counts as a refusal -- a gated tool is never run without an explicit
yes. The prompt message is edited in place with the outcome, so the
channel keeps a record of who cleared what.
``channel`` is the channel the turn is replying in -- a guild channel,
a thread or a DM alike -- taken straight from the placeholder message,
so the prompt always lands where the user is looking and never depends
on a channel-cache lookup that misses for DMs. Returns the human
decision; a send failure or an unanswered prompt counts as a refusal,
so a gated tool is never run without an explicit yes. The prompt is
edited in place with the outcome, so the channel keeps a record of who
cleared what.
"""
channel = self.bot.get_channel(channel_id)
if channel is None:
return False
timeout = float(max(5, Config.AGENT_APPROVAL_TIMEOUT_S))
decision: asyncio.Future[bool] = (
asyncio.get_running_loop().create_future()
Expand Down
6 changes: 6 additions & 0 deletions railway.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Persistent storage: attach a volume to this service in the Railway
# dashboard with the mount path /data. The agent file workspace then lives on
# it -- WORKSPACE_ROOT is set to /data/workspace in the Dockerfile -- and
# survives every redeploy. Railway volumes are attached in the dashboard, not
# declared in this file.

[build]
builder = "DOCKERFILE"
dockerfilePath = "Dockerfile"
Expand Down
Loading