How Paperclip Propagates Goal Hierarchy Context to Agents

Paperclip propagates hierarchical goal context to agents through a scoped ctx.goals service that exposes parent-child relationships from the database layer, automatically injected into the agent sandbox via the plugin SDK's RPC host.

The Paperclip AI platform orchestrates autonomous agents using a tree-structured goal system that maps organizational objectives to executable tasks. Understanding how this Paperclip goal hierarchy propagates context to agents requires tracing the flow from the database schema through the service layer to the runtime sandbox environment.

The Database Schema: Parent-Child Relationships

At the foundation of the hierarchy lies the goals table defined in packages/db/src/schema/goal.ts. This schema establishes a self-referential foreign key relationship through the parentId field, which enables the construction of an arbitrary depth goal tree.

The schema enforces referential integrity at the database level, ensuring that child goals cannot exist without valid parents. It also defines enumerated fields such as level (organizational scope) and status (execution state), which agents frequently query to understand their operational context within the hierarchy.

The Service Layer: GoalService Implementation

The GoalService class in packages/db/src/client.ts abstracts database operations into a company-scoped API. This service implements four primary methods that power the hierarchy:

  • list({ companyId, … }) – Returns paginated goal arrays ordered by the parentId relationship, allowing reconstruction of the tree structure client-side.
  • get(goalId, companyId) – Retrieves a single node, including its parentId and level, with lazy resolution of ancestor chains.
  • create(payload, companyId) – Inserts new goals, accepting an optional parentId to establish hierarchy position.
  • update(goalId, patch, companyId) – Modifies goal attributes, including reparenting via parentId updates, with automatic cache invalidation for descendant references.

Crucially, every method requires a companyId parameter, enforcing strict tenant isolation at the service boundary.

The Agent Runtime: ctx.goals Injection

When an agent initializes, the system constructs a runtime context object that bridges the service layer and the sandboxed agent code. The interface declared in packages/plugins/sdk/src/types.ts exposes the goal service as ctx.goals, providing agents with typed access to the hierarchy.

The packages/plugins/sdk/src/worker-rpc-host.ts file implements the WorkerHostCallContext builder. During agent startup, this module instantiates a GoalService instance bound to the authenticated company's ID, then injects it into the context object passed to the sandbox. This injection occurs over an RPC boundary, ensuring agents execute in isolated processes while maintaining access to the hierarchical data.

The Context Propagation Pipeline

The propagation of goal hierarchy context follows a four-stage pipeline that ensures security and data consistency:

  1. Authentication scoping – Agents authenticate using an API key bound to a specific company. The extracted companyId becomes the mandatory scope for all subsequent ctx.goals operations, preventing cross-tenant data leakage.

  2. Run initialization – When a run starts (via CLI or API), the system loads the associated goalId from the originating issue or project into the RunContext. This ID represents the agent's entry point into the goal tree.

  3. Context assembly – The worker RPC host in packages/plugins/sdk/src/worker-rpc-host.ts assembles the final context. It instantiates GoalService with the resolved companyId and maps the service methods onto the ctx.goals property, preserving the run's specific goalId for reference.

  4. Sandbox exposure – Inside the isolated agent process, the SDK exposes ctx.goals as a proxied object. Method calls serialize across the RPC boundary to the host process, which executes the database queries and returns hierarchical data to the agent.

Working with Goal Hierarchy in Agent Code

Agents interact with the hierarchy through the ctx.goals API to discover their operational context or create structured sub-tasks. The following example demonstrates listing root goals, resolving ancestry, and creating child objectives:

// packages/plugins/example-agent/src/index.ts
export async function run(ctx: AgentContext) {
  // Retrieve top-level goals (those without parents)
  const rootGoals = await ctx.goals.list({
    companyId: ctx.companyId,
    limit: 50,
    where: (g) => !g.parentId,
  });

  // Resolve full ancestry for the first goal
  const targetGoal = await ctx.goals.get(rootGoals[0].id, ctx.companyId);
  console.log("Parent ID:", targetGoal.parentId);

  // Create a sub-goal under the current run's goal context
  const parentId = ctx.run?.goalId ?? targetGoal.id;
  const subGoal = await ctx.goals.create(
    {
      title: "Automate security audit",
      description: "Run nightly vulnerability scans",
      level: "team",
      status: "planned",
      parentId: parentId,
    },
    ctx.companyId
  );

  return subGoal.id;
}

On the server side, the context assembly looks conceptually like this simplified implementation:

// server/src/services/agent-context.ts
import { GoalService } from "packages/db/src/client";
import { createWorkerRpcHost } from "packages/plugins/sdk/src/worker-rpc-host";

export async function buildAgentContext(runId: string) {
  const run = await runsDao.get(runId);
  const goalService = new GoalService({ companyId: run.companyId });

  const ctx = {
    companyId: run.companyId,
    goalId: run.goalId,
    goals: {
      list: (opts) => goalService.list({ ...opts, companyId: run.companyId }),
      get: (id) => goalService.get(id, run.companyId),
      create: (payload) => goalService.create(payload, run.companyId),
      update: (id, patch) => goalService.update(id, patch, run.companyId),
    },
  };

  return createWorkerRpcHost(ctx);
}

Summary

  • Database layer – packages/db/src/schema/goal.ts defines the hierarchical structure via the parentId foreign key, enforcing referential integrity for the goal tree.
  • Service abstraction – packages/db/src/client.ts implements GoalService with company-scoped CRUD operations that respect the parent-child relationships.
  • Runtime injection – packages/plugins/sdk/src/worker-rpc-host.ts instantiates the service and exposes it as ctx.goals within the agent sandbox.
  • Security boundary – Context propagation requires a valid companyId from the agent's authentication token, ensuring complete isolation between organizational tenants.
  • Agent API – Sandboxed agents manipulate the hierarchy through ctx.goals.list(), get(), create(), and update(), with all parentId relationships resolved automatically by the underlying service.

Frequently Asked Questions

How is the goal hierarchy physically stored in Paperclip?

The hierarchy persists in the goals table defined in packages/db/src/schema/goal.ts, which uses a parentId column referencing the same table's primary key. This self-referential foreign key pattern allows arbitrary nesting depth while maintaining referential integrity at the database level.

What specific methods does ctx.goals expose to agents?

According to packages/plugins/sdk/src/types.ts, the ctx.goals interface exposes four primary asynchronous methods: list(options) for paginated retrieval with filtering, get(goalId, companyId) for single-node access, create(payload, companyId) for inserting new hierarchical nodes, and update(goalId, patch, companyId) for modifying existing goals including reparenting operations.

How does Paperclip prevent agents from accessing other companies' goals?

Every method in the GoalService class requires a mandatory companyId parameter that must match the company encoded in the agent's API key. The packages/db/src/client.ts implementation uses this parameter to scope all SQL queries, effectively adding a tenant filter to every database operation before data reaches the agent sandbox.

Can an agent modify its position within the goal hierarchy during execution?

Yes. Agents can call ctx.goals.update() with a modified parentId field to repurpose themselves or create new child goals under different parents. The GoalService automatically handles the foreign key validation and invalidates cached descendant data, ensuring the hierarchy remains consistent across concurrent agent executions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →