Skip to content

Commit 1fed286

Browse files
HamptonMakesclaude
andcommitted
Token metadata and provenance join for agent writes
Identity facts beyond a display name — harness, harness version, model — now travel with the token, not the attribution rows. Minting accepts a schemaless `metadata` JSON object (capped at 4 KB, non-hash input coerced to {} rather than failing the mint), and every version, event, and comment an agent writes records `api_token_id` as a provenance join back to the token that produced it. actor/author stays the human; agent_name stays the denormalized display string the history UI already renders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 91bcb32 commit 1fed286

25 files changed

Lines changed: 231 additions & 36 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# This migration comes from co_plan (originally 20260815000002)
2+
class AddAgentProvenance < ActiveRecord::Migration[8.1]
3+
def change
4+
# Identity facts (harness, harness version, model, …) live on the token,
5+
# captured at mint time — schemaless, because the set of facts worth
6+
# recording grows faster than anyone wants to migrate three tables.
7+
add_column :coplan_api_tokens, :metadata, :json
8+
9+
# Attribution rows keep agent_name as the display string, and point at
10+
# the token for everything else. actor/author stays the human.
11+
add_column :coplan_plan_versions, :api_token_id, :string, limit: 36
12+
add_column :coplan_plan_events, :api_token_id, :string, limit: 36
13+
add_column :coplan_comments, :api_token_id, :string, limit: 36
14+
15+
add_index :coplan_plan_versions, :api_token_id
16+
add_index :coplan_plan_events, :api_token_id
17+
add_index :coplan_comments, :api_token_id
18+
19+
add_foreign_key :coplan_plan_versions, :coplan_api_tokens, column: :api_token_id
20+
add_foreign_key :coplan_plan_events, :coplan_api_tokens, column: :api_token_id
21+
add_foreign_key :coplan_comments, :coplan_api_tokens, column: :api_token_id
22+
end
23+
end

db/schema.rb

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

engine/app/controllers/coplan/api/v1/attachments_controller.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def create
3232
user: current_user,
3333
actor_type: api_author_type,
3434
actor_id: api_user_id,
35-
agent_name: api_agent_name
35+
agent_name: api_agent_name,
36+
api_token_id: api_token_id
3637
)
3738

3839
if result.success?
@@ -63,7 +64,8 @@ def destroy
6364
metadata: { content_type: content_type },
6465
actor_type: api_author_type,
6566
actor_id: api_user_id,
66-
agent_name: api_agent_name
67+
agent_name: api_agent_name,
68+
api_token_id: api_token_id
6769
)
6870

6971
head :no_content

engine/app/controllers/coplan/api/v1/base_controller.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ def api_agent_name
111111
)
112112
end
113113

114+
# The provenance join: attribution rows keep the display string in
115+
# agent_name and point here for everything else the token knows
116+
# about its run (harness, versions, model — see ApiToken#metadata).
117+
def api_token_id
118+
@api_token&.id
119+
end
120+
114121
def set_plan
115122
@plan = CoPlan::Plan.find_by(id: params[:plan_id] || params[:id])
116123
unless @plan

engine/app/controllers/coplan/api/v1/comments_controller.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ def create
2121
author_type: api_author_type,
2222
author_id: current_user&.id,
2323
body_markdown: params[:body_markdown],
24-
agent_name: api_agent_name
24+
agent_name: api_agent_name,
25+
api_token_id: api_token_id
2526
)
2627

2728
reason = comment.agent? ? "agent_response" : "new_comment"
@@ -131,7 +132,8 @@ def reply
131132
author_type: api_author_type,
132133
author_id: current_user&.id,
133134
body_markdown: params[:body_markdown],
134-
agent_name: api_agent_name
135+
agent_name: api_agent_name,
136+
api_token_id: api_token_id
135137
)
136138

137139
reason = comment.agent? ? "agent_response" : "reply"

