Task Monitoring & Inter-Agent Communication
Chronological task feeds. Zero-content signal pointers. Multi-agent coordination without polling.
Two tools handle the operational side of Gnosis: task_feed answers "what changed?" and signal answers "who needs to know?" Together they let multiple AI agents coordinate work through shared collections without ever polling each other, passing messages, or duplicating context.
task_feed
task_feed returns recently updated tasks sorted by time, not relevance. It is a chronological timeline of task activity — which tasks were created, toggled, or had their status changed — ordered by most recently updated first. There is no semantic ranking, no embedding comparison, no scoring. The sort key is the updated_at timestamp, descending.
Chronological, Not Ranked
This is a deliberate design choice. task_feed answers time-oriented questions: "What needs attention right now?" "What changed since yesterday?" "Which tasks are blocked?" These questions have temporal answers, not semantic ones. A task that was updated 30 seconds ago matters more than a task whose title happens to match some query, regardless of textual similarity.
Each task in the feed includes:
preview
The first line of the task content — enough to identify what the task is about without retrieving the full memory.
progress
A [done, total] pair representing checkbox completion, or null if the task has no checklist. [3, 7] means 3 of 7 steps are checked off.
status
The task's workflow state: pending, active, blocked, review, or done. Filter the feed by any of these.
topics & collection
The task's topic tags and, if the task comes from a shared collection, the collection slug. Personal tasks omit the collection field.
Filters
All filters are optional. Without any filters, the feed returns all active tasks from all sources.
- since — ISO 8601 timestamp. Only tasks updated after this time are returned. Omit to see everything.
- status — restrict to a single workflow state.
status: "blocked"shows only blocked tasks. - topic — restrict to tasks tagged with a specific topic.
topic: "auth"shows only auth-related tasks. - collection — scope to a single collection by name or slug.
collection: "personal"excludes shared collections entirely. - limit — maximum number of tasks to return. Defaults to 32, maximum 500.
Fan-out across namespaces
When no collection filter is specified, task_feed automatically fans out across the caller's personal namespace and every shared collection they belong to. A single call returns a unified timeline — personal tasks and team tasks interleaved by update time.
The fan-out resolves collection memberships, reads task progress blobs from each namespace in a single database query, merges them (first-seen wins when personal and collection blobs overlap), applies filters, sorts chronologically, and caps at the requested limit. Collection-scoped agents — agents whose default collection is set — see their collection tasks without needing to specify each one.
When to Use task_feed vs memory_search
These two tools look at the same underlying tasks but answer fundamentally different questions.
Answers: "What needs attention?" — "What changed since yesterday?" — "Show me all blocked tasks."
Sorted by: Time. Most recently updated first.
Works without a query. The AI calls it with no arguments and gets a timeline. Filters narrow the results by status, topic, or collection — but the ordering is always chronological.
Answers: "Find tasks about auth." — "What did we decide about the deploy pipeline?" — "Show me OAuth-related work."
Sorted by: Relevance. Semantic similarity to the query, reranked by a cross-encoder.
Requires a query. The AI passes search terms and gets results ranked by meaning. Use type_filter: "task" to restrict results to tasks only.
The distinction is practical: an AI starting a session calls task_feed to see what's active and what's changed. When a user asks about a specific topic, the AI calls memory_search to find relevant tasks by content. Different tools, different questions.
The Signal System
Signals are the inter-agent communication layer. One agent points another agent at specific memory IDs through a shared collection. No content is transmitted — signals carry only pointers. The recipient retrieves the referenced memories through normal search or retrieve calls.
Pointers, Not Payloads
A signal says "look at these memory IDs." It does not contain the memory content, a summary, instructions, or metadata. The recipient calls memory_retrieve with the IDs to see what the sender is pointing at. This keeps the signal system minimal — it is a notification layer, not a message bus. Signals cannot carry arbitrary data, which means the shared collection's access controls are the only gate: if you can't read the collection, you can't read the memories the signal points to.
Three Actions
send
Send memory pointer(s) to a recipient by email address or agent name. Both sender and recipient must be members of the specified shared collection — the collection is the access gate. Up to 100 memory IDs per signal. Default expiry: 48 hours (configurable 1–720 hours).
check
Peek at pending signals without consuming them. Non-destructive — safe to call from any device or context. Returns signals from all collections the caller belongs to. The same signal will appear on every check until it is explicitly acknowledged or expires.
ack
Acknowledge signals by ID after processing. Marks them as delivered so they no longer appear in subsequent checks. Only the agent that actually processes a signal should ack it — this prevents double-processing when multiple contexts (different sessions, different devices) check the same inbox.
Why signals expire
Signals default to a 48-hour expiry. An unacknowledged signal older than its expiry is swept by a daily cleanup job (and a startup sweep). The expiry exists because signals are transient coordination events, not durable records. If an agent hasn't checked its inbox in two days, the context that prompted the signal has likely shifted. The sender can always re-signal if the situation is still relevant.
The expiry window is configurable per signal: expires_hours: 168 gives a week. The maximum is 720 hours (30 days). The minimum is 1 hour.
How Signals Surface
Pending signals appear in two places: the init_core_memories response and the signal(action:"check") call.
During init_core_memories, signals are delivered in peek mode. The AI sees a list of pending signals — sender, collection, memory IDs, timestamp — but the signals are not consumed. They remain in the queue. This is deliberate: init_core_memories runs in many contexts, including sessions where the AI may not be set up to process signals (a quick question, a different topic, a non-agent context). Peek mode ensures signals are never silently cleared by an init call that wasn't going to act on them.
Consumption requires an explicit signal(action:"ack") call with the signal IDs. The processing flow is:
- AI calls
init_core_memoriesorsignal(action:"check")and sees pending signals. - AI calls
memory_retrievewith the memory IDs from the signal to understand what was sent. - AI processes the work — reads the task, toggles steps, records findings, updates status.
- AI calls
signal(action:"ack")with the signal IDs to mark them consumed.
Until step 4 completes, the signal persists. Checking from a different device, a different session, or a different agent will still show it.
Inline Signal Shortcuts
Two tools accept a signal parameter that combines a store or edit operation with a signal send in a single call:
memory_add(content, topics, ..., signal: "deploy-bot", collection: "ops")— stores a new memory in the "ops" collection, then immediately sends a signal pointing the "deploy-bot" agent at the new memory's ID.memory_edit(memory_id, set_status: "blocked", signal: "dev-agent", collection: "ops")— updates the task status to blocked, then signals "dev-agent" with the task's memory ID.
The shortcut eliminates a separate signal(action:"send") call. The signal is sent after the memory operation completes, using the resulting memory ID. If the memory operation fails, no signal is sent. The collection parameter is required when using the shortcut — signals route through shared collections and need a collection context.
Multi-Agent Coordination
A concrete example of two agents coordinating through task_feed and signals. Both agents share a collection called "deployments." Neither agent polls the other or sends direct messages.
Scenario: coordinated deployment
Step 1: The monitoring agent creates a task.
A monitoring agent detects that a new commit has landed on the main branch. It creates a deployment task with steps:
One call stores the task and signals the deploy agent. The task exists in the "deployments" collection where both agents have access.
Step 2: The deploy agent picks up the signal.
The deploy agent calls init_core_memories and sees a pending signal in its response. It retrieves the referenced memory ID, reads the task, and begins working through the steps.
Step 3: The deploy agent records progress.
As each step completes, the deploy agent toggles checkboxes and attaches findings:
The toggle parameter flips the first checkbox. The output parameter creates a linked fact memory with the finding, inheriting the task's topics. Both operations happen in one call. The task's updated_at timestamp advances, so it appears at the top of task_feed.
Step 4: Blocking and escalation.
Integration tests fail. The deploy agent sets the task to blocked and signals back:
The test step is toggled (it ran, even though it failed), the status changes to blocked, the failure detail is captured as a linked finding, and the monitoring agent is notified. No separate signal call needed.
Step 5: Visibility without coordination overhead.
Any agent or user with access to the "deployments" collection can call task_feed(collection: "deployments") and see this task at the top of the timeline with its current status, progress ([2, 6]), and the "blocked" flag. They don't need to know which agents are involved or how they communicate. The task feed shows the state of work. The linked findings show the history.
Design Notes
Why signals carry no content
Early designs included a message field on signals. This was removed because it created a parallel communication channel that bypassed the memory system. Agents would send instructions via signal messages instead of storing findings as memories. The result was important context that existed only in ephemeral signals and disappeared after 48 hours.
By restricting signals to memory ID pointers, all content flows through the memory system where it is searchable, retrievable, and permanent. The signal is the notification. The memory is the content. They are deliberately separate concerns.
Why ack is separate from check
Signals can be checked from multiple contexts: different sessions, different devices, different agents with shared access. If checking automatically consumed signals, the first context to call check would clear the queue for all others. Separating check (peek) from ack (consume) means any context can see what's pending without affecting other contexts.
The same reasoning applies to init_core_memories. Init runs at the start of every conversation, including conversations that have nothing to do with the pending signals. Peek mode prevents a casual "what's the weather?" session from silently consuming a deployment notification.
Collection as access gate
Signals require a shared collection. The collection serves as both the routing namespace and the access control boundary. Both sender and recipient must be members. This means signal access is governed by the same membership system as memory access — there is no separate permissions model for signals. If you remove someone from a collection, they lose the ability to send signals through it and to retrieve the memories those signals point to.