| name | fuse-agent |
| description | Agent-specific interaction patterns for working with FUSE-mounted spaces. Use when deploying patterns via FUSE, working with Activity Logs, Annotations, or coordinating agent workflows that read/write pieces through the filesystem. Triggers include "deploy a pattern", "log an event", "create annotation", "agent workflow", or managing piece lifecycle via FUSE.
|
| user-invocable | false |
FUSE Agent Workflows
Patterns for agents that interact with FUSE-mounted Common Fabric spaces. For
FUSE mounting, filesystem layout, and low-level read/write mechanics, see the
fuse-workflow skill.
Deploying Patterns
cd ~/code/labs
export CF_IDENTITY=./shared.key CF_API_URL=http://localhost:8000
ID=$(cf piece new packages/patterns/<path>.tsx \
--space SPACE --root packages/patterns 2>/dev/null | head -1)
cf piece call --quiet --piece $ID --space SPACE setTitle -- --value "My Title"
cf piece step --piece $ID --space SPACE
cat "MOUNT/SPACE/pieces/pieces.json"
Pattern index: cat ~/code/labs/packages/patterns/index.md
After deploying a structural piece:
- Re-read
pieces.json — get the current name with count suffix
- Read
.handlers — discover available operations, never assume schema
- Populate using direct handler invocation
- Verify via
result/summary
- Append wikilink to any source note that motivated the deploy
Identifying the running pattern
pieces/pieces.json and each piece's meta.json expose patternRef:
{
"identity": "<content-hash>",
"symbol": "default",
"source": {
"ref": "cf:pattern:<content-hash>",
"repository": "https://github.com/commontoolsinc/labs",
"entry": "/packages/patterns/annotation.tsx"
}
}
The prefix-free identity + symbol are the authoritative reference to the
running artifact (cf:module/<identity>#<symbol> in display form). source.ref
addresses the immutable source closure; source.repository is an optional,
explicitly supplied repository locator; source.entry is its optional authored
entry path; and source.origin is optional update provenance. For pattern-kind
discovery, match the entry filename and fall back to the origin path when the
entry is absent; do not infer it from the piece's mutable display name.
Lifecycle Gotchas
Piece name instability
Every handler call that changes piece state updates the count suffix in the
name: Reading List (0) -> Reading List (1) -> Reading List (2)
All previously constructed FUSE paths are immediately invalid. Before every
handler call:
cat "MOUNT/SPACE/pieces/pieces.json" | python3 -c \
"import json,sys; p=json.load(sys.stdin); print(next(x['name'] for x in p if 'Reading' in x['name']))"
When to run cf piece step
| Operation | Step needed? |
|---|
cf piece call (CLI) | Always |
cf piece set (CLI) | Always |
| FUSE handler invocation | Sometimes — if count suffix doesn't update |
Read/Write/Edit on index.md | Never |
When in doubt after a FUSE handler call: run
cf piece step --piece $ID --space SPACE, then re-read pieces.json.
NFS timeout and remount
macOS logs nfs server fuse-t: not responding / is alive again under agent
load. Reads stall during the window.
ls /tmp/cf-mount/
cf fuse mount /tmp/cf-mount --background
--background waits until the daemon reports it has mounted, so no delay is
needed after it. It exits non-zero if the child dies during startup, or if the
child does not report readiness within about 20 seconds.
If reads still stall, read the mount status rather than waiting:
cat /tmp/cf-mount/.status
connection.disconnected — the transport is dead. Remount; waiting does not
recover it.
rebuilds.pending — a subtree is still rebuilding. Those reads settle on
their own.
- On macOS/FUSE-T, staleness beyond about a second suggests the mount was
created with
--attrcache-timeout 0 or --noattrcache; remount with the
default of 1. Neither option applies on Linux or macFUSE.
Transport disconnection (silent write failures)
Long-running FUSE mounts (24h+) can lose their backend transport. Symptom: all
writes appear to succeed (no error), but values don't persist — cells stay empty
or revert. The FUSE process is still running but useless.
Diagnose:
tail -20 /tmp/ct-fuse-<mount-name>.log
Fix: Kill and remount. Remount before each experiment run to be safe.
Agent handlers (markIdle.handler, appendLearned.handler, etc.) will also
fail silently with a dead transport — the handler appears to execute but no
state changes. If agents report "learned" entries that don't show up in
input/learned, check the transport first.
Secure-mode time/random pitfalls in handlers
The pattern sandbox gates the ambient intrinsics Date.now(), no-argument
new Date(), and Math.random(). They are allowed inside a handler (the
clock coarsened to one-second resolution; entropy passes through) and throw a
TimeCapabilityError in a lift/computed or at pattern-body level. Call the
built-ins directly — they are not importable helpers.
const now = Date.now();
const iso = new Date(now).toISOString();
const id = `${now.toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
This specifically matters for:
activity-log.tsx event creation / timestamps
agent.tsx lifecycle handlers (markIdle, markError)
- any handler-generated IDs or timestamps in experiment support patterns
For event IDs in authored patterns, Math.random() is fine inside a handler:
const now = Date.now();
const id = `${now.toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
const timestamp = new Date(now).toISOString();
For reactive time in a computed, read the live clock with the #now wish rather
than calling Date.now().
If a handler fails with messages like:
secure mode Calling new %SharedDate%() with no arguments throws
secure mode %SharedMath%.random() throws
- a
TimeCapabilityError
check the pattern source — the clock/entropy call may be running in a
lift/computed or at pattern-body level rather than inside a handler.
Activity Log Pattern
The Activity Log (packages/patterns/activity-log/activity-log.tsx) is a
structured event stream for recording agent actions. Log events incrementally as
you work — not in one batch at the end.
Calling logEvent.handler:
LOG_NAME=$(cat "MOUNT/SPACE/pieces/pieces.json" | python3 -c \
"import json,sys; p=json.load(sys.stdin); \
print(next(x['name'] for x in p if 'Activity Log' in x['name']))")
"MOUNT/SPACE/pieces/$LOG_NAME/result/logEvent.handler" \
--agent "deployer" \
--action "deployed" \
--piece-name "Contact Book" \
--note "Contacts mentioned in standup notes with no structured tracking"
Input fields (all string, all optional except agent and action):
| Field | Type | Example |
|---|
agent | string | "deployer" |
action | string | "deployed", "populated", "linked" |
pieceName | string? | "Contact Book" |
note | string? | one-line detail |
Read log state (after any agent has run):
cat "MOUNT/SPACE/pieces/Activity Log (N)/result/summary"
Annotation Pattern
Annotations (annotation.tsx) are pieces that record observations, flags, and
wishes. Use them to leave notes about things noticed without necessarily acting.
Deploy and configure via CF CLI:
ID=$(cf piece new packages/patterns/annotation.tsx \
--space SPACE --root packages/patterns 2>/dev/null | head -1)
echo '"Standup notes mention 5 people with no structured contact list"' \
| cf piece set --piece $ID content --space SPACE
echo '"wish"' | cf piece set --piece $ID kind --space SPACE
cf piece step --piece $ID --space SPACE
Kind values: "note" | "todo" | "wish"
Status values: "open" | "in-progress" | "resolved" | "dismissed"
When to use each kind:
"note" — record an observation without acting: "3 standup entries reference
a project that has no piece"
"wish" — request for another agent or a future pass: "Deploy a Calendar
pattern — there are 4 dated events in the Work Journal"
"todo" — flag incomplete work: "Contact Book has 3 entries with no email
address"
Mark a wish resolved (when fulfilling another agent's annotation):
echo '"resolved"' | cf piece set --piece $WISH_ID status --space SPACE
cf piece step --piece $WISH_ID --space SPACE
Discover open annotations — deploy annotation-manager.tsx for an
aggregated view, or query pieces.json directly:
cat "MOUNT/SPACE/pieces/pieces.json" | python3 -c "
import json, sys
p = json.load(sys.stdin)
for x in p:
ref = x.get('patternRef', {})
source = ref.get('source', {}) if isinstance(ref, dict) else {}
locator = (source.get('entry') or source.get('origin', '')) if isinstance(source, dict) else ''
if locator.rsplit('/', 1)[-1] == 'annotation.tsx':
print(x['name'], '—', x.get('summary','')[:60])
"
Agent Piece (packages/patterns/agent/agent.tsx)
Each agent is a piece in the space with its own cells for directive, learned
state, and lifecycle. Deploy one per agent in the space.
MOUNT/SPACE/pieces/🤖 Deployer/
result/
summary ← "Deployer: last run summary" or "Deployer (no runs yet)"
markRunning.handler ← call at start of run (auto-logs to Activity Log)
markIdle.handler ← call when done: --summary "what you did"
markError.handler ← call on failure: --summary "what went wrong"
appendLearned.handler ← append a learning: --entry "today I learned X"
setDirective.handler ← update directive: --value "new directive text"
setLearned.handler ← replace all learned: --value "full learned text"
input/
agentName ← raw text: "Deployer"
directive ← raw text: the agent's full directive/instructions
enabled ← raw text: "true" or "false"
learned ← raw text: accumulated learnings
status ← raw text: "idle" | "running" | "error"
lastRun ← raw text: ISO timestamp of last run
lastRunSummary ← raw text: summary from last markIdle/markError
.handlers
meta.json
Agent lifecycle
Important: Always re-resolve the piece name before each handler call. Piece
name suffixes can change after handler invocations (e.g. Counter-1 becomes
Counter-2), so a stale $AGENT_NAME will target a non-existent path.
resolve_agent() {
cat "MOUNT/SPACE/pieces/pieces.json" | python3 -c \
"import json,sys; p=json.load(sys.stdin); \
print(next(x['name'] for x in p if 'Deployer' in x['name']))"
}
AGENT_NAME=$(resolve_agent)
cat "MOUNT/SPACE/pieces/$AGENT_NAME/input/directive"
AGENT_NAME=$(resolve_agent)
"MOUNT/SPACE/pieces/$AGENT_NAME/result/markRunning.handler"
AGENT_NAME=$(resolve_agent)
"MOUNT/SPACE/pieces/$AGENT_NAME/result/appendLearned.handler" \
--entry "2026-04-07: Calendar addEvent throws pattern-load-error but succeeds"
AGENT_NAME=$(resolve_agent)
"MOUNT/SPACE/pieces/$AGENT_NAME/result/markIdle.handler" \
--summary "Deployed Contact Book and Calendar, left 2 wishes for Populator"
AGENT_NAME=$(resolve_agent)
"MOUNT/SPACE/pieces/$AGENT_NAME/result/markError.handler" \
--summary "FUSE mount unresponsive: .status reports transport disconnected"
markRunning, markIdle, and markError automatically log to the Activity Log
via wish("#activity-log"). You still log individual actions (deploys,
populates, links) manually — the lifecycle handlers just record start/stop.
Discovering agents
cat "MOUNT/SPACE/pieces/pieces.json" | python3 -c "
import json, sys
p = json.load(sys.stdin)
for x in p:
ref = x.get('patternRef', {})
source = ref.get('source', {}) if isinstance(ref, dict) else {}
locator = (source.get('entry') or source.get('origin', '')) if isinstance(source, dict) else ''
if locator.rsplit('/', 1)[-1] == 'agent.tsx':
print(x['name'], '—', x.get('summary','')[:60])
"
Cleanup
cf piece rm --piece $ID --space SPACE
Use this to clean up duplicate pieces deployed by accident (no --confirm
needed). Check pieces.json for orphaned pieces with -2 or -3 suffixes.