File Operation Flow
Every file mutation in OfficePlane follows a 5-step pipeline. This ensures atomic edits, prevents conflicts between concurrent agents/requests, and guarantees the file is always in a consistent state.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. Open │───>│ 2. Queue │───>│ 3. Change │───>│ 4. Pretty │───>│ 5. Get │
│ Instance │ │ (atomic) │ │ (harness) │ │ (format) │ │ File │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
Step 1 — Open Instance
An instance represents an open file session. Opening an instance:
- Loads the document from the database
- Acquires exclusive access to the underlying file
- Marks the document as
IN_USE - Starts the backing driver (LibreOffice) if format conversion is needed
POST /api/instances
{ "documentId": "<uuid>" }
# Returns: Instance { id, state: OPEN, documentId }
Rules:
- One active instance per document at a time (enforced by the queue)
- Instance must be explicitly closed or will time out
- All subsequent operations reference the instance ID
Instance Lifecycle
OPENING → OPEN → IDLE → IN_USE → CLOSING → CLOSED
→ ERROR
→ CRASHED
Instances have a 5-second heartbeat monitor. If the heartbeat is missed, the instance transitions to CRASHED and releases its locks.
Step 2 — Queue (Atomic Control)
The task queue serializes all mutations on a given document. Changes go through the queue — never directly to the file.
POST /api/tasks
{
"taskType": "file_change",
"documentId": "<uuid>",
"instanceId": "<uuid>",
"payload": { "prompt": "Add an executive summary section..." }
}
Infrastructure
┌─────────────┐ LPUSH/RPUSH ┌───────────────┐
│ API Server │ ─────────────────────> │ Redis 7 │
│ (producer) │ │ queue + │
└─────────────┘ │ locks + │
│ pub/sub │
┌─────────────┐ BRPOP └───────────────┘
│ Workers │ <─────────────────────
│ (consumers) │
└─────────────┘
- Redis list (
officeplane:tasks) — tasks pushed, workers BRPOP (instant wakeup) - Redis SETNX (
officeplane:doclock:{doc_id}) — document-level lock - PostgreSQL — source of truth for task state, payloads, retries
Atomicity Guarantees
- Tasks targeting the same
documentIdexecute sequentially (Redis lock) - Multiple agents/users can submit tasks concurrently — the queue orders them
- Each task either completes fully or rolls back (no partial file corruption)
- Lock auto-expires after 10 min (crashed workers can't hold forever)
Step 3 — Change (Agent Harness)
The agent harness performs the actual file modification:
Agent Harness
├── Reads current document from workspace
├── Analyzes what changes are needed
├── Executes changes using tools:
│ ├── python-docx (Word editing)
│ ├── pptxgenjs (PowerPoint creation)
│ ├── python-pptx (PowerPoint manipulation)
│ └── custom scripts as needed
├── Writes modified file back to workspace
└── Reports completion via SSE stream
SSE Events (via Redis pub/sub)
The agent publishes to officeplane:sse:{job_id}. The HTTP handler subscribes and streams to the client:
event: start → Agent begins working
event: delta → Agent thinking/planning text
event: tool_call → Agent invokes a tool
event: tool_result → Tool output
event: stop → Done (includes document_id)
Step 4 — Pretty (Format/Beautify)
After changes are applied, an optional normalization step runs:
- DOCX/PPTX — Normalize styles, fix heading hierarchy, consistent fonts/sizes
- HTML — Format/indent markup, validate structure
- General — Ensure metadata is up to date
pretty(workspace_path, file_format)
This is idempotent — running it twice produces the same result. It can be skipped via "skip_pretty": true on the task.
Step 5 — Get File
Download the final file in any supported format:
GET /api/documents/{id}/download?format=original # Original bytes
GET /api/documents/{id}/download?format=docx # Regenerated DOCX
GET /api/documents/{id}/download?format=markdown # Markdown export
GET /api/documents/{id}/download?format=pdf # PDF via LibreOffice
Complete Example
User: "Add a risk analysis section to my pitch deck"
1. POST /api/instances { documentId: "abc" }
→ Instance opened, document locked
2. POST /api/tasks { prompt: "Add risk analysis section...", documentId: "abc" }
→ Task pushed to Redis, worker wakes up immediately
→ Worker acquires doc lock via SETNX
3. Agent harness runs:
→ Reads current document structure
→ Identifies where to insert new section
→ Creates risk analysis content
→ Writes modified file
→ SSE events streamed to client
4. pretty() runs:
→ Normalizes heading levels
→ Ensures consistent font usage
5. GET /api/documents/abc/download?format=pptx
→ Returns the updated file