engine/app/controllers/coplan/api/v1/content_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def update
4242
actor_type: api_author_type,
4343
actor_id: api_user_id,
4444
agent_name: api_agent_name,
45+
api_token_id: api_token_id,
4546
change_summary: params[:change_summary],
4647
reason: params[:reason]
4748
)

engine/app/controllers/coplan/api/v1/libraries_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def organize
136136
actor_type: api_author_type,
137137
actor_label: @api_token&.name,
138138
agent_name: api_agent_name,
139+
api_token_id: api_token_id,
139140
dry_run: params[:dry_run].to_s == "true"
140141
)
141142
unless result.success?

engine/app/controllers/coplan/api/v1/operations_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def commit_version(current_content, result)
213213
actor_type: api_author_type,
214214
actor_id: api_user_id,
215215
agent_name: api_agent_name,
216+
api_token_id: api_token_id,
216217
change_summary: params[:change_summary],
217218
diff_unified: diff.presence,
218219
operations_json: result[:applied],

engine/app/controllers/coplan/api/v1/plans_controller.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def update
106106
if params.key?(:folder_id) || params.key?(:folder_path)
107107
folder = resolve_folder_params
108108
return if performed? # resolve_folder_params rendered an error
109-
result = Plans::Place.call(plan: @plan, folder: folder, actor: current_user, actor_type: api_author_type, agent_name: api_agent_name)
109+
result = Plans::Place.call(plan: @plan, folder: folder, actor: current_user, actor_type: api_author_type, agent_name: api_agent_name, api_token_id: api_token_id)
110110
unless result.success?
111111
render json: { error: result.error }, status: :unprocessable_content
112112
raise ActiveRecord::Rollback
@@ -126,15 +126,15 @@ def update
126126
Plans::LogEvent.call(
127127
plan: @plan, actor: current_user, event_type: "title_changed",
128128
before: old_title, after: @plan.title,
129-
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name
129+
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name, api_token_id: api_token_id
130130
)
131131
end
132132

133133
if @plan.saved_change_to_visibility? && @plan.published? && old_visibility == "draft"
134134
Plans::LogEvent.call(
135135
plan: @plan, actor: current_user, event_type: "published",
136136
before: "draft", after: "published",
137-
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name
137+
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name, api_token_id: api_token_id
138138
)
139139
CoPlan::Analytics.track(
140140
"plan_published",
@@ -149,7 +149,7 @@ def update
149149
Plans::LogEvent.call(
150150
plan: @plan, actor: current_user,
151151
event_type: @plan.archived? ? "archived" : "unarchived",
152-
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name
152+
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name, api_token_id: api_token_id
153153
)
154154
end
155155

@@ -158,13 +158,13 @@ def update
158158
(new_tag_names - old_tag_names).each do |added|
159159
Plans::LogEvent.call(
160160
plan: @plan, actor: current_user, event_type: "tag_added", after: added,
161-
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name
161+
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name, api_token_id: api_token_id
162162
)
163163
end
164164
(old_tag_names - new_tag_names).each do |removed|
165165
Plans::LogEvent.call(
166166
plan: @plan, actor: current_user, event_type: "tag_removed", before: removed,
167-
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name
167+
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name, api_token_id: api_token_id
168168
)
169169
end
170170
end
@@ -183,7 +183,7 @@ def update
183183
Plans::LogEvent.call(
184184
plan: @plan, actor: current_user, event_type: "reference_added",
185185
after: ref.url, metadata: { title: ref.title, reference_type: ref.reference_type },
186-
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name
186+
actor_type: api_author_type, actor_id: api_user_id, agent_name: api_agent_name, api_token_id: api_token_id
187187
)
188188
end
189189
end

engine/app/controllers/coplan/api/v1/sessions_controller.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ def commit
4141
session: @session,
4242
change_summary: params[:change_summary],
4343
actor_id: api_user_id,
44-
agent_name: api_agent_name
44+
agent_name: api_agent_name,
45+
api_token_id: api_token_id
4546
)
4647

4748
response = {

0 commit comments

Comments
 (0